拦截器使用
一、概念理解
1.执行顺序图
2.特性区别
Tomcat是一个web容器,也叫web服务器。Tomcat采用Java servlet API的标准实现,servlet采用Java Servlet API接口,实现了Tomcat与Servlet的请求传递。
Filter,过滤器,顾名思义,即是对数据等的过滤,预处理过程。引入目的,在平常访问网站的时候,有时候发一些敏感的信息,发出后显示时 就会将敏感信息用*等字符替代,这就是用过滤器对信息进行了处理。
Filter是在servlet与Tomcat之间的一个类似于过滤网的东西。Servlet是一个Java应用程序,运行在服务器端,用来处理客户端请求并作出响应的程序。 通俗点说,servlet 就是运行在java服务器后端的一个程序,如果有人想访问我们的服务器,发来一个 http 请求,我们的 Tomcat 会将它解析为 request 请求。但是怎么处理这个请求呢?servlet 就是用于处理这个请求的,它接收request请求,并处理它,返回response响应。由Tomcat转换为http请求,返回给客户端。
拦截器是Spring中的概念,和过滤器类似,可以对用户请求进行拦截过滤处理。但是相对于过滤器而言,拦截器要的控制更加的细节,拦截器可以在三个地方进行执行。
3.实现方式
1) 实现Spring的HandlerInterceptor接口
2) 继承Spring的HandlerInterceptorAdapter类
其实HandlerInterceptorAdapter类 也是实现的HandlerInterceptor接口
HandlerInterceptor接口方法:
- preHandler(): 这个方法在业务处理器处理请求之前被调用,Interceptor是链式调用的,该方法返回false时表示请求结束,后续的Interceptor和Controller都不会再执行,当返回true时就会调用下一个Interceptor的preHandler()方法。如果已经是最后一个Interceptor时会调用当前请求的Controller方法。
- postHandler(): 在Controller方法调用之后执行,但是它会在DispatcherServlet进行视图返回之前被调用,所有可以在这个方法中对Controller处理后的ModelAndView对象进行操作。
- afterCompletion(): preHandler()方法返回值为true时才会执行。
4.拦截器注册
有了拦截器的类之后还需要注册拦截器,编写一个类继承WebMvcConfigurationSupport(或实现 WebMvcConfigurer 接口),并重写addInterceptors来注册拦截器。
多个拦截器的执行顺序取决于拦截器注册的顺序。
二、应用场景
拦截器本质上是面向切面编程(AOP),符合横切关注点的功能都可以放在拦截器中来实现,主要的应用场景包括:
登录验证,判断用户是否登录
权限验证,判断用户是否有权限访问资源,比如校验token
日志记录,记录请求操作日志(用户IP,访问时间等),以便统计请求访问量
处理cookie、本地化、国际化、主题等
性能监控,监控请求处理时长等
通用行为:读取cookie得到用户信息并将用户对象放入请求,从而方便后续流程使用,还有比如提取Locale、Theme信息等,只要是多个处理器都需要的即可使用拦截器实现
三、扩展示例
应用场景:日志记录,记录指定请求操作日志,便于对慢接口进行优化
1.创建拦截器
首先写一个类(TestLogInterceptor),让他继承HandlerInterceptorAdapter,并重写其中的三个方法,例如:
package com.yonyou.ucf.mdf.app.interceptor;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
* @project: devcom2021-be
* @package: com.yonyou.ucf.mdf.app.interceptor
* @description: TODO 利用Spring的拦截器监测每个Controller或方法的执行时长
* @author: zhangshuai
* @date: 2021年11月24日 9:17
* @version: v1.0
*/
@Slf4j
@Component
@RequiredArgsConstructor
public class TestLogInterceptor extends HandlerInterceptorAdapter {
private long time;
private String url;
/**
* 1.进入Controller方法之前执行
*
* @param request
* @param response
* @param handler
* @return 返回值为true时可继续执行Controller,preHandle()和afterCompletion(),为false时停止执行任何方法。
* @throws Exception
*/
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
time = System.currentTimeMillis();
url = request.getRequestURL().toString();
log.info("请求url:{}",url);
// 可以处理些逻辑
// 放开拦截
return true;
}
/**
* 2.Controller方法执行完毕但是未进行视图渲染时执行
*
* @param request
* @param response
* @param handler
* @param modelAndView
* @throws Exception
*/
@Override
public void postHandle(HttpServletRequest request,HttpServletResponse response,Object handler,ModelAndView modelAndView)
throws Exception {
super.postHandle(request, response, handler, modelAndView);
}
/**
* 3.视图渲染完成(整个请求结束)执行
*
* @param request
* @param response
* @param handler
* @param ex
* @throws Exception
*/
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
log.info("请求url:{},耗时{} ms", url , (System.currentTimeMillis() - time));
super.afterCompletion(request, response, handler, ex);
}
}
2.注册拦截器
后台脚手架也有注册拦截器,这里自己模块里扩展的(TestWebMvcConfigurer)集成脚手架的(ISVWebMvcConfigurer)即可
package com.yonyou.ucf.mdf.app.interceptor;
import com.yonyou.ucf.mdf.app.controller.interceptor.CookieValueInterceptor;
import com.yonyou.ucf.mdf.app.controller.interceptor.HeaderInterceptor;
import com.yonyou.ucf.mdf.app.controller.interceptor.ISVWebMvcConfigurer;
import com.yonyou.ucf.mdf.app.controller.interceptor.InnerRequestInterceptor;
import com.yonyoucloud.iuap.ucf.mdd.starter.core.UCFCoreProperties;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
/**
* @project: devcom2021-be
* @package: com.yonyou.ucf.mdf.app.interceptor
* @description: TODO
* @author: zhangshuai
* @date: 2021年11月24日 10:48
* @version: v1.0
*/
@Component
public class TestWebMvcConfigurer extends ISVWebMvcConfigurer {
private final TestLogInterceptor logInterceptor;
public TestWebMvcConfigurer(UCFCoreProperties properties, CookieValueInterceptor cookieValueInterceptor, HeaderInterceptor headerInterceptor, InnerRequestInterceptor innerRequestInterceptor, TestLogInterceptor logInterceptor) {
super(properties, cookieValueInterceptor, headerInterceptor, innerRequestInterceptor);
this.logInterceptor = logInterceptor;
}
@Override
public void addInterceptors(InterceptorRegistry registry) {
// 自己扩展的拦截器
// 注册自建拦截器
registry.addInterceptor(logInterceptor)
// 拦截指定controller下路径
.addPathPatterns("/isv/rest/**");
}
}
3.测试接口
@RestController
@Slf4j
@RequestMapping("/isv/rest")
public class MDDTestController {
@Value("${ucf.mdd.open-api.tenant-id}")
String tenantId;
/**
* 本地测试:http://127.0.0.1:8080/isv/rest/sqlHelp/test001
* @throws Exception
*/
@RequestMapping("/sqlHelp/test001")
public void test001sql() throws Exception {
// SqlHelper
// 1.查询集合
List<Object> objectsList = SqlHelper.selectList("com.yonyou.ucf.mdf.app.mapper.AdminTestMapper.getListById", this.tenantId);
System.err.println(objectsList);
// 2.查询一条
HashMap<String, String> paramMap = new HashMap<>();
paramMap.put("tenantId", this.tenantId);
paramMap.put("new1","0001");
Object obj = SqlHelper.selectOne("com.yonyou.ucf.mdf.app.mapper.AdminTestMapper.getObjById", paramMap);
System.err.println(obj);
}
}