背景
在工作中看到了不少项目用到了拦截器,这里去总结一下spring-boot使用拦截器。拦截器是Spring提供的HandlerInterceptor(拦截器),其功能和过滤器类似,但是提供更精细的控制能力:在request被响应之前、request被响应之后、视图渲染之前以及request全部结束之后。我们不能通过拦截器修改request的内容,但可以通过抛出异常(或者返回false)来暂停request的执行。
使用步骤
配置拦截器也很简单,spring给我们提供了WebMvcConfigurerAdapter,我们在addInterceptors方法中添加注册拦截器即可。总结起来就是三步:
1.创建我们自己的拦截器类并实现HandlerInterceptor接口。
2.创建一个Java类继承WebMvcConfigurerAdapter,并重写addInterceptors方法。
3.实例化我们自定义的拦截器,然后将对象手动添加到拦截器链中。
代码示例
自定义Session信息写入ThreadLocal
1 | @Slf4j |
可以看到实现了HandlerInterceptor接口之后,要实现其中的三个方法。
preHandle方法:在请求controller方法之前调用,这里就可以做一些session对象的校验及写入ThreadLocal方便方法调用等。只有这个方法返回true,请求才能继续下去。
postHandle方法:这个方法是在请求了controller方法之后但在视图渲染之前调用的。这里可以去做ThreadLocal中资源的清除。
afterCompletion方法:这个方法是在整个请求结束之后调用的,也就是在Dispatcher渲染整个视图之后进行的,主要进行资源清理工作。(这里也是去补偿了ThreadLocal中资源的清除)。
注册拦截器
在WebMvcConfigurerAdapter的子类中注册这个拦截器。WebMvcConfigurerAdapter看名字就提供了很多springmvc关于web访问的配置。
1 | @Component |
通过spring管理把自定义的拦截器注册成bean对象,然后通过register的addInterceptor方法注册到拦截器执行链中,这里也可以设置包括/过滤的访问地址等相关子属性。
我们写了个controller去测试这个自定义拦截器。其中helloService是把ThreadLocal中的对象给直接返回
1 | @RestController |
1 | @Override |
在浏览器中访问http://localhost:7001/threadLocal/getSessionUser 可以看到结果返回:
这说明我们的拦截器是正确将sessionUser设置进入ThreadLocal对象的。看控制台的日志输入:
多个自定义拦截器的执行顺序
如果再定义一个自定义拦截器,那么执行的顺序是什么呢?
1 | @Slf4j |
在webMvcConfig中注册这两个拦截器,并且在浏览器中访问同样的url。看到控制台的输入。
在webMvcConfig中,我们注册的顺序是先注册了myInterceptor,然后注册了SessionInterceptor,看到的执行顺序为:
preHandle方法:myInterceptor先执行
postHandle方法:sessionInterceptor先执行
afterCompletion方法:sessionInterceptor先执行。
可见多个自定义拦截器在执行链中的执行顺序是与注册顺序相关的,preHandle方法是先注册先执行,其他两个方法是后注册的先执行。具体执行的顺序的分析可以见下图。
拦截器的缺点
它依赖于web框架,在SpringMVC中就是依赖于SpringMVC框架。在实现上,基于Java的反射机制,属于面向切面编程(AOP)的一种运用,就是在service或者一个方法前,调用一个方法,或者在方法后,调用一个方法,比如动态代理就是拦截器的简单实现,在调用方法前打印出字符串(或者做其它业务逻辑的操作),也可以在调用方法后打印出字符串,甚至在抛出异常的时候做业务逻辑的操作。由于拦截器是基于web框架的调用,因此可以使用Spring的依赖注入(DI)进行一些业务操作,同时一个拦截器实例在一个controller生命周期之内可以多次调用。但是缺点是只能对controller请求进行拦截,对其他的一些比如直接访问静态资源的请求则没办法进行拦截处理。
同时,反射也有可能对性能有些影响。
HandlerInterceptor接口分析
1 | public interface HandlerInterceptor { |
HandlerInterceptorAdapter
springMvc还提供了HandlerInterceptorAdapter这个抽象类,这个抽象类中实现了AsyncHandlerInterceptor接口,而AsyncHandlerInterceptor接口又继承了HandlerInterceptor接口,我们可以首先看下AsyncHandlerInterceptor接口:
1 | public interface AsyncHandlerInterceptor extends HandlerInterceptor { |
可以看到在这个接口中添加了一个afterConcurrentHandlingStarted方法。 该方法是用来处理异步请求。当Controller中有异步请求方法的时候会触发该方法。异步请求先支持preHandle、然后执行afterConcurrentHandlingStarted,之后才会执行postHandle的方法。
比如现在我们配置了一个拦截器是用来拦截异步请求的:
1 | @Component |
这个时候我们有个异步请求处理:(对于springMvc的异步请求可以看看这篇博客:springmvc的异步请求)
1 | @RestController |
这时在浏览器中调用我们的异步请求,可以看到控制台中输出
可见这时先调用的是afterConcurrentHandlingStarted方法,而后调用的是postHandle方法。
我们再去看适配器HandlerInterceptorAdapter的代码:
1 | public abstract class HandlerInterceptorAdapter implements AsyncHandlerInterceptor { |
可以看到preHandle方法默认实现返回了true,比如我们只想去定义一个拦截器去在方法执行完之后去释放掉一些资源,如果去实现HandlerInterceptor则显得有点麻烦。这里只要去继承这个抽象类,实现afterCompletion方法即可。
github
上述代码都能在gitHub上看到:
https://github.com/zhanglijun1217/spring-boot-demo