责任链模式
责任链模式用于把一串处理步骤拆成多个处理器,让请求沿着链路依次处理。每个处理器只负责自己的职责,可以选择继续传递,也可以中断流程。
常见场景:登录校验、接口鉴权、风控规则、订单校验、网关过滤器、审批流、数据导入校验、Servlet Filter、Spring Security 过滤器链。
为什么需要责任链
假设创建订单前要做很多校验:
- 参数校验。
- 用户状态校验。
- 商品状态校验。
- 库存校验。
- 优惠券校验。
- 风控校验。
如果全部写在一个方法里:
public void createOrder(OrderCommand command) {
checkParam(command);
checkUser(command);
checkProduct(command);
checkStock(command);
checkCoupon(command);
checkRisk(command);
saveOrder(command);
}早期看起来没问题,但校验越来越多后会出现:
| 问题 | 后果 |
|---|---|
| 方法越来越长 | 主流程被校验细节淹没 |
| 新增规则总改老代码 | 容易引入回归 |
| 校验顺序散落 | 不同接口校验顺序不一致 |
| 部分规则需要开关 | if/else 越来越多 |
| 规则复用困难 | 多个业务重复复制 |
责任链把每个规则拆成独立处理器。
总体流程
flowchart TD
A["请求进入"] --> B["参数校验处理器"]
B --> C["用户校验处理器"]
C --> D["库存校验处理器"]
D --> E["风控校验处理器"]
E --> F["业务执行"]每个处理器只关心一件事。
责任链的执行模型
责任链的核心是“同一个请求对象,按确定顺序经过多个处理器”。它有三个关键点:
| 关键点 | 解释 | 为什么重要 |
|---|---|---|
| 上下文 | 链路中共享的数据对象,例如订单、用户、库存、校验结果 | 避免处理器之间互相直接依赖 |
| 顺序 | 哪个处理器先执行,哪个后执行 | 参数校验必须早于数据库查询,权限校验必须早于业务操作 |
| 中断策略 | 失败后继续、跳过还是立即终止 | 风控失败不能继续创建订单,日志失败通常不能影响主流程 |
flowchart TD
A["创建上下文"] --> B["按 order 排序处理器"]
B --> C["执行当前处理器"]
C --> D{"是否通过"}
D -- "否" --> E["抛异常或返回失败"]
D -- "是" --> F{"是否还有下一个"}
F -- "有" --> C
F -- "没有" --> G["链路通过"]责任链不是把代码随便拆散,而是把“可插拔、可排序、可独立测试”的步骤拆出来。
Java Demo:订单校验责任链
上下文:
public class OrderContext {
private final Long userId;
private final Long skuId;
private final int count;
public OrderContext(Long userId, Long skuId, int count) {
this.userId = userId;
this.skuId = skuId;
this.count = count;
}
public Long userId() {
return userId;
}
public Long skuId() {
return skuId;
}
public int count() {
return count;
}
}处理器接口:
public interface OrderCheckHandler {
void check(OrderContext context);
int order();
}参数校验:
@Component
public class ParamCheckHandler implements OrderCheckHandler {
@Override
public void check(OrderContext context) {
if (context.userId() == null || context.skuId() == null || context.count() <= 0) {
throw new IllegalArgumentException("订单参数不合法");
}
}
@Override
public int order() {
return 10;
}
}库存校验:
@Component
public class StockCheckHandler implements OrderCheckHandler {
@Override
public void check(OrderContext context) {
int stock = 100;
if (stock < context.count()) {
throw new IllegalStateException("库存不足");
}
}
@Override
public int order() {
return 30;
}
}责任链执行器:
@Component
public class OrderCheckChain {
private final List<OrderCheckHandler> handlers;
public OrderCheckChain(List<OrderCheckHandler> handlers) {
this.handlers = handlers.stream()
.sorted(Comparator.comparingInt(OrderCheckHandler::order))
.toList();
}
public void check(OrderContext context) {
for (OrderCheckHandler handler : handlers) {
handler.check(context);
}
}
}业务调用:
@Service
public class OrderService {
private final OrderCheckChain checkChain;
public OrderService(OrderCheckChain checkChain) {
this.checkChain = checkChain;
}
public void createOrder(OrderContext context) {
checkChain.check(context);
// 校验通过后创建订单
}
}新增风控规则时,只要新增一个 OrderCheckHandler Bean,不需要修改 OrderService。
可以中断的责任链
有些链路需要决定是否继续。
public interface Handler {
boolean handle(Request request);
}for (Handler handler : handlers) {
boolean next = handler.handle(request);
if (!next) {
break;
}
}适合网关过滤、登录认证、权限校验:某个环节失败后直接中断。
商业场景:数据导入校验链
医疗数据采集与资产平台里,经常要导入设备资产、接口资产、数据库资产。导入不是简单保存文件,而是要经过一条稳定校验链:
| 顺序 | 处理器 | 作用 | 不做会怎样 |
|---|---|---|---|
| 10 | 文件格式校验 | 判断扩展名、大小、表头 | 后续解析报错,错误信息不友好 |
| 20 | 必填字段校验 | 检查资产编码、名称、科室 | 入库后产生无效资产 |
| 30 | 字典校验 | 检查资产类型、状态、等级 | 统计口径混乱 |
| 40 | 唯一性校验 | 检查资产编码是否重复 | 覆盖历史数据或产生重复资产 |
| 50 | 权限校验 | 检查用户是否能导入对应科室 | 造成越权写入 |
| 60 | 风险标记 | 标记敏感资产、异常资产 | 后续治理无法识别风险数据 |
flowchart TD
A["上传资产文件"] --> B["文件格式校验"]
B --> C["必填字段校验"]
C --> D["字典值校验"]
D --> E["唯一性校验"]
E --> F["数据权限校验"]
F --> G["风险标记"]
G --> H["保存导入结果"]这种场景用责任链的好处是:新增“敏感字段校验”时新增一个处理器,不需要改导入主流程;某个处理器失败时能明确知道失败在哪一步。
Demo:可返回错误集合的校验链
有些导入任务不希望遇到第一个错误就停止,而是希望一次返回所有错误。这时可以让处理器往上下文里追加错误。
public class ImportContext {
private final List<Map<String, String>> rows;
private final List<String> errors = new ArrayList<>();
public ImportContext(List<Map<String, String>> rows) {
this.rows = rows;
}
public List<Map<String, String>> getRows() {
return rows;
}
public void addError(String error) {
errors.add(error);
}
public boolean hasError() {
return !errors.isEmpty();
}
public List<String> getErrors() {
return errors;
}
}public interface ImportCheckHandler {
int order();
void check(ImportContext context);
}@Component
public class RequiredFieldCheckHandler implements ImportCheckHandler {
@Override
public int order() {
return 20;
}
@Override
public void check(ImportContext context) {
for (int i = 0; i < context.getRows().size(); i++) {
Map<String, String> row = context.getRows().get(i);
if (row.get("assetCode") == null || row.get("assetCode").isBlank()) {
context.addError("第 " + (i + 1) + " 行资产编码不能为空");
}
}
}
}@Component
public class ImportCheckChain {
private final List<ImportCheckHandler> handlers;
public ImportCheckChain(List<ImportCheckHandler> handlers) {
this.handlers = handlers.stream()
.sorted(Comparator.comparingInt(ImportCheckHandler::order))
.collect(Collectors.toList());
}
public void check(ImportContext context) {
for (ImportCheckHandler handler : handlers) {
handler.check(context);
}
}
}使用:
ImportContext context = new ImportContext(rows);
importCheckChain.check(context);
if (context.hasError()) {
throw new IllegalArgumentException(String.join(",", context.getErrors()));
}如果是支付、扣库存、创建订单这类强一致流程,通常遇到错误就中断;如果是 Excel 导入校验,通常更适合收集错误后统一返回。
Spring 中的责任链
Servlet Filter
flowchart TD
A["HTTP 请求"] --> B["Filter 1"]
B --> C["Filter 2"]
C --> D["Filter 3"]
D --> E["Servlet / Controller"]每个 Filter 都可以在请求前后处理,也可以不调用 chain.doFilter 来中断。
Spring Security
Spring Security 的核心就是一组过滤器链,例如认证、会话、CSRF、授权、异常处理等。每个过滤器负责一个安全职责。
简化链路可以这样理解:
flowchart TD
A["HTTP 请求"] --> B["SecurityContext 处理"]
B --> C["登录认证过滤器"]
C --> D["异常处理过滤器"]
D --> E["授权过滤器"]
E --> F{"是否允许访问"}
F -- "否" --> G["返回 401 或 403"]
F -- "是" --> H["进入 Controller"]这里每个过滤器就是一个处理器。认证失败可以中断,授权失败可以中断,只有通过后才进入业务接口。
和策略模式区别
| 对比 | 责任链 | 策略模式 |
|---|---|---|
| 关注点 | 多个处理器按顺序协作 | 多个算法选一个执行 |
| 执行数量 | 通常执行多个 | 通常执行一个 |
| 是否有顺序 | 有顺序 | 一般无链式顺序 |
| 场景 | 校验链、过滤器链、审批链 | 支付、折扣、解析 |
商业场景
医疗数据导入可以用责任链做数据校验:
- 必填字段校验。
- 字典值校验。
- 资产编码唯一性校验。
- 科室存在性校验。
- 数据权限校验。
- 风险数据标记。
每个校验器独立,新增规则不会改导入主流程。
常见坑
| 问题 | 后果 | 建议 |
|---|---|---|
| 链顺序不明确 | 结果不稳定 | 给处理器明确 order |
| 处理器互相依赖 | 解耦失败 | 通过上下文传递必要数据 |
| 链太长但无日志 | 排查不知道卡在哪 | 每个处理器记录关键结果 |
| 所有逻辑都塞责任链 | 主流程割裂 | 只把可插拔步骤放入链 |
| 异常策略不统一 | 有的中断,有的吞异常 | 明确失败是否继续 |
生产排查方法
责任链一旦用于商业项目,必须能排查“到底卡在哪个处理器”。建议至少记录这些字段:
| 字段 | 说明 |
|---|---|
traceId | 一次请求的链路编号 |
chainName | 链名称,例如 orderCreateCheckChain |
handlerName | 当前处理器名称 |
order | 当前处理器顺序 |
costMs | 当前处理器耗时 |
result | 通过、失败、跳过 |
errorCode | 失败原因编码 |
排查流程:
flowchart TD
A["发现接口失败或变慢"] --> B["按 traceId 查链路日志"]
B --> C["定位失败或耗时最高的 handler"]
C --> D{"是业务失败还是系统异常"}
D -- "业务失败" --> E["检查入参、配置、权限、数据状态"]
D -- "系统异常" --> F["检查依赖服务、数据库、缓存、网络"]
E --> G["补充测试或修复规则"]
F --> G如果链路没有日志,责任链会比普通代码更难排查,因为调用栈被拆散了。
面试标准回答
责任链模式把多个处理步骤拆成一组处理器,请求沿链依次经过处理器。它适合校验链、过滤器链、审批链等场景。优点是每个处理器职责单一,新增规则可以新增处理器,减少主流程修改;缺点是链路过长时排查困难,必须明确顺序、异常策略和日志。Servlet Filter 和 Spring Security 过滤器链都是典型应用。追问补充:
| 追问 | 回答方向 |
|---|---|
| 责任链一定会执行所有处理器吗 | 不一定。校验收集型可以全部执行,认证鉴权型通常失败就中断 |
| 怎么保证顺序 | 使用 order、@Order 或配置文件排序,不要依赖 Bean 注入顺序 |
| 链路太长怎么办 | 拆分链路、记录每个处理器日志、只把可插拔步骤放进链 |
| 和过滤器有什么关系 | Servlet Filter、Gateway Filter、Spring Security Filter 都是责任链思想 |
本章小结
责任链解决的是“多个步骤按顺序处理同一个请求”。它不是为了炫技拆类,而是当规则会持续增加、顺序可定义、每个规则相对独立时,用链路把变化点隔离出来。
