关于 HandlerInterceptorAdapter 的相关内容。 由于经常使用到这个来实现自己的拦截器,记录一下~
通过查看源码可以看到,HandlerInterceptorAdapter
实现了 AsyncHandlerInterceptor
接口,
而 AsyncHandlerInterceptor
又继承了 HandlerInterceptor
接口,对 HandlerInterceptor
进行了扩展,增加了 afterConcurrentHandlingStarted
方法
所以我们实现拦截器常用有两种方式:
- 直接实现
HandlerInterceptor
接口 - 继承
HandlerInterceptorAdapter
抽象类
源码如下: HandlerInterceptorAdapter :
public abstract class HandlerInterceptorAdapter implements AsyncHandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
throws Exception {
return true;
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler,
@Nullable ModelAndView modelAndView) throws Exception {
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler,
@Nullable Exception ex) throws Exception {
}
@Override
public void afterConcurrentHandlingStarted(HttpServletRequest request, HttpServletResponse response,
Object handler) throws Exception {
}
}
AsyncHandlerInterceptor :
public interface AsyncHandlerInterceptor extends HandlerInterceptor {
default void afterConcurrentHandlingStarted(HttpServletRequest request, HttpServletResponse response,
Object handler) throws Exception {
}
}
HandlerInterceptor :
public interface HandlerInterceptor {
default boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
throws Exception {
return true;
}
default void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler,
@Nullable ModelAndView modelAndView) throws Exception {
}
default void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler,
@Nullable Exception ex) throws Exception {
}
}
在 HandlerInterceptorAdapter
中提供了以下的方法:
- preHandle:在方法被调用前执行。在该方法中可以做类似校验的功能。如果返回 true,则继续调用下一个拦截器。如果返回 false,则中断执行,也就是说我们想调用的方法 不会被执行,但是你可以修改 response 为你想要的响应。
- postHandle:在方法执行后调用。
- afterCompletion:在整个请求处理完毕后进行回调,也就是说视图渲染完毕或者调用方已经拿到响应。
- afterConcurrentHandlingStarted:这个方法会在 Controller 方法异步执行时开始执行。 当处理程序启动一个异步请求时,它会在DispatcherServlet 不调用的情况下退出postHandle,afterCompletion就像它通常为同步请求所做的那样,因为请求处理的结果(例如 ModelAndView)可能还没有准备好,并且将从另一个线程并发生成。在这种情况下,afterConcurrentHandlingStarted(javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse, java.lang.Object) 被调用,允许实现在将线程释放到 Servlet 容器之前执行诸如清理线程绑定属性之类的任务。
关于 afterConcurrentHandlingStarted 官方文档是这样写的:当处理程序同时执行时,调用而不是postHandle和afterCompletion。实现可以使用提供的请求和响应,但应避免以与处理程序的并发执行冲突的方式修改它们。此方法的典型用途是清理线程局部变量。
代码实现
新建一个类 MyInterceptor
继承 HandlerInterceptorAdapter
public class MyInterceptor extends HandlerInterceptorAdapter {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
request.setAttribute("startTime", System.currentTimeMillis());
System.out.println(">>>>> MyInterceptor preHandle >>>>>>>>>>>>>>>>>>>>>>");
return super.preHandle(request, response, handler);
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
long startTime = (long) request.getAttribute("startTime");
System.out.println("MyInterceptor 执行:" (System.currentTimeMillis() - startTime));
System.out.println(">>>>> MyInterceptor postHandle >>>>>>>>>>>>>>>>>>>>>>");
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
request.removeAttribute("startTime");
System.out.println(">>>>> MyInterceptor afterCompletion >>>>>>>>>>>>>>>>>>>>>>");
}
@Override
public void afterConcurrentHandlingStarted(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
super.afterConcurrentHandlingStarted(request, response, handler);
System.out.println(">>>>> MyInterceptor afterConcurrentHandlingStarted >>>>>>>>>>>>>>>>>>>>>>");
}
}
新建配置类 MyConfig
@Configuration
public class MyConfig implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new MyInterceptor()).addPathPatterns("/**");
}
}
控制器测试类 MyController
@RestController
public class MyController {
@GetMapping(value = "hello_world")
public String helloWorld() {
System.out.println("hello_world");
return "hello world!";
}
}
启动应用进行访问, http://localhost:8080/hello_world
输出结果:
代码语言:javascript复制>>>>> MyInterceptor preHandle >>>>>>>>>>>>>>>>>>>>>>
hello_world
MyInterceptor 执行:39
>>>>> MyInterceptor postHandle >>>>>>>>>>>>>>>>>>>>>>
>>>>> MyInterceptor afterCompletion >>>>>>>>>>>>>>>>>>>>>>
运行流程如下:
- 拦截器执行顺序是按照Spring配置文件中定义的顺序而定的。
- 会先按照顺序执行所有拦截器的preHandle方法,一直遇到return false为止,比如第二个preHandle方法是return false,则第三个以及以后所有拦截器都不会执行。若都是return true,则按顺序加载完preHandle方法。
- 然后执行主方法(自己的controller接口),若中间抛出异常,则跟return false效果一致,不会继续执行postHandle,只会倒序执行afterCompletion方法。
- 在主方法执行完业务逻辑(页面还未渲染数据)时,按倒序执行postHandle方法。若第三个拦截器的preHandle方法return false,则会执行第二个和第一个的postHandle方法和afterCompletion(postHandle都执行完才会执行这个,也就是页面渲染完数据后,执行after进行清理工作)方法。(postHandle和afterCompletion都是倒序执行)
HandlerInterceptorAdapter
用来做拦截器还是很方便的,可以用来实现如下应用场景:
- 日志记录,可以记录请求信息的日志,以便进行信息监控、信息统计等。
- 权限检查:如登陆检测,进入处理器检测是否登陆,如果没有直接返回到登陆页面。
- 性能监控:典型的是慢日志。
不过,有时不一定要用拦截器,可以在网关就做校验,这样可能对业务的侵入更少一些,具体还是按照业务场景来定吧~
End.
Copyright: 采用 知识共享署名4.0 国际许可协议进行许可 Links: https://lixj.fun/archives/springboot中的handlerinterceptoradapter