💕重要接口及类介绍
HandlerExecutionChain
类
由HandlerMethod
和Interceptor
集合组成的类,会被HandlerMapping
接口的getHandler
方法获取。HandlerInterceptor
接口SpringMVC
拦截器基础接口。AbstractHandlerMapping
HandlerMapping
的基础抽象类。AsyncHandlerInterceptor
继承HandlerInterceptor
的接口,额外提供了afterConcurrentHandlingStarted
方法,该方法是用来处理异步请求。当Controller
中有异步请求方法的时候会触发该方法。楼主做过测试,异步请求先支持preHandle
、然后执行afterConcurrentHandlingStarted
。异步线程完成之后执行preHandle
、postHandle、afterCompletion
。有兴趣的读者可自行研究。HandlerInterceptorAdapter
实现AsyncHandlerInterceptor
接口的抽象类,一般我们使用拦截器的话都会继承这个类。然后复写相应的方法。WebRequestInterceptor
与HandlerInterceptor
接口类似,区别是WebRequestInterceptor
的preHandle
没有返回值。还有WebRequestInterceptor
是针对请求的,接口方法参数中没有response
。-
AbstractHandlerMapping
内部的interceptors
是个Object
类型集合。处理的时候判断为MappedInterceptor
[加入到mappedInterceptors
集合中];HandlerInterceptor、WebRequestInterceptor
(适配成WebRequestHandlerInterceptorAdapter
)[加入到adaptedInterceptors中
] MappedInterceptor
一个包括includePatterns
和excludePatterns
字符串集合并带有HandlerInterceptor
的类。很明显,就是对于某些地址做特殊包括和排除的拦截器。ConversionServiceExposingInterceptor
默认的<annotation-driven/>
标签初始化的时候会初始化ConversionServiceExposingInterceptor
这个拦截器,并被当做构造方法的参数来构造MappedInterceptor
。之后会被加入到AbstractHandlerMapping
的mappedInterceptors
集合中。该拦截器会在每个请求之前往request
中丢入ConversionService
。主要用于spring:eval
标签的使用。
🤞源码分析
首先我们看下拦截器的如何被调用的。
Web
请求被 DispatcherServlet
截获后,会调用 DispatcherServlet
的 doDispatcher
方法。
很明显地看到,在 HandlerAdapter
处理之后,以及处理完成之后会调用 HandlerExecutionChain
的方法。
HandlerExecutionChain的applyPreHandle、applyPostHandle、triggerAfterCompletion
方法如下:
很明显,就是调用内部实现 HandlerInterceptor
该接口集合的各个对应方法。
下面我们看下 HandlerExecutionChain
的构造过程。
HandlerExecutionChain
是从 HandlerMapping
接口的 getHandler
方法获取的。
HandlerMapping
的基础抽象类 AbstractHandlerMapping
中:
我们看到,HandlerExecutionChain
的拦截器是从 AbstractHandlerMapping
中的 adaptedInterceptors
和 mappedInterceptors
属性中获取的。
✌清楚了HandlerExecutionChain的拦截器属性如何构造之后,下面来看下SpringMVC是如何配置拦截器的。
- *-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="/**"/>
会被解析成 MappedInterceptor
的includePatterns
属性;<mvc:exclude-mapping path="/**"/>
会被解析成 MappedInterceptor
的 excludePatterns
属性;<bean/>
会被解析成 MappedInterceptor
的 interceptor
属性。
<mvc:interceptors>
这个标签是被 InterceptorsBeanDefinitionParser
类解析。
- 配置
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
集合中。
如果配置了:
<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
接口的类),读者可自行研究。