本文源码基于Spring 5.2.7版本
什么是拦截器
顾名思义,拦截器用来拦截请求,并在请求的前后统一加上逻辑。拦截器有很多使用场景,例如:统一鉴权、统一日志等等。SpringMVC中拦截器需要实现org.springframework.web.servlet.HandlerInterceptor。该接口有3个方法:
- preHandle:DispatcherServlet确定了请求的Handler之后,Handler未执行前,preHandle将被调用。preHandle可以决定是否终止DispatcherServlet流程。
- posthandle:Handler执行完之后,视图渲染之前,postHandler将会被调用。postHandler可以添加一些额外的数据到model。
- triggerAfterCompletion:请求处理完之后(视图渲染完或者异常或者preHandle返回false),将调用triggerAfterCompletion。只有preHandle被执行了的HandlerInterceptor的triggerAfterCompletion才会被调用,其余的HandlerInterceptor的triggerAfterCompletion不会被调用。
关于拦截器,我们需要知道以下几点:
- 拦截器是如何初始化的
- 拦截器是如何被调用的
- 拦截器的各种配置方式
拦截器初始化
拦截器是跟Handler绑在一起的,所以拦截器的初始化借助HandlerMapping完成,在AbstractHandlerMapping(HandlerMapping的实现类)初始化中探测所有IOC容器中MappedInterceptor的实例。
public abstract class AbstractHandlerMapping extends WebApplicationObjectSupport
implements HandlerMapping, Ordered, BeanNameAware {
... ...
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());
}
... ...
}
HandlerMapping查找reques对应的Handler时也不是直接返回Handler,返回的对象是org.springframework.web.servlet.HandlerExecutionChain。
- handler:实际的Handler。
- interceptors:匹配的拦截器。
- interceptorList:同interceptors,在构建HandlerExecutionChain过程中与interceptors相互转换使用。
HandlerMapping构建HandlerExecutionChain对象的过程如下:
public abstract class AbstractHandlerMapping extends WebApplicationObjectSupport
implements HandlerMapping, Ordered, BeanNameAware {
... ...
protected HandlerExecutionChain getHandlerExecutionChain(Object handler, HttpServletRequest request) {
HandlerExecutionChain chain = (handler instanceof HandlerExecutionChain ?
(HandlerExecutionChain) handler : new HandlerExecutionChain(handler));
String lookupPath = this.urlPathHelper.getLookupPathForRequest(request, LOOKUP_PATH);
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;
}
... ...
}
拦截器的核心逻辑
从DispatcherServlet执行过程中可以看出(《抽丝剥茧DispatcherServlet》),拦截器的3个方法在不同的阶段被执行。preHandle方法是在DispatcherServlet确定了HandlerAdapter之后且在HandlerAdapter执行handle之前被调用;而postHandle是在HandlerAdapter执行完handle之后且异常处理或视图渲染之前被调用;triggerAfterCompletion则是在请求被处理完后被调用。请求被处理完包括3种情况,其一是有异常抛出,其二是某个拦截器的preHandle返回false终止DispatcherServlet,其三是请求被正常处理完。
preHandle处理
HandlerExecutionChain依次调用拦截器的preHandle方法,过程很简单。值得注意的是其中任何一个拦截器返回false则终止后面的拦截器执行,所以拦截器的顺序比较重要。如果某个拦截器返回false,则立即调用triggerAfterCompletion方法,确保已经执行类preHandle的拦截器的triggerAfterCompletion方法能被执行。
public class HandlerExecutionChain {
... ...
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;
}
... ...
}
postHandle处理
HandlerExecutionChain依次调用拦截器的postHandle方法,过程也很简单。值得注意的是postHandle的执行顺序是从后往前。如果在DispatcherServlet执行postHandle之前抛了异常或者某个拦截器的preHandle返回false,则DispatcherServlet不会调用到postHandle。这一点在DispatcherServlet流程中可以看出。
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);
}
}
}
triggerAfterCompletion处理
triggerAfterCompletion的执行逻辑依旧很简单。它的执行顺序也是从后面的拦截器开始执行。值得注意的是,它是一个中间变量interceptorIndex开始往前执行的,interceptorIndex在【preHandle处理】中被赋值,表示当前执行了哪些拦截器的preHandle。triggerAfterCompletion从这个值开始即表明只有preHandle被正确执行了的拦截器才会执行triggerAfterCompletion方法。
这里还需要关注的是无论是否有异常或者是否有拦截器的preHandle返回false,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);
}
}
}
}
拦截器配置
定义一个LogInterceptor作为配置demo,在LogInterceptor中只重写afterCompletion方法,打印一些日志。
public class LogInterceptor extends HandlerInterceptorAdapter {
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler,
@Nullable Exception ex) throws Exception {
System.out.println("/*********************************************/");
System.out.println("/******afterCompletion for LogInterceptor*****/");
System.out.println("/*********************************************/");
}
}
web.xml方式启动
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:mvc="http://www.springframework.org/schema/mvc"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/mvc https://www.springframework.org/schema/mvc/spring-mvc.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd">
<mvc:annotation-driven />
<context:component-scan base-package="com.focuse.demo" />
<mvc:interceptors>
<mvc:interceptor>
<mvc:mapping path="/focuse/hello"/>
<mvc:mapping path="/focuse/hello2" />
<bean class="com.focuse.demo.config.LogInterceptor" />
</mvc:interceptor>
</mvc:interceptors>
</beans>
<mvc:interceptor>表示注入一个拦截器
<mvc:mapping>说明该拦截器拦截哪些请求
<bean class="com.focuse.demo.config.LogInterceptor" />就是拦截器实现类
@EnableWebMVC方式启动
@Component
public class MvcConfigurer implements WebMvcConfigurer {
public void addInterceptors(InterceptorRegistry registry) {
InterceptorRegistration logInterceptor = registry.addInterceptor(new LogInterceptor());
logInterceptor.addPathPatterns("/focuse/hello");
}
}
@EnableWebMVC方式注入了org.springframework.web.servlet.config.annotation.DelegatingWebMvcConfiguration类型的Bean,DelegatingWebMvcConfiguration调用所有的org.springframework.web.servlet.config.annotation.WebMvcConfigurer对象的方法addInterceptors。
public class DelegatingWebMvcConfiguration extends WebMvcConfigurationSupport {
... ...
@Override
protected void addInterceptors(InterceptorRegistry registry) {
this.configurers.addInterceptors(registry);
}
... ...
}
SpringBoot方式启动
与【@EnableWebMVC方式启动】一样,也是实现WebMvcConfigurer。因为springboot启动的时候自动注入了DelegatingWebMvcConfiguration的bean。
@Component
public class MvcConfigurer implements WebMvcConfigurer {
public void addInterceptors(InterceptorRegistry registry) {
InterceptorRegistration logInterceptor = registry.addInterceptor(new LogInterceptor());
logInterceptor.addPathPatterns("/focuse/hello");
}
}