【Spring】抽丝剥茧SpringMVC-拦截器HandlerInterceptor

本文源码基于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");
    }
}

 

 

 

  • 4
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值