0002 - Spring MVC 拦截器源码简析:拦截器加载与执行

时间:2023-03-09 21:20:39
0002 - Spring MVC 拦截器源码简析:拦截器加载与执行

1、概述

Spring MVC中的拦截器(Interceptor)类似于Servlet中的过滤器(Filter),它主要用于拦截用户请求并作相应的处理。例如通过拦截器可以进行权限验证、记录请求信息的日志、判断用户是否登录等。

2、简单示例

2.1.继承 HandlerInterceptorAdapter 抽象类实现一个拦截器。代码如下:

public class DemoInterceptor extends HandlerInterceptorAdapter {

@Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        System.out.println("[DemoInterceptor]preHandle");
        return true;
    }

@Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
        System.out.println("[DemoInterceptor]postHandle");
    }

@Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        System.out.println("[DemoInterceptor]afterCompletion");
    }

}
2.2.在指定给 DispatcherServlet 的配置文件中新增相关拦截器配置。

<mvc:interceptors>
    <mvc:interceptor>  
        <mvc:mapping path="/**"/>  
        <bean class="com.ryan.springtest.interceptor.DemoInterceptor"></bean>  
    </mvc:interceptor>
</mvc:interceptors>
此时启动服务访问任一 URL,即可看到相应的输出信息。

[DemoInterceptor]preHandle
[DemoInterceptor]postHandle
[DemoInterceptor]afterCompletion

3、示例分析

简单分析一下上面的实例代码。
0002 - Spring MVC 拦截器源码简析:拦截器加载与执行

0002 - Spring MVC 拦截器源码简析:拦截器加载与执行

通过观察 HandlerInterceptorAdapter 的继承关系与数据结构,可知 HandlerInterceptorAdapter 实现了 AsyncHandlerInterceptor 与 HandlerInterceptor 接口,并对里面的抽象方法做了默认实现。

  • preHandle:Controller 执行之前执行,返回 true 表示继续流程,false 中断当前流程,但会执行之前拦截器的 afterCompletion 方法;
  • postHandle:Controller 方法调用之后,视图渲染之前执行;
  • afterCompletion:渲染视图之后执行;
  • afterConcurrentHandlingStarted:涉及到 Spring 的异步处理特性,先不讨论。

3.1、运行流程图如下:

0002 - Spring MVC 拦截器源码简析:拦截器加载与执行

0002 - Spring MVC 拦截器源码简析:拦截器加载与执行

4、源码分析

源码的分析将分为三部分:拦截器的加载,拦截器链的生成,拦截器链的执行。

4.1、拦截器的加载

配置文件简析

观察示例代码中的配置文件,省略了与分析无关的配置后,如下所示:

<beans
    xmlns:mvc="http://www.springframework.org/schema/mvc"
    http://www.springframework.org/schema/mvc  
    http://www.springframework.org/schema/mvc/spring-mvc-3.2.xsd">

<mvc:interceptors>
        <mvc:interceptor>  
            <mvc:mapping path="/**"/>
            <bean class="com.ryan.springtest.interceptor.DemoInterceptor"></bean>  
        </mvc:interceptor>
    </mvc:interceptors>

</beans>
配置信息分为两部分:(1)声明 Schema 的命名空间并指定 xsd 文件;(2)拦截器的具体配置。

interceptors 的配置采用了 Schema Based XML 的组件定义方式,这是 Spring 为了简化组件配置而引入的重要手段,这里不作展开。

通过配置文件,我们可以定位到具体解析拦截器配置的类,类的路径在 spring-webmvc 下的 META-INF 中的 spring.handlers 文件中指定。
0002 - Spring MVC 拦截器源码简析:拦截器加载与执行

spring.handlers 文件内容如下,可知实际解析配置的类是 MvcNamespaceHandler。
  http\://www.springframework.org/schema/mvc=org.springframework.web.servlet.config.MvcNamespaceHandler

暂时不对 MvcNamespaceHandler 进行具体的分析,先来看看 MvcNamespaceHandler 是在何时被创建和执行的。

4.2、拦截器的加载过程

拦截器的加载是在 DispatcherServlet 初始化的过程中完成的,下面通过 DispatcherServlet 初始化时的调用图来了解一下拦截器加载的具体触发时机。
0002 - Spring MVC 拦截器源码简析:拦截器加载与执行

通过继承关系,可以发现 DispatcherServlet 实际上是一个 Servlet,根据 Servlet 的规范,服务启动时会调用 Servlet 的 init 方法,init 方法在 HttpServletBean 中实现,HttpServletBean 又会调用 FrameworkServlet 来创建 SpringMVC 单独的容器,并向容器进行了设置,然后调用容器的 refresh 方法触发容器的初始化操作。

在 DispatcherServlet 的初始化中,分为两个层次。

  • DispatcherServlet 对容器的初始化。
  • 容器对配置文件的解析以及相关组件的初始化。

所以真正初始化拦截器的动作是由容器(XmlWebApplicationContext)来完成的,观察下图容器初始化过程。

0002 - Spring MVC 拦截器源码简析:拦截器加载与执行

上图展示的是,从 XmlWebApplicationContext 的 refresh 方法开始,到调用 MvcNamespaceHandler 解析拦截器配置的执行过程。

初始化过程涉及到多个组件间的调用,简要分析如下。

  • XmlWebApplicationContext 中创建了 beanFactory,通过 beanFactory 获取到 XmlBeanDefinitionReader 实例,接着获取到配置文件的路径,使用 XmlBeanDefinitionReader 来解析配置文件。
  • XmlBeanDefinitionReader 将配置文件转换为 Document 对象,并创建了 BeanDefinitionDocumentReader 对象来解析 Document 对象。
  • BeanDefinitionDocumentReader 遍历 Document 中的所有节点,拦截器的配置节点不属于默认的配置节点,将创建对应的 NamespaceHandler 来解析,即上文提到的 MvcNamespaceHandler。
  • 创建 MvcNamespaceHandler 后,首先会调用对应的 init 方法注册所有的 BeanDefinitionParser,接着会遍历注册的 BeanDefinitionParser,找到合适的 BeanDefinitionParser 对传入的节点进行解析。

下面开始对 MvcNamespaceHandler 的分析,观察 MvcNamespaceHandler 的继承关系。
0002 - Spring MVC 拦截器源码简析:拦截器加载与执行

MvcNamespaceHandler 继承了 NamespaceHandlerSupport 抽象类并实现了 NamespaceHandler 接口,上文的分析提到,创建 MvcNamespaceHandler 后首先会调用 init 方法进行初始化,init 方法在 NamespaceHandler 中定义并在 MvcNamespaceHandler 中实现,截取代码如下。

public class MvcNamespaceHandler extends NamespaceHandlerSupport {

@Override
    public void init() {
        //略
        registerBeanDefinitionParser("interceptors", new InterceptorsBeanDefinitionParser());
        //略
    }

}
init 方法中,调用了 registerBeanDefinitionParser 方法注册了相关的解析器,拦截器对应的解析器是 InterceptorsBeanDefinitionParser。

private final Map<String, BeanDefinitionParser> parsers = new HashMap<String, BeanDefinitionParser>();

protected final void registerBeanDefinitionParser(String elementName, BeanDefinitionParser parser) {
        this.parsers.put(elementName, parser);
    }
registerBeanDefinitionParser 方法在 NamespaceHandlerSupport 中实现,如上所示,向 parsers 这个 Map 中 put 进了对应的 Key 与 Value。

初始化完成之后,便会开始遍历配置文件中的节点,当扫描到 <mvc:interceptors> 节点时,便会调用 MvcNamespaceHandler 中的 parse 方法进行解析。

parse 方法在 NamespaceHandler 中定义,并在 NamespaceHandlerSupport 中实现。

public BeanDefinition parse(Element element, ParserContext parserContext) {
        return findParserForElement(element, parserContext).parse(element, parserContext);
    }

private BeanDefinitionParser findParserForElement(Element element, ParserContext parserContext) {
        String localName = parserContext.getDelegate().getLocalName(element);
        BeanDefinitionParser parser = this.parsers.get(localName);
        if (parser == null) {
            parserContext.getReaderContext().fatal(
                    "Cannot locate BeanDefinitionParser for element [" + localName + "]", element);
        }
        return parser;
    }
如上所示,获取到节点的 localName,即 interceptors,接着从 parsers 获取对应的解析器,调用解析器的 parse 方法进行解析,即上文注册的 InterceptorsBeanDefinitionParser,具体代码如下所示。

public BeanDefinition parse(Element element, ParserContext parserContext) {
    CompositeComponentDefinition compDefinition = new
        CompositeComponentDefinition(element.getTagName(),
        parserContext.extractSource(element));
    parserContext.pushContainingComponent(compDefinition);

RuntimeBeanReference pathMatcherRef = null;
    if (element.hasAttribute("path-matcher")) {
        pathMatcherRef = new RuntimeBeanReference(element.getAttribute("path-matcher"));
    }

List<Element> interceptors = DomUtils.getChildElementsByTagName(element, "bean", "ref", "interceptor");
    for (Element interceptor : interceptors) {
        RootBeanDefinition mappedInterceptorDef = new
            RootBeanDefinition(MappedInterceptor.class);

mappedInterceptorDef.setSource(parserContext.extractSource(interceptor));
        mappedInterceptorDef.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);

ManagedList<String> includePatterns = null;
        ManagedList<String> excludePatterns = null;
        Object interceptorBean;
        if ("interceptor".equals(interceptor.getLocalName())) {
            includePatterns = getIncludePatterns(interceptor, "mapping");
            excludePatterns = getIncludePatterns(interceptor, "exclude-mapping");
            Element beanElem = DomUtils.getChildElementsByTagName(interceptor, "bean", "ref").get(0);
            interceptorBean = parserContext.getDelegate().parsePropertySubElement(beanElem, null);
        }
        else {
            interceptorBean = parserContext.getDelegate().parsePropertySubElement(interceptor, null);
        }
        mappedInterceptorDef.getConstructorArgumentValues().
                addIndexedArgumentValue(0, includePatterns);
        mappedInterceptorDef.getConstructorArgumentValues().
            addIndexedArgumentValue(1, excludePatterns);
        mappedInterceptorDef.getConstructorArgumentValues().
            addIndexedArgumentValue(2, interceptorBean);

if (pathMatcherRef != null) {
            mappedInterceptorDef.getPropertyValues().add("pathMatcher", pathMatcherRef);
        }

String beanName = parserContext.getReaderContext().registerWithGeneratedName(mappedInterceptorDef);
        parserContext.registerComponent(new BeanComponentDefinition(mappedInterceptorDef, beanName));
    }

parserContext.popAndRegisterContainingComponent();
    return null;
    }
传入的 element 即为 mvc:interceptors 节点对象,上述代码主要做了如下一些事情:

  • 获取 mvc:interceptors 节点下的 bean、ref、interceptor 节点数组。
  • 遍历节点数组,创建 MappedInterceptor 对象。若节点对象为 interceptor,将mapping、exclude-mapping、bean 或 ref 节点信息设置到 MappedInterceptor中。若节点对象为 bean 或 ref,则仅设置 bean 或 ref 节点信息,mapping 与 exclude-mapping 则设置为空,即会拦截所有请求。
  • 将 MappedInterceptor 对象注册到容器中,以便后续使用。

至此,我们已经根据配置文件中的拦截器配置,生成了 MappedInterceptor 拦截器对象。

SpringMVC 接收到 Web 请求时,会由 HandlerMapping 生成对应的拦截器链,为了便于处理,SpringMVC 还会将 MappedInterceptor 对象加载到 HandlerMapping 的成员变量中,这一步的加载稍微隐藏得比较深,可以观察下面的流程图。
0002 - Spring MVC 拦截器源码简析:拦截器加载与执行
handlerMaping初始化

FrameworkServlet 在 configureAndRefreshWebApplicationContext 方法中,向容器中注册了一个监听器(ContextRefreshListener),正是这一步操作触发了上述流程,简要分析如下:

  • AbstractApplicationContext 在 refresh 方法中加载完拦截器后,会调用 finishRefresh 方法,触发 ContextRefreshListener 的执行。
  • ContextRefreshListener 触发 FrameworkServlet 的 onApplicationEvent 方法,把初始化的控制权交给 DispatcherServlet。
  • DispatcherServlet 触发 HandlerMapping 进行初始化,HandlerMapping 默认实现之一是 RequestMappingHandlerMapping,这里以此为例进行说明。
  • 观察 RequestMappingHandlerMapping 的继承关系,实现了 ApplicationContextAware 接口,Spring 在初始化 ApplicationContextAware 实现类的时候,会调用其 setApplicationContext 方法。
  • setApplicationContext 方法由 ApplicationObjectSupport 实现,最终会调用 AbstractHandlerMapping 的 initApplicationContext 方法,完成拦截器的加载,相关代码展示如下。

protected void initApplicationContext() throws BeansException {
    extendInterceptors(this.interceptors);
    detectMappedInterceptors(this.adaptedInterceptors);
    initInterceptors();
}

protected void detectMappedInterceptors(List<HandlerInterceptor> mappedInterceptors) {
    mappedInterceptors.addAll(
        BeanFactoryUtils.beansOfTypeIncludingAncestors(
            obtainApplicationContext(), MappedInterceptor.class, true, false).values());
}
detectMappedInterceptors 方法探测 ApplicationContext 中已经解析过的 MappedInterceptor,全部存放到 AbstractHandlerMapping 的 adaptedInterceptors 属性上,extendInterceptors 方法留给子类扩展,目前还没有子类实现。

至此,拦截器的相关信息已经加载完成,后续有请求进来的时候就可以直接进行匹配。

4.3、拦截器链生成

当请求进来的时候会被匹配的拦截器拦截,多个拦截器组成一个拦截器链,并按照我们定义的顺序执行。

SpringMVC 中的所有请求都需要经过 DispatcherServlet 进行分发,拦截器链也是在其中生成的,当请求被 DispatcherServlet 拦截后,会在 doDispatch 方法中进行分发,下面截取部分关键代码。

protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
        HttpServletRequest processedRequest = request;
        HandlerExecutionChain mappedHandler = null;
        boolean multipartRequestParsed = false;

WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);

try {
            ModelAndView mv = null;
            Exception dispatchException = null;

try {
                processedRequest = checkMultipart(request);
                multipartRequestParsed = (processedRequest != request);

// Determine handler for the current request.
                mappedHandler = getHandler(processedRequest);
                //略
            }
            catch (Exception ex) {
                //略
            }
        }
        catch (Exception ex) {
            //略
        }
    }
每个请求需生成对应的 HandlerExecutionChain,HandlerExecutionChain 中包含了目标 service 和对应的拦截器链,从上面的源码可以看到,getHandler 会返回一个 HandlerExecutionChain。

protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
    if (this.handlerMappings != null) {
        for (HandlerMapping hm : this.handlerMappings) {
            if (logger.isTraceEnabled()) {
                logger.trace("Testing handler map [" + hm + "] in DispatcherServlet with name '" + getServletName() + "'");
            }
            HandlerExecutionChain handler = hm.getHandler(request);
            if (handler != null) {
                return handler;
            }
        }
    }
    return null;
}
getHandler 方法中会遍历找到所有 HandlerMapping,调用其中的 getHandler 方法,直到找到一个合适的。其中就包括上文提到的 RequestMappingHandlerMapping。

public final HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
    Object handler = getHandlerInternal(request);
    //略

HandlerExecutionChain executionChain = getHandlerExecutionChain(handler, request);
    //略
    return executionChain;
    }
getHandler 的实现在 AbstractHandlerMapping 类中,找到对应请求的 handler 后,便会调用 getHandlerExecutionChain 方法获取 HandlerExecutionChain,这里就是生成拦截器链的关键代码。

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;
}
getHandlerExecutionChain 方法中,遍历 AbstarctHandlerMapping 的 adaptedInterceptors 属性,使用默认的 pathMatcher,判断当前的请求是否符合拦截条件,若符合则将 mappedInterceptor 放进 HandlerExecutionChain 中。

至此一个 HandlerExecutionChain 便构建好了,包含一个 handler 和我们想要的拦截器链。

4.4、拦截器链执行

获取到拦截器链之后,接下来就是按照一定的顺序执行其中的方法,回到上一步分析开始的 doDispatch 方法中,可以看到拦截器链的具体执行过程。

protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
        HttpServletRequest processedRequest = request;
        HandlerExecutionChain mappedHandler = null;
        //略
        try {
            ModelAndView mv = null;
            Exception dispatchException = null;
            try {
                //略
                mappedHandler = getHandler(processedRequest);
                if (mappedHandler == null) {
                    noHandlerFound(processedRequest, response);
                    return;
                }
                // Determine handler adapter for the current request.
                HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
                //略
                if (!mappedHandler.applyPreHandle(processedRequest, response)) {
                    return;
                }
                // Actually invoke the handler.
                mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
                //略
                applyDefaultViewName(processedRequest, mv);
                mappedHandler.applyPostHandle(processedRequest, response, mv);
            }
            catch (Exception ex) {
                dispatchException = ex;
            }
            catch (Throwable err) {
                //略
            }
            processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
        }
        catch (Exception ex) {
            triggerAfterCompletion(processedRequest, response, mappedHandler, ex);
        }
        catch (Throwable err) {
            triggerAfterCompletion(processedRequest, response, mappedHandler,
                    new NestedServletException("Handler processing failed", err));
        }
        finally {
            //略
        }
    }
在本文开始的代码示例中,我们实现了拦截器中的三个方法,这三个方法的具体执行时机和顺序分析如下。

(1)preHandle

if (!mappedHandler.applyPreHandle(processedRequest, response)) {
    return;
}
boolean applyPreHandle(HttpServletRequest request, HttpServletResponse response) throws Exception {
        HandlerInterceptor[] interceptors = getInterceptors();
        if (!ObjectUtils.isEmpty(interceptors)) {
            for (int i = 0; i < interceptors.length; i++) {
                HandlerInterceptor interceptor = interceptors[i];
                if (!interceptor.preHandle(request, response, this.handler)) {
                    triggerAfterCompletion(request, response, null);
                    return false;
                }
                this.interceptorIndex = i;
            }
        }
        return true;
}
preHandle 的执行顺序在 HandlerExecutionChain 中控制,正序遍历拦截器链,执行拦截器的 preHandle 方法,并会记录下当前执行的下标,若 preHandle 返回 false 则会执行 triggerAfterCompletion 方法,若异常则抛出。

(2)postHandle

mappedHandler.applyPostHandle(processedRequest, response, mv);
ha.handle 执行目标 Controller 的方法,执行完后就会执行 applyPostHandle。

void applyPostHandle(HttpServletRequest request, HttpServletResponse response, @Nullable ModelAndView mv)
            throws Exception {

HandlerInterceptor[] interceptors = getInterceptors();
        if (!ObjectUtils.isEmpty(interceptors)) {
            for (int i = interceptors.length - 1; i >= 0; i--) {
                HandlerInterceptor interceptor = interceptors[i];
                interceptor.postHandle(request, response, this.handler, mv);
            }
        }
    }
postHandle 的执行顺序在 HandlerExecutionChain 中控制,倒序遍历拦截器链,并执行拦截器的 postHandle 方法,若异常则抛出。

(3)afterCompletion

processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);

triggerAfterCompletion(processedRequest, response, mappedHandler, ex);
在末尾的 processDispatchResult 方法和异常处理的 triggerAfterCompletion 方法中,都会调用 HandlerExecutionChain的triggerAfterCompletion 方法。

void triggerAfterCompletion(HttpServletRequest request, HttpServletResponse response, @Nullable Exception ex)
            throws Exception {

HandlerInterceptor[] interceptors = getInterceptors();
        if (!ObjectUtils.isEmpty(interceptors)) {
            for (int i = this.interceptorIndex; i >= 0; i--) {
                HandlerInterceptor interceptor = interceptors[i];
                try {
                    interceptor.afterCompletion(request, response, this.handler, ex);
                }
                catch (Throwable ex2) {
                    logger.error("HandlerInterceptor.afterCompletion threw exception", ex2);
                }
            }
        }
    }
triggerAfterCompletion 方法从指定下标 interceptorIndex 开始,倒序遍历拦截器链,并执行拦截器的 afterCompletion 方法,若异常则记录到日志中,interceptorIndex 的下标是在 preHandle 中设置的,这里就是上文中提到的当 preHandle 返回 false,仍会执行之前拦截器的 afterCompletion 方法的原因。

至此,拦截器链的执行便完成了。