概述
责任链模式(Chain of Responsibility Pattern)是面向对象设计中最为经典的行为型模式之一。其核心意图在于使多个对象都有机会处理请求,从而避免请求发送者与接收者之间的耦合。这些对象被组织成一条链,请求沿着链条传递,直到某个对象处理它为止。
在实际开发中,责任链模式解决的核心问题异常鲜明:请求发送者与多个潜在处理者之间的解耦、动态组装处理流程的能力、以及避免庞大的 if-else 分支地狱。试想一个请假审批系统,若使用硬编码判断,将充斥着 if (days <= 3) ... else if (days <= 7) ... 的逻辑,任何审批层级的调整都会导致代码修改,严重违反开闭原则。责任链模式则优雅地将每个审批节点独立为处理者对象,并通过链表结构自由组装,实现了流程配置化与代码稳定性的统一。
本文将带领读者完成一次从原始反模式代码到专家级责任链应用的演进之旅。首先,我们会从 GoF 标准定义与 UML 类图出发,建立理论基础;接着,通过完整可运行的 Java 代码展示从 if-else 重构为经典责任链的全过程,并探讨纯与不纯责任链、双向链等变体;随后,深入 JDK、Servlet、Spring、Netty、MyBatis、Dubbo 等主流框架源码,剖析责任链在日志、过滤器、拦截器、Pipeline、插件机制中的精妙运用;第四章节将视野拓展至分布式环境,解读网关过滤器链、微服务调用链、分布式事务协调等场景;最后辅以模式辨析、适用场景梳理及专家级面试题解答。全文逾万字,力求为 Java 开发者构建一套完整的责任链模式知识体系。
一、模式定义与结构
1.1 GoF 标准定义
Chain of Responsibility: Avoid coupling the sender of a request to its receiver by giving more than one object a chance to handle the request. Chain the receiving objects and pass the request along the chain until an object handles it.
责任链模式:使多个对象都有机会处理请求,从而避免请求发送者与接收者之间的耦合。将这些对象连成一条链,并沿着该链传递请求,直到有一个对象处理它为止。
1.2 UML 类图(Mermaid Flowchart)
以下使用 Mermaid flowchart 语法绘制责任链模式的标准结构,清晰标注抽象处理者、具体处理者及客户端三类角色。
classDiagram
class Client
class Handler {
<<abstract>>
+setNext(Handler next)
+handleRequest(Request req)
}
class ConcreteHandlerA {
+handleRequest(Request req)
}
class ConcreteHandlerB {
+handleRequest(Request req)
}
Client --> Handler : 创建并组装链
Handler <|-- ConcreteHandlerA
Handler <|-- ConcreteHandlerB
Handler --> Handler : successor
1.3 类图详解与链表结构说明
上图展示了责任链模式的经典结构,其本质是一条单向链表。抽象处理者 Handler 内部持有指向自身类型的引用 successor(后继者),通过 setNext(Handler next) 方法将各个处理者对象串联起来。客户端 Client 负责创建具体处理者实例并按业务逻辑组装成链。当客户端发起请求时,仅需将请求对象提交给链首的处理者;链首处理者若无法处理,则调用 successor.handleRequest(req) 将请求传递给下一个处理者,如此递归或循环直至某个处理者消费该请求或链尾结束。
责任链模式将请求的发送者与接收者解耦的关键在于:客户端只认识链的入口,并不关心请求最终由谁处理,甚至不关心链条的具体组成。这种设计允许在运行时动态增删或重排处理节点,极大地提升了系统的灵活性与可扩展性。
各角色职责详述:
- Handler(抽象处理者):定义处理请求的接口规范,通常声明一个
handleRequest()方法,并维护一个指向后继处理者的引用。在 Java 实现中,Handler可以是接口、抽象类或具体类,取决于是否需要在基类中提供默认的传递行为。 - ConcreteHandler(具体处理者):实现
handleRequest()方法,在其内部判断当前请求是否应由自己处理;若是,则执行处理逻辑并返回;若否(或处理完毕后仍需继续传递),则调用successor.handleRequest()将请求转交下一节点。 - Client(客户端):负责实例化各个具体处理者,并通过调用
setNext()方法将它们链接成一条完整的责任链。随后,客户端将请求对象发送给链首处理者,启动链式处理流程。
值得强调的是,责任链的链表结构并非固定为单向传递。在后续章节中,我们将看到双向责任链(如 Netty 的出站与入站事件传播)以及树状责任链(如 Spring 的 BeanPostProcessor 链)等变体,但核心思想始终如一:将多个处理单元解耦为独立节点,并通过链式引用动态编排处理顺序。
二、代码演进与实现
本章通过完整可运行的 Java 代码,展示从反模式的分支判断到经典责任链模式的演进过程,并进一步探讨纯责任链、不纯责任链及双向责任链等变体。
2.1 不使用模式的原始代码:if-else 地狱
考虑一个简单的请假审批场景:请假天数 ≤3 天由组长审批,≤7 天由经理审批,≤30 天由总监审批,超过 30 天不允许申请。
/**
* 原始反模式:使用大量if-else分支判断审批逻辑
* 问题分析:
* 1. 代码臃肿:随着审批层级增加,if-else 链会越来越长
* 2. 违反开闭原则:每增加一个审批层级,必须修改此方法
* 3. 难以动态调整处理顺序:无法在运行时改变审批流程
* 4. 耦合严重:客户端直接依赖所有审批者的逻辑
*/
public class LeaveApprovalOld {
public static void main(String[] args) {
LeaveRequest request1 = new LeaveRequest("张三", 2);
LeaveRequest request2 = new LeaveRequest("李四", 5);
LeaveRequest request3 = new LeaveRequest("王五", 15);
LeaveRequest request4 = new LeaveRequest("赵六", 40);
processLeaveRequest(request1);
processLeaveRequest(request2);
processLeaveRequest(request3);
processLeaveRequest(request4);
}
public static void processLeaveRequest(LeaveRequest request) {
int days = request.getDays();
if (days <= 3) {
System.out.println("组长审批通过:" + request.getName() + " 请假 " + days + " 天");
} else if (days <= 7) {
System.out.println("经理审批通过:" + request.getName() + " 请假 " + days + " 天");
} else if (days <= 30) {
System.out.println("总监审批通过:" + request.getName() + " 请假 " + days + " 天");
} else {
System.out.println("请假天数过多,不予批准:" + request.getName());
}
}
static class LeaveRequest {
private String name;
private int days;
// 构造器、getter/setter 省略
public LeaveRequest(String name, int days) { this.name = name; this.days = days; }
public String getName() { return name; }
public int getDays() { return days; }
}
}
上述代码虽然简单,但扩展性极差。若公司新增“部门主管”审批层级,或调整各级别审批天数上限,都必须修改 processLeaveRequest 方法。在大型企业应用中,审批流程可能涉及数十个节点且频繁变动,这种硬编码方式无疑是维护的噩梦。
2.2 经典责任链模式重构
首先定义抽象处理者接口(或抽象类),包含处理请求的方法及设置下一处理者的方法。
/**
* 抽象处理者:定义处理请求的接口,并维护后继者引用
*/
abstract class ApprovalHandler {
protected ApprovalHandler nextHandler; // 后继处理者
// 设置下一处理者,返回当前处理者以支持链式调用
public ApprovalHandler setNext(ApprovalHandler next) {
this.nextHandler = next;
return this.nextHandler; // 返回 next 以便继续链式调用
}
// 处理请求的模板方法,由子类实现具体逻辑
public abstract void handleRequest(LeaveRequest request);
}
实现三个具体处理者类,各自在 handleRequest 中判断是否处理,否则转发给后继者。
/**
* 具体处理者:组长审批(处理3天及以内请假)
*/
class GroupLeaderHandler extends ApprovalHandler {
@Override
public void handleRequest(LeaveRequest request) {
if (request.getDays() <= 3) {
System.out.println("组长审批通过:" + request.getName() + " 请假 " + request.getDays() + " 天");
} else {
// 当前无法处理,转发给下一个处理者
if (nextHandler != null) {
nextHandler.handleRequest(request);
} else {
System.out.println("请求未被处理:" + request.getName());
}
}
}
}
/**
* 具体处理者:经理审批(处理4~7天)
*/
class ManagerHandler extends ApprovalHandler {
@Override
public void handleRequest(LeaveRequest request) {
if (request.getDays() <= 7) {
System.out.println("经理审批通过:" + request.getName() + " 请假 " + request.getDays() + " 天");
} else {
if (nextHandler != null) {
nextHandler.handleRequest(request);
} else {
System.out.println("请求未被处理:" + request.getName());
}
}
}
}
/**
* 具体处理者:总监审批(处理8~30天)
*/
class DirectorHandler extends ApprovalHandler {
@Override
public void handleRequest(LeaveRequest request) {
if (request.getDays() <= 30) {
System.out.println("总监审批通过:" + request.getName() + " 请假 " + request.getDays() + " 天");
} else {
// 超过30天,可在此处理拒绝逻辑,或继续转发给更高级别(此处为链尾,直接拒绝)
System.out.println("请假天数过多,不予批准:" + request.getName());
}
}
}
客户端负责组装责任链并提交请求。
/**
* 客户端:构建责任链并启动处理流程
*/
public class ChainOfResponsibilityDemo {
public static void main(String[] args) {
// 1. 创建具体处理者实例
ApprovalHandler groupLeader = new GroupLeaderHandler();
ApprovalHandler manager = new ManagerHandler();
ApprovalHandler director = new DirectorHandler();
// 2. 组装责任链:组长 -> 经理 -> 总监
groupLeader.setNext(manager).setNext(director); // 链式调用设置后继
// 3. 提交请求
LeaveRequest req1 = new LeaveRequest("张三", 2);
LeaveRequest req2 = new LeaveRequest("李四", 5);
LeaveRequest req3 = new LeaveRequest("王五", 15);
LeaveRequest req4 = new LeaveRequest("赵六", 40);
groupLeader.handleRequest(req1);
groupLeader.handleRequest(req2);
groupLeader.handleRequest(req3);
groupLeader.handleRequest(req4);
}
}
运行结果:
组长审批通过:张三 请假 2 天
经理审批通过:李四 请假 5 天
总监审批通过:王五 请假 15 天
请假天数过多,不予批准:赵六
重构后的代码将每个审批节点封装为独立的类,新增或调整审批逻辑只需添加新的 Handler 并在组装时插入链中,完全遵循开闭原则。同时,处理顺序可在运行时动态决定,极大地提升了灵活性。
2.3 责任链模式的变体
a. 纯责任链(Pure Chain of Responsibility)
纯责任链要求:请求必须被某一个处理者处理,或者一直传递到链尾仍未处理则丢弃。其特点是处理者要么完全处理请求,要么将请求原样传递给后继者,不允许处理一部分后再传递。上述请假审批示例即属纯责任链。
b. 不纯责任链(Impure Chain of Responsibility)
不纯责任链允许每个处理者都可以处理请求的一部分,然后继续向下传递。典型例子是 Servlet 过滤器链(FilterChain):每个 Filter 可以对请求和响应进行预处理和后处理,但必须调用 chain.doFilter() 将请求传递下去,否则链路中断。
/**
* 不纯责任链示例:日志记录 + 性能监控过滤器链
*/
interface Filter {
void doFilter(Request req, Response res, FilterChain chain);
}
class LogFilter implements Filter {
public void doFilter(Request req, Response res, FilterChain chain) {
System.out.println("LogFilter: 请求到达,记录日志");
chain.doFilter(req, res); // 继续传递
System.out.println("LogFilter: 响应返回,记录日志");
}
}
class PerformanceFilter implements Filter {
public void doFilter(Request req, Response res, FilterChain chain) {
long start = System.currentTimeMillis();
chain.doFilter(req, res);
long end = System.currentTimeMillis();
System.out.println("PerformanceFilter: 耗时 " + (end - start) + "ms");
}
}
c. 双向责任链(Bidirectional Chain)
双向责任链常见于网络框架(如 Netty),请求(入站事件)沿一个方向传递,而响应(出站事件)沿相反方向传递。处理者可以同时处理入站和出站事件,实现请求与响应的分离处理。
2.4 责任链请求传递流程图
以下 Mermaid flowchart 展示了请求沿责任链传递直至被处理的完整流程。
flowchart TD
Start([客户端发起请求]) --> A[Handler1 处理?]
A -->|是| Process1[Handler1 处理请求]
Process1 --> End([流程结束])
A -->|否| B[传递给 Handler2]
B --> C[Handler2 处理?]
C -->|是| Process2[Handler2 处理请求]
Process2 --> End
C -->|否| D[传递给 Handler3]
D --> E[Handler3 处理?]
E -->|是| Process3[Handler3 处理请求]
Process3 --> End
E -->|否| F[...]
F --> Tail[到达链尾仍未处理]
Tail --> Discard[丢弃请求或默认处理]
Discard --> End
classDef startend fill:#e8f5e9,stroke:#2e7d32,stroke-width:2px;
classDef process fill:#fff3e0,stroke:#ef6c00,stroke-width:2px;
classDef decision fill:#e3f2fd,stroke:#1565c0,stroke-width:2px;
class Start,End startend;
class Process1,Process2,Process3 process;
class A,C,E decision;
流程图解读:请求自客户端发出后,首先到达链首的 Handler1。Handler1 根据自身条件判断是否能处理该请求;若能,则执行处理逻辑并终止传递(纯责任链)或处理后继续传递(不纯责任链);若不能,则将请求转交给后继 Handler2。这一判断—传递的循环沿链表持续进行,直到某个处理者消费请求或到达链尾。若到达链尾仍无处理者响应,则通常采取丢弃请求或触发默认处理逻辑。该流程图清晰地揭示了责任链模式的单向传递性与短路终止性,是理解 FilterChain、拦截器链等框架机制的基石。
三、源码级应用分析
责任链模式在 Java 生态及主流框架中无处不在。本章深入剖析 JDK、Servlet、Spring、Netty、MyBatis、Dubbo 等源码中的典型应用,揭示其实现精髓。
3.1 JDK 中的责任链模式
3.1.1 java.util.logging.Logger 的 Handler 链
Java 标准日志框架 java.util.logging 中,Logger 可以将日志消息转发给零个或多个 Handler 对象进行处理,这些 Handler 形成了典型的责任链结构。
// Logger 内部维护 Handler 数组
private Handler[] handlers;
public void log(LogRecord record) {
// ... 日志级别判断
for (Handler handler : getHandlers()) {
handler.publish(record);
}
}
与标准责任链略有不同,日志 Handler 链是广播式的,每个 Handler 都会收到日志记录,而非短路传递。但就其“多个处理者依次处理同一请求”的结构而言,仍可视为责任链的变体。
3.1.2 ClassLoader 的双亲委派模型
类加载器的双亲委派机制是责任链模式在 JDK 中的经典体现。当一个类加载器收到类加载请求时,它首先将请求委派给父加载器,依次向上递归,直到启动类加载器。若父加载器无法加载,才由子加载器尝试自己加载。
// ClassLoader.loadClass() 简化版源码
protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
synchronized (getClassLoadingLock(name)) {
// 1. 检查是否已加载
Class<?> c = findLoadedClass(name);
if (c == null) {
try {
if (parent != null) {
// 2. 委派给父加载器(沿链向上传递)
c = parent.loadClass(name, false);
} else {
c = findBootstrapClassOrNull(name);
}
} catch (ClassNotFoundException e) {
// 父加载器无法加载,继续向下传递
}
if (c == null) {
// 3. 父加载器均无法加载,自己尝试加载
c = findClass(name);
}
}
if (resolve) {
resolveClass(c);
}
return c;
}
}
此处的“链”是类加载器的父子层级关系,请求沿父链向上传递,一旦某个加载器能够加载,则终止传递。这种设计既保证了核心类库的安全性,又实现了类的共享。
3.1.3 java.awt.Component 的事件处理链
AWT/Swing 中的事件处理采用了责任链模式。当事件发生在某个组件上时,事件会首先传递给该组件,若组件未处理,则向上传递给其父容器,直至顶层窗口。这种机制称为事件向上传播(Event Bubbling),本质是沿组件树的责任链。
3.1.4 FilterInputStream 装饰器链与责任链的区别
FilterInputStream 及其子类(如 BufferedInputStream、DataInputStream)常常被误解为责任链,实际上它们是装饰器模式的典型应用。装饰器链强调动态地为对象添加功能,每个装饰器在调用目标对象方法的前后可以附加行为,而责任链强调请求的传递与处理者选择。区别在于:装饰器链的每个节点都会执行(除非抛出异常),且最终必须到达被装饰的核心对象;而责任链可能在中间节点终止。
3.2 Servlet 与 Tomcat 深度剖析
3.2.1 javax.servlet.FilterChain 标准实现
Servlet 规范中的 FilterChain 是不纯责任链模式的教科书级范例。Filter 接口定义如下:
public interface Filter {
void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException;
}
开发者通过在 doFilter 方法中调用 chain.doFilter(req, res) 将请求传递给下一个过滤器或目标 Servlet。
3.2.2 Tomcat 中 ApplicationFilterChain 的内部机制
Tomcat 的 org.apache.catalina.filters.ApplicationFilterChain 是 FilterChain 的核心实现,其内部使用数组存储过滤器及一个位置游标 pos,通过递归调用实现链式传递。
// ApplicationFilterChain 简化源码
public final class ApplicationFilterChain implements FilterChain {
private ApplicationFilterConfig[] filters = new ApplicationFilterConfig[0];
private Servlet servlet = null;
private int pos = 0; // 当前执行的过滤器索引
private int n = 0; // 过滤器总数
@Override
public void doFilter(ServletRequest request, ServletResponse response)
throws IOException, ServletException {
if (pos < n) {
ApplicationFilterConfig filterConfig = filters[pos++];
Filter filter = filterConfig.getFilter();
// 递归调用过滤器的 doFilter 方法
filter.doFilter(request, response, this);
// 过滤器返回后,执行后续处理(后置拦截)
return;
}
// 所有过滤器执行完毕,调用目标 Servlet
servlet.service(request, response);
}
}
这种递归结构精妙地实现了前置拦截(调用 filter.doFilter 之前)和后置拦截(递归返回之后)的能力。开发者若不慎遗漏 chain.doFilter() 调用,将导致责任链断裂,请求无法到达后续过滤器及 Servlet。
3.2.3 Filter 执行顺序控制
在 Servlet 3.0+ 中,可通过 @WebFilter 注解的 filterName 或 web.xml 中的 <filter-mapping> 顺序控制过滤器链的顺序。Spring Boot 中则通过 @Order 注解或 Ordered 接口指定顺序。
3.2.4 FilterChain 执行流程图
flowchart TD
Start([请求到达]) --> F1[Filter1 前置处理]
F1 --> Call1[调用 chain.doFilter]
Call1 --> F2[Filter2 前置处理]
F2 --> Call2[调用 chain.doFilter]
Call2 --> F3[Filter3 前置处理]
F3 --> Call3[调用 chain.doFilter]
Call3 --> Servlet[目标 Servlet 处理]
Servlet --> Resp3[Filter3 后置处理]
Resp3 --> Resp2[Filter2 后置处理]
Resp2 --> Resp1[Filter1 后置处理]
Resp1 --> End([响应返回])
classDef filter fill:#e3f2fd,stroke:#1565c0,stroke-width:2px;
classDef servlet fill:#fff3e0,stroke:#ef6c00,stroke-width:2px;
class F1,F2,F3 filter;
class Servlet servlet;
执行流程解读:该流程图展示了经典的洋葱模型。请求到达后,依次穿过 Filter1、Filter2、Filter3 的前置处理逻辑,最终抵达目标 Servlet。Servlet 处理完毕后,响应沿相反顺序逐层返回,依次执行各 Filter 的后置处理。这种双向拦截能力是责任链模式与递归调用的完美结合。在框架应用中,前置处理常用于鉴权、参数校验、日志记录,后置处理则用于响应压缩、统计分析等。若任一 Filter 未调用 chain.doFilter(),则请求传递中断,后续 Filter 与 Servlet 将不会执行,因此开发时必须格外留意。
3.3 Spring 框架深度剖析
3.3.1 HandlerInterceptor 拦截器链
Spring MVC 的 HandlerInterceptor 提供了三个拦截点:preHandle(前置处理)、postHandle(后置处理,视图渲染前)、afterCompletion(完成处理,视图渲染后)。这些拦截器被组织成责任链,由 DispatcherServlet 统一调度。
// HandlerExecutionChain 内部维护拦截器列表
public class HandlerExecutionChain {
private HandlerInterceptor[] interceptors;
private int interceptorIndex = -1;
boolean applyPreHandle(HttpServletRequest request, HttpServletResponse response) throws Exception {
for (int i = 0; i < interceptors.length; i++) {
HandlerInterceptor interceptor = interceptors[i];
if (!interceptor.preHandle(request, response, this.handler)) {
triggerAfterCompletion(request, response, null);
return false; // 某个 preHandle 返回 false,中断链
}
this.interceptorIndex = i;
}
return true;
}
}
preHandle 采用正向遍历且支持短路:若某拦截器返回 false,则链终止,后续拦截器及处理器不再执行。postHandle 和 afterCompletion 则采用反向遍历,体现了责任链的双向性。
3.3.2 AOP 切面链
Spring AOP 通过责任链模式将多个切面(Aspect)的通知(Advice)组织成一条调用链。当目标方法被调用时,Spring 会创建一个 MethodInvocation 对象,内部维护通知列表及当前索引,通过递归调用依次执行各切面的增强逻辑。
// ReflectiveMethodInvocation 简化逻辑
public Object proceed() throws Throwable {
if (this.currentInterceptorIndex == this.interceptorsAndDynamicMethodMatchers.size() - 1) {
return invokeJoinpoint(); // 执行目标方法
}
Object interceptorOrInterceptionAdvice =
this.interceptorsAndDynamicMethodMatchers.get(++this.currentInterceptorIndex);
// 调用拦截器的 invoke 方法,内部会递归调用 this.proceed()
return ((MethodInterceptor) interceptorOrInterceptionAdvice).invoke(this);
}
切面执行顺序可通过 @Order 注解或实现 Ordered 接口控制,数值越小优先级越高。
3.3.3 BeanPostProcessor 后置处理器链
Spring 容器在 Bean 初始化前后会遍历所有 BeanPostProcessor,依次调用其 postProcessBeforeInitialization 和 postProcessAfterInitialization 方法。这是一种典型的遍历式责任链,每个处理器都有机会对 Bean 实例进行加工(例如 AutowiredAnnotationBeanPostProcessor 处理 @Autowired 注入)。
// AbstractAutowireCapableBeanFactory 中初始化 Bean 的片段
protected Object initializeBean(String beanName, Object bean, RootBeanDefinition mbd) {
// ...
Object wrappedBean = bean;
if (mbd == null || !mbd.isSynthetic()) {
wrappedBean = applyBeanPostProcessorsBeforeInitialization(wrappedBean, beanName);
}
invokeInitMethods(beanName, wrappedBean, mbd);
if (mbd == null || !mbd.isSynthetic()) {
wrappedBean = applyBeanPostProcessorsAfterInitialization(wrappedBean, beanName);
}
return wrappedBean;
}
3.3.4 Spring Security 过滤器链
Spring Security 的核心是 SecurityFilterChain,它内部包含一组有序的 Filter(如 UsernamePasswordAuthenticationFilter、ExceptionTranslationFilter、FilterSecurityInterceptor 等)。这些过滤器组成了一条标准 Servlet 过滤器链,负责认证、授权、会话管理等安全功能。Spring Security 通过 FilterChainProxy 作为入口,根据请求路径匹配对应的 SecurityFilterChain,实现多链支持。
3.4 Netty 框架:ChannelPipeline 与 ChannelHandler
Netty 的 ChannelPipeline 是责任链模式在网络编程中的巅峰应用。每个 Channel 拥有一个 ChannelPipeline,其中包含一系列 ChannelHandler,用于处理入站(Inbound)和出站(Outbound)事件。
// ChannelPipeline 接口(简化)
public interface ChannelPipeline {
ChannelPipeline addLast(ChannelHandler... handlers);
ChannelPipeline fireChannelRead(Object msg); // 触发入站事件
ChannelPipeline write(Object msg); // 触发出站事件
}
入站事件(如数据读取、通道激活)从 ChannelPipeline 头部向尾部传播;出站事件(如写数据、绑定端口)则从尾部向头部传播。这种双向传播机制使得处理者可以灵活地插入编解码、业务逻辑、异常处理等功能。
flowchart LR
subgraph Pipeline
direction LR
Head --> In1[InboundHandler1]
In1 --> In2[InboundHandler2]
In2 --> Tail
end
subgraph 入站事件传播
direction LR
Read([fireChannelRead]) --> In1 --> In2 --> Business([业务处理])
end
subgraph 出站事件传播
direction RL
Write([write]) --> In2 --> In1 --> Socket([Socket发送])
end
classDef inbound fill:#e8f5e9,stroke:#2e7d32,stroke-width:2px;
classDef outbound fill:#fff3e0,stroke:#ef6c00,stroke-width:2px;
class In1,In2 inbound;
传播机制详解:Netty 通过 ChannelHandlerContext 作为处理者与 Pipeline 的交互上下文。每个 Handler 被添加到 Pipeline 时,都会被包装成一个 DefaultChannelHandlerContext,这些 Context 对象彼此持有前后引用,形成双向链表。当调用 ctx.fireChannelRead(msg) 时,事件会传递给下一个入站处理器;调用 ctx.write(msg) 时,事件会传递给前一个出站处理器。这种设计将事件传播逻辑与具体处理逻辑分离,使得开发者可以专注于业务处理,而无需关心链表遍历细节。同时,Netty 通过 @Sharable 注解和线程模型保证了处理者的线程安全性,是高并发网络编程的典范。
3.5 MyBatis 插件机制
MyBatis 的插件机制通过动态代理和责任链模式实现对 Executor、StatementHandler、ParameterHandler、ResultSetHandler 等核心组件的增强。
// InterceptorChain 负责管理所有插件
public class InterceptorChain {
private final List<Interceptor> interceptors = new ArrayList<>();
public Object pluginAll(Object target) {
for (Interceptor interceptor : interceptors) {
target = interceptor.plugin(target); // 层层包装
}
return target;
}
}
Interceptor.plugin(target) 方法内部通常调用 Plugin.wrap(target, this),后者使用 JDK 动态代理生成代理对象。当调用目标对象方法时,代理会依次经过各插件的 intercept 方法,形成责任链式拦截。
3.6 Dubbo Filter 链
Dubbo 在服务提供者(Provider)和消费者(Consumer)两端都提供了过滤器链机制。Filter 接口定义如下:
@SPI
public interface Filter {
Result invoke(Invoker<?> invoker, Invocation invocation) throws RpcException;
}
Dubbo 将所有 Filter 组织成一条链,通过 ProtocolFilterWrapper 构建调用链。开发者可以通过 @Activate 注解指定过滤器的激活条件和顺序,实现监控、限流、日志、权限控制等功能。
四、分布式环境下的责任链模式
在微服务与云原生时代,责任链模式的应用从单体内部扩展到了分布式系统各层面。
4.1 网关中的过滤器链
Spring Cloud Gateway 基于 WebFlux 构建,其核心是 GlobalFilter 和 GatewayFilter 组成的过滤器链。请求到达网关后,依次经过各个过滤器,完成路由定位、鉴权、限流、请求改写、日志记录等操作。
// 自定义全局过滤器示例
@Component
public class TraceIdFilter implements GlobalFilter, Ordered {
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
String traceId = exchange.getRequest().getHeaders().getFirst("X-Trace-Id");
if (traceId == null) {
traceId = UUID.randomUUID().toString();
}
exchange.getAttributes().put("traceId", traceId);
return chain.filter(exchange).then(Mono.fromRunnable(() -> {
// 后置处理,如记录响应日志
}));
}
@Override
public int getOrder() { return -100; }
}
Zuul 1.x 基于 Servlet,其过滤器链(ZuulFilter)同样采用了责任链模式,通过 FilterProcessor 执行前置(pre)、路由(route)、后置(post)三种类型的过滤器。
4.2 微服务中间件调用链追踪
在分布式链路追踪系统(如 SkyWalking、Zipkin)中,Trace 信息(TraceId、SpanId)需要在整个调用链中透传。这通常通过在框架层注入拦截器(如 Spring Cloud Sleuth 的 TraceFeignClientAutoConfiguration)实现。拦截器从请求头中提取 TraceId 并存入线程上下文,随后在发起下游调用时将其注入请求头,形成跨越多个服务的逻辑责任链。
4.3 分布式事务中的事务协调器链
Seata 的 AT 模式中,全局事务的提交或回滚涉及 TM(事务管理器)、TC(事务协调器)和 RM(资源管理器)的多方协调。TM 向 TC 发起全局事务提交请求后,TC 会遍历所有涉及的 RM,依次通知它们执行分支提交。如果某个 RM 失败,则触发全局回滚,TC 再次遍历 RM 进行回滚补偿。这一流程本质上是协调器与各资源管理器之间的责任链交互。
4.4 消息队列的消费者责任链
RocketMQ 提供了 ConsumeMessageHook 接口,允许在消息消费前后执行自定义逻辑。多个 Hook 可以通过 DefaultMQPushConsumerImpl.registerConsumeMessageHook 注册,并按照注册顺序依次执行,形成责任链。
4.5 分布式工作流引擎
Flowable、Camunda 等工作流引擎中,流程定义中的多个用户任务(UserTask)、服务任务(ServiceTask)等节点在运行时形成一条执行链。流程引擎根据流程定义依次触发节点行为,节点处理完毕后方可进入下一节点,这与责任链模式高度契合。
4.6 分布式请求透传代码示例
以下展示如何利用责任链模式实现从网关到业务服务的 TraceId 透传。
// 网关层 Filter 提取或生成 TraceId
@Component
public class GatewayTraceFilter implements GlobalFilter, Ordered {
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
String traceId = exchange.getRequest().getHeaders().getFirst("X-Trace-Id");
if (traceId == null) traceId = UUID.randomUUID().toString().replace("-", "");
// 将 traceId 放入 ThreadLocal 或 Reactor Context
return chain.filter(exchange)
.contextWrite(ctx -> ctx.put("traceId", traceId));
}
}
// 下游服务 Feign 拦截器自动添加 TraceId 请求头
public class FeignTraceInterceptor implements RequestInterceptor {
@Override
public void apply(RequestTemplate template) {
String traceId = MDC.get("traceId"); // 或从 Context 获取
if (traceId != null) {
template.header("X-Trace-Id", traceId);
}
}
}
// 业务服务 Filter 接收 TraceId
@WebFilter
public class TraceFilter implements Filter {
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
HttpServletRequest httpReq = (HttpServletRequest) request;
String traceId = httpReq.getHeader("X-Trace-Id");
MDC.put("traceId", traceId);
try {
chain.doFilter(request, response);
} finally {
MDC.remove("traceId");
}
}
}
4.7 分布式场景下异常处理策略
在分布式责任链中,某个节点失败后的处理策略至关重要:
- 阻断链(Fail-Fast):适用于鉴权失败、参数非法等场景,应立即返回错误,不再继续调用下游。
- 继续传递(Best-Effort):适用于日志、监控等非核心功能,即使当前节点失败也不应影响主流程。
- 降级与熔断:结合 Hystrix/Sentinel,当某个下游节点失败率达到阈值时,触发熔断,后续请求直接走降级逻辑。
- 补偿机制:在分布式事务中,若某节点失败,需执行已成功节点的逆操作(如 Seata 的回滚)。
五、对比辨析
5.1 责任链模式 vs 装饰器模式
| 对比维度 | 责任链模式 | 装饰器模式 |
|---|---|---|
| 意图 | 多个处理者有机会处理请求,避免耦合 | 动态给对象添加额外职责 |
| 链终止 | 某个处理者处理请求后可能终止传递 | 装饰器必须调用目标对象,最终到达核心对象 |
| 关注点 | 请求的传递与处理者选择 | 功能的增强与组合 |
| 典型实例 | Servlet FilterChain、Spring Interceptor | Java I/O 流、Collections.synchronizedXXX |
5.2 责任链模式 vs 状态模式
- 责任链:请求在多个不同对象之间传递,传递方向由链结构决定。
- 状态模式:对象内部状态变化导致行为改变,状态对象可以自行决定下一状态。
5.3 责任链模式 vs 观察者模式
- 责任链:请求沿着链传递,每个处理者有权终止传递。
- 观察者模式:主题状态变化时,广播通知所有观察者,每个观察者都会收到通知且无法阻止广播。
5.4 责任链模式 vs 策略模式
- 策略模式:客户端主动选择具体策略。
- 责任链模式:处理者自行决定是否处理,客户端不关心最终由谁处理。
5.5 纯责任链 vs 不纯责任链 vs 过滤器链
| 类型 | 纯责任链 | 不纯责任链 | 过滤器链 |
|---|---|---|---|
| 请求处理 | 某处理者完全处理或传递 | 每个处理者处理部分并继续传递 | 每个过滤器均执行,链式递归 |
| 终止性 | 处理即终止 | 通常不终止(除非异常) | 必须调用 chain.doFilter 继续 |
| 代表案例 | 请假审批 | 日志处理器链(广播式) | Servlet Filter、Netty Pipeline |
六、适用场景深度剖析(含完整可运行Demo与流程图)
理论终究需要实践落地。本章精选责任链模式最具代表性的四大应用场景,为每个场景提供完整可运行的代码示例、Mermaid流程图以及深度设计解读,确保读者不仅知其然,更能快速上手。
6.1 多级审批流程
6.1.1 场景描述与技术理由
在企业OA系统中,请假、报销、合同审批等流程通常根据金额或天数划分审批层级。例如:3天以内组长审批,7天以内经理审批,30天以内总监审批,超过30天由CEO审批。若使用if-else硬编码,任何层级调整或新增审批节点都需要修改核心逻辑,违背开闭原则。
责任链模式将每个审批节点独立为Handler,通过链式组装实现流程可配置化。当审批规则变化时,仅需调整链的顺序或替换节点,业务代码保持稳定。
6.1.2 完整可运行代码
package com.example.chain.approval;
import java.util.ArrayList;
import java.util.List;
/**
* 请假请求对象
*/
class LeaveRequest {
private String applicant; // 申请人
private int days; // 请假天数
private String reason; // 请假原因
private boolean approved; // 是否批准
private String approver; // 批准人
public LeaveRequest(String applicant, int days, String reason) {
this.applicant = applicant;
this.days = days;
this.reason = reason;
}
// Getters and Setters
public String getApplicant() { return applicant; }
public int getDays() { return days; }
public String getReason() { return reason; }
public boolean isApproved() { return approved; }
public void setApproved(boolean approved) { this.approved = approved; }
public String getApprover() { return approver; }
public void setApprover(String approver) { this.approver = approver; }
@Override
public String toString() {
return String.format("申请人:%s,天数:%d,事由:%s", applicant, days, reason);
}
}
/**
* 抽象审批处理者
*/
abstract class ApprovalHandler {
protected ApprovalHandler next; // 后继处理者
protected String handlerName; // 处理者名称(用于日志)
protected int maxDays; // 最大可审批天数
protected boolean autoApprove = true; // 是否自动批准(演示用)
public ApprovalHandler(String handlerName, int maxDays) {
this.handlerName = handlerName;
this.maxDays = maxDays;
}
// 设置下一处理者,返回下一节点以便链式调用
public ApprovalHandler setNext(ApprovalHandler next) {
this.next = next;
return next;
}
/**
* 模板方法:处理审批请求
* 子类可覆盖 canHandle 和 doHandle 方法定制行为
*/
public final void handleRequest(LeaveRequest request) {
if (canHandle(request)) {
doHandle(request);
// 纯责任链:处理完毕后是否继续传递由子类决定,此处默认终止
} else {
// 当前无法处理,转发给下一处理者
if (next != null) {
System.out.printf("[%s] 无权限审批 %d 天请假,转交上级...\n",
handlerName, request.getDays());
next.handleRequest(request);
} else {
// 到达链尾,请求无法被处理
System.out.printf("【最终结果】%s 的 %d 天请假无人能审批,请求被拒绝。\n",
request.getApplicant(), request.getDays());
request.setApproved(false);
}
}
}
/**
* 判断当前处理者能否处理该请求
*/
protected boolean canHandle(LeaveRequest request) {
return request.getDays() <= maxDays;
}
/**
* 具体处理逻辑
*/
protected void doHandle(LeaveRequest request) {
request.setApproved(autoApprove);
request.setApprover(handlerName);
String result = autoApprove ? "批准" : "拒绝";
System.out.printf("【最终结果】%s %s了 %s 的 %d 天请假申请。\n",
handlerName, result, request.getApplicant(), request.getDays());
}
}
/**
* 具体处理者:组长(最多批3天)
*/
class GroupLeaderHandler extends ApprovalHandler {
public GroupLeaderHandler() {
super("组长", 3);
}
}
/**
* 具体处理者:经理(最多批7天)
*/
class ManagerHandler extends ApprovalHandler {
public ManagerHandler() {
super("经理", 7);
}
}
/**
* 具体处理者:总监(最多批30天)
*/
class DirectorHandler extends ApprovalHandler {
public DirectorHandler() {
super("总监", 30);
}
}
/**
* 具体处理者:CEO(可批任意天数)
*/
class CEOHandler extends ApprovalHandler {
public CEOHandler() {
super("CEO", Integer.MAX_VALUE);
}
}
/**
* 责任链构建器(结合建造者模式)
*/
class ApprovalChainBuilder {
private ApprovalHandler head;
private ApprovalHandler tail;
public ApprovalChainBuilder addHandler(ApprovalHandler handler) {
if (head == null) {
head = tail = handler;
} else {
tail.setNext(handler);
tail = handler;
}
return this;
}
public ApprovalHandler build() {
return head;
}
}
/**
* 客户端演示
*/
public class ApprovalChainDemo {
public static void main(String[] args) {
// 1. 构建责任链:组长 -> 经理 -> 总监 -> CEO
ApprovalHandler chain = new ApprovalChainBuilder()
.addHandler(new GroupLeaderHandler())
.addHandler(new ManagerHandler())
.addHandler(new DirectorHandler())
.addHandler(new CEOHandler())
.build();
// 2. 模拟多个请假请求
List<LeaveRequest> requests = new ArrayList<>();
requests.add(new LeaveRequest("张三", 2, "参加朋友婚礼"));
requests.add(new LeaveRequest("李四", 5, "陪产假"));
requests.add(new LeaveRequest("王五", 15, "出国旅游"));
requests.add(new LeaveRequest("赵六", 45, "长期病假"));
// 3. 提交请求
System.out.println("========== 请假审批流程演示 ==========\n");
for (LeaveRequest req : requests) {
System.out.println("【发起申请】" + req);
chain.handleRequest(req);
System.out.println("-----------------------------------");
}
}
}
运行结果:
========== 请假审批流程演示 ==========
【发起申请】申请人:张三,天数:2,事由:参加朋友婚礼
【最终结果】组长 批准了 张三 的 2 天请假申请。
-----------------------------------
【发起申请】申请人:李四,天数:5,事由:陪产假
[组长] 无权限审批 5 天请假,转交上级...
【最终结果】经理 批准了 李四 的 5 天请假申请。
-----------------------------------
【发起申请】申请人:王五,天数:15,事由:出国旅游
[组长] 无权限审批 15 天请假,转交上级...
[经理] 无权限审批 15 天请假,转交上级...
【最终结果】总监 批准了 王五 的 15 天请假申请。
-----------------------------------
【发起申请】申请人:赵六,天数:45,事由:长期病假
[组长] 无权限审批 45 天请假,转交上级...
[经理] 无权限审批 45 天请假,转交上级...
[总监] 无权限审批 45 天请假,转交上级...
【最终结果】CEO 批准了 赵六 的 45 天请假申请。
6.1.3 审批流程时序图
sequenceDiagram
participant Client as 客户端
participant Group as 组长Handler
participant Manager as 经理Handler
participant Director as 总监Handler
participant CEO as CEOHandler
Client->>Group: handleRequest(请假2天)
activate Group
Group->>Group: canHandle? (2<=3) true
Group->>Group: doHandle 批准
Group-->>Client: 返回
deactivate Group
Client->>Group: handleRequest(请假5天)
activate Group
Group->>Group: canHandle? (5<=3) false
Group->>Manager: 传递请求
deactivate Group
activate Manager
Manager->>Manager: canHandle? (5<=7) true
Manager->>Manager: doHandle 批准
Manager-->>Client: 返回
deactivate Manager
Client->>Group: handleRequest(请假15天)
activate Group
Group->>Group: canHandle? false
Group->>Manager: 传递请求
deactivate Group
activate Manager
Manager->>Manager: canHandle? false
Manager->>Director: 传递请求
deactivate Manager
activate Director
Director->>Director: canHandle? (15<=30) true
Director->>Director: doHandle 批准
Director-->>Client: 返回
deactivate Director
Client->>Group: handleRequest(请假45天)
activate Group
Group->>Manager: 传递
deactivate Group
activate Manager
Manager->>Director: 传递
deactivate Manager
activate Director
Director->>CEO: 传递
deactivate Director
activate CEO
CEO->>CEO: canHandle? true
CEO->>CEO: doHandle 批准
CEO-->>Client: 返回
deactivate CEO
时序图解读:该图精确描述了四个不同天数请假请求的传递路径。请求首先统一提交给链首的组长Handler。组长判断自身权限(maxDays=3):
- 对于2天请求,直接处理并终止传递;
- 对于5天请求,组长无法处理,将请求转发给经理,经理处理后终止;
- 对于15天请求,组长→经理均无法处理,最终由总监处理;
- 对于45天请求,连续传递至CEO才被处理。
该图清晰展示了责任链模式“请求沿链传递,直至被处理”的核心机制,同时也体现了纯责任链的特点:一旦某节点处理了请求,传递即停止。在实际企业应用中,我们可通过配置文件定义各处理者的maxDays和顺序,实现审批流程的动态调整,这正是责任链模式相较于if-else的压倒性优势。
6.2 请求参数校验链
6.2.1 场景描述与技术理由
Web应用中,请求参数的校验通常包含多项规则:非空校验、格式校验(邮箱、手机号)、业务规则校验(用户是否存在、余额是否充足)。若将所有校验写在一个方法中,方法体会急剧膨胀,且校验项增删改极不灵活。
将每个校验规则封装为一个独立的Handler,通过责任链串联,不仅使代码清晰、易于测试,还能在运行时动态决定启用哪些校验规则(例如通过配置中心开关控制某些校验器的启用状态)。此外,校验链支持“快速失败”(任一校验失败即中断)或“全量校验”(收集所有失败信息再返回)两种模式。
6.2.2 完整可运行代码
package com.example.chain.validation;
import java.util.ArrayList;
import java.util.List;
/**
* 用户注册请求对象
*/
class RegisterRequest {
private String username;
private String password;
private String email;
private String mobile;
public RegisterRequest(String username, String password, String email, String mobile) {
this.username = username;
this.password = password;
this.email = email;
this.mobile = mobile;
}
// Getters
public String getUsername() { return username; }
public String getPassword() { return password; }
public String getEmail() { return email; }
public String getMobile() { return mobile; }
}
/**
* 校验结果封装
*/
class ValidationResult {
private boolean valid = true;
private List<String> errors = new ArrayList<>();
public void addError(String error) {
this.valid = false;
errors.add(error);
}
public boolean isValid() { return valid; }
public List<String> getErrors() { return errors; }
@Override
public String toString() {
return valid ? "校验通过" : "校验失败: " + String.join("; ", errors);
}
}
/**
* 抽象校验处理器
*/
abstract class ValidationHandler {
protected ValidationHandler next;
protected boolean fastFail = true; // 快速失败模式:一旦失败不继续传递
public ValidationHandler setNext(ValidationHandler next) {
this.next = next;
return next;
}
/**
* 模板方法:执行校验
*/
public final ValidationResult validate(RegisterRequest request, ValidationResult result) {
// 执行当前校验逻辑
boolean currentPass = doValidate(request, result);
// 快速失败模式且当前校验未通过,终止传递
if (fastFail && !currentPass) {
return result;
}
// 传递至下一校验器
if (next != null) {
return next.validate(request, result);
}
return result;
}
/**
* 子类实现具体校验规则
* @return true表示校验通过,false表示校验失败
*/
protected abstract boolean doValidate(RegisterRequest request, ValidationResult result);
}
/**
* 非空校验器
*/
class NotNullValidationHandler extends ValidationHandler {
@Override
protected boolean doValidate(RegisterRequest request, ValidationResult result) {
boolean pass = true;
if (isBlank(request.getUsername())) {
result.addError("用户名不能为空");
pass = false;
}
if (isBlank(request.getPassword())) {
result.addError("密码不能为空");
pass = false;
}
return pass;
}
private boolean isBlank(String str) {
return str == null || str.trim().isEmpty();
}
}
/**
* 格式校验器:邮箱与手机号格式
*/
class FormatValidationHandler extends ValidationHandler {
private static final String EMAIL_REGEX = "^[A-Za-z0-9+_.-]+@[A-Za-z0-9.-]+$";
private static final String MOBILE_REGEX = "^1[3-9]\\d{9}$";
@Override
protected boolean doValidate(RegisterRequest request, ValidationResult result) {
boolean pass = true;
String email = request.getEmail();
if (email != null && !email.matches(EMAIL_REGEX)) {
result.addError("邮箱格式不正确");
pass = false;
}
String mobile = request.getMobile();
if (mobile != null && !mobile.matches(MOBILE_REGEX)) {
result.addError("手机号格式不正确");
pass = false;
}
return pass;
}
}
/**
* 密码强度校验器
*/
class PasswordStrengthHandler extends ValidationHandler {
@Override
protected boolean doValidate(RegisterRequest request, ValidationResult result) {
String password = request.getPassword();
if (password == null) return true; // 非空已由前驱校验
if (password.length() < 6) {
result.addError("密码长度至少6位");
return false;
}
// 可扩展更多强度规则
return true;
}
}
/**
* 业务校验器:检查用户名是否已存在(模拟)
*/
class BusinessValidationHandler extends ValidationHandler {
// 模拟已存在的用户名数据库
private static final List<String> EXISTED_USERNAMES = List.of("admin", "test", "user001");
@Override
protected boolean doValidate(RegisterRequest request, ValidationResult result) {
String username = request.getUsername();
if (username != null && EXISTED_USERNAMES.contains(username)) {
result.addError("用户名 '" + username + "' 已被注册");
return false;
}
return true;
}
}
/**
* 客户端演示
*/
public class ValidationChainDemo {
public static void main(String[] args) {
// 1. 构建校验链:非空 → 格式 → 密码强度 → 业务校验
ValidationHandler chain = new NotNullValidationHandler();
chain.setNext(new FormatValidationHandler())
.setNext(new PasswordStrengthHandler())
.setNext(new BusinessValidationHandler());
// 2. 测试不同请求
RegisterRequest req1 = new RegisterRequest("john_doe", "pass123", "john@example.com", "13800138000");
RegisterRequest req2 = new RegisterRequest("", "123", "invalid-email", "12345678901");
RegisterRequest req3 = new RegisterRequest("admin", "pass", "admin@test.com", "13912345678");
testValidation(chain, req1);
testValidation(chain, req2);
testValidation(chain, req3);
}
private static void testValidation(ValidationHandler chain, RegisterRequest request) {
System.out.println("\n===== 校验请求 =====");
System.out.printf("用户名:%s,密码:%s,邮箱:%s,手机:%s\n",
request.getUsername(), request.getPassword(), request.getEmail(), request.getMobile());
ValidationResult result = chain.validate(request, new ValidationResult());
System.out.println("校验结果:" + result);
}
}
运行结果:
===== 校验请求 =====
用户名:john_doe,密码:pass123,邮箱:john@example.com,手机:13800138000
校验结果:校验通过
===== 校验请求 =====
用户名:,密码:123,邮箱:invalid-email,手机:12345678901
校验结果:校验失败: 用户名不能为空; 邮箱格式不正确; 手机号格式不正确; 密码长度至少6位
===== 校验请求 =====
用户名:admin,密码:pass,邮箱:admin@test.com,手机:13912345678
校验结果:校验失败: 用户名 'admin' 已被注册; 密码长度至少6位
6.2.3 校验链流程图(全量失败收集模式)
flowchart TD
Start([接收注册请求]) --> H1[NotNull校验器]
H1 -->|执行校验| C1{有缺失字段?}
C1 -->|是| R1[记录错误到ValidationResult]
C1 -->|否| Pass1[无错误]
R1 --> H2
Pass1 --> H2[Format校验器]
H2 -->|执行校验| C2{格式错误?}
C2 -->|是| R2[记录错误]
C2 -->|否| Pass2[无错误]
R2 --> H3
Pass2 --> H3[PasswordStrength校验器]
H3 -->|执行校验| C3{密码强度不足?}
C3 -->|是| R3[记录错误]
C3 -->|否| Pass3[无错误]
R3 --> H4
Pass3 --> H4[Business校验器]
H4 -->|执行校验| C4{用户名已存在?}
C4 -->|是| R4[记录错误]
C4 -->|否| Pass4[无错误]
R4 --> End
Pass4 --> End([返回ValidationResult])
classDef handler fill:#e3f2fd,stroke:#1565c0,stroke-width:2px;
classDef decision fill:#fff3e0,stroke:#ef6c00,stroke-width:2px;
classDef error fill:#ffebee,stroke:#c62828,stroke-width:2px;
class H1,H2,H3,H4 handler;
class C1,C2,C3,C4 decision;
class R1,R2,R3,R4 error;
流程图解读:该流程图展示了“全量失败收集”模式下的参数校验链执行过程。请求从NotNull校验器开始,依次流经Format、PasswordStrength、Business四个校验器。与快速失败模式不同,即使某个校验器发现错误,请求仍会继续传递给后续校验器,从而一次性收集所有校验失败信息返回给客户端,提升用户体验。
每个校验器执行完毕后,无论结果如何,都将请求(及累积的错误结果对象)传递给下一节点。这种设计体现了不纯责任链的特征——每个处理者都会对请求产生副作用(记录错误),但不中断链的传递。实际开发中,可通过配置 fastFail 参数灵活切换两种模式:注册场景适合全量收集,而支付密码校验等安全敏感场景则适合快速失败。
6.3 敏感词过滤链
6.3.1 场景描述与技术理由
论坛、评论、即时通讯等系统需要对用户输入内容进行敏感词过滤。过滤需求往往包含多个层次:HTML标签转义、广告链接替换、政治敏感词过滤、自定义违禁词替换等。若将所有过滤逻辑写在一个类中,不仅难以维护,新增过滤规则还需修改核心类。
责任链模式允许我们将每种过滤规则封装为独立的Filter Handler,并通过配置文件灵活组装。例如:HTML过滤 → 敏感词替换 → 表情符号转换 → 长度截断。每个Handler只需关注自身过滤逻辑,符合单一职责原则。此外,过滤链支持动态顺序调整,例如在特殊时期可将敏感词过滤优先级提前。
6.3.2 完整可运行代码
package com.example.chain.sensitive;
import java.util.HashMap;
import java.util.Map;
import java.util.regex.Pattern;
/**
* 文本处理上下文(携带原始文本和处理后文本)
*/
class TextContext {
private String text;
public TextContext(String text) { this.text = text; }
public String getText() { return text; }
public void setText(String text) { this.text = text; }
}
/**
* 抽象过滤器
*/
abstract class TextFilter {
protected TextFilter next;
public TextFilter setNext(TextFilter next) {
this.next = next;
return next;
}
/**
* 过滤方法:处理文本后传递给下一过滤器
*/
public final void filter(TextContext context) {
// 执行当前过滤逻辑
doFilter(context);
// 传递给下一过滤器
if (next != null) {
next.filter(context);
}
}
protected abstract void doFilter(TextContext context);
}
/**
* HTML标签转义过滤器
*/
class HtmlEscapeFilter extends TextFilter {
private static final Map<Character, String> ESCAPE_MAP = new HashMap<>();
static {
ESCAPE_MAP.put('<', "<");
ESCAPE_MAP.put('>', ">");
ESCAPE_MAP.put('&', "&");
ESCAPE_MAP.put('"', """);
ESCAPE_MAP.put('\'', "'");
}
@Override
protected void doFilter(TextContext context) {
String text = context.getText();
StringBuilder sb = new StringBuilder();
for (char c : text.toCharArray()) {
sb.append(ESCAPE_MAP.getOrDefault(c, String.valueOf(c)));
}
context.setText(sb.toString());
System.out.println("[HtmlEscape] 处理后:" + context.getText());
}
}
/**
* 敏感词替换过滤器(使用DFA算法简化版演示)
*/
class SensitiveWordFilter extends TextFilter {
private static final Map<String, String> SENSITIVE_MAP = new HashMap<>();
static {
SENSITIVE_MAP.put("fuck", "f**k");
SENSITIVE_MAP.put("shit", "s**t");
SENSITIVE_MAP.put("暴力", "**");
SENSITIVE_MAP.put("色情", "**");
}
@Override
protected void doFilter(TextContext context) {
String text = context.getText();
for (Map.Entry<String, String> entry : SENSITIVE_MAP.entrySet()) {
text = text.replaceAll("(?i)" + Pattern.quote(entry.getKey()), entry.getValue());
}
context.setText(text);
System.out.println("[SensitiveWord] 处理后:" + context.getText());
}
}
/**
* 链接替换过滤器(将URL替换为[链接])
*/
class LinkReplaceFilter extends TextFilter {
private static final Pattern URL_PATTERN = Pattern.compile(
"https?://[-\\w.]+(:\\d+)?(/[\\w./?%&=]*)?", Pattern.CASE_INSENSITIVE);
@Override
protected void doFilter(TextContext context) {
String text = context.getText();
text = URL_PATTERN.matcher(text).replaceAll("[链接]");
context.setText(text);
System.out.println("[LinkReplace] 处理后:" + context.getText());
}
}
/**
* 表情符号转换过滤器(例如 :) 转为 😊)
*/
class EmojiFilter extends TextFilter {
private static final Map<String, String> EMOJI_MAP = new HashMap<>();
static {
EMOJI_MAP.put(":)", "😊");
EMOJI_MAP.put(":(", "☹️");
EMOJI_MAP.put(":D", "😃");
}
@Override
protected void doFilter(TextContext context) {
String text = context.getText();
for (Map.Entry<String, String> entry : EMOJI_MAP.entrySet()) {
text = text.replace(entry.getKey(), entry.getValue());
}
context.setText(text);
System.out.println("[Emoji] 处理后:" + context.getText());
}
}
/**
* 客户端演示
*/
public class SensitiveFilterDemo {
public static void main(String[] args) {
// 1. 构建过滤链:HTML转义 → 敏感词 → 链接替换 → 表情符号
TextFilter chain = new HtmlEscapeFilter();
chain.setNext(new SensitiveWordFilter())
.setNext(new LinkReplaceFilter())
.setNext(new EmojiFilter());
// 2. 测试文本
String rawText1 = "Hello <b>World</b>! Visit http://example.com :) fuck";
String rawText2 = "这是包含暴力和色情的内容,还有链接 https://bad.com 和表情 :D";
System.out.println("===== 示例1 =====");
System.out.println("原始文本:" + rawText1);
TextContext ctx1 = new TextContext(rawText1);
chain.filter(ctx1);
System.out.println("最终结果:" + ctx1.getText());
System.out.println("\n===== 示例2 =====");
System.out.println("原始文本:" + rawText2);
TextContext ctx2 = new TextContext(rawText2);
chain.filter(ctx2);
System.out.println("最终结果:" + ctx2.getText());
}
}
运行结果:
===== 示例1 =====
原始文本:Hello <b>World</b>! Visit http://example.com :) fuck
[HtmlEscape] 处理后:Hello <b>World</b>! Visit http://example.com :) fuck
[SensitiveWord] 处理后:Hello <b>World</b>! Visit http://example.com :) f**k
[LinkReplace] 处理后:Hello <b>World</b>! Visit [链接] :) f**k
[Emoji] 处理后:Hello <b>World</b>! Visit [链接] 😊 f**k
最终结果:Hello <b>World</b>! Visit [链接] 😊 f**k
===== 示例2 =====
原始文本:这是包含暴力和色情的内容,还有链接 https://bad.com 和表情 :D
[HtmlEscape] 处理后:这是包含暴力和色情的内容,还有链接 https://bad.com 和表情 :D
[SensitiveWord] 处理后:这是包含**和**的内容,还有链接 https://bad.com 和表情 :D
[LinkReplace] 处理后:这是包含**和**的内容,还有链接 [链接] 和表情 :D
[Emoji] 处理后:这是包含**和**的内容,还有链接 [链接] 和表情 😃
最终结果:这是包含**和**的内容,还有链接 [链接] 和表情 😃
6.3.3 过滤链处理时序图
sequenceDiagram
participant Client
participant HtmlEscape
participant SensitiveWord
participant LinkReplace
participant Emoji
Client->>HtmlEscape: filter(ctx)
activate HtmlEscape
HtmlEscape->>HtmlEscape: doFilter 转义HTML
HtmlEscape->>SensitiveWord: next.filter(ctx)
deactivate HtmlEscape
activate SensitiveWord
SensitiveWord->>SensitiveWord: doFilter 替换敏感词
SensitiveWord->>LinkReplace: next.filter(ctx)
deactivate SensitiveWord
activate LinkReplace
LinkReplace->>LinkReplace: doFilter 替换链接
LinkReplace->>Emoji: next.filter(ctx)
deactivate LinkReplace
activate Emoji
Emoji->>Emoji: doFilter 转换表情
Emoji-->>Client: 返回
deactivate Emoji
时序图解读:该时序图直观展示了文本过滤的链式处理过程。原始文本ctx依次被传递给HtmlEscape、SensitiveWord、LinkReplace、Emoji四个过滤器。每个过滤器在自己的doFilter方法中修改ctx对象的文本内容,然后显式调用next.filter(ctx)将修改后的文本传递给下一节点。
这里体现的是不纯责任链的典型特征:每个处理者都会对请求(文本内容)进行部分处理(副作用),并且必须调用后继者以确保整条链被执行完毕。与纯责任链的“处理即终止”不同,不纯责任链要求所有节点都参与处理。在实际应用中,这种模式广泛应用于Servlet Filter、Spring Interceptor、Netty ChannelHandler等框架组件。另外,通过在各过滤器的doFilter前后添加日志,可以清晰地追踪文本的逐层变换,便于调试与审计。
6.4 分布式网关过滤器链(模拟实现)
6.4.1 场景描述与技术理由
API网关作为微服务架构的流量入口,需要对所有请求进行统一的横切处理:鉴权、限流、日志、灰度路由、请求改写等。Spring Cloud Gateway和Zuul均采用过滤器链模式,将不同职责的过滤器串联起来,每个过滤器专注于单一功能,通过责任链实现高内聚低耦合。
本示例模拟一个简化版网关过滤器链,包含:TraceId生成过滤器、IP黑名单过滤器、鉴权过滤器、路由转发过滤器(Mock)。通过该示例可以清晰理解网关过滤器链的内部执行逻辑及短路机制(如鉴权失败立即返回,不进行后续路由)。
6.4.2 完整可运行代码
package com.example.chain.gateway;
import java.util.*;
/**
* 网关请求上下文
*/
class GatewayContext {
private final String requestPath;
private final Map<String, String> headers = new HashMap<>();
private boolean shouldContinue = true;
private Object response;
public GatewayContext(String requestPath) {
this.requestPath = requestPath;
}
public String getRequestPath() { return requestPath; }
public void addHeader(String key, String value) { headers.put(key, value); }
public String getHeader(String key) { return headers.get(key); }
public Map<String, String> getHeaders() { return headers; }
public boolean shouldContinue() { return shouldContinue; }
public void terminate(Object response) {
this.shouldContinue = false;
this.response = response;
}
public Object getResponse() { return response; }
}
/**
* 抽象网关过滤器
*/
abstract class GatewayFilter {
protected String name;
public GatewayFilter(String name) { this.name = name; }
/**
* 执行过滤,返回true表示继续传递,false表示中断链
*/
public abstract boolean filter(GatewayContext ctx, GatewayFilterChain chain);
}
/**
* 过滤器链容器
*/
class GatewayFilterChain {
private final List<GatewayFilter> filters;
private int pos = 0;
public GatewayFilterChain(List<GatewayFilter> filters) {
this.filters = filters;
}
/**
* 执行链中的下一个过滤器
*/
public void doFilter(GatewayContext ctx) {
if (pos < filters.size() && ctx.shouldContinue()) {
GatewayFilter filter = filters.get(pos++);
System.out.println("[Gateway] 进入过滤器:" + filter.name);
boolean result = filter.filter(ctx, this);
if (!result) {
System.out.println("[Gateway] 过滤器 " + filter.name + " 中断了链传递");
}
}
}
}
/**
* TraceId生成过滤器
*/
class TraceIdFilter extends GatewayFilter {
public TraceIdFilter() { super("TraceIdFilter"); }
@Override
public boolean filter(GatewayContext ctx, GatewayFilterChain chain) {
String traceId = UUID.randomUUID().toString().replace("-", "").substring(0, 16);
ctx.addHeader("X-Trace-Id", traceId);
System.out.println(" [" + name + "] 生成 TraceId: " + traceId);
chain.doFilter(ctx); // 继续传递
System.out.println(" [" + name + "] 后置处理:记录请求日志");
return true;
}
}
/**
* IP黑名单过滤器(模拟)
*/
class IpBlacklistFilter extends GatewayFilter {
private static final Set<String> BLACKLIST = Set.of("192.168.1.100", "10.0.0.5");
public IpBlacklistFilter() { super("IpBlacklistFilter"); }
@Override
public boolean filter(GatewayContext ctx, GatewayFilterChain chain) {
// 模拟从Header获取客户端IP
String clientIp = ctx.getHeader("X-Forwarded-For");
if (clientIp == null) clientIp = "unknown";
System.out.println(" [" + name + "] 检查IP:" + clientIp);
if (BLACKLIST.contains(clientIp)) {
ctx.terminate("403 Forbidden: IP in blacklist");
return false; // 中断链
}
chain.doFilter(ctx);
return true;
}
}
/**
* 鉴权过滤器(模拟JWT校验)
*/
class AuthFilter extends GatewayFilter {
public AuthFilter() { super("AuthFilter"); }
@Override
public boolean filter(GatewayContext ctx, GatewayFilterChain chain) {
String token = ctx.getHeader("Authorization");
System.out.println(" [" + name + "] 校验Token: " + token);
if (token == null || !token.startsWith("Bearer ")) {
ctx.terminate("401 Unauthorized: Missing or invalid token");
return false;
}
// 模拟解析用户信息
ctx.addHeader("X-User-Id", "10086");
chain.doFilter(ctx);
return true;
}
}
/**
* 路由转发过滤器(模拟转发到后端服务)
*/
class RoutingFilter extends GatewayFilter {
private static final Map<String, String> ROUTE_MAP = new HashMap<>();
static {
ROUTE_MAP.put("/api/user", "http://user-service:8080");
ROUTE_MAP.put("/api/order", "http://order-service:8081");
}
public RoutingFilter() { super("RoutingFilter"); }
@Override
public boolean filter(GatewayContext ctx, GatewayFilterChain chain) {
String path = ctx.getRequestPath();
String target = ROUTE_MAP.getOrDefault(path, null);
System.out.println(" [" + name + "] 路由 " + path + " -> " + target);
if (target == null) {
ctx.terminate("404 Not Found: No route for " + path);
return false;
}
// 模拟发起HTTP请求
String response = "Response from " + target + " for user " + ctx.getHeader("X-User-Id");
ctx.terminate(response);
// 路由是最后一个过滤器,不再调用chain.doFilter
return true;
}
}
/**
* 网关入口
*/
public class GatewayChainDemo {
public static void main(String[] args) {
// 1. 构建过滤器链(顺序至关重要)
List<GatewayFilter> filters = Arrays.asList(
new TraceIdFilter(),
new IpBlacklistFilter(),
new AuthFilter(),
new RoutingFilter()
);
GatewayFilterChain chain = new GatewayFilterChain(filters);
// 2. 模拟正常请求
System.out.println("========== 场景1:正常请求 ==========");
GatewayContext ctx1 = new GatewayContext("/api/user");
ctx1.addHeader("X-Forwarded-For", "192.168.1.50");
ctx1.addHeader("Authorization", "Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9");
chain.doFilter(ctx1);
System.out.println("最终响应:" + ctx1.getResponse());
// 3. 模拟黑名单IP请求
System.out.println("\n========== 场景2:黑名单IP ==========");
GatewayContext ctx2 = new GatewayContext("/api/user");
ctx2.addHeader("X-Forwarded-For", "192.168.1.100");
ctx2.addHeader("Authorization", "Bearer xxx");
chain = new GatewayFilterChain(filters); // 重置链
chain.doFilter(ctx2);
System.out.println("最终响应:" + ctx2.getResponse());
// 4. 模拟无Token请求
System.out.println("\n========== 场景3:缺少Token ==========");
GatewayContext ctx3 = new GatewayContext("/api/order");
ctx3.addHeader("X-Forwarded-For", "10.0.0.1");
chain = new GatewayFilterChain(filters);
chain.doFilter(ctx3);
System.out.println("最终响应:" + ctx3.getResponse());
// 5. 模拟未知路由
System.out.println("\n========== 场景4:未知路径 ==========");
GatewayContext ctx4 = new GatewayContext("/api/unknown");
ctx4.addHeader("Authorization", "Bearer token");
chain = new GatewayFilterChain(filters);
chain.doFilter(ctx4);
System.out.println("最终响应:" + ctx4.getResponse());
}
}
运行结果(关键部分截取):
========== 场景1:正常请求 ==========
[Gateway] 进入过滤器:TraceIdFilter
[TraceIdFilter] 生成 TraceId: 3f8a2b1c4d5e6f7a
[Gateway] 进入过滤器:IpBlacklistFilter
[IpBlacklistFilter] 检查IP:192.168.1.50
[Gateway] 进入过滤器:AuthFilter
[AuthFilter] 校验Token: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9
[Gateway] 进入过滤器:RoutingFilter
[RoutingFilter] 路由 /api/user -> http://user-service:8080
[TraceIdFilter] 后置处理:记录请求日志
最终响应:Response from http://user-service:8080 for user 10086
========== 场景2:黑名单IP ==========
[Gateway] 进入过滤器:TraceIdFilter
[TraceIdFilter] 生成 TraceId: 7a8b9c0d1e2f3a4b
[Gateway] 进入过滤器:IpBlacklistFilter
[IpBlacklistFilter] 检查IP:192.168.1.100
[Gateway] 过滤器 IpBlacklistFilter 中断了链传递
[TraceIdFilter] 后置处理:记录请求日志
最终响应:403 Forbidden: IP in blacklist
6.4.3 网关过滤器链执行流程图
flowchart TD
Start([请求到达网关]) --> TraceId[TraceIdFilter]
TraceId -->|生成TraceId| LogPre["记录前置日志"]
LogPre --> Blacklist[IpBlacklistFilter]
Blacklist -->|IP检查| IsBlack{在黑名单?}
IsBlack -->|是| Terminate1[设置响应403<br/>中断链]
Terminate1 --> PostTrace[TraceId后置处理]
PostTrace --> End([返回响应])
IsBlack -->|否| Auth[AuthFilter]
Auth -->|Token校验| HasToken{Token有效?}
HasToken -->|否| Terminate2[设置响应401<br/>中断链]
Terminate2 --> PostTrace
HasToken -->|是| Routing[RoutingFilter]
Routing -->|路由匹配| RouteExist{路由存在?}
RouteExist -->|是| Forward[转发请求<br/>获取响应]
Forward --> PostTrace
RouteExist -->|否| Terminate3[设置响应404]
Terminate3 --> PostTrace
classDef filter fill:#e3f2fd,stroke:#1565c0,stroke-width:2px;
classDef decision fill:#fff3e0,stroke:#ef6c00,stroke-width:2px;
classDef terminal fill:#ffebee,stroke:#c62828,stroke-width:2px;
class TraceId,Blacklist,Auth,Routing filter;
class IsBlack,HasToken,RouteExist decision;
class Terminate1,Terminate2,Terminate3 terminal;
流程图解读:该流程图完整描绘了网关过滤器链的执行路径及短路逻辑。请求首先进入TraceIdFilter,生成追踪ID并记录前置日志。随后进入IpBlacklistFilter进行IP校验,若命中黑名单则立即设置403响应并中断链(不再执行后续过滤器),但注意后置处理(TraceIdFilter的日志记录)仍会在递归返回时执行。
若IP通过,则继续进入AuthFilter进行Token鉴权;鉴权失败同样短路返回401。鉴权成功后,RoutingFilter负责路由匹配与转发,若路由不存在返回404,否则正常转发并获得响应。整个流程体现了过滤器链的洋葱模型:前置处理沿链正向执行,后置处理沿递归返回逆序执行。同时,通过shouldContinue标志和terminate方法实现了可控的链路中断,这在安全校验场景中尤为重要——一旦发现非法请求,应立即阻断,避免资源浪费和潜在风险。
七、面试题精选与专家级解答
1. 责任链模式和装饰器模式在结构上很相似,如何从意图上区分二者?
解答:
- 意图差异:责任链旨在将请求的发送者与接收者解耦,允许多个对象处理请求,请求可能被中途处理而终止。装饰器旨在动态地给对象添加额外功能,装饰器必定会调用被装饰对象,最终执行核心功能。
- 链终止性:责任链可在某个节点处理请求后不再传递;装饰器链必须保证调用链最终到达原始对象。
- 示例:Java I/O 的
BufferedInputStream装饰FileInputStream,无论如何都会读取文件;而 ServletFilterChain中,若某个 Filter 未调用chain.doFilter(),请求将不会到达 Servlet。
2. Servlet Filter 的 doFilter 方法中为什么要调用 chain.doFilter()?不调用会有什么后果?
解答:
chain.doFilter() 的作用是将请求传递给过滤器链中的下一个过滤器,或最终传递给目标 Servlet。若不调用,责任链将断裂,后续过滤器及目标资源均无法处理请求,客户端将得不到响应或收到空白页面。同时,后置处理逻辑(如日志记录、响应压缩)也不会执行。
3. Netty 的 ChannelPipeline 中,inbound 事件和 outbound 事件在责任链中的传播方向有何不同?为什么这样设计?
解答:
- Inbound 事件(如读数据、通道激活):从 Pipeline 的头部向尾部传播。
- Outbound 事件(如写数据、绑定端口):从 Pipeline 的尾部向头部传播。
- 设计原因:入站事件通常与数据读取相关,需要按添加顺序依次解码、处理;出站事件与数据写入相关,需要按添加逆序依次编码、发送。这种双向设计使得编解码处理器可以成对添加且顺序自然,例如
addLast(new Decoder(), new Encoder(), new BusinessHandler()),入站时Decoder → BusinessHandler,出站时BusinessHandler → Encoder。
4. Spring AOP 中的多个切面是如何形成责任链的?如何控制切面的执行顺序?
解答:
Spring AOP 通过 ReflectiveMethodInvocation 将多个通知(Advice)组织成一条调用链。当目标方法被调用时,会按顺序依次执行各切面的前置通知,递归调用 proceed() 直到执行目标方法,之后逆序执行后置通知和最终通知。
- 顺序控制:通过
@Order注解或实现Ordered接口指定优先级,数值越小越先执行(前置通知),后置通知则相反。
5. 责任链模式中的纯与不纯如何理解?请结合框架实例说明。
解答:
- 纯责任链:请求必须被某一个处理者完全处理,或者一直传递到链尾未被处理。处理者要么处理请求,要么原样传递。例如请假审批流程,每个审批节点要么批准要么转交。
- 不纯责任链:每个处理者都可以处理请求的一部分,然后继续传递给下一个处理者。例如 Servlet Filter,每个 Filter 可以对请求和响应做预处理和后处理,但必须调用
chain.doFilter()确保传递。 - 框架实例:纯责任链——ClassLoader 双亲委派;不纯责任链——Servlet FilterChain、Netty ChannelPipeline。
6. 如何实现一个可以动态增加或删除节点的责任链?需要考虑哪些线程安全问题?
解答:
- 实现方式:可以使用
CopyOnWriteArrayList存储处理者节点,并提供addHandler()、removeHandler()方法。执行时遍历列表依次处理。 - 线程安全考虑:
- 若链在执行过程中被修改,可能导致遍历时抛出
ConcurrentModificationException,建议使用并发集合或读写锁。 - 若处理者本身有状态,需保证状态线程安全。
- 在高并发下,频繁增删节点可能影响性能,可考虑使用无锁并发链表(如
ConcurrentLinkedDeque)。
- 若链在执行过程中被修改,可能导致遍历时抛出
7. 责任链模式与建造者模式是否可以组合使用?如何用建造者模式优雅地构建责任链?
解答:
可以组合使用,建造者模式非常适合逐步构建复杂对象,而责任链通常需要按特定顺序串联多个处理者。通过建造者模式,可以链式调用 addHandler() 方法,最终调用 build() 返回链首节点,使客户端代码更加清晰易读。
ChainBuilder builder = new ChainBuilder();
Handler chain = builder
.addHandler(new LogHandler())
.addHandler(new AuthHandler())
.addHandler(new BusinessHandler())
.build();
8. ClassLoader 的双亲委派模型是如何体现责任链模式的?这种设计的优势是什么?
解答: 双亲委派模型中,类加载请求首先委派给父加载器,若父加载器无法加载,才由子加载器尝试。这正是责任链的“沿链传递,直到被处理”的体现。
- 优势:
- 避免类的重复加载:父加载器加载过的类,子加载器无需再次加载。
- 保证核心类库安全:如
java.lang.String只能由启动类加载器加载,防止恶意篡改。
9. 在分布式网关中,如何利用责任链模式实现请求的灰度路由与 AB 测试?
解答:
在网关中,可设计一个灰度路由过滤器,它根据请求头中的标签(如 version: v2)、用户 ID 哈希值或权重配置,决定将请求转发到灰度环境还是稳定环境。该过滤器作为责任链的一环,通过 ServerWebExchange 修改路由目标 URI,然后调用 chain.filter() 继续后续处理(如负载均衡过滤器)。灰度策略可动态配置,过滤器只需读取配置中心的最新规则即可。
10. 责任链模式在调试时可能遇到“责任链断裂”的问题,如何快速定位哪个 Handler 处理了请求或中断了传递?
解答:
- 日志埋点:在每个 Handler 的入口和出口打印日志,包含请求唯一标识(如 TraceId)及当前 Handler 名称。
- 链路追踪:集成 OpenTelemetry 等工具,在 Handler 中创建 Span,可视化整个链的调用路径。
- 断言式检查:在开发测试环境,可编写一个 TestHandler 插在链尾,若请求到达该 Handler 则说明前面均未处理或传递失败。
- 代码审查:重点关注未调用后继节点的分支条件,确保每个分支都正确处理传递逻辑。
结语
责任链模式以其优雅的解耦能力和动态编排特性,成为构建灵活、可扩展系统的利器。从 JDK 的类加载器到 Tomcat 的过滤器链,从 Spring 的拦截器到 Netty 的 Pipeline,再到分布式网关与微服务调用链,责任链模式无处不在。深入理解其原理与变体,不仅有助于阅读框架源码,更能在架构设计中做出精准的权衡与选择。希望本文能成为您掌握责任链模式的案头参考,助您在 Java 专家之路上更进一步。