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

扫码关注公众号:Java 技术驿站

发送:vip
将链接复制到本浏览器,永久解锁本站全部文章

【公众号:Java 技术驿站】 【加作者微信交流技术,拉技术群】

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


来源:http://ddrv.cn

赞(0) 打赏
版权归原创作者所有,任何形式的转载请联系博主:daming_90:Java 技术驿站 » Spring Boot拦截器示例及源码原理分析

评论 抢沙发

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

微信扫一扫打赏