type
status
date
slug
summary
tags
category
icon
password
一、Interceptor 介绍
Interceptor 是 Spring MVC 提供的拦截器机制,类似于 Servlet 规范中的过滤器 Filter,作用是拦截控制器的执行并添加自定义操作,一般用来做一些通用的功能,如请求日志打印、权限控制等。
Interceptor 和 Filter 的区别
- 规范不同:Filter 是 Servlet 规范规定的,被 Tomcat 调用;Interceptor 是 Spring 框架提供的,被 Spring 调用。
- 是否受 Spring 管理:Filter 不能够使用 Spring 容器资源,Interceptor 作为 Spring 容器的组件,可以使用 Spring 容器的任何资源。
- 作用范围不同:Filter 只在 Servlet 的前后起作用,Filter 组件实际上并不知道后续内部处理是 Spring MVC 提供的
DispatcherServlet
还是其他 Servlet 组件,它可以应用在所有 Servlet 上;而 Interceptor 仅仅针对DispatcherServlet
的 Controller 前后进行拦截,相比 Filter 更深入方法内部。
面试常问题目:如果 Listener、Filter、 Interceptor、 AOP都存在,它们的执行顺序如何? Listener => Filter => Interceptor => AOP
Interceptor 接口
在 Spring MVC 中定义一个 Interceptor 有两种方法:
- 实现
HandlerInterceptor
接口。
- 实现
WebRequestInterceptor
接口。
HandlerInterceptor
和WebRequestInterceptor
的异同:相同点:
- 两个接口都可用于 Contrller 层请求拦截,接口中定义的方法作用位置也是一样的。
WebRequestInterceptor
通过WebRequestHandlerInterceptorAdapter
间接实现了HandlerInterceptor
。不同点:
- 入参不同:
HandlerInterceptor
的入参是 Servlet 原生的 HttpServletRequest 和 HttpServletResponse;WebRequestInterceptor
的入参只有 WebRequest,里面对 HttpServletRequest 进行了一层包装,方便获取 Request 内容(这个接口的实现就是为了方便获取 Request 中的信息,或者预设一些参数供后续流程使用)。
preHandle
返回值不同:WebRequestInterceptor
的preHandle
是没有返回值的,说明该方法中的逻辑并不影响后续的方法执行;HandlerInterceptor
可以在preHandle
方法返回 false,拒绝请求进入 controller 方法。
我们一般主要使用
HandlerInterceptor
接口,HandlerInterceptor
接口的定义如下:这个接口里面包含三个方法:
- preHandle:处理器执行之前执行,如果返回 false 将跳过处理器、拦截器 postHandle 方法、视图渲染等,直接执行拦截器 afterCompletion 方法。
- postHandle:处理器执行后,视图渲染前执行,如果处理器抛出异常,将跳过该方法直接执行拦截器 afterCompletion 方法。
- afterCompletion:视图渲染后执行,不管处理器是否抛出异常,该方法都将执行。
上面这个过程使用流程图表示如下:
注意:自从前后端分离之后,Spring MVC 中的处理器方法执行后通常不会再返回视图,而是返回表示 json 或 xml 的对象,@Controller 方法返回值类型如果为 ResponseEntity 或标注了 @ResponseBody 注解,此时处理器方法一旦执行结束,Spring 将使用 HandlerMethodReturnValueHandler 对返回值进行处理,具体来说会将返回值转换为 json 或 xml,然后写入响应,后续也不会进行视图渲染,这时postHandle
将没有机会修改响应体内容。如果需要更改响应内容,可以定义一个实现ResponseBodyAdvice
接口的类,然后将这个类直接定义到 RequestMappingHandlerAdapter 中的 requestResponseBodyAdvice 或通过 @ControllerAdvice 注解添加到 RequestMappingHandlerAdapter。
二、Interceptor 使用
要实现一个自定义的 Interceptor,以
HandlerInterceptor
接口为例,直接实现 HandlerInterceptor
接口即可。为了避免实现该接口的所有方法,Spring 5 之前提供了一个抽象的实现HandlerInterceptorAdapter
,Java 8 接口 default 方法新特性出现后,我们直接实现HandlerInterceptor
接口即可。
要把实现的 Interceptor 添加到 Spring 容器发挥作用,有四种配置方式:
- XML 配置
@Bean
注解配置
- 实现
WebMvcConfigurer
接口(推荐使用)
继承WebMvcConfigurationSupport
(不推荐使用)
XML 配置
- bean:mvc:interceptors 标签下的拦截器 bean 将应用到所有的处理器。
- mvc:interceptor:这个标签下的子标签可以指定拦截器应用到哪些请求路径。
- mvc:mapping:指定处理的请求路径。
- mvc:exclude-mapping:指定排除的请求路径。
- bean:指定应用到给定路径的拦截器 bean。
@Bean
注解配置
实现 WebMvcConfigurer
接口(推荐使用)
这里在配置类上添加了 @EnableWebMvc 注解开启了 Spring MVC 中的某些特性,然后就可以实现 WebMvcConfigurer 接口中的 addInterceptors 方法向 Spring MVC 中添加拦截器。如果你使用了 spring-boot-starter-web,不再需要手工添加 @EnableWebMvc 注解。
旧版本的 Spring 的写法是继承WebMvcConfigurerAdapter
来重写addCorsMappings
。Spring 5.x 以后已经抛弃了WebMvcConfigurerAdapter
,理由同HandlerInterceptorAdapter
,所以尽量不要再使用WebMvcConfigurerAdapter
。
继承 WebMvcConfigurationSupport
(不推荐使用)
WebMvcConfigurationSupport
用法和
WebMvcConfigurer
接口类似,但是不推荐这种写法。原因是 Spring Boot 限制,只有当 WebMvcConfigurationSupport 类不存在的时候 WebMvcAutoConfiguration 类才会生效。WebMvcAutoConfiguration 类中不仅定义了 classpath:/META-INF/resources/
, classpath:/resources/
,classpath:/static/
,classpath:/public/
等路径的映射,还定义了配置文件 spring.mvc
开头的配置信息等。当 WebMvcAutoConfiguration 不生效时会导致以下几个问题:- WebMvcProperties 和 ResourceProperties 失效
- 类路径上的 HttpMessageConverter 失效
定义多个 Interceptor
如果定义了多个 Interceptor,多个 Interceptor 的执行顺序如下。
- preHandle 按照 Interceptor 的顺序先后执行。如果任意一次调用返回 false 则直接跳到最后一个 Interceptor 的 afterCompletion 开始逆序执行。
- postHandle 按照 Interceptor 的逆序先后执行,也就说后面的 Interceptor 先执行 postHandle。
- afterCompletion 也按照 Interceptor 的逆序先后执行,后面的拦截器先执行 afterCompletion。
上面执行顺序使用流程图表示如下:
那么多个 Interceptor 的顺序是如何指定的呢?
- 如果使用 xml 配置,Spring 将记录 bean 声明的顺序,先声明的拦截器将排在前面。
- 如果使用
@Bean
注解配置,由于通过反射读取方法无法保证顺序,因此需要在方法上添加@Order
注解指定 bean 的声明顺序。@Order
数值越小,优先级越高,排在队列更前面。
- 如果实现
WebMvcConfigurer
接口,拦截器的顺序并非和添加顺序完全保持一致,为了控制先后顺序,需要自定义的拦截器实现Ordered
接口。Ordered
使用同@Order
注解。
三、Interceptor 配置
Pattern 配置
简单来说:
- 一个
*
:只匹配字符,不匹配路径/
- 两个
**
:匹配字符,和路径/
假如我们需要拦截以下 swagger 请求之外的其他所有请求。
我们的拦截器表达式可以这样写
参考
- Author:mcbilla
- URL:http://mcbilla.com/article/1ff48271-849b-4163-b52f-1545f050aef8
- Copyright:All articles in this blog, except for special statements, adopt BY-NC-SA agreement. Please indicate the source!
Relate Posts