《Spring MVC的HandlerExecutionChain:从快递分拣到灭霸响指的奇幻漂流》
引言:当Spring MVC遇上快递小哥
想象一下,你网购了一箱肥宅快乐水,快递小哥却在中转站被保安拦下检查、被分拣员贴标签、被客服小姐姐温柔提醒——这就是HandlerExecutionChain的日常。它像一位忙碌的调度员,决定你的请求(快递)要经过哪些拦截器(中转站),最终由哪个Controller(你家)处理。今天,我们就来揭开这位"调度员"的神秘面纱!
一、介绍:HandlerExecutionChain是谁?
定义:
HandlerExecutionChain是Spring MVC的“任务执行链”,封装了目标处理器(Handler)和一堆拦截器(Interceptors)。它像一条流水线,负责在请求处理前后“搞事情”。
组成:
- Handler:真正的打工人,比如
@Controller中的方法。 - Interceptors:一群“事多”的监工,比如日志记录、权限检查。
生命周期:
- 快递分拣:DispatcherServlet根据URL找到合适的Handler,打包成Chain。
- 安检流程:拦截器们依次检查请求(
preHandle)。 - 送货上门:Handler处理请求。
- 售后服务:拦截器们处理响应(
postHandle、afterCompletion)。
二、用法:如何“调教”HandlerExecutionChain?
1. 配置拦截器:给监工们发工牌
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new LogInterceptor()).order(1); // 日志监工
registry.addInterceptor(new AuthInterceptor()).order(2); // 保安监工
}
}
2. 自定义拦截器:监工的自我修养
public class LogInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
System.out.println("监工日记:有人来买快乐水了!");
return true; // 放行!false则直接退货
}
}
三、案例:从肥宅快乐水到灭霸响指
场景:用户访问/buy/cola购买快乐水,需记录日志并检查钱包。
代码:
// 1. 日志拦截器
public class LogInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
System.out.println("用户" + request.getRemoteUser() + "试图购买快乐水!");
return true;
}
}
// 2. 钱包检查拦截器
public class WalletInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
if (user.getBalance() < 5) {
response.sendError(400, "余额不足,快去搬砖!");
return false; // 拦截请求!
}
return true;
}
}
// 3. Controller:处理购买请求
@RestController
public class ColaController {
@GetMapping("/buy/cola")
public String buyCola() {
return "灭霸:你成功打了个响指,快乐水已送达!";
}
}
四、原理:DispatcherServlet的“秘密会议”
- 找Handler:
DispatcherServlet调用HandlerMapping,像查快递单号一样找到对应的Handler。 - 打包Chain:将Handler和拦截器们打包成
HandlerExecutionChain。 - 拦截器流水线:
preHandle:拦截器们按顺序检查请求(像安检层层关卡)。postHandle:Handler处理完后,拦截器们倒序加工响应(像给快递贴广告)。afterCompletion:请求完成后收尾(无论成功与否,像清理快递站点)。
源码片段(简化版):
// DispatcherServlet.java
HandlerExecutionChain chain = getHandler(request);
if (chain.applyPreHandle(request, response)) {
chain.handle(/*...*/); // 执行Handler
chain.applyPostHandle(request, response, mv);
}
chain.triggerAfterCompletion(/*...*/);
五、对比:HandlerExecutionChain vs Filter
| 对比项 | HandlerExecutionChain | Filter |
|---|---|---|
| 管辖范围 | Spring MVC内部流程 | Servlet容器级别 |
| 拦截目标 | 针对Handler(如Controller方法) | 所有请求(包括静态资源) |
| 执行顺序 | 在Filter之后执行 | 在Interceptor之前执行 |
| 依赖关系 | 依赖Spring容器 | 不依赖Spring,纯Servlet |
通俗版:
Filter是小区门卫,Interceptor是楼栋保安,HandlerExecutionChain是负责协调保安和住户的物业经理。
六、避坑指南:别让拦截器变“拦精灵”
-
顺序问题:
拦截器默认按添加顺序执行,用order()方法明确排序,否则可能变成“先贴快递单再检查包裹”。 -
preHandle返回值:
忘记return true?恭喜你,请求永远卡在第一个拦截器! -
异步请求:
在异步处理中,afterCompletion可能提前触发,改用AsyncHandlerInterceptor。 -
路径匹配:
拦截器配置路径时,/**表示所有路径,但别手滑写成/*(只匹配一级目录)。
七、最佳实践:做个优雅的“调度员”
-
职责单一:
一个拦截器只做一件事(比如日志记录和权限检查分开)。 -
避免阻塞:
别在拦截器里做耗时操作(比如查数据库),否则快递堆积成山。 -
合理排序:
权限检查拦截器应放在日志拦截器之后,避免记录无效请求。 -
善用排除:
用excludePathPatterns("/static/**")放过静态资源,别让保安追着CSS文件跑。
八、面试考点:如何让面试官眼前一亮?
Q1:HandlerExecutionChain的作用是什么?
A:它像快递分拣系统,封装了处理请求的Handler和拦截器,管理请求的前后处理流程。
Q2:拦截器的执行顺序是怎样的?
A:preHandle按添加顺序执行,postHandle和afterCompletion倒序执行,像“先安检再验票,回来时先发水再送客”。
Q3:拦截器和Filter有什么区别?
A:Filter是Servlet规范,拦截器是Spring的机制。Filter更底层,拦截器更贴合Spring MVC。
Q4:preHandle返回false会发生什么?
A:请求被拦截,后续拦截器和Handler都不执行,直接跳转到afterCompletion。
九、总结:HandlerExecutionChain的终极奥义
HandlerExecutionChain就像灭霸的手套,Handler是宝石,Interceptor是灭霸的部下。只有正确组合它们,才能让请求处理如响指般优雅!记住:别让拦截器变成“拦精灵”,合理调度才是王道!
终极口诀:
一链在手,Spring我有;
拦截有序,bug没有;
preHandle要放行,postHandle倒序走;
面试八股全不怕,Offer拿到手发抖!
(注:灭霸手套的宝石掉了概不负责,请妥善保管你的拦截器。)