在SpringMVC源码分析一文中,已经对SpringMVC的初始化及请求响应过程进行了分析,但未对拦截器进行深入研究。
本文将首先通过示例的方式了解拦截器的工作原理,然后再深入分析其源码来了解其内部原理。
本文代码基于Spring Boot+Kotlin。
1 自定义拦截器
GITHUB地址: https://github.com/icarusliu/learn
1.1 Interceptor定义
第一步我们先来定义一个Interceptor;
拦截器一般需要继承自HandlerInterceptor,并需要实现以下三个接口:
接口 | 接口名称 | 说明 |
---|---|---|
preHandle | 前置处理 | 在实际的Handle执行前执行;有Boolean类型的返回值,如果返回为False,则Handle本身及postHandle/afterCompletion以及后续的拦截器全部都不会再继续执行;为True则反之。 |
postHandle | 后置处理 | Handle执行后视图渲染前执行 |
afterCompletion | 完成后处理 | Handle执行且视图渲染完成后执行 |
Spring为方便使用实现了HandlerInterceptorAdapter的抽象类;需要实现的方法都实现为空的方法,在使用时只需实现必要的方法即可。
定义的测试拦截器见以下代码:
class TestInterceptor: HandlerInterceptorAdapter() {
private val logger = LoggerFactory.getLogger(HandlerInterceptorAdapter::class.java)
/** * This implementation always returns `true`. */
@Throws(Exception::class)
override fun preHandle(request: HttpServletRequest, response: HttpServletResponse, handler: Any): Boolean {
if (logger.isDebugEnabled) {
logger.debug("TestInterceptor preHandle begin to execute!")
}
return true
}
/** * This implementation is empty. */
@Throws(Exception::class)
override fun postHandle(request: HttpServletRequest, response: HttpServletResponse, handler: Any,
@Nullable modelAndView: ModelAndView?) {
if (logger.isDebugEnabled) {
logger.debug("TestInterceptor postHandle begin to execute!")
}
}
/** * This implementation is empty. */
@Throws(Exception::class)
override fun afterCompletion(request: HttpServletRequest, response: HttpServletResponse, handler: Any,
@Nullable ex: Exception?) {
if (logger.isDebugEnabled) {
logger.debug("TestInterceptor afterCompletion begin to execute!")
}
}
/** * This implementation is empty. */
@Throws(Exception::class)
override fun afterConcurrentHandlingStarted(request: HttpServletRequest, response: HttpServletResponse,
handler: Any) {
if (logger.isDebugEnabled) {
logger.debug("TestInterceptor afterConcurrentHandlingStarted begin to execute!")
}
}
}
1.2 拦截器配置
拦截器定义完成后,还需要将拦截器引入,并指定该拦截器所拦截的场景。
在SpringBoot中,一般通过使用EnableWebMvc及Configuration两个注解,并实现WebMvcConfigurer接口来添加拦截器,实现代码如下:
@EnableWebMvc
@Configuration
class WebConfig: WebMvcConfigurer {
override fun addInterceptors(registry: InterceptorRegistry) {
registry.addInterceptor(TestInterceptor()).addPathPatterns("/**")
}
}
注解一定要通过addPathPatterns来指定该拦截器所拦截的URL,如果不指定将不会拦截任何请求。.
1.3 定义Controller
Controller的定义比较简单,在此不细说,代码如下:
@RestController
@RequestMapping("/test")
class TestController {
private val logger = LoggerFactory.getLogger(TestController::class.java)
@RequestMapping("/test")
fun test(): String {
if (logger.isDebugEnabled) {
logger.debug("Test controller begin to execute!")
}
logger.info("Test!")
if (logger.isDebugEnabled) {
logger.debug("Test controller execution has been completed!")
}
return "test";
}
}
1.4 测试类定义
@RunWith(SpringRunner::class)
@WebMvcTest(TestController::class)
class LearninterceptorApplicationTests {
private val logger = LoggerFactory.getLogger(LearninterceptorApplicationTests::class.java)
@Autowired
private lateinit var mvc: MockMvc
@Test
fun testTestController() {
mvc.perform(get("/test/test")).andExpect(status().isOk)
.andExpect(content().string("test"));
}
}
在此,一个测试的Interceptor及其测试的Controller及单元测试类即定义完成。
可以通过执行测试类看到测试结果,在此不细述。
1.5 配置分析
在1.2章节中我们通过@EnableWebMvc注解来进行拦截器的自定义配置,通过分析该类及相关类,各个类的作用如下
1.5.1 EnableWebMvc
与Configuration注解结合,可从WebMvcConfigurationSupport中引入SpringMVC的相关配置;如果需要修改引入的配置,需要通过实现WebMvcConfigurer接口提供的方法来进行。
注解EnableWebMvc在一个工程中只能注解在一个类上; 但实现WebMvcConfigurer的类可以有多个。
EnableWebMvc是如何引入WebMvcConfigurationSupport中的相关配置的呢?
我们来看下其本身实现:
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Import(DelegatingWebMvcConfiguration.class)
public @interface EnableWebMvc {
}
可以看到其通过Import引入了DelegatingWebMvcConfiguration配置类, 而这个类又继承了WebMvcConfigurationSupport类。
关于这部分如何生效的后文将会进行详解。
1.5.2 WebMvcConfigurer
WebMvcConfigurer主要是提供接口来实现SpringMVC的自定义配置,其中它与Interceptor相关的就是addInterceptors方法,通过覆盖该方法,可以添加自定义Interceptor。
addInterceptors返回类型为InterceptorRegistration对象,通过查看该类实现,看到其提供的主要方法是: addPathPatterns/excludePathPatterns/pathMatcher/order,主要完成两个功能:一是提供配置所添加的Interceptor的映射路径的方法;二是提供配置所添加的Interceptor的Order的方法,通过Order可控制所添加的Interceptor在所有Interceptors中的执行顺序。
其使用代码如下:
override fun addInterceptors(registry: InterceptorRegistry) {
registry.addInterceptor(TestInterceptor())
.addPathPatterns("/**")
.order(1000)
}
2 拦截器生效过程源码分析
在SpringMVC源码分析一文的2.2.2.1章节中,已经分析过DiapatcherServlet中的Service方法的执行过程;跟拦截器相关的执行流程如下:
Created with Raphaël 2.1.0 开始 查找Handle及拦截器 执行拦截器前置处理preHandle 执行Handle 执行拦截器后置处理postHandle 执行视图渲染 执行拦截器afterCompletion 结束
其关键就在Handle及拦截器的查找中;至于执行过程较为简单不再详细说明。
接下来我们分析拦截器的查找过程。
在SpringMVC源码分析一文中,已经分析过查找过程在AbstractHandlerMapping中实现,实际查找拦截器在方法getHandlerExecutionChain中:
protected HandlerExecutionChain getHandlerExecutionChain(Object handler, HttpServletRequest request) {
HandlerExecutionChain chain = (handler instanceof HandlerExecutionChain ?
(HandlerExecutionChain) handler : new HandlerExecutionChain(handler));
String lookupPath = this.urlPathHelper.getLookupPathForRequest(request);
for (HandlerInterceptor interceptor : this.adaptedInterceptors) {
if (interceptor instanceof MappedInterceptor) {
MappedInterceptor mappedInterceptor = (MappedInterceptor) interceptor;
if (mappedInterceptor.matches(lookupPath, this.pathMatcher)) {
chain.addInterceptor(mappedInterceptor.getInterceptor());
}
}
else {
chain.addInterceptor(interceptor);
}
}
return chain;
}
该方法就是从adaptedInterceptors属性中,根据URL查找添加条件的Interceptor并组装成HandlerExecutionChain并返回。
结合1.5.2中的分析,可以知道此处是否满足条件的判断是根据添加拦截器配置时调用的addPathPatterns方法决定的。具体判定过程不再赘述。
那么,现在的问题就是adaptedInterceptors属性是如何初始化的。
通过分析AbstractHandlerMapping类,其adaptedInterceptors属性实际是在initInterceptors方法中根据interceptors来进行初始化的。现在的问题转变成interceptors这个属性是如何初始化的了。 实际上这个属性是通过setInterceptors方法来设置的,但通过Alt+F7的搜索并未搜索到该方法是在哪个地方调用的。
我们换个思路,通过@EnableWebMvc来分析看通过addInterceptors方法配置的Interceptor在到底添加到哪去了。
前言已经分析,通过@EnableWebMvc注解实际上引入了DelegatingWebMvcConfiguration这个类;查看这个类,在其中有一方法被Autowired注解:
@Autowired(required = false)
public void setConfigurers(List<WebMvcConfigurer> configurers) {
if (!CollectionUtils.isEmpty(configurers)) {
this.configurers.addWebMvcConfigurers(configurers);
}
}
通过查看Autowired注解定义,了解到当它使用在List参数的方法上时,会查找List所包含的对象类型的所有Bean然后进行注入。这也意味着,此处会将所有实现WebMvcConfigurer接口的类进行注入,然后添加到configurers属性中去;在此处,我们自定义的继承自WebMvcConfigurer的类会被注入。
再查看 DelegatingWebMvcConfiguration 这个类,它继承了 WebMvcConfigurationSupport 类。分析WebMvcConfigurationSupport,可以看到以下方法:
@Bean
public RequestMappingHandlerMapping requestMappingHandlerMapping() {
RequestMappingHandlerMapping mapping = createRequestMappingHandlerMapping();
mapping.setOrder(0);
mapping.setInterceptors(getInterceptors());
mapping.setContentNegotiationManager(mvcContentNegotiationManager());
mapping.setCorsConfigurations(getCorsConfigurations());
PathMatchConfigurer configurer = getPathMatchConfigurer();
Boolean useSuffixPatternMatch = configurer.isUseSuffixPatternMatch();
Boolean useRegisteredSuffixPatternMatch = configurer.isUseRegisteredSuffixPatternMatch();
Boolean useTrailingSlashMatch = configurer.isUseTrailingSlashMatch();
if (useSuffixPatternMatch != null) {
mapping.setUseSuffixPatternMatch(useSuffixPatternMatch);
}
if (useRegisteredSuffixPatternMatch != null) {
mapping.setUseRegisteredSuffixPatternMatch(useRegisteredSuffixPatternMatch);
}
if (useTrailingSlashMatch != null) {
mapping.setUseTrailingSlashMatch(useTrailingSlashMatch);
}
UrlPathHelper pathHelper = configurer.getUrlPathHelper();
if (pathHelper != null) {
mapping.setUrlPathHelper(pathHelper);
}
PathMatcher pathMatcher = configurer.getPathMatcher();
if (pathMatcher != null) {
mapping.setPathMatcher(pathMatcher);
}
return mapping;
}
可以看到RequestMappingHandlerMapping类被注入Spring容器。
同时通过mapping.setInterceptors(getInterceptors())将所有的Interceptors设置到HandperMapping对象中 。
这样就找到了ReuqestMappingHandlerMapping的setInterceptors方法调用处了。
接下来的问题就是此处调用的getInterceptors方法的实现:
protected final Object[] getInterceptors() {
if (this.interceptors == null) {
InterceptorRegistry registry = new InterceptorRegistry();
addInterceptors(registry);
registry.addInterceptor(new ConversionServiceExposingInterceptor(mvcConversionService()));
registry.addInterceptor(new ResourceUrlProviderExposingInterceptor(mvcResourceUrlProvider()));
this.interceptors = registry.getInterceptors();
}
return this.interceptors.toArray();
}
此处如果interceptors对象为空时,会调用addInterceptors方法;其实现在DelegatingWebMvcConfiguration类中:
@Override
protected void addInterceptors(InterceptorRegistry registry) {
this.configurers.addInterceptors(registry);
}
在前文已经描述到,DelegatingWebMvcConfiguration类中的configurers属性会将所有继承了WebMvcConfigurer的配置类全部添加进去。如我们自定义的配置类;在此处调用DelegatingWebMvcConfiguration的addInterceptors方法时,实际就是调用各个WebMvcConfigurer对象的addInterceptors方法来完成自定义的Interceptor注册过程。
通过这一系列过程,RequestMappingHandlerMapping的getInterceptors方法就可以获取到所有自定义的Interceptor了。