责任链模式:让每个处理器都有机会说“不”
责任链模式,说白了就是一条处理流水线。每个节点只关心自己能不能处理,处理不了就往后传。我刚开始接触这个模式时,觉得它跟if-else没啥区别。后来在项目中踩过坑才明白——它解决的是“请求发送者”和“请求接收者”之间的解耦问题。
你想想看,如果一段审批流程要经过组长、经理、总监、CEO,你用if-else写出来会是什么样子?嵌套个四五层,代码就成了一团乱麻。责任链模式就是专门对付这种场景的。
责任链模式的结构
责任链模式的核心角色就三个:
- Handler(抽象处理器):定义处理请求的接口,通常包含一个指向下一个处理器的引用
- ConcreteHandler(具体处理器):实现自己的处理逻辑,决定是否处理或转发
- Client(客户端):发起请求,交给链的第一个处理器
我画了一张结构图,你看一眼就明白了:
这张图里,客户端只跟第一个处理器打交道。至于后面还有几个处理器、谁处理谁不处理,客户端一概不知。这就是解耦。
纯责任链与不纯责任链
这里有个重要的概念区分,我当年面试时就被问过——纯责任链和不纯责任链有什么区别?
| 对比维度 | 纯责任链 | 不纯责任链 |
|---|---|---|
| 处理方式 | 只有一个处理器处理请求 | 多个处理器都可以处理 |
| 终止条件 | 找到第一个能处理的就停止 | 所有处理器都有机会处理 |
| 典型场景 | 审批流程、日志级别过滤 | 拦截器链、过滤器链 |
| 请求流向 | 直线传递,遇处理即止 | 可以继续传递,也可以中断 |
举个实际例子。我在做OA系统时,审批流程就是纯责任链——组长批了就不需要经理批了,一个人拍板就行。但后来做网关中间件时,发现完全不是这么回事。鉴权、限流、日志记录,这些拦截器每个都要执行一遍,谁也不能跳过谁。这就是不纯责任链。
核心区别一句话总结:纯责任链是“谁行谁上”,不纯责任链是“大家轮流上”。
代码示例:纯责任链的实现
先看一个纯责任链的经典例子——请假审批:
// 抽象处理器
public abstract class Approver {
protected Approver next;
public void setNext(Approver next) {
this.next = next;
}
public abstract void handleRequest(int days);
}
// 组长:能批3天以内
public class TeamLeader extends Approver {
@Override
public void handleRequest(int days) {
if (days <= 3) {
System.out.println("组长批准了" + days + "天假期");
} else if (next != null) {
next.handleRequest(days);
}
}
}
// 经理:能批7天以内
public class Manager extends Approver {
@Override
public void handleRequest(int days) {
if (days <= 7) {
System.out.println("经理批准了" + days + "天假期");
} else if (next != null) {
next.handleRequest(days);
}
}
}
// 总监:能批30天以内
public class Director extends Approver {
@Override
public void handleRequest(int days) {
if (days <= 30) {
System.out.println("总监批准了" + days + "天假期");
} else {
System.out.println("假期天数超出范围,需要特批");
}
}
}
// 客户端使用
public class Client {
public static void main(String[] args) {
Approver teamLeader = new TeamLeader();
Approver manager = new Manager();
Approver director = new Director();
teamLeader.setNext(manager);
manager.setNext(director);
teamLeader.handleRequest(5); // 经理批准
teamLeader.handleRequest(2); // 组长批准
teamLeader.handleRequest(20); // 总监批准
}
}
这段代码很直观。每个处理器只判断自己能不能处理,不能就往后传。注意看,客户端只调用了第一个处理器,后面的链式调用是内部自动完成的。
代码示例:不纯责任链的实现
不纯责任链的典型代表就是拦截器。每个拦截器都执行,但可以决定是否继续:
// 拦截器接口
public interface Interceptor {
boolean preHandle(String request);
}
// 鉴权拦截器
public class AuthInterceptor implements Interceptor {
@Override
public boolean preHandle(String request) {
System.out.println("鉴权拦截器执行");
// 假设鉴权通过
return true; // true表示继续执行下一个
}
}
// 日志拦截器
public class LogInterceptor implements Interceptor {
@Override
public boolean preHandle(String request) {
System.out.println("日志拦截器执行,记录请求:" + request);
return true;
}
}
// 限流拦截器
public class RateLimitInterceptor implements Interceptor {
@Override
public boolean preHandle(String request) {
System.out.println("限流拦截器执行");
// 假设没有触发限流
return true;
}
}
// 拦截器链
public class InterceptorChain {
private List<Interceptor> interceptors = new ArrayList<>();
private int index = 0;
public void addInterceptor(Interceptor interceptor) {
interceptors.add(interceptor);
}
public boolean execute(String request) {
for (Interceptor interceptor : interceptors) {
if (!interceptor.preHandle(request)) {
return false; // 某个拦截器中断了链
}
}
return true;
}
}
个人经验:我在写拦截器链时,习惯把执行顺序和业务逻辑分开。拦截器的顺序通过配置文件管理,而不是硬编码在代码里。这样调整顺序时不用改代码,改个配置就行。
责任链模式在Spring MVC拦截器中的应用
说到不纯责任链,Spring MVC的拦截器就是教科书级的实现。我当初看Spring源码时,发现它的HandlerExecutionChain就是一条标准的责任链。
Spring MVC拦截器的核心流程是这样的:
- 请求到达DispatcherServlet
- DispatcherServlet获取HandlerExecutionChain
- 按顺序执行所有拦截器的preHandle方法
- 如果某个preHandle返回false,请求被拦截
- 如果全部通过,执行目标Controller方法
- 按逆序执行所有拦截器的postHandle方法
- 渲染视图后,按逆序执行afterCompletion方法
你看,这就是不纯责任链的典型用法——每个拦截器都有机会处理,但任何一个拦截器都可以中断整个流程。
Spring源码中,HandlerExecutionChain的核心代码大致是这样的:
public class HandlerExecutionChain {
private final Object handler;
private final List<HandlerInterceptor> interceptorList = new ArrayList<>();
private int interceptorIndex = -1;
public boolean applyPreHandle(HttpServletRequest request,
HttpServletResponse response) throws Exception {
for (int i = 0; i < interceptorList.size(); i++) {
HandlerInterceptor interceptor = interceptorList.get(i);
if (!interceptor.preHandle(request, response, this.handler)) {
// 如果返回false,触发已执行拦截器的afterCompletion
triggerAfterCompletion(request, response, null);
return false;
}
interceptorIndex = i;
}
return true;
}
public void applyPostHandle(HttpServletRequest request,
HttpServletResponse response,
ModelAndView mv) throws Exception {
// 逆序执行postHandle
for (int i = interceptorList.size() - 1; i >= 0; i--) {
HandlerInterceptor interceptor = interceptorList.get(i);
interceptor.postHandle(request, response, this.handler, mv);
}
}
}
我曾经踩过的坑:有一次我在拦截器里做了数据库操作,结果preHandle返回false时,数据库连接没有及时释放。后来才发现,afterCompletion才是做资源清理的正确地方。记住,preHandle返回false后,只有已经执行过的拦截器的afterCompletion会被调用,后面的拦截器根本不会执行。
责任链模式的使用场景总结
什么时候该用责任链模式?我总结了几个判断标准:
- 多个处理器都有可能处理同一个请求——比如审批流程、日志过滤
- 处理器顺序很重要——比如鉴权必须在业务处理之前
- 客户端不想知道具体谁处理——解耦,让客户端只关心发起请求
- 处理器的组合可以动态变化——比如通过配置增减拦截器
嗯,责任链模式就讲到这里。它不是什么高深的技术,但用好了能让代码结构清晰很多。尤其是做中间件、网关、框架这类需要扩展性的系统时,责任链几乎是标配。