找回密码
立即注册
搜索
热搜: Java Python Linux Go
发回帖 发新帖

1538

积分

0

好友

193

主题
发表于 2025-12-30 21:13:04 | 查看: 22| 回复: 0

在进行Spring开发时,你很可能使用过HandlerInterceptor来实现接口鉴权或日志记录——但你是否深入思考过,它究竟是如何“嵌入”到请求处理流程中的?为什么preHandle方法返回false就能中断请求?postHandleafterCompletion的执行顺序背后又有着怎样的设计考量?本文将深入源码,剖析拦截器的工作机制,为你厘清其中的逻辑。

一、拦截器的起点:从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列表。后续所有的拦截逻辑,都将围绕这个执行链展开。

二、拦截器的核心三部曲:preHandlepostHandleafterCompletion

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是拦截器生命周期的“收官”方法。无论请求处理是正常完成还是中途抛出异常,该方法都会被调用(在doDispatchcatch块或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是进行资源清理(如关闭数据库连接、释放线程局部变量)和记录最终操作日志(如统计请求总耗时)的绝佳位置。因为它能确保在请求生命周期的最后阶段被执行。

三、可视化流程:一张图看懂执行顺序

为了更直观地理解上述源码逻辑,我们可以将其绘制成请求处理时序图。

Spring MVC HandlerInterceptor请求处理流程
图1:Spring MVC请求处理流程,清晰展示了DispatcherServlet、HandlerExecutionChain与多个HandlerInterceptor的协作顺序。

从上图可以明确看到:

  • preHandle顺序执行(Interceptor1 → Interceptor2)。
  • postHandleafterCompletion逆序执行(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生态乃至其他开源框架的责任链设计中也能看到其身影,值得细细体会。




上一篇:Scan4All安全扫描工具快速入门:基于Go的端口扫描与漏洞检测实战
下一篇:嵌入式分层管理实践:STM32设备驱动框架如何实现软硬件解耦
您需要登录后才可以回帖 登录 | 立即注册

手机版|小黑屋|网站地图|云栈社区 ( 苏ICP备2022046150号-2 )

GMT+8, 2026-1-10 18:36 , Processed in 0.317182 second(s), 40 queries , Gzip On.

Powered by Discuz! X3.5

© 2025-2025 云栈社区.

快速回复 返回顶部 返回列表