Interceptor(即处理器拦截器、拦截器),类似于 Servlet 开发中的过滤器 Filter,用于对处理器(Controller)进行预处理和后处理。
常见的应用场景:
日志记录:记录请求信息的日志,以便进行信息监控、信息统计、计算PV(Page View)等。
权限检查:如登录检测,进入处理器检测检测是否登录,如果没有直接返回到登录页面。
性能监控:有时候系统在某段时间莫名其妙的慢,可以通过拦截器在进入处理器之前记录开始时间,在处理完后记录结束时间,从而得到该请求的处理时间(如果有反向代理,如apache可以自动记录)。
通用行为:读取 cookie 得到用户信息并将用户对象放入请求,从而方便后续流程使用。
public interface HandlerInterceptor {
boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception;
void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception;
void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex)throws Exception;
}
preHandle
预处理回调方法,实现处理器的预处理(即在处理方法被调用之前生效)。
返回值:true表示继续流程(如调用下一个拦截器或处理器);false表示流程中断,不会继续调用其他的拦截器或处理器,此时需要通过response来产生响应;
postHandle
后处理回调方法,实现处理器的后处理(但在渲染视图之前)。
此时可以通过 modelAndView(模型和视图对象)对模型数据进行处理或对视图进行处理,modelAndView 也可能为 null。
afterCompletion
整个请求处理完毕回调方法,即在视图渲染完毕时回调。
类似于try-catch-finally中的finally,但仅调用处理器执行链中 preHandle 返回 true 的拦截器的 afterCompletion。
public interface WebRequestInterceptor {
void preHandle(WebRequest request) throws Exception;
void postHandle(WebRequest request, ModelMap model) throws Exception;
void afterCompletion(WebRequest request, Exception ex) throws Exception;
}
与HandlerInterceptor接口类似,区别是 WebRequestInterceptor 的 preHandle 没有返回值。
WebRequestInterceptor 是针对请求的,接口方法参数中没有response。
// <mvc:annotation-driven /> 该标签初始化的时候会初始化这个拦截器
// 作用是暴露 conversionService 到请求中以便如<spring:eval>标签使用
// 该拦截器用于静态资源访问,定义了以下的标签,在该标签初始化的时候会初始化这个拦截器
"/js/**" location="/js/" />
public class LoginInterceptor extends HandlerInterceptorAdapter {
// 重写父类的方法(父类默认返回 true)
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handelr) throws Exception {
return loginValidate(request, response);
}
// 登录验证
private boolean loginValidate(HttpServletRequest request, HttpServletResponse response) throws IOException {
// 存在 session 信息返回 true
if (request.getSession().getAttribute("username") != null) {
return true;
}
// 不存在重定向到错误页面,并返回 false
response.sendRedirect(request.getContextPath()+"/login/error");
return false;
}
}
public class HelloInterceptor extends HandlerInterceptorAdapter {
private Logger log = Logger.getLogger(HelloInterceptor.class);
private NamedThreadLocal startTimeThreadLocal = new NamedThreadLocal("StopWatch-StartTime");
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handelr) throws Exception {
// 开始时间
long beginTime = System.currentTimeMillis();
// 线程绑定变量(该数据只有当前请求的线程可见)
startTimeThreadLocal.set(beginTime);
return true;
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex)
throws Exception {
// 计算页面加载时间
long endTime = System.currentTimeMillis();
long beginTime = startTimeThreadLocal.get();
long consumeTime = endTime - beginTime;
// 大于 500 毫秒表示请求为慢请求
if(consumeTime > 500){
// 记录日志
log.info(String.format("%s consume %d millis", request.getRequestURI(), consumeTime));
}
}
}
首先来拦截器在 xml 中的配置。
"/login/**"/>
"/index/**"/>
"com.interceptor.LoginInterceptor" />
拦截器会在 springmvc 初始化过程中被解析,然后被装载进 springmvc 的 Ioc 容器。具体的实现发生在 InterceptorsBeanDefinitionParser 类的 parse 方法。
ublic BeanDefinition parse(Element element, ParserContext parserContext) {
CompositeComponentDefinition compDefinition =
parserContext.pushContainingComponent(compDefinition);
RuntimeBeanReference pathMatcherRef = null;
if (element.hasAttribute("path-matcher")) {
pathMatcherRef = new RuntimeBeanReference(element.getAttribute("path-matcher"));
}
// 取得 的子元素 ,即
List interceptors = DomUtils.getChildElementsByTagName(element, "bean", "ref", "interceptor");
// 遍历所有的 ,即拦截器
for (Element interceptor : interceptors) {
// 将拦截器统一注册成 MappedInterceptor 类型
RootBeanDefinition mappedInterceptorDef = new RootBeanDefinition(MappedInterceptor.class);
mappedInterceptorDef.setSource(parserContext.extractSource(interceptor));
mappedInterceptorDef.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
ManagedList includePatterns = null;
ManagedList 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);
}
// 注册到 IOC 容器
String beanName = parserContext.getReaderContext().registerWithGeneratedName(mappedInterceptorDef);
parserContext.registerComponent(new BeanComponentDefinition(mappedInterceptorDef, beanName));
}
parserContext.popAndRegisterContainingComponent();
return null;
}
拦截器的执行过程属于 springmvc 请求转发的一部分。
首先找到 DispatcherServlet 的 doDispatch 方法,该方法负责实现 springmvc 的请求转发,同时也包含了 interceptor 的执行过程。
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);
// 取得拦截器
mappedHandler = getHandler(processedRequest);
// 省略部分代码...
// ① PreHandle ③ AfterCompletion
if (!mappedHandler.applyPreHandle(processedRequest, response)) {
return;
}
// 调用处理器(controller )匹配请求路径的方法
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
// 省略部分代码...
// 取得默认的视图名(即返回的页面路径)
applyDefaultViewName(processedRequest, mv);
// ② PostHandle
mappedHandler.applyPostHandle(processedRequest, response, mv);
}
catch (Exception ex) {
dispatchException = ex;
}
// ③渲染视图(即生成 html 页面)
processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
}
catch (Exception ex) {
// ③ AfterCompletion
triggerAfterCompletion(processedRequest, response, mappedHandler, ex);
}
catch (Error err) {
// ③ AfterCompletion
triggerAfterCompletionWithError(processedRequest, response, mappedHandler, err);
}
finally {
if (asyncManager.isConcurrentHandlingStarted()) {
// Instead of postHandle and afterCompletion
if (mappedHandler != null) {
mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);
}
}
else {
// Clean up any resources used by a multipart request.
if (multipartRequestParsed) {
cleanupMultipart(processedRequest);
}
}
}
}
观察代码以及注释,分析如下:
springmvc 的请求转发过程大概可分为:匹配请求路径 -> 执行处理器的对应方法 -> 获取视图名称 -> 渲染视图。
interceptor 的执行过程为:PreHandle,AfterCompletion (处理器方法执行之前)-> PostHandle(获取视图名称之后)-> AfterCompletion(渲染视图之后)。
值得注意的是 AfterCompletion 这个方法,它在多个地方(即不同的条件下)都会调用到,下面会详细讲到。
下面来看 mappedHandler.applyPreHandle ,在该方法中拦截器会调用它的 preHandle 方法 。
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];
// 执行 preHandle 方法
if (!interceptor.preHandle(request, response, this.handler)) {
// 一旦存在返回 false,触发该方法【逆序】执行【前面所有拦截器】的 afterCompletion 方法
triggerAfterCompletion(request, response, null);
return false;
}
// 关键 --> 只有在 preHandle 方法返回 true 时才执行
// 在 triggerAfterCompletion 会用到
this.interceptorIndex = i;
}
}
return true;
}
再来看 triggerAfterCompletion 这个方法,会调用到该方法的情况如下:
preHandle 返回 false 时
applyDefaultViewName(渲染视图,上面提过了)
private void processDispatchResult(HttpServletRequest request, HttpServletResponse response,
HandlerExecutionChain mappedHandler, ModelAndView mv, Exception exception) throws Exception {
// 省略部分源码...
if (mappedHandler != null) {
mappedHandler.triggerAfterCompletion(request, response, null);
}
}
抛出异常
抛出错误
该方法具体的定义如下:
void triggerAfterCompletion(HttpServletRequest request, HttpServletResponse response, 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);
}
}
}
}
最后来看 mappedHandler.applyPostHandle,该方法会调用拦截器的 postHandle 方法。
void applyPostHandle(HttpServletRequest request, HttpServletResponse response, 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);
}
}
}