责任链模式:请求传递的艺术

责任链模式,说白了就是让多个对象都有机会处理同一个请求。它们排成一条链,请求沿着链传递,直到某个对象处理它为止。这就像公司里的审批流程——你的请假申请先到组长那,组长批不了就往上递,直到有人能拍板。

我最早接触这个模式,是在做Web框架的FilterChain时。那时候我还在想:为什么要把过滤器串起来?直接一个个调用不就行了?后来踩了坑才明白,责任链的精髓在于解耦——发送者不需要知道谁会处理请求,接收者也不需要知道请求从哪来。

模式结构

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

  • Handler(处理者):定义处理请求的接口,并且持有下一个处理者的引用
  • ConcreteHandler(具体处理者):实现处理逻辑,决定是自己处理还是传给下一个
  • Client(客户端):构建责任链,并向链头发起请求

嗯,这里要注意:责任链可以是单向的,也可以是双向的。Netty的Pipeline就是双向链,请求从头部进来,经过所有处理器,再原路返回。这个设计非常巧妙,我后面会细说。

Java实现:从FilterChain说起

先看一个经典的FilterChain实现。我记得在写一个Web框架时,需要支持多个过滤器对请求进行预处理。

// 过滤器接口
public interface Filter {
    void doFilter(Request request, Response response, FilterChain chain);
}

// 责任链
public class FilterChain {
    private List<Filter> filters = new ArrayList<>();
    private int index = 0;
    
    public FilterChain addFilter(Filter filter) {
        filters.add(filter);
        return this;
    }
    
    public void doFilter(Request request, Response response) {
        if (index < filters.size()) {
            Filter filter = filters.get(index++);
            filter.doFilter(request, response, this);
        }
    }
}

// 具体过滤器
public class AuthFilter implements Filter {
    @Override
    public void doFilter(Request request, Response response, FilterChain chain) {
        System.out.println("认证过滤器:检查用户身份");
        // 继续传递
        chain.doFilter(request, response);
        System.out.println("认证过滤器:响应后处理");
    }
}

public class LogFilter implements Filter {
    @Override
    public void doFilter(Request request, Response response, FilterChain chain) {
        System.out.println("日志过滤器:记录请求信息");
        chain.doFilter(request, response);
        System.out.println("日志过滤器:记录响应信息");
    }
}

你看,每个过滤器只关心自己的事,处理完就传给下一个。这种设计让添加新过滤器变得非常简单——只需要实现Filter接口,然后加到链里就行。

关键点:FilterChain本身也实现了doFilter方法,但它不是真正的处理器,而是负责维护链的遍历。这种模式在Java Servlet规范中广泛使用。

C++实现:更贴近底层

C++的实现思路类似,但要注意内存管理。我个人习惯用智能指针来管理链上的节点。

#include <iostream>
#include <memory>

class Handler {
public:
    virtual ~Handler() = default;
    void setNext(std::shared_ptr<Handler> next) {
        this->next = next;
    }
    virtual void handle(int request) {
        if (next) {
            next->handle(request);
        }
    }
protected:
    std::shared_ptr<Handler> next;
};

class ConcreteHandler1 : public Handler {
public:
    void handle(int request) override {
        if (request >= 0 && request < 10) {
            std::cout << "Handler1 处理请求: " << request << std::endl;
        } else {
            Handler::handle(request);
        }
    }
};

class ConcreteHandler2 : public Handler {
public:
    void handle(int request) override {
        if (request >= 10 && request < 20) {
            std::cout << "Handler2 处理请求: " << request << std::endl;
        } else {
            Handler::handle(request);
        }
    }
};

// 使用
int main() {
    auto h1 = std::make_shared<ConcreteHandler1>();
    auto h2 = std::make_shared<ConcreteHandler2>();
    h1->setNext(h2);
    
    h1->handle(5);   // Handler1处理
    h1->handle(15);  // Handler2处理
    h1->handle(25);  // 无人处理
    return 0;
}

避坑指南:我曾经在C++项目中忘记处理链尾的情况,导致请求丢失。建议在基类Handler中提供一个默认的handle实现,要么传给下一个,要么记录日志表示无人处理。

Netty Pipeline:责任链的巅峰之作

说到责任链模式,不得不提Netty的Pipeline。它把责任链玩出了新高度——双向链、事件驱动、动态增删处理器。

Netty的Pipeline结构是这样的:

// Netty中的ChannelHandler
public class SimpleServerHandler extends ChannelInboundHandlerAdapter {
    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) {
        System.out.println("服务端收到消息: " + msg);
        // 处理完后继续传递
        ctx.fireChannelRead(msg);
    }
    
    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
        cause.printStackTrace();
        ctx.close();
    }
}

// 添加处理器到Pipeline
ChannelPipeline pipeline = ch.pipeline();
pipeline.addLast("decoder", new StringDecoder());
pipeline.addLast("handler", new SimpleServerHandler());

为什么Netty要设计成双向链?因为网络通信中,请求进来和响应出去是两个方向。入站处理器处理请求,出站处理器处理响应。这种设计让代码非常清晰——你只需要关注当前方向的处理逻辑。

Netty Pipeline 双向责任链 Decoder Handler1 Handler2 Encoder Handler3 Tail 请求处理完成 入站 出站 Socket 请求从入站处理器链进入,处理完成后从出站处理器链返回

实际应用场景

责任链模式在真实项目中随处可见。我总结了几种典型场景:

场景 说明 示例
请求预处理 对请求进行认证、授权、日志、限流等 Spring Security FilterChain
事件处理 事件沿着链传递,每个处理器决定是否消费 GUI事件处理、Netty Pipeline
审批流程 不同级别的审批人处理不同范围的请求 OA系统、报销审批
异常处理 异常从底层向上层传递,直到被捕获 Java异常机制、try-catch链

注意事项:责任链模式虽然灵活,但也不是万能的。如果链太长,性能会受影响。我曾经在一个项目中看到过20多个过滤器串在一起,每次请求都要遍历一遍,响应时间直接翻倍。建议控制链的长度,或者考虑用树形结构替代。

优缺点总结

责任链模式的好处很明显:

  • 解耦:请求发送者和接收者完全解耦
  • 灵活:可以动态调整链的顺序和成员
  • 可扩展:新增处理器不影响现有代码

但也有它的短板:

  • 性能:链越长,性能损耗越大
  • 调试困难:请求在链中传递,很难定位问题
  • 可能被忽略:如果链尾没有兜底处理,请求可能丢失

我个人建议,在需要多个处理器按顺序处理同一请求时,优先考虑责任链模式。但如果你只需要一个处理器来处理请求,那就别用——杀鸡焉用牛刀。

避坑指南:我曾经在一个项目中,把责任链和观察者模式搞混了。责任链是串行传递,每个处理器决定是否处理;观察者模式是广播通知,所有观察者都会收到。这两个模式的应用场景完全不同,千万别用错。

好了,责任链模式就聊到这。记住它的核心思想:让多个对象都有机会处理请求,直到有人接手。下次你写过滤器、拦截器或者Pipeline时,不妨想想这个模式,说不定能帮你写出更优雅的代码。


公众号:蓝海资料掘金营,微信deep3321