Loading...

Spring Boot拦截器示例及源码原理分析

SpringMVC源码分析一文中,已经对SpringMVC的初始化及请求响应过程进行了分析,但未对拦截器进行深入研究。
本文将首先通过示例的方式了解拦截器的工作原理,然后再深入分析其源码来了解其内部原理。
本文代码基于Spring Boot+Kotlin。

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!")         }returntrue     }/**      * 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)@Autowiredprivate lateinit var mvc: MockMvc      @Test     funtestTestController() {         mvc.perform(get("/test/test")).andExpect(status().isOk)                 .andExpect(content().string("test"));     }  }

在此,一个测试的Interceptor及其测试的Controller及单元测试类即定义完成。
可以通过执行测试类看到测试结果,在此不细述。

在1.2章节中我们通过@EnableWebMvc注解来进行拦截器的自定义配置,通过分析该类及相关类,各个类的作用如下

与Configuration注解结合,可从WebMvcConfigurationSupport中引入SpringMVC的相关配置;如果需要修改引入的配置,需要通过实现WebMvcConfigurer接口提供的方法来进行。
注解EnableWebMvc在一个工程中只能注解在一个类上; 但实现WebMvcConfigurer的类可以有多个。
EnableWebMvc是如何引入WebMvcConfigurationSupport中的相关配置的呢?
我们来看下其本身实现:

@Retention(RetentionPolicy.RUNTIME)@Target(ElementType.TYPE)@Documented@Import(DelegatingWebMvcConfiguration.class)public @interfaceEnableWebMvc { }

可以看到其通过Import引入了DelegatingWebMvcConfiguration配置类, 而这个类又继承了WebMvcConfigurationSupport类。
关于这部分如何生效的后文将会进行详解。

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) }

SpringMVC源码分析一文的2.2.2.1章节中,已经分析过DiapatcherServlet中的Service方法的执行过程;跟拦截器相关的执行流程如下:

其关键就在Handle及拦截器的查找中;至于执行过程较为简单不再详细说明。

接下来我们分析拦截器的查找过程。
SpringMVC源码分析一文中,已经分析过查找过程在AbstractHandlerMapping中实现,实际查找拦截器在方法getHandlerExecutionChain中:

protected HandlerExecutionChaingetHandlerExecutionChain(Object handler, HttpServletRequest request) {     HandlerExecutionChain chain = (handlerinstanceof HandlerExecutionChain ?             (HandlerExecutionChain) handler :new HandlerExecutionChain(handler));      String lookupPath =this.urlPathHelper.getLookupPathForRequest(request);for (HandlerInterceptor interceptor :this.adaptedInterceptors) {if (interceptorinstanceof 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)publicvoidsetConfigurers(List<WebMvcConfigurer> configurers) {if (!CollectionUtils.isEmpty(configurers)) {this.configurers.addWebMvcConfigurers(configurers);     } }

通过查看Autowired注解定义,了解到当它使用在List参数的方法上时,会查找List所包含的对象类型的所有Bean然后进行注入。这也意味着,此处会将所有实现WebMvcConfigurer接口的类进行注入,然后添加到configurers属性中去;在此处,我们自定义的继承自WebMvcConfigurer的类会被注入。
再查看 DelegatingWebMvcConfiguration 这个类,它继承了 WebMvcConfigurationSupport 类。分析WebMvcConfigurationSupport,可以看到以下方法:

@Beanpublic RequestMappingHandlerMappingrequestMappingHandlerMapping() {     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方法的实现:

protectedfinal 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();     }returnthis.interceptors.toArray(); }

此处如果interceptors对象为空时,会调用addInterceptors方法;其实现在DelegatingWebMvcConfiguration类中:

@OverrideprotectedvoidaddInterceptors(InterceptorRegistry registry) {this.configurers.addInterceptors(registry); }

在前文已经描述到,DelegatingWebMvcConfiguration类中的configurers属性会将所有继承了WebMvcConfigurer的配置类全部添加进去。如我们自定义的配置类;在此处调用DelegatingWebMvcConfiguration的addInterceptors方法时,实际就是调用各个WebMvcConfigurer对象的addInterceptors方法来完成自定义的Interceptor注册过程。
通过这一系列过程,RequestMappingHandlerMapping的getInterceptors方法就可以获取到所有自定义的Interceptor了。