拦截器(Interceptor)是除Filter之外的另一个管控Servlet下Http请求的另一机制,他能够对web请求进行鉴权等一系列的处理,同时也有比较灵活的生效位置
配置方法
使用springboot可以比较方便的配置拦截器,基本实现见如下推文:
此处,我们讨论HandlerInterceptor的三个方法的作用细节,当我们定位接口方法调用,发现在DispatcherServlet.doDispatch中它们使用如下:
try {ModelAndView mv = null;Exception dispatchException = null;try {...if (!mappedHandler.applyPreHandle(processedRequest, response)) {return;}// Actually invoke the handler.mv = ha.handle(processedRequest, response, mappedHandler.getHandler());if (asyncManager.isConcurrentHandlingStarted()) {return;}applyDefaultViewName(processedRequest, mv);mappedHandler.applyPostHandle(processedRequest, response, mv);}catch (Exception ex) {dispatchException = ex;}catch (Throwable err) {// As of 4.3, we're processing Errors thrown from handler methods as well,// making them available for @ExceptionHandler methods and other scenarios.dispatchException = new NestedServletException("Handler dispatch failed", err);}processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
可以看到,这个pre和post,相对位点是HandlerChain对请求的处理,也即通常地,在Controller里的一个Mapping的方法,而afterComplete的调用则位于processDispatchResult方法内,它与postHandle的区别是能够获取到handler处理过程中抛出的错误。
一些讨论
更多的handler
我利用上述的Interceptor测试如下接口
@RestControllerpublic class TestController {@GetMapping("/hello")public String hello(){return "hello,world";}}
意外发现,有些情况下三个回调函数都被调用了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拦截执行。
