拦截器(Interceptor)是除Filter之外的另一个管控Servlet下Http请求的另一机制,他能够对web请求进行鉴权等一系列的处理,同时也有比较灵活的生效位置

配置方法

使用springboot可以比较方便的配置拦截器,基本实现见如下推文:

SpringBoot2.x - 基于注解实现拦截器

此处,我们讨论HandlerInterceptor的三个方法的作用细节,当我们定位接口方法调用,发现在DispatcherServlet.doDispatch中它们使用如下:

  1. try {
  2. ModelAndView mv = null;
  3. Exception dispatchException = null;
  4. try {
  5. ...
  6. if (!mappedHandler.applyPreHandle(processedRequest, response)) {
  7. return;
  8. }
  9. // Actually invoke the handler.
  10. mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
  11. if (asyncManager.isConcurrentHandlingStarted()) {
  12. return;
  13. }
  14. applyDefaultViewName(processedRequest, mv);
  15. mappedHandler.applyPostHandle(processedRequest, response, mv);
  16. }
  17. catch (Exception ex) {
  18. dispatchException = ex;
  19. }
  20. catch (Throwable err) {
  21. // As of 4.3, we're processing Errors thrown from handler methods as well,
  22. // making them available for @ExceptionHandler methods and other scenarios.
  23. dispatchException = new NestedServletException("Handler dispatch failed", err);
  24. }
  25. processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);

可以看到,这个pre和post,相对位点是HandlerChain对请求的处理,也即通常地,在Controller里的一个Mapping的方法,而afterComplete的调用则位于processDispatchResult方法内,它与postHandle的区别是能够获取到handler处理过程中抛出的错误。

一些讨论

更多的handler

我利用上述的Interceptor测试如下接口

  1. @RestController
  2. public class TestController {
  3. @GetMapping("/hello")
  4. public String hello(){
  5. return "hello,world";
  6. }
  7. }

意外发现,有些情况下三个回调函数都被调用了3次,打印对应的handler分别是:

  • lzy.root.learn.mvc.controller.TestController#hello()
  • ResourceHttpRequestHandler ["classpath:/META-INF/resources/", "classpath:/resources/", "classpath:/static/", "classpath:/public/", "/"]
  • org.springframework.boot.autoconfigure.web.servlet.error.BasicErrorController#error(HttpServletRequest)

我们可以看到,除了自定义的handler,还有两个Servlet内定义的handler,这其实体现了SpringMVC的HandlerChain结构,在收到了http请求并进行处理时,HandlerChain组成了如下结构:
自定义handler -> ResourceHttpRequestHandler -> 错误handler
这是一般通过Controller配置的接口情况下的handler链条,所以我们在编写preHandle方法时,应当对handler作出一些限定,一般是handler instanceof HandlerMethod,这样可以正确定位在自编写的handler上。

Filter、Interceptor和AOP拦截的取舍

之前在Servlet篇讨论过添加Filter的Http请求拦截方法,此处辨析其与Interceptor的区别:

  • Filter是面向Servlet的,实现方式是Servlet接受请求时的回调;而Interceptor则需要以bean的形式被提取并通过反射注入到DispatcherServlet的任务流中,它并不依赖Servlet容器
  • 在action(是struts的过时概念,实际上适应绝大多数web请求)请求的生命周期中,拦截器可以多次被调用,而过滤器只能在容器初始化时被调用一次。
  • Filter的作用时间比Interceptor早,应从功能理解它们:Filter在请求的接收端进行过滤操作,而Interceptor则是在handler的执行前后以及整个处理执行完成时作用,它能够在处理请求后兜底

如果了解Interceptor也通过反射机制加入到请求处理工作流中,加上上述的理解,我们能够总结出一般的使用方法:

  • 通过@WebFilter注入的Filter是spring的Bean,能够执行复杂业务逻辑,所以在请求处理(handle)之前的功能,如过滤非法请求,鉴权等操作,都应尽量放在Filter中,因它早于Interceptor中preHandle执行,能够省略尽可能多的步骤
  • 需要在请求处理(handle)之后进行的同步操作,如请求后log,异常处理等,应使用Interceptor操作,因为它通过反射注入到Servelet执行流中,相对于AOP拦截开销小。
  • 阅读上述源码,我们了解到,asyncManager.isConcurrentHandlingStarted()在检查到请求是异步类型且正在异步执行时,会直接返回true,因此postHandle不会执行(但因为afterComplete函数在finally块中,所以还会执行)所以,当有异步请求时,需要把逻辑放在afterComplete执行或使用AOP拦截执行。