在现代软件系统中,业务流程日益复杂。无论是金融交易、电商订单处理、内容审核,还是物联网设备指令分发,我们常常面临一个共同挑战:如何将一个复杂的请求处理过程拆解为多个可独立开发、测试和部署的单元,并以灵活、可配置的方式组合执行?
传统的线性编程模型(如 if-else 链或 switch-case 块)在面对这类需求时显得力不从心:
正是在这样的背景下,责任链模式(Chain of Responsibility Pattern)应运而生。该模式允许将请求沿着处理者链传递,每个处理者决定是否处理该请求,或将请求传递给链中的下一个处理者。这种“流水线式”的处理机制天然契合复杂业务流程的建模需求。
Apache Commons Chain 是 Apache 软件基金会提供的一个轻量级 Java 库,它对责任链模式进行了标准化、工程化的实现,提供了 Command、Chain、Context、Filter 和 Catalog 等核心抽象,使得开发者能够以声明式、模块化的方式构建高度可扩展的业务流程引擎。
本文将以 ATM 取款流程 为贯穿案例,通过万字系统性地剖析 Apache Commons Chain 的设计哲学、核心组件、高级特性及工程实践。内容涵盖:
无论你是初学者希望掌握责任链模式的实战应用,还是资深架构师寻求构建企业级流程引擎的参考方案,本文都将为你提供全面而深入的指导。
责任链模式(Chain of Responsibility Pattern)属于行为型设计模式,其官方定义为:
“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.”
即:通过将多个对象链接成一条链,并沿链传递请求,直到有对象处理它为止,从而避免请求发送者与接收者之间的耦合。
其标准 UML 类图如下:
classDiagram
class Handler {
<<abstract>>
+setNext(Handler handler)
+handle(Request request)
}
class ConcreteHandlerA {
+handle(Request request)
}
class ConcreteHandlerB {
+handle(Request request)
}
class Client
Client --> Handler
Handler <|-- ConcreteHandlerA
Handler <|-- ConcreteHandlerB
ConcreteHandlerA --> ConcreteHandlerB : next责任链模式存在两种典型实现方式:
Apache Commons Chain 主要支持不纯责任链,但通过返回值控制可模拟纯责任链行为。
责任链模式特别适用于以下场景:
Apache Commons Chain 在经典责任链基础上进行了扩展,引入了 上下文(Context)、命令(Command)、链(Chain)、过滤器(Filter)和 目录(Catalog)五大核心概念。
Context 是整个处理链共享的数据容器,相当于流程的“黑板”(Blackboard)。所有处理单元通过读写 Context 来交换信息。
Commons Chain 提供了 ContextBase 作为基类,它内部使用 Map 存储键值对:
public class ContextBase extends HashMap<String, Object> implements Context {
// 实现 Context 接口方法
}关键特性:
Object,需强制转换;ContextBase 定义强类型字段。💡 最佳实践:为每个业务流程定义专用 Context 子类,如
AtmRequestContext,以提升代码可读性与类型安全。
Command 是处理链的基本执行单元,对应责任链中的“处理者”。其接口极为简洁:
public interface Command {
boolean execute(Context context) throws Exception;
}返回值语义:
false:继续执行链中下一个 Command;true:立即终止整个链的执行。⚠️ 注意:此设计与直觉相反——
true表示“已处理完毕,无需继续”,而非“成功”。
Chain 本身也是一个 Command,它按顺序执行内部注册的子命令:
public interface Chain extends Command {
void addCommand(Command command);
List<Command> getCommands();
}Commons Chain 提供 ChainBase 作为默认实现:
public class ChainBase extends ArrayList<Command> implements Chain {
@Override
public boolean execute(Context context) throws Exception {
for (Command command : this) {
if (command.execute(context)) {
return true; // 提前终止
}
}
return false;
}
}Filter 是 Command 的扩展,增加了 postprocess 方法:
public interface Filter extends Command {
boolean postprocess(Context context, Exception exception);
}执行流程:
execute);postprocess;postprocess。📌 典型用途:事务管理(
execute开启事务,postprocess提交/回滚)、资源清理、审计日志。
Catalog 提供了逻辑名称到 Command/Chain 的映射,类似于 Spring 的 Bean 容器:
public interface Catalog {
void addCommand(String name, Command command);
Command getCommand(String name);
}通过 Catalog,客户端无需直接依赖具体实现类,提升了系统的灵活性与可配置性。
ATM 取款流程包含以下步骤:
这些步骤具有明显的顺序依赖和条件分支特征,非常适合用责任链建模。
首先定义 AtmRequestContext,封装流程所需的所有状态:
public class AtmRequestContext extends ContextBase {
// 输入参数
private int totalAmountToBeWithdrawn;
// 输出结果
private int noOfHundredsDispensed;
private int noOfFiftiesDispensed;
private int noOfTensDispensed;
// 中间状态
private int amountLeftToBeWithdrawn;
// 账户信息
private String accountId;
private double accountBalance;
// 交易结果
private boolean success = true;
private String errorMessage;
// 标准 getter/setter
// ...
}✅ 优势:强类型字段避免频繁的类型转换,提升代码健壮性。
public class HundredDenominationDispenser implements Command {
@Override
public boolean execute(Context context) throws Exception {
AtmRequestContext ctx = (AtmRequestContext) context;
int amountLeft = ctx.getAmountLeftToBeWithdrawn();
if (amountLeft >= 100) {
int hundreds = amountLeft / 100;
ctx.setNoOfHundredsDispensed(hundreds);
ctx.setAmountLeftToBeWithdrawn(amountLeft % 100);
}
return false; // 继续执行
}
}public class FiftyDenominationDispenser implements Command {
@Override
public boolean execute(Context context) throws Exception {
AtmRequestContext ctx = (AtmRequestContext) context;
int amountLeft = ctx.getAmountLeftToBeWithdrawn();
if (amountLeft >= 50) {
ctx.setNoOfFiftiesDispensed(amountLeft / 50);
ctx.setAmountLeftToBeWithdrawn(amountLeft % 50);
}
return false;
}
}
public class TenDenominationDispenser implements Command {
@Override
public boolean execute(Context context) throws Exception {
AtmRequestContext ctx = (AtmRequestContext) context;
int amountLeft = ctx.getAmountLeftToBeWithdrawn();
if (amountLeft >= 10 && amountLeft % 10 == 0) {
ctx.setNoOfTensDispensed(amountLeft / 10);
ctx.setAmountLeftToBeWithdrawn(0);
} else if (amountLeft > 0) {
// 无法整除10,取款失败
ctx.setSuccess(false);
ctx.setErrorMessage("Amount must be multiple of 10");
return true; // 终止链
}
return false;
}
}public class BalanceValidator implements Command {
@Override
public boolean execute(Context context) throws Exception {
AtmRequestContext ctx = (AtmRequestContext) context;
if (ctx.getAccountBalance() < ctx.getTotalAmountToBeWithdrawn()) {
ctx.setSuccess(false);
ctx.setErrorMessage("Insufficient balance");
return true; // 终止链
}
return false;
}
}public class AccountDebiter implements Command {
@Override
public boolean execute(Context context) throws Exception {
AtmRequestContext ctx = (AtmRequestContext) context;
// 模拟数据库操作
double newBalance = ctx.getAccountBalance() - ctx.getTotalAmountToBeWithdrawn();
ctx.setAccountBalance(newBalance);
return false;
}
}审计过滤器负责记录日志和发送通知:
public class AuditFilter implements Filter {
private static final Logger logger = LoggerFactory.getLogger(AuditFilter.class);
@Override
public boolean execute(Context context) throws Exception {
// execute 通常为空,主要逻辑在 postprocess
return false;
}
@Override
public boolean postprocess(Context context, Exception exception) {
AtmRequestContext ctx = (AtmRequestContext) context;
if (exception != null) {
logger.error("ATM withdrawal failed: {}", exception.getMessage());
return false;
}
if (ctx.isSuccess()) {
// 记录成功日志
logger.info("Withdrawal successful: {} from account {}",
ctx.getTotalAmountToBeWithdrawn(), ctx.getAccountId());
// 发送通知(模拟)
sendSmsToCustomer(ctx);
sendEmailToBank(ctx);
} else {
logger.warn("Withdrawal rejected: {} for account {}",
ctx.getErrorMessage(), ctx.getAccountId());
}
return false;
}
private void sendSmsToCustomer(AtmRequestContext ctx) {
System.out.println("SMS sent to customer: Withdrawn " +
ctx.getTotalAmountToBeWithdrawn());
}
private void sendEmailToBank(AtmRequestContext ctx) {
System.out.println("Email sent to bank: Transaction ID " +
System.currentTimeMillis());
}
}定义完整的 ATM 取款链:
public class AtmWithdrawalChain extends ChainBase {
public AtmWithdrawalChain() {
super();
// 验证阶段
addCommand(new BalanceValidator());
// 计算阶段
addCommand(new HundredDenominationDispenser());
addCommand(new FiftyDenominationDispenser());
addCommand(new TenDenominationDispenser());
// 执行阶段
addCommand(new AccountDebiter());
// 审计阶段(Filter 必须放在最后)
addCommand(new AuditFilter());
}
}🔍 执行顺序说明:
将链注册到目录中:
public class AtmCatalog extends CatalogBase {
public AtmCatalog() {
super();
addCommand("atmWithdrawalChain", new AtmWithdrawalChain());
}
}编写单元测试验证流程正确性:
public class AtmChainTest {
@Test
public void testSuccessfulWithdrawal() throws Exception {
AtmRequestContext context = new AtmRequestContext();
context.setTotalAmountToBeWithdrawn(460);
context.setAmountLeftToBeWithdrawn(460);
context.setAccountId("ACC123");
context.setAccountBalance(1000.0);
Catalog catalog = new AtmCatalog();
Command chain = catalog.getCommand("atmWithdrawalChain");
chain.execute(context);
assertTrue(context.isSuccess());
assertEquals(0, context.getAmountLeftToBeWithdrawn());
assertEquals(4, context.getNoOfHundredsDispensed());
assertEquals(1, context.getNoOfFiftiesDispensed());
assertEquals(1, context.getNoOfTensDispensed());
assertEquals(540.0, context.getAccountBalance(), 0.01);
}
@Test
public void testInsufficientBalance() throws Exception {
AtmRequestContext context = new AtmRequestContext();
context.setTotalAmountToBeWithdrawn(1500);
context.setAmountLeftToBeWithdrawn(1500);
context.setAccountId("ACC123");
context.setAccountBalance(1000.0);
Catalog catalog = new AtmCatalog();
Command chain = catalog.getCommand("atmWithdrawalChain");
chain.execute(context);
assertFalse(context.isSuccess());
assertEquals("Insufficient balance", context.getErrorMessage());
// 验证后续步骤未执行
assertEquals(0, context.getNoOfHundredsDispensed());
}
}Commons Chain 的异常处理机制较为简单——任何 Command 抛出异常都会中断链执行,并触发已执行 Filter 的 postprocess。
增强方案:
// 局部容错示例
public class SafeAccountDebiter implements Command {
@Override
public boolean execute(Context context) throws Exception {
try {
// 执行扣款
} catch (DatabaseException e) {
((AtmRequestContext) context).setErrorMessage("System error, please try later");
return true; // 终止链
}
return false;
}
}有时需要根据运行时条件动态调整链结构:
public class DynamicAtmChainBuilder {
public Chain buildChain(AtmRequestContext context) {
ChainBase chain = new ChainBase();
chain.addCommand(new BalanceValidator());
// 根据金额动态添加纸币分配器
if (context.getTotalAmountToBeWithdrawn() >= 100) {
chain.addCommand(new HundredDenominationDispenser());
}
if (context.getAmountLeftToBeWithdrawn() >= 50) {
chain.addCommand(new FiftyDenominationDispenser());
}
chain.addCommand(new TenDenominationDispenser());
chain.addCommand(new AccountDebiter());
chain.addCommand(new AuditFilter());
return chain;
}
}在 Spring 应用中,可通过依赖注入管理 Command:
@Configuration
public class AtmChainConfig {
@Bean
public Command hundredDenominationDispenser() {
return new HundredDenominationDispenser();
}
@Bean
public Chain atmWithdrawalChain(
BalanceValidator validator,
HundredDenominationDispenser hundred,
// ... other commands
AuditFilter auditFilter
) {
ChainBase chain = new ChainBase();
chain.addCommand(validator);
chain.addCommand(hundred);
// ...
chain.addCommand(auditFilter);
return chain;
}
}📊 基准测试建议:使用 JMH 对比责任链与传统 if-else 的性能差异。
维度 | 责任链模式 | 策略模式 |
|---|---|---|
目的 | 多对象处理同一请求 | 多算法实现同一接口 |
执行方式 | 顺序尝试,直到处理 | 单一选择,直接执行 |
耦合度 | 链内耦合(顺序依赖) | 完全解耦 |
适用场景 | 流水线处理 | 算法切换 |
整合示例:在责任链的某个节点使用策略模式选择具体算法。
整合示例:ATM 的不同状态(空闲、服务中、故障)对应不同的处理链。
特性 | Apache Commons Chain | Drools |
|---|---|---|
复杂度 | 轻量级,代码驱动 | 重量级,规则驱动 |
学习曲线 | 低 | 高 |
动态性 | 需重启应用 | 规则热更新 |
适用规模 | 中小型流程 | 大型复杂规则集 |
💡 建议:简单流程用 Commons Chain,复杂规则用 Drools。
public abstract class LoggedCommand implements Command {
private final String commandName;
protected LoggedCommand(String name) {
this.commandName = name;
}
@Override
public final boolean execute(Context context) throws Exception {
long start = System.currentTimeMillis();
try {
boolean result = doExecute(context);
log.info("{} executed in {}ms, result: {}",
commandName, System.currentTimeMillis() - start, result);
return result;
} catch (Exception e) {
log.error("{} failed after {}ms", commandName,
System.currentTimeMillis() - start, e);
throw e;
}
}
protected abstract boolean doExecute(Context context) throws Exception;
}通过 XML 或 YAML 定义链结构,实现动态调整:
atm-withdrawal-chain:
commands:
- balanceValidator
- hundredDenominationDispenser
- fiftyDenominationDispenser
- tenDenominationDispenser
- accountDebiter
- auditFilter对于简单场景,可自行实现轻量级责任链:
@FunctionalInterface
public interface Handler<T> {
boolean handle(T context);
default Handler<T> then(Handler<T> next) {
return ctx -> this.handle(ctx) || next.handle(ctx);
}
}
// 使用
Handler<Context> chain = new Validator()
.then(new Dispenser())
.then(new Auditor());利用 Stream 和 Optional 构建声明式链:
List<Function<Context, Context>> processors = Arrays.asList(
ctx -> validate(ctx),
ctx -> dispense(ctx),
ctx -> audit(ctx)
);
Context result = processors.stream()
.reduce(Function.identity(), Function::andThen)
.apply(initialContext);在微服务架构中,可考虑:
Apache Commons Chain 虽是一个“古老”的库,但其背后的责任链思想在当今软件架构中依然熠熠生辉。从 Spring Security 的过滤器链,到 Netty 的 ChannelPipeline,再到现代 Serverless 架构中的函数编排,责任链模式以其解耦、可扩展、可组合的特性,持续为复杂系统提供优雅的解决方案。
在实际应用中,我们不必拘泥于 Commons Chain 的具体实现,而应把握其核心思想:将复杂流程分解为独立、可复用的处理单元,并通过灵活的组合机制应对变化的需求。
正如本文 ATM 示例所示,一个精心设计的责任链不仅能提升代码质量,更能使业务逻辑清晰可见,为系统的长期演进奠定坚实基础。
最后建议: