1. 概述
本文,我们来分享 Spring MVC 的拦截器,可能是大家最最最熟悉的组件。如果胖友没有实现过自定义的 Spring MVC 的拦截器,那可能有丢丢遗憾。所以,在看这篇文章之余,胖友也可以自己去尝试写一个 Spring MVC 的拦截器。
2. HandlerInterceptor
org.springframework.web.servlet.HandlerInterceptor
,处理器拦截器接口。代码如下:
// HandlerInterceptor.java |
一共有三个方法,胖友看看方法上的注释。
3. HandlerExecutionChain
org.springframework.web.servlet.HandlerExecutionChain
,处理器执行链。
3.1 构造方法
// HandlerExecutionChain.java |
3.2 addInterceptor
#addInterceptor(HandlerInterceptor interceptor)
方法,添加拦截器到 interceptorList
中。代码如下:
// HandlerExecutionChain.java |
首先,会调用
#initInterceptorList()
方法,保证interceptorList
已初始化。代码如下:// HandlerExecutionChain.java
private List<HandlerInterceptor> initInterceptorList() {
// 如果 interceptorList 为空,则初始化为 ArrayList
if (this.interceptorList == null) {
this.interceptorList = new ArrayList<>();
// 如果 interceptors 非空,则添加到 interceptorList 中
if (this.interceptors != null) {
// An interceptor array specified through the constructor
CollectionUtils.mergeArrayIntoCollection(this.interceptors, this.interceptorList);
}
}
// 置空 interceptors
this.interceptors = null;
// 返回 interceptorList
return this.interceptorList;
}- 虽然代码有点长,但是逻辑很简单。实际上,我们将
interceptorList
是interceptors
的配置。
- 虽然代码有点长,但是逻辑很简单。实际上,我们将
- 然后,添加
interceptor
到interceptorList
中。
#addInterceptor(HandlerInterceptor interceptor)
方法,添加拦截器们到 interceptorList
中。代码如下:
// HandlerExecutionChain.java |
3.3 getInterceptors
#getInterceptors()
方法,获得 interceptors
数组。代码如下:
// HandlerExecutionChain.java |
3.4 applyPreHandle
#applyPreHandle(HttpServletRequest request, HttpServletResponse response)
方法,应用拦截器的前置处理。代码如下:
// HandlerExecutionChain.java |
<1>
处,调用#getInterceptors()
方法,获得拦截器数组。即,「3.3 getInterceptors」 处的逻辑。<2>
处,遍历拦截器数组,逐个调用。<3>
处,调用HandlerInterceptor#preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
方法,执行拦截器的前置处理。如果成功,则返回true
,否则返回false
。<3.1>
处,执行失败,则调用#triggerAfterCompletion(HttpServletRequest request, HttpServletResponse response, Exception ex)
方法,触发已完成处理。详细解析,见 「3.5 triggerAfterCompletion」 。注意,此处不是触发当前拦截器的已完成逻辑,而是触发[0, interceptorIndex)
这几个拦截器已完成的逻辑( 不包括当前这个拦截器 ),并且是按照倒序执行的。- 返回
false
,因为有拦截器执行失败。
- 返回
<3.2>
处,标记interceptorIndex
位置。
<4>
处,返回true
,如果全部拦截器执行成功。
3.5 triggerAfterCompletion
#triggerAfterCompletion(HttpServletRequest request, HttpServletResponse response, Exception ex)
方法,触发拦截器的已完成处理。代码如下:
// HandlerExecutionChain.java |
- 代码比较简单,正如上文所说。胖友自己瞅瞅,每一行的注释,都要仔细看。
3.6 applyPostHandle
#applyPostHandle(HttpServletRequest request, HttpServletResponse response, ModelAndView mv)
方法,应用拦截器的后置处理。代码如下:
// HandlerExecutionChain.java |
- 代码比较简单。胖友自己瞅瞅,每一行的注释,都要仔细看。
3.7 applyAfterConcurrentHandlingStarted
TODO 1003
4. HandlerInterceptor 实现类
HandlerInterceptor 的实现类,如下图所示:
比较多,本文我们只看几个重要的实现类。
4.1 MappedInterceptor
org.springframework.web.servlet.handler.MappedInterceptor
,实现 HandlerInterceptor 接口,支持地址匹配的 HandlerInterceptor 实现类。
示例如下:
<mvc:interceptors> |
- 每一个
<mvc:interceptor />
标签,将被解析成一个 MappedInterceptor Bean 对象。
4.1.1 构造方法
// MappedInterceptor.java |
- 属性比较简单,胖友自己瞅瞅。
includePatterns
+excludePatterns
+pathMatcher
属性,匹配路径。interceptor
拦截器。
4.1.2 matches
#matches(String lookupPath, PathMatcher pathMatcher)
方法,判断路径是否匹配。代码如下:
// MappedInterceptor.java |
简单,胖友自己瞅瞅
4.1.3 拦截方法实现
// MappedInterceptor.java |
直接调用拦截器对应的方法。
4.2 其他
TODO WebRequestInterceptor
TODO ASYC
TODO
5. 拦截器配置
在 Spring MVC 中,有多种方式,配置拦截器。那么对拦截器的配置是怎么解析和初始化的呢?下面,我们逐小节来解析。当然,本文暂时不会特别详细解析,而是交给胖友自己去研读。😝
5.1 <mvc:interceptors />
标签
实际上,我们已经看过 <mvc:interceptors />
的配置示例。那么,再来看一次,哈哈哈哈。
<mvc:interceptors> |
- 每一个
<mvc:interceptor />
标签,会被org.springframework.web.servlet.config.InterceptorsBeanDefinitionParser
解析成 「4.1 MappedInterceptor」 对象,注册到 Spring IOC 容器中。 在 AbstractHandlerMapping 的
#detectMappedInterceptors(List<HandlerInterceptor> mappedInterceptors)
方法中,会扫描 MappedInterceptor Bean 。代码如下:// AbstractHandlerMapping.java
protected void detectMappedInterceptors(List<HandlerInterceptor> mappedInterceptors) {
// 扫描已注册的 MappedInterceptor 的 Bean 们,添加到 mappedInterceptors 中
// MappedInterceptor 会根据请求路径做匹配,是否进行拦截。
mappedInterceptors.addAll(
BeanFactoryUtils.beansOfTypeIncludingAncestors(
obtainApplicationContext(), MappedInterceptor.class, true, false).values());
}
所以,这种方式,胖友只要看看 InterceptorsBeanDefinitionParser 类即可。
当然,基于这样的思路,我们直接配置 MappedInterceptor 的 Bean 对象也是可以的,无论是通过 XML ,还是通过 @Bean
注解。
5.2 Java Config
在使用 Spring Boot 时,这是主流的方式。示例如下:
// SecurityInterceptor.java |
- SecurityInterceptor 是拦截器,通过
@Component
注册到 Spring IOC 容器中。因为它是 HandlerInterceptorAdapter 的子类,而不是 MappedInterceptor 的子类,所以不会被 AbstractHandlerMapping 的#detectMappedInterceptors(List<HandlerInterceptor> mappedInterceptors)
方法扫描到。 - 在 MVCConfiguration 的
#addInterceptors(InterceptorRegistry registry)
方法中,我们将securityInterceptor
拦截器添加到 InterceptorRegistry 这个拦截器注册表中。
所以,这种方式,胖友可能要看的类是不少的,艿艿暂时没有细看。
- InterceptorRegistry
- WebMvcConfigurerAdapter
通过 WebMvcConfigurationSupport 的 #getInterceptors()
方法,获得拦截器们。代码如下:
// WebMvcConfigurationSupport.java |
代码虽然比较长,重点在
<x>
处,调用#addInterceptors(InterceptorRegistry registry)
方法,添加拦截器到interceptors
中。代码如下:// WebMvcConfigurationSupport.java
protected void addInterceptors(InterceptorRegistry registry) {
}- 这是一个空方法,可由子类来进行实现。
我们一起来看 DelegatingWebMvcConfiguration 对
#addInterceptors(InterceptorRegistry registry)
方法,从 WebMvcConfigurer 对象中,获得拦截器,并添加到registry
中。代码如下:// DelegatingWebMvcConfiguration.java
private final WebMvcConfigurerComposite configurers = new WebMvcConfigurerComposite();
protected void addInterceptors(InterceptorRegistry registry) {
this.configurers.addInterceptors(registry);
}
// WebMvcConfigurerComposite.class
public void addInterceptors(InterceptorRegistry registry) {
for (WebMvcConfigurer delegate : this.delegates) {
delegate.addInterceptors(registry);
}
}- 其中,我们在示例给的 MVCConfiguration ,其实就是一个 WebMvcConfigurer 对象。这样,我们自己实现的 MVCConfiguration 的
#addInterceptors(InterceptorRegistry registry)
方法,就会被调用列。
- 其中,我们在示例给的 MVCConfiguration ,其实就是一个 WebMvcConfigurer 对象。这样,我们自己实现的 MVCConfiguration 的
而通过这样的方式的方式配置拦截器,最终通过 AbstractMappingHandler 的 #setInterceptors(Object... interceptors)
方法,设置到 MappingHandler 中。代码如下:
// AbstractHandlerMapping.java |
- 当然,具体哪些地方调用 AbstractMappingHandler 的
#setInterceptors(Object... interceptors)
方法,请使用 IDEA 的搜索下,哪些地方调用了 WebMvcConfigurationSupport 的#getInterceptors()
方法。
艿艿:关于这个过程,可以搭建一个 Spring Boot 项目,然后配置一个自定义拦截器,进行调试。
666. 彩蛋
正如文头所说,如果没实现过 Spring MVC 自定义拦截器的胖友,赶紧先来一发。😈
参考和推荐如下文章:
- 丶Format 《Spring MVC 拦截器详解[附带源码分析]》
- 郝佳 《Spring 源码深度解析》 的 「11.3 DispatcherServlet」 小节
- 韩路彪 《看透 Spring MVC:源代码分析与实践》 的 「第12章 HandlerMapping」 小节