💕重要接口及类介绍
  1. HandlerExecutionChain
    HandlerMethodInterceptor 集合组成的类,会被 HandlerMapping 接口的 getHandler 方法获取。
  2. 开发springmvc拦截器需要实现什么接口_SpringMVC拦截器的原理

  3. HandlerInterceptor 接口 
  4. 开发springmvc拦截器需要实现什么接口_SpringMVC拦截器的原理_02

  5. SpringMVC 拦截器基础接口。
  6. AbstractHandlerMappingHandlerMapping 的基础抽象类。
  7. 开发springmvc拦截器需要实现什么接口_拦截器_03

  8. AsyncHandlerInterceptor 继承 HandlerInterceptor 的接口,额外提供了 afterConcurrentHandlingStarted 方法,该方法是用来处理异步请求。当 Controller 中有异步请求方法的时候会触发该方法。楼主做过测试,异步请求先支持 preHandle 、然后执行 afterConcurrentHandlingStarted。异步线程完成之后执行 preHandlepostHandle、afterCompletion 。有兴趣的读者可自行研究。
  9. HandlerInterceptorAdapter 实现 AsyncHandlerInterceptor 接口的抽象类,一般我们使用拦截器的话都会继承这个类。然后复写相应的方法。
  10. WebRequestInterceptorHandlerInterceptor 接口类似,区别是 WebRequestInterceptorpreHandle 没有返回值。还有 WebRequestInterceptor 是针对请求的,接口方法参数中没有 response
  11. 开发springmvc拦截器需要实现什么接口_mvc_04

  12.   
    AbstractHandlerMapping 内部的 interceptors 是个 Object 类型集合。处理的时候判断为 MappedInterceptor [加入到 mappedInterceptors 集合中]; HandlerInterceptor、WebRequestInterceptor (适配成WebRequestHandlerInterceptorAdapter)[加入到adaptedInterceptors中]
  13. MappedInterceptor 一个包括 includePatternsexcludePatterns 字符串集合并带有 HandlerInterceptor 的类。很明显,就是对于某些地址做特殊包括和排除的拦截器。
  14. 开发springmvc拦截器需要实现什么接口_抽象类_05

  15. ConversionServiceExposingInterceptor 默认的 <annotation-driven/> 标签初始化的时候会初始化 ConversionServiceExposingInterceptor 这个拦截器,并被当做构造方法的参数来构造 MappedInterceptor 。之后会被加入到 AbstractHandlerMappingmappedInterceptors 集合中。该拦截器会在每个请求之前往 request 中丢入 ConversionService 。主要用于 spring:eval 标签的使用。

🤞源码分析

首先我们看下拦截器的如何被调用的。

Web 请求被 DispatcherServlet 截获后,会调用 DispatcherServletdoDispatcher 方法。

开发springmvc拦截器需要实现什么接口_抽象类_06


开发springmvc拦截器需要实现什么接口_SpringMVC拦截器的原理_07


很明显地看到,在 HandlerAdapter 处理之后,以及处理完成之后会调用 HandlerExecutionChain 的方法。

HandlerExecutionChain的applyPreHandle、applyPostHandle、triggerAfterCompletion

方法如下:

开发springmvc拦截器需要实现什么接口_SpringMVC拦截器的原理_08


开发springmvc拦截器需要实现什么接口_mvc_09


开发springmvc拦截器需要实现什么接口_抽象类_10

很明显,就是调用内部实现 HandlerInterceptor 该接口集合的各个对应方法。

下面我们看下 HandlerExecutionChain 的构造过程。

HandlerExecutionChain 是从 HandlerMapping 接口的 getHandler 方法获取的。

HandlerMapping 的基础抽象类 AbstractHandlerMapping 中:

开发springmvc拦截器需要实现什么接口_SpringMVC拦截器的原理_11


开发springmvc拦截器需要实现什么接口_mvc_12


我们看到,HandlerExecutionChain 的拦截器是从 AbstractHandlerMapping 中的 adaptedInterceptorsmappedInterceptors 属性中获取的。


✌清楚了HandlerExecutionChain的拦截器属性如何构造之后,下面来看下SpringMVC是如何配置拦截器的。
  1. *-dispatcher.xml配置文件中添加 mvc:interceptors配置
<mvc:interceptors>
  <mvc:interceptor>
    <mvc:mapping path="/**"/>
    <mvc:exclude-mapping path="/login"/>   
    <mvc:exclude-mapping path="/index"/>
    <bean class="package.interceptor.XXInterceptor"/>
  </mvc:interceptor>
</mvc:interceptors>

这里配置的每个 <mvc:interceptor> 都会被解析成 MappedInterceptor

其中子标签 <mvc:mapping path="/**"/> 会被解析成 MappedInterceptorincludePatterns 属性;<mvc:exclude-mapping path="/**"/> 会被解析成 MappedInterceptorexcludePatterns 属性;<bean/> 会被解析成 MappedInterceptorinterceptor 属性。

<mvc:interceptors> 这个标签是被 InterceptorsBeanDefinitionParser 类解析。

开发springmvc拦截器需要实现什么接口_拦截器_13

  1. 配置 RequestMappingHandlerMapping,并配置该 bean 对应的 interceptors集合属性。这里的 interceptors 集合是个 Object 类型的泛型集合。

AbstractHandlerMapping 抽象类只暴露了1个拦截器的set方法 -> interceptors

adaptedInterceptors和mappedInterceptors 均没有暴露 set 方法,因此我们只能为 RequestMappingHandlerMapping 配置 interceptors 属性。

其实 AbstractHandlerMapping 内部的 initInterceptors 方法中,会遍历 interceptors 集合,然后判断各个项是否是 MappedInterceptor、HandlerInterceptor、WebRequestInterceptor

其中 MappedInterceptor 类型的拦截器会被加到 mappedInterceptors 集合中, HandlerInterceptor 类型的会被加到 adaptedInterceptors 集合中, WebRequestInterceptor 类型的会被适配成 WebRequestHandlerInterceptorAdapter 加到 adaptedInterceptors 集合中。

开发springmvc拦截器需要实现什么接口_mvc_14


开发springmvc拦截器需要实现什么接口_抽象类_15

如果配置了:

<mvc:annotation-driven/>

那么配置如下:

<bean class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping">
  <property name="interceptors">
    <bean class="package.interceptor.XXInterceptor"/>
  </property>  
  <property name="order" value="-1"/>
</bean>

否则,可以去掉order这个属性的设置。

一般建议使用第一种方法。

编写自定义的拦截器

public class LoginInterceptor extends HandlerInterceptorAdapter {
  
  @Override
  public boolean preHandle(HttpServletRequest request, HttpServletResponse response,
                             Object handler) throws Exception { 
        // 获得请求路径的uri
        String uri = request.getRequestURI();

        // 判断路径是登出还是登录验证,是这两者之一的话执行Controller中定义的方法
        if(uri.endsWith("/login/auth") || uri.endsWith("/login/out")) {
            return true;
        }

        // 进入登录页面,判断session中是否有key,有的话重定向到首页,否则进入登录界面
        if(uri.endsWith("/login/") || uri.endsWith("/login")) {
            if(request.getSession() != null && request.getSession().getAttribute("loginUser") != null) {
                response.sendRedirect(request.getContextPath() + "/index");
            } else {
                return true;
            }
        }

        // 其他情况判断session中是否有key,有的话继续用户的操作
        if(request.getSession() != null && request.getSession().getAttribute("loginUser") != null) {
            return true;
        }

        // 最后的情况就是进入登录页面
        response.sendRedirect(request.getContextPath() + "/login");
        return false;
  }
  
}

登录Controller:

@Controller
@RequestMapping(value = "/login")
public class LoginController {
  
    @RequestMapping(value = {"/", ""})
    public String index() {
        return "login";
    }

    @RequestMapping("/auth")
    public String auth(@RequestParam String username, HttpServletRequest req) {
        req.getSession().setAttribute("loginUser", username);
        return "redirect:/index";
    }

    @RequestMapping("/out")
    public String out(HttpServletRequest req) {
        req.getSession().removeAttribute("loginUser");
        return "redirect:/login";
    }
  
}

*-diapatcher.xml配置:

<mvc:interceptors>
  <mvc:interceptor>
    <mvc:mapping path="/**"/>
    <bean class="org.format.demo.interceptor.LoginInterceptor"/>
  </mvc:interceptor>
</mvc:interceptors>

**PS:**我们看到 LoginInterceptor 里的 preHandle 方法对于地址 “/login/auth”"/login/out" 不处理。

因此,可以写点配置,少写带 java 代码。在拦截器配置中添加2个 exclude-mapping ,并且去掉 LoginInterceptor 里的

if(uri.endsWith("/login/auth") || uri.endsWith("/login/out")) {
  return true;
}

配置新增:

<mvc:exclude-mapping path="/login/out"/>
<mvc:exclude-mapping path="/login/auth"/>

SpringMVC 拦截器的原理以及各种配置,像网上很多人会问为什么拦截器执行 preHandle 方法返回 false 之后还是会执行 afterCompletion 方法,其实我们看下源码就知道了。

关于异步请求方面的拦截器以及第二种配置方法( interceptors 集合属性可加入继承自 HandlerInterceptorAdapter 抽象类的类以及实现 WebRequestInterceptor 接口的类),读者可自行研究。