1. 什么是拦截器?
拦截器(Interceptor)是 Spring MVC 中的一个很重要的组件,主要用于拦截用户的请求,可以在指定方法前后, 根据业务需要执行预先设定的代码。它的作用主要有:
- 权限验证:验证用户是否登录、是否有权限访问某个接口。
- 日志记录:记录请求信息的日志,如请求参数,响应信息等。
- 性能监控:监控系统的运行性能,如慢查询接口等。
- 通用行为:插入一些通用的行为,比如开发环境忽略某些请求。
也就是说, 允许开发⼈员提前预定义⼀些逻辑, 在用户的请求响应前后执行。也可以在用户请求前阻止其执行。在拦截器当中,开发⼈员可以在应⽤程序中做⼀些通用性的操作, 比如通过拦截器来拦截前端发来的请求, 判断Session中是否有登录用户的信息。如果有就可以放行, 如果没有就进行拦截。
2. 拦截器的实现
2.1 定义拦截器
自定义一个拦截器非常简单,只需要实现HandlerInterceptor这个接口即可,并重写该接口的方法。该接口有三个可以实现的方法:
- preHandle():该方法会在目标方法执行前执行,当其返回值为true时,表示继续向下执行;当其返回值为false时,会中断后续的所有操作(包括调用下一个拦截器和控制器类Controller中的方法执行等 )。
- postHandle(): 该方法会在目标方法调用之后,且解析视图之前执行。可以通过此方法对请求域中的模型和视图作出进一步的修改。
- afterCompletion():该方法会在整个请求完成,即视图渲染结束之后执行。可以通过此方法实现一些资源清理、记录日志信息等工作。
@Slf4j
public class LoginInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
log.info("LoginInterceptor 目标方法执行前执行...");
// true-放行 false-拦截
return true;
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
log.info("LoginInterceptor 目标方法执行后执行...");
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
log.info("LoginInterceptor 视图渲染完毕之后执行,最后执行....");
}
}
2.2 注册配置拦截器
想要在Spring Boot生效,只需要定义一个配置类,实现WebMvcConfigurer这个接口,并且实现其中的addInterceptiors()方法即可。
public class WebConfig implements WebMvcConfigurer {
// 注入自定义的拦截器对象
@Autowired
private LoginInterceptor loginInterceptor;
@Override
public void addInterceptors(InterceptorRegistry registry) {
// 注册自定义的拦截器对象
registry.addInterceptor(loginInterceptor)
.addPathPatterns("/**")// 设置拦截器拦截的请求路径(/** 表示拦截所有请求)
.excludePathPatterns("/user/login");//设置拦截器 排除拦截的路径
}
}
我们试试启动服务,访问任意一个请求,观察后端日志:
可以看到preHandle 方法执行之后就放行了, 开始执⾏目标方法, 目标⽅法执⾏完成之后执⾏postHandle和afterCompletion⽅法。
我们把拦截器中preHandle⽅法的返回值改为false, 再观察运⾏结果:
可以看到, 拦截器拦截了请求, 没有进⾏响应。
3. 拦截器的工作原理
3.1 执行流程
没有拦截器时,程序的调用顺序:
有了拦截器之后,程序会在调用Controller 之前进行相应的业务处理:
注意:
- 添加拦截器后, 程序在执⾏Controller的⽅法之前, 请求会先被拦截器拦截住。执⾏ preHandle() ⽅法,这个⽅法需要返回⼀个布尔类型的值。 如果返回true, 就表示放⾏本次操作, 继续访问Controller中的⽅法;如果返回false,则不会放⾏(Controller中的⽅法也不会执⾏)。
- Controller当中的⽅法执⾏完毕后,再回过来执⾏ postHandle() 这个⽅法以及afterCompletion() ⽅法,执⾏完毕之后,最终给浏览器响应数据。
3.2 源码分析
Spring Boot 拦截器是基于 Java 的 Servlet 规范实现的,通过实现 HandlerInterceptor 接口来实现拦截器功能。
当Tomcat启动之后, 有⼀个核⼼的类DispatcherServlet,它来控制程序的执行顺序。所有请求都会先进到DispatcherServlet,执⾏doDispatch 调度⽅法。如果有拦截器, 会先执⾏拦截器preHandle() ⽅法的代码, 如果 preHandle() 返回true, 继续访问controller中的⽅法。Controller当中的⽅法执⾏完毕后,再回过来执⾏ postHandle() 和 afterCompletion() ,返回给DispatcherServlet,最终给浏览器响应数据。
DispatcherServlet 接收到请求后, 执⾏doDispatch 调度⽅法, 再将请求转给Controller。
doDispatch 源码实现如下:
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
HttpServletRequest processedRequest = request;
HandlerExecutionChain mappedHandler = null;
boolean multipartRequestParsed = false;
WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
try {
try {
ModelAndView mv = null;
Exception dispatchException = null;
try {
processedRequest = this.checkMultipart(request);
multipartRequestParsed = processedRequest != request;
//1. 获取执⾏链
//遍历所有的 HandlerMapping 找到与请求对应的Handler
mappedHandler = this.getHandler(processedRequest);
if (mappedHandler == null) {
this.noHandlerFound(processedRequest, response);
return;
}
//2. 获取适配器
//遍历所有的 HandlerAdapter,找到可以处理该 Handler 的HandlerAdapter
HandlerAdapter ha = this.getHandlerAdapter(mappedHandler.getHandler());
String method = request.getMethod();
boolean isGet = HttpMethod.GET.matches(method);
if (isGet || HttpMethod.HEAD.matches(method)) {
long lastModified = ha.getLastModified(request, mappedHandler.getHandler());
if ((new ServletWebRequest(request, response)).checkNotModified(lastModified) && isGet) {
return;
}
}
//3. 执⾏拦截器preHandle⽅法
if (!mappedHandler.applyPreHandle(processedRequest, response)) {
return;
}
//4. 执⾏⽬标⽅法
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
if (asyncManager.isConcurrentHandlingStarted()) {
return;
}
this.applyDefaultViewName(processedRequest, mv);
//5. 执⾏拦截器postHandle⽅法
mappedHandler.applyPostHandle(processedRequest, response, mv);
} catch (Exception var20) {
dispatchException = var20;
} catch (Throwable var21) {
dispatchException = new NestedServletException("Handler dispatch failed", var21);
}
//6. 处理视图, 处理之后执⾏拦截器afterCompletion⽅法
this.processDispatchResult(processedRequest, response, mappedHandler, mv, (Exception)dispatchException);
} catch (Exception var22) {
//7. 执⾏拦截器afterCompletion⽅法
this.triggerAfterCompletion(processedRequest, response, mappedHandler, var22);
} catch (Throwable var23) {
this.triggerAfterCompletion(processedRequest, response, mappedHandler, new NestedServletException("Handler processing failed", var23));
}
} finally {
if (asyncManager.isConcurrentHandlingStarted()) {
if (mappedHandler != null) {
mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);
}
} else if (multipartRequestParsed) {
this.cleanupMultipart(processedRequest);
}
}
}
从上述源码可以看出在开始执⾏ Controller 之前,会先调⽤ 预处理⽅法 applyPreHandle,⽽applyPreHandle ⽅法的实现源码如下:
boolean applyPreHandle(HttpServletRequest request, HttpServletResponse response) throws Exception {
for(int i = 0; i < this.interceptorList.size(); this.interceptorIndex = i++) {
// 获取项⽬中使⽤的拦截器 HandlerInterceptor
HandlerInterceptor interceptor = (HandlerInterceptor)this.interceptorList.get(i);
if (!interceptor.preHandle(request, response, this.handler)) {
this.triggerAfterCompletion(request, response, (Exception)null);
return false;
}
}
return true;
}
在 applyPreHandle 中会获取所有的拦截器 HandlerInterceptor , 并执⾏拦截器中的preHandle ⽅法,这样就会咱们前⾯定义的拦截器对应上了,如下图所示:
如果拦截器返回true, 整个发放就返回true, 继续执⾏后续逻辑处理;如果拦截器返回fasle, 则中断后续操作。
因此,可以得出结论,拦截器的实现主要是依赖 Servlet 或 Spring 执行流程来进行拦截和功能增强的。