在进行Spring开发时,你很可能使用过HandlerInterceptor来实现接口鉴权或日志记录——但你是否深入思考过,它究竟是如何“嵌入”到请求处理流程中的?为什么preHandle方法返回false就能中断请求?postHandle和afterCompletion的执行顺序背后又有着怎样的设计考量?本文将深入源码,剖析拦截器的工作机制,为你厘清其中的逻辑。
一、拦截器的起点:从DispatcherServlet的getHandler方法入手
Spring MVC框架的核心是DispatcherServlet,所有的请求都会首先抵达这里。拦截器之所以能够“拦截”请求,其本质在于DispatcherServlet在处理流程中主动调用了拦截器链的相关方法。
要找到拦截器的入口,我们需要关注DispatcherServlet中获取处理器的核心方法getHandler。该方法会从配置的HandlerMapping(如RequestMappingHandlerMapping)中获取一个HandlerExecutionChain(处理器执行链),而这条链里就封装了我们预先配置好的所有拦截器。
// DispatcherServlet.java
protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
if (this.handlerMappings != null) {
// 遍历所有HandlerMapping
for (HandlerMapping mapping : this.handlerMappings) {
HandlerExecutionChain handlerChain = mapping.getHandler(request);
if (handlerChain != null) {
return handlerChain; // 返回包含拦截器的执行链
}
}
}
return null;
}
HandlerExecutionChain是一个关键的包装器:它内部既持有真正处理请求的目标Controller(或HandlerMethod),也维护着一个HandlerInterceptor列表。后续所有的拦截逻辑,都将围绕这个执行链展开。
二、拦截器的核心三部曲:preHandle、postHandle与afterCompletion
DispatcherServlet在doDispatch方法中获取到HandlerExecutionChain后,便会按照一个固定的顺序来调用拦截器的各个方法。以下是简化后的核心流程:
// DispatcherServlet.java (doDispatch方法简化版)
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
HandlerExecutionChain handlerChain = getHandler(request); // 1. 获取执行链
if (handlerChain == null) {
// 处理404等情况
return;
}
try {
// 2. 按顺序执行所有拦截器的preHandle方法
if (!handlerChain.applyPreHandle(request, response)) {
return; // 任一preHandle返回false,则终止整个请求
}
// 3. 执行真正的Controller业务逻辑
ModelAndView mv = handlerAdapter.handle(request, response, handlerChain.getHandler());
// 4. 逆序执行所有拦截器的postHandle方法
handlerChain.applyPostHandle(request, response, mv);
// 5. 视图渲染等后续处理
processDispatchResult(request, response, handlerChain, mv);
} catch (Exception ex) {
// 6. 触发已成功执行preHandle的拦截器的afterCompletion方法
handlerChain.triggerAfterCompletion(request, response, ex);
}
}
这段代码是整个拦截器机制的“总调度器”。要彻底理解它,我们需要深入分析HandlerExecutionChain中的三个核心方法。
1. preHandle:顺序执行与拦截的关键
applyPreHandle方法是拦截逻辑的“入口闸门”,它会按照添加顺序依次调用所有拦截器的preHandle方法。
// HandlerExecutionChain.java
boolean applyPreHandle(HttpServletRequest request, HttpServletResponse response) throws Exception {
for (int i = 0; i < this.interceptors.size(); i++) {
HandlerInterceptor interceptor = this.interceptors.get(i);
// 调用当前拦截器的preHandle
if (!interceptor.preHandle(request, response, this.handler)) {
// 若返回false,立即触发已执行拦截器的afterCompletion(用于资源清理)
triggerAfterCompletion(request, response, null);
return false; // 终止整个请求流程,Controller将不会被执行
}
this.interceptorIndex = i; // 记录最后一个成功执行的拦截器索引
}
return true;
}
关键细节解析:
- 立即终止:当某个拦截器的
preHandle返回false时,循环立即中断,后续的拦截器以及最终的Controller都不会被执行。
- 逆向清理:在返回
false之前,会调用triggerAfterCompletion,逆序触发所有已经成功执行了preHandle的拦截器的afterCompletion方法。这确保了资源的正确清理。
- 索引记录:
interceptorIndex变量记录了最后一个成功通过preHandle的拦截器位置,该值在后续的afterCompletion调用中起到关键作用。
这解释了为什么诸如鉴权、登录检查这类需要“拦截”的逻辑,最适合放在preHandle中实现——拦截时机最早,且配套的资源清理机制完善。
2. postHandle:逆序执行以增强响应
applyPostHandle方法在Controller执行完毕后、视图渲染之前被调用。值得注意的是,它逆序遍历拦截器列表并执行postHandle方法。
// HandlerExecutionChain.java
void applyPostHandle(HttpServletRequest request, HttpServletResponse response, ModelAndView mv) throws Exception {
// 从最后一个拦截器往前遍历
for (int i = this.interceptors.size() - 1; i >= 0; i--) {
HandlerInterceptor interceptor = this.interceptors.get(i);
interceptor.postHandle(request, response, this.handler, mv);
}
}
为何采用逆序? 设想一个典型场景:你拥有两个拦截器,LogInterceptor(记录日志)和ModifyResponseInterceptor(修改响应体)。逆序执行能保证ModifyResponseInterceptor先修改响应内容,然后LogInterceptor记录下最终的结果日志。这种“后进先出”的顺序与责任链模式在处理“后置增强”时的逻辑是吻合的。
3. afterCompletion:请求收尾的资源清理卫士
triggerAfterCompletion是拦截器生命周期的“收官”方法。无论请求处理是正常完成还是中途抛出异常,该方法都会被调用(在doDispatch的catch块或applyPreHandle的拦截处理中)。
// HandlerExecutionChain.java
void triggerAfterCompletion(HttpServletRequest request, HttpServletResponse response, Exception ex) throws Exception {
// 从最后一个成功执行的拦截器往前遍历(依据interceptorIndex)
for (int i = this.interceptorIndex; i >= 0; i--) {
HandlerInterceptor interceptor = this.interceptors.get(i);
try {
interceptor.afterCompletion(request, response, this.handler, ex);
} catch (Throwable t) {
logger.error(\"HandlerInterceptor.afterCompletion threw exception\", t);
}
}
}
核心作用:afterCompletion是进行资源清理(如关闭数据库连接、释放线程局部变量)和记录最终操作日志(如统计请求总耗时)的绝佳位置。因为它能确保在请求生命周期的最后阶段被执行。
三、可视化流程:一张图看懂执行顺序
为了更直观地理解上述源码逻辑,我们可以将其绘制成请求处理时序图。

图1:Spring MVC请求处理流程,清晰展示了DispatcherServlet、HandlerExecutionChain与多个HandlerInterceptor的协作顺序。
从上图可以明确看到:
preHandle顺序执行(Interceptor1 → Interceptor2)。
postHandle与afterCompletion逆序执行(Interceptor2 → Interceptor1)。
- 任一
preHandle返回false,则后续流程全部终止,并逆序触发已成功preHandle的拦截器的afterCompletion。
四、实战应用:构建“鉴权+日志”拦截器
理解了源码机制,我们就能更精准地设计拦截器。下面实现一个典型的“接口鉴权 + 请求日志”组合功能。
- 鉴权拦截器:逻辑置于
preHandle,验证Token,无效则返回401状态并拦截。
- 日志拦截器:在
preHandle中记录开始时间,在afterCompletion中计算耗时并记录日志(即使请求异常也能记录)。
// 1. 鉴权拦截器
public class AuthInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
String token = request.getHeader(\"Authorization\");
if (token == null || !token.equals(\"valid-token\")) {
response.setStatus(401);
return false; // 拦截请求
}
return true;
}
}
// 2. 日志拦截器
public class LogInterceptor implements HandlerInterceptor {
private long startTime;
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
startTime = System.currentTimeMillis(); // 记录请求开始时间
return true;
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
long cost = System.currentTimeMillis() - startTime;
String log = String.format(\"请求URL:%s,耗时:%dms,状态码:%d\",
request.getRequestURI(), cost, response.getStatus());
System.out.println(log); // 实际项目中可接入Log框架
}
}
对于希望深入理解此类机制背后原理的开发者,云栈社区的Java板块提供了大量关于Spring核心机制的源码解析与深度讨论。
五、厘清概念:拦截器(Interceptor)与过滤器(Filter)的区别
初学者常常混淆HandlerInterceptor与Servlet规范中的Filter,它们的关键区别在于作用域和可获取的上下文:
- Filter:基于Servlet规范,作用于更底层,可以拦截所有到达Servlet容器的请求(包括静态资源),属于“Servlet层”的粗粒度拦截。
- HandlerInterceptor:基于Spring MVC框架,作用于DispatcherServlet内部,只拦截映射到
Controller的请求。它可以获取到Spring MVC丰富的上下文信息,如本次请求对应的Handler(控制器方法)、ModelAndView等,属于“MVC层”的细粒度拦截。
简单总结:Filter是“城门守卫”,检查所有进出人员;HandlerInterceptor是“内殿侍卫”,只针对进入特定房间(Controller)的人员进行更细致的检查和处理。对于需要结合Controller业务逻辑的拦截需求(如基于注解的权限控制),HandlerInterceptor是更合适的选择。
结语
深入剖析HandlerInterceptor的源码后,其核心工作机制可以概括为:DispatcherServlet作为调度中心,通过HandlerExecutionChain这个载体,严格按照“preHandle顺序执行 → Controller处理 → postHandle逆序执行 → afterCompletion逆序清理”的流程,将拦截逻辑织入MVC请求处理的生命周期中。掌握interceptorIndex的作用以及各方法的执行顺序,能帮助我们更精准地控制拦截行为,编写出更健壮、高效的拦截器组件。这套设计模式不仅应用于拦截器,在Spring生态乃至其他开源框架的责任链设计中也能看到其身影,值得细细体会。