责任链模式:让每个处理器都有机会说“不”

责任链模式,说白了就是一条处理流水线。每个节点只关心自己能不能处理,处理不了就往后传。我刚开始接触这个模式时,觉得它跟if-else没啥区别。后来在项目中踩过坑才明白——它解决的是“请求发送者”和“请求接收者”之间的解耦问题。

你想想看,如果一段审批流程要经过组长、经理、总监、CEO,你用if-else写出来会是什么样子?嵌套个四五层,代码就成了一团乱麻。责任链模式就是专门对付这种场景的。

责任链模式的结构

责任链模式的核心角色就三个:

  • Handler(抽象处理器):定义处理请求的接口,通常包含一个指向下一个处理器的引用
  • ConcreteHandler(具体处理器):实现自己的处理逻辑,决定是否处理或转发
  • Client(客户端):发起请求,交给链的第一个处理器

我画了一张结构图,你看一眼就明白了:

责任链模式结构图 Client(客户端) Handler(抽象处理器) + setNext(handler): Handler ConcreteHandler1 handleRequest(request) ConcreteHandler2 handleRequest(request) ConcreteHandler3 handleRequest(request) 每个具体处理器持有下一个处理器的引用,形成链式结构

这张图里,客户端只跟第一个处理器打交道。至于后面还有几个处理器、谁处理谁不处理,客户端一概不知。这就是解耦。

纯责任链与不纯责任链

这里有个重要的概念区分,我当年面试时就被问过——纯责任链和不纯责任链有什么区别?

对比维度 纯责任链 不纯责任链
处理方式 只有一个处理器处理请求 多个处理器都可以处理
终止条件 找到第一个能处理的就停止 所有处理器都有机会处理
典型场景 审批流程、日志级别过滤 拦截器链、过滤器链
请求流向 直线传递,遇处理即止 可以继续传递,也可以中断

举个实际例子。我在做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拦截器的核心流程是这样的:

  1. 请求到达DispatcherServlet
  2. DispatcherServlet获取HandlerExecutionChain
  3. 按顺序执行所有拦截器的preHandle方法
  4. 如果某个preHandle返回false,请求被拦截
  5. 如果全部通过,执行目标Controller方法
  6. 按逆序执行所有拦截器的postHandle方法
  7. 渲染视图后,按逆序执行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会被调用,后面的拦截器根本不会执行。

责任链模式的使用场景总结

什么时候该用责任链模式?我总结了几个判断标准:

  • 多个处理器都有可能处理同一个请求——比如审批流程、日志过滤
  • 处理器顺序很重要——比如鉴权必须在业务处理之前
  • 客户端不想知道具体谁处理——解耦,让客户端只关心发起请求
  • 处理器的组合可以动态变化——比如通过配置增减拦截器

嗯,责任链模式就讲到这里。它不是什么高深的技术,但用好了能让代码结构清晰很多。尤其是做中间件、网关、框架这类需要扩展性的系统时,责任链几乎是标配。