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

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了。

    原文作者:Spring Boot
    原文地址: https://blog.csdn.net/icarusliu/article/details/78833520
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞