责任链模式中顺序和短路为什么很重要?
简化版
责任链的执行结果往往依赖处理器顺序,顺序错了会导致校验失效、重复处理、性能浪费甚至安全问题。短路则决定请求什么时候停止向后传递,常用于鉴权失败、限流命中、参数非法、审批驳回等场景。
详细版
责任链不是把处理器随便放进列表就结束了。很多处理器之间存在天然顺序:
- 鉴权通常要在业务执行前;
- 限流通常要尽量靠前;
- 参数校验要在数据库操作前;
- 日志和监控可能需要包住整个链路;
- 事务要覆盖真正需要原子性的操作。
短路是责任链的关键能力。某个节点发现请求不应该继续执行时,可以直接返回或抛出异常,不再调用后续节点。比如用户未登录时,鉴权节点直接返回失败,不应该继续访问库存、优惠券和下单逻辑。
设计责任链时要明确:每个节点是否必须执行、是否可以短路、短路后返回什么、异常是否继续传播、后置逻辑是否还能执行。
完整版教学
一、顺序决定语义,不只是代码排列
责任链中处理器的顺序经常代表业务语义。
例如接口请求链:
限流 -> 鉴权 -> 参数校验 -> 风控 -> 业务处理
如果把业务处理放到鉴权前面,就可能出现未登录用户已经触发业务副作用的问题。
如果把数据库查询放在参数校验前面,非法参数可能造成无意义查询,甚至引发异常。
如果把限流放得太靠后,系统可能已经消耗了大量资源,限流才开始工作,保护效果会下降。
所以顺序不是样式问题,而是正确性和性能问题。
二、短路让链路具备“拦截”能力
短路表示某个处理器决定请求不能继续向后走。
典型例子:
class AuthHandler implements Handler {
public void handle(Request request, HandlerChain chain) {
if (!request.isLogin()) {
throw new RuntimeException("未登录");
}
chain.doHandle(request);
}
}
如果用户未登录,chain.doHandle(request) 不会被调用,后面的节点自然不会执行。
这就是责任链非常适合做过滤器和拦截器的原因:每个节点都能判断是否放行。
三、短路不等于异常
短路可以通过异常实现,但不一定必须抛异常。
常见方式有:
- 返回失败结果;
- 设置响应状态并停止传递;
- 抛出业务异常;
- 标记上下文状态,由链对象统一结束;
- 跳转到兜底处理器。
比如网关限流命中时,可以直接返回 429;审批驳回时,可以返回“审批不通过”;参数校验失败时,可以返回明确错误码。
选哪种方式取决于系统的错误处理规范。
四、后置逻辑要小心短路影响
很多过滤器有“前置 + 后置”结构:
public void doFilter(Request request, Chain chain) {
before();
chain.doFilter(request);
after();
}
如果中间节点短路,外层节点的后置逻辑是否执行,取决于调用栈是否正常返回。
比如日志统计通常希望无论成功还是失败都记录耗时,这时应该用 try finally:
public void doFilter(Request request, Chain chain) {
long start = System.currentTimeMillis();
try {
chain.doFilter(request);
} finally {
logCost(start);
}
}
这是工程里很重要的细节:责任链不是只考虑正向传递,还要考虑异常、返回和资源清理。
五、链路顺序应该显式可见
责任链越长,越需要让顺序可见。否则出现问题时,排查会很痛苦。
常见做法包括:
- 启动时打印链路顺序;
- 配置中声明处理器顺序;
- 用
order字段统一排序; - 单元测试覆盖关键顺序;
- 在日志里记录经过的处理器。
比如启动日志输出:
OrderChain: RateLimitHandler -> AuthHandler -> ParamHandler -> RiskHandler -> BusinessHandler
这比只看一堆 Bean 注册要清晰得多。
六、顺序依赖太复杂时要警惕设计过度
如果链上每个节点都依赖前一个节点写入的上下文,并且顺序稍微变化就出错,说明责任链可能已经承载了过多隐式协议。
这时可以考虑:
- 拆分多条更短的链;
- 把强依赖逻辑合并成一个明确模块;
- 用状态机表达流程;
- 用编排服务统一组织关键业务步骤;
- 给上下文加明确字段和校验。
责任链适合拆横切处理和可插拔处理,不适合把所有业务流程都塞成一条神秘长链。
七、前置、后置和短路的嵌套语义
A、B、C 依次进入时日志为 A前→B前→C前→C后→B后→A后;若 B 短路,C 及其后置逻辑执行次数为 0。
A before -> B before -> [stop or C] -> B after -> A after
责任链是否正确,不能只看节点都被注册,还要看请求到底经过哪些节点、在哪里停止以及返回阶段如何展开。应把“继续、已处理、拒绝、系统失败”建成可区分的结果,并让日志能够还原一次请求的完整路径。
八、链路顺序、短路与可观测性
| 检查维度 | 应确认的内容 |
|---|---|
| 机制正确性 | 链顺序是业务规则,必须可见、可测试、可观测,不能依赖容器偶然返回顺序。 |
| 适用边界 | 短路要明确是否仍执行已进入节点的 finally/after;权限拒绝、缓存命中和异常不是同一种终止语义。 |
| 测试证据 | 用记录型处理器覆盖全通过、主动短路、业务拒绝、节点异常和链尾无人处理 |
| 工程代价 | 重点测量链长、各节点耗时、最坏路径延迟和短路率,并确认动态装配不会产生重复节点或排序漂移 |
易错点:链顺序是业务规则,必须可见、可测试、可观测,不能依赖容器偶然返回顺序。
九、常见误区与追问
- 误区:短路就是抛异常。 短路也可返回已处理结果或拒绝结果;异常表示故障,两者应分开建模。
- 误区:责任链一定会让每个处理器都执行。 纯链可能命中一个节点就结束,不纯链也可能因拒绝、异常或短路提前终止,必须由链契约定义。
- 追问:怎样测试后置逻辑顺序? 用记录型处理器捕获进入和退出序列,分别覆盖正常、短路和异常三条路径。
- 追问:责任链如何避免请求到链尾无人处理? 提供明确的默认处理器或链尾失败结果,并对未处理请求记录指标,不能静默成功。
- 追问:责任链上线后最少要记录哪些信息? 至少记录链版本、节点顺序、逐节点耗时与决策、短路位置、异常和最终处理结果。
十、加强记忆
责任链的顺序决定正确性,短路决定请求能否继续。答这类题时要抓住四个关键词:前置校验靠前、横切增强可包裹、失败场景要短路、链路顺序要可观测。链越长,越要警惕隐式依赖和排查困难。