一:概述
MVC框架中基于AOP实现的拦截器是比较重要的组件,本文讲解Spring MVC的拦截器Interceptor相关使用方法及其注意事项。为了更有层次结构掌握拦截器的使用,本文从以下几方面描述拦截器:
- 拦截器相关接口、类
- 拦截器相关接口、类中方法作用
- 拦截器XML配置及其标签含义
- 拦截器及拦截器链方法执行顺序
二:相关接口、类
2.1 基础接口
定义拦截器基础行为的接口为HandlerIntercetor,看名字就知道是处理拦截。提供三个方法分别实现对请求处理前后以及最终的逻辑加强
2.2 适配抽象
建议大家实现拦截器都采用继承抽象类HandlerInterceptorAdapter,该类实现接口AsyncHandlerInterceptor,AsyncHandlerInterceptor接口继承自HandlerInterceptor接口。这个抽象类主要作用就是为接口方法提供默认实现,当程序需要哪一块逻辑处理直接重写相关方法即可,不用类似于实现Interceptor接口需要重写三个方法。区别版本的谬论
注意查看2.1中的截图,JDK1.8中为接口新增了默认方法,所以在Spring5中的HandleIntercetor接口中三个方法全部采用默认方法实现。即不用再重写三个方法,只需要重写相关逻辑方法即可
具体的HandleInterceptorAdapter实现及其继承结构如下图所示
三:方法作用
基础接口HandlerInterceptor提供preHandle()、postHandle()、afterCompletion()三个方法
3.1 preHandle()
拦截方法执行前执行,相当于AOP中的Before通知类型。参数具备请求对象Request、响应对象Response、自身拦截器对象Handle。可以考虑使用这个方法完成权限校验、登录判断等操作。如果该方法返回false则后续所有包括拦截方法都不会执行
3.2 postHandle()
方法执行完毕视图渲染之前执行,相当于AOP中的After通知类型。参数除了preHandle()具备的三个参数外新增ModleAndView,但是注意这个参数可能为Null空值,使用前请判断避免发生空指针异常。比如拦截控制层方法返回值类型是String并不是ModelAndView那么这个参数就是空值Null。可以考虑用这个方法操作日志输出记录等,同时注意以下两点:
- preHandle()执行返回false则postHandle()不执行
- 执行方法抛出异常则postHandle()不执行
3.3 afterCompletion()
方法执行完毕视图渲染之后执行,相当于AOP的AfterReturning + AfterThrowing,不论是方法正常执行还是异常抛出都会执行的方法。所以该方法适合用作资源清理的场景,相对于前面两种方法来讲最直观的就是多了对参数Exception的处理。也就是说如果执行方法抛出异常,那么在这个方法中是可以根据这个异常执行一些操作的,但是注意这个参数与ModelAndView一样也可能为空。同时注意这个方法如果是preHandle()返回false就不会被执行
四:配置方式
4.1 接口实现
当然如前面所讲,如果是在Spring5中不管是实现HandlerInterceptor接口亦或是继承HandlerInterceptorAdapter适配都是相同效果,都能选择实现部分自己需要的逻辑方法。这里通过实现接口完成三部分逻辑扩展实现
4.2 XML配置
- <mvc:interceptors>相当于AOP中的<aop:config>,该标签中统一进行拦截器声明
- <mvc:interceptor>标签就代表一个拦截器
- <mvc:mapping>配置当前拦截器拦截路径
- <mvc:exclude-mapping>配置当前拦截器不拦截路径
- <bean>自然就是注入拦截器实现实例
注意一点就是如果如下图所示直接在<mvc:interceptors>标签中配置拦截器实例默认代表拦截所有方法
4.3 测试
通过上面拦截器的实现实例编写以及XML文件中配置,我们编写对应的控制层方法看是否生效
通过浏览器访问测试发现控制台输出结果如下所示,确实不论是<mvc:mapping>亦或是<mvc:exclude-mapping>都实现了其作用效果
五:执行顺序
AOP编程中同一个切面亦或是不同切面中有不同的通知类型,针对同一方法拦截时会根据时间轴执行不同的方法。当然,Spring MVC的拦截器也是有不同的执行顺序
5.1 同一个拦截器
前面在讲解方法作用时其实已经讲过同一个拦截器中方法执行的顺序以及条件,接下来就是根据上面测试截图总结下
- preHandle()首先执行
- 拦截方法第二执行,若preHandle()返回false则不执行
- postHandle()视图渲染前即拦截方法执行后未返回返回值前执行,若preHandle()返回false亦或是拦截方法抛出异常则不执行
- afterCompletion()视图渲染后即拦截方法返回返回值后执行,若preHandle()返回false则不执行。拦截方法不论是否异常都会执行这个方法
5.2 不同拦截器正常情况
其实在之前的小节中已经给出了这个测试答案,如上图所示。依然采用两个拦截器,最后的执行顺序整理如下图所示。其顺序与XML配置文件中声明拦截器的顺序有关
5.3 不同拦截器preHandle()返回false
如果在第一个拦截器的preHandle()返回false则后续所有逻辑都不会执行
如果在第二个拦截器的preHandle()返回false那么第一个返回true的拦截器对应的afterCompletion()会执行。也就是说afterCompletion()的执行与preHandle()关系为仅仅与本拦截器的preHandle()有关,与其它拦截器的preHandle()返回值无关
5.4 不同拦截器postHandle()异常
postHandle()应该可以说是一个受限较多的方法,下面测试下当第二个拦截器的postHandle()抛出异常时第一个拦截器的postHandle()执行情况。结果如下图所示第一个拦截器的postHandle()并不会执行
5.5:执行顺序总结
- 拦截器链中的preHandle()是否执行与前一个preHandle()返回值有关
- 拦截器链中的postHandle()是否执行与所有拦截器的preHandle()返回值以及前面需要执行的postHandle()是否异常以及拦截方法是否异常有关
- 拦截器链中的afterCompletion()是否执行仅仅与本拦截器的preHandle()返回值有关,与其它任何因素都无关