Java八股设计模式责任链模式:从 if else 到 Spring 自动装配
YYT责任链模式:从 if else 到 Spring 自动装配
一、责任链模式是什么?
责任链模式,英文叫 Chain of Responsibility Pattern。
它的核心思想是:
一个请求来了,不是由一个大方法统一处理所有逻辑,而是交给一组处理器按顺序处理。每个处理器只负责自己的职责,处理完后继续交给下一个处理器。
简单理解就是:
1
| 请求 -> 处理器 A -> 处理器 B -> 处理器 C -> 结束
|
比如在下单场景中,创建订单之前通常要做很多校验:
1 2 3 4 5
| 登录校验 库存校验 风控校验 优惠券校验 活动规则校验
|
如果全部写在一个方法里,就很容易变成一大堆 if else。
责任链模式就是把这些逻辑拆成一个个独立节点:
1
| LoginHandler -> StockHandler -> RiskHandler -> CouponHandler
|
每个节点只处理自己的事情。
二、为什么不用普通 if else?
假设我们有一个下单方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| public void createOrder(OrderContext context) { if (context.getUserId() == null) { throw new RuntimeException("用户未登录"); }
if (context.getBuyCount() <= 0) { throw new RuntimeException("购买数量不合法"); }
if (!context.isRiskPass()) { throw new RuntimeException("风控不通过"); }
if (!context.isCouponValid()) { throw new RuntimeException("优惠券不可用"); }
}
|
这种写法不是不能用,小项目里甚至很直接。
但是问题是:
1 2 3 4
| 1. 规则越来越多,方法会越来越长 2. 每次新增校验,都要改核心业务方法 3. 不同校验逻辑混在一起,不好维护 4. 某些校验想复用到别的业务里,不方便
|
责任链模式解决的不是“完全不用改代码”,而是:
1 2 3 4
| 把复杂逻辑拆成多个独立处理器 让每个处理器只负责一件事 让主流程保持干净 让新增规则尽量只新增类
|
三、先定义订单上下文
责任链里的每个节点都要处理同一个请求对象。
我们可以定义一个 OrderContext,表示这次下单请求携带的数据:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33
| public class OrderContext {
private Long userId; private Long productId; private Integer buyCount; private String couponCode; private boolean riskPass; private boolean couponValid;
public Long getUserId() { return userId; }
public Long getProductId() { return productId; }
public Integer getBuyCount() { return buyCount; }
public String getCouponCode() { return couponCode; }
public boolean isRiskPass() { return riskPass; }
public boolean isCouponValid() { return couponValid; } }
|
这个 context 可以理解成:
后面的登录校验、库存校验、风控校验都会从这个对象里面拿数据。
四、定义统一的 Handler 接口
所有责任链节点都实现同一个接口:
1 2 3 4 5 6 7 8 9 10 11 12
| public interface OrderHandler {
int order();
void handle(OrderContext context); }
|
这里有两个方法。
第一个是:
它用来控制节点顺序。
比如:
1 2 3 4
| LoginHandler = 100 StockHandler = 200 RiskHandler = 300 CouponHandler = 400
|
第二个是:
1
| void handle(OrderContext context);
|
它表示每个节点真正要执行的业务逻辑。
五、实现登录校验节点
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| import org.springframework.stereotype.Component;
@Component public class LoginHandler implements OrderHandler {
@Override public int order() { return 100; }
@Override public void handle(OrderContext context) { if (context.getUserId() == null) { throw new RuntimeException("用户未登录"); }
System.out.println("登录校验通过"); } }
|
注意这里的:
它的意思是:
1
| 把 LoginHandler 交给 Spring 管理
|
只要这个类被 Spring 扫描到,它就会成为 Spring 容器里的一个 Bean。
六、实现库存校验节点
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| import org.springframework.stereotype.Component;
@Component public class StockHandler implements OrderHandler {
@Override public int order() { return 200; }
@Override public void handle(OrderContext context) { if (context.getBuyCount() == null || context.getBuyCount() <= 0) { throw new RuntimeException("购买数量不合法"); }
System.out.println("库存校验通过"); } }
|
这个节点只负责库存相关校验。
它不关心登录,也不关心优惠券。
七、实现风控校验节点
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| import org.springframework.stereotype.Component;
@Component public class RiskHandler implements OrderHandler {
@Override public int order() { return 300; }
@Override public void handle(OrderContext context) { if (!context.isRiskPass()) { throw new RuntimeException("风控不通过"); }
System.out.println("风控校验通过"); } }
|
如果风控不通过,直接抛异常。
一旦抛出异常,责任链后面的节点就不会继续执行了。
八、实现优惠券校验节点
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| import org.springframework.stereotype.Component;
@Component public class CouponHandler implements OrderHandler {
@Override public int order() { return 400; }
@Override public void handle(OrderContext context) { if (context.getCouponCode() != null && !context.isCouponValid()) { throw new RuntimeException("优惠券不可用"); }
System.out.println("优惠券校验通过"); } }
|
到这里,我们已经有了四个节点:
1 2 3 4
| LoginHandler StockHandler RiskHandler CouponHandler
|
它们都实现了 OrderHandler 接口,也都加了 @Component。
九、责任链核心类:OrderHandlerChain
接下来就是最关键的部分。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| import org.springframework.stereotype.Component;
import java.util.Comparator; import java.util.List;
@Component public class OrderHandlerChain {
private final List<OrderHandler> handlers;
public OrderHandlerChain(List<OrderHandler> handlers) { this.handlers = handlers.stream() .sorted(Comparator.comparingInt(OrderHandler::order)) .toList(); }
public void handle(OrderContext context) { for (OrderHandler handler : handlers) { handler.handle(context); } } }
|
这段代码看起来简单,但里面有几个关键点。
十、为什么构造方法可以自动装配?
关键在这里:
1
| public OrderHandlerChain(List<OrderHandler> handlers)
|
很多人第一次看会疑惑:
1
| 这个 List<OrderHandler> 是谁传进来的?
|
答案是:Spring 自动传进来的。
因为 OrderHandlerChain 上面有:
这说明 OrderHandlerChain 不是我们自己手动 new 的,而是交给 Spring 创建的。
Spring 创建这个对象时,会看它的构造方法需要什么参数。
它发现构造方法需要:
于是 Spring 就会去容器里找所有 OrderHandler 类型的 Bean。
比如容器里有:
1 2 3 4
| LoginHandler StockHandler RiskHandler CouponHandler
|
而且它们都实现了 OrderHandler 接口。
所以 Spring 会自动把它们收集起来,组成一个 List,然后传给构造方法。
等价于 Spring 在背后帮我们做了这件事:
1 2 3 4 5 6 7 8
| List<OrderHandler> handlers = List.of( loginHandler, stockHandler, riskHandler, couponHandler );
OrderHandlerChain chain = new OrderHandlerChain(handlers);
|
只不过这些代码不需要我们自己写。
所以这就是为什么构造方法可以自动装配。
前提有三个:
1 2 3
| 1. OrderHandlerChain 要交给 Spring 管理,也就是加 @Component 2. 每个 Handler 节点也要交给 Spring 管理,也就是加 @Component 3. 每个节点都要实现同一个接口 OrderHandler
|
十一、为什么要排序?
这段代码的作用是排序:
1 2 3
| this.handlers = handlers.stream() .sorted(Comparator.comparingInt(OrderHandler::order)) .toList();
|
因为 Spring 注入进来的 List<OrderHandler> 不一定是我们想要的业务顺序。
所以我们让每个 Handler 自己提供一个顺序值:
1 2 3 4
| @Override public int order() { return 100; }
|
然后按照 order() 从小到大排序。
最终得到的顺序就是:
1 2 3 4
| LoginHandler 100 StockHandler 200 RiskHandler 300 CouponHandler 400
|
也就是:
1
| 登录校验 -> 库存校验 -> 风控校验 -> 优惠券校验
|
十二、for 循环执行是什么意思?
这段代码是责任链真正的执行入口:
1 2 3 4 5
| public void handle(OrderContext context) { for (OrderHandler handler : handlers) { handler.handle(context); } }
|
它的意思是:
1
| 把 handlers 里面的每个处理器拿出来,按顺序执行它的 handle 方法
|
假设 handlers 排序后是:
1 2 3 4
| LoginHandler StockHandler RiskHandler CouponHandler
|
那么这段代码实际等价于:
1 2 3 4
| loginHandler.handle(context); stockHandler.handle(context); riskHandler.handle(context); couponHandler.handle(context);
|
执行流程就是:
1 2 3 4 5 6 7 8 9
| 订单请求进来 ↓ 登录校验 ↓ 库存校验 ↓ 风控校验 ↓ 优惠券校验
|
如果中间某个节点失败,比如库存不足:
1
| throw new RuntimeException("库存不足");
|
那么后面的风控校验、优惠券校验就不会执行。
所以它可以实现:
十三、业务代码怎么使用责任链?
在订单服务里,只需要注入 OrderHandlerChain:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| import org.springframework.stereotype.Service;
@Service public class OrderService {
private final OrderHandlerChain orderHandlerChain;
public OrderService(OrderHandlerChain orderHandlerChain) { this.orderHandlerChain = orderHandlerChain; }
public void createOrder(OrderContext context) { orderHandlerChain.handle(context);
System.out.println("创建订单成功"); } }
|
主流程变得很干净:
1
| orderHandlerChain.handle(context);
|
这一句就代表:
具体有哪些校验,交给责任链内部处理。
十四、新增节点要不要改原来的代码?
这是责任链模式里很关键的问题。
如果使用最原始的手动责任链写法:
1
| new LoginHandler(new StockHandler(new CouponHandler(null)));
|
那么新增节点确实要改装配代码。
但是在 Spring 项目里,我们通常使用自动装配写法。
比如现在要新增一个黑名单校验节点,只需要新增一个类:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| import org.springframework.stereotype.Component;
@Component public class BlackListHandler implements OrderHandler {
@Override public int order() { return 250; }
@Override public void handle(OrderContext context) { System.out.println("黑名单校验通过"); } }
|
因为它:
1 2 3
| 1. 实现了 OrderHandler 2. 加了 @Component 3. 提供了 order 顺序
|
所以 Spring 启动时会自动把它加入:
1
| List<OrderHandler> handlers
|
最终链路变成:
1 2 3 4 5
| LoginHandler 100 StockHandler 200 BlackListHandler 250 RiskHandler 300 CouponHandler 400
|
我们不需要修改 OrderHandlerChain。
这就是 Spring 自动装配责任链的价值。
十五、责任链是不是本质上就是 for 循环?
从代码落地上看,确实很像:
1 2 3
| for (OrderHandler handler : handlers) { handler.handle(context); }
|
但是责任链模式强调的不是这个 for 循环本身,而是背后的设计思想:
1 2 3 4
| 多个处理器按顺序处理同一个请求 每个处理器只负责自己的职责 新增处理器时尽量不影响已有处理器 主业务流程不关心具体有哪些处理器
|
所以不要把责任链理解得太玄乎。
在实际工程里,它经常就是:
1
| 接口 + 多个实现类 + Spring 自动注入 List + 排序 + for 循环执行
|
这就是一个非常实用的责任链实现。
十六、责任链模式适合什么场景?
责任链模式适合这种场景:
1 2 3 4 5
| 一个请求需要经过多个处理步骤 每个步骤可以独立拆出来 步骤之间有明确顺序 中间某一步失败,可以终止后续执行 后续可能经常新增、删除、调整步骤
|
常见业务场景包括:
1 2 3 4 5 6 7 8 9 10 11
| 订单创建前校验 支付前校验 优惠券使用校验 拼团规则校验 风控规则校验 网关过滤器 登录认证 权限校验 Servlet Filter Spring Interceptor 审批流程
|
比如拼团系统里,下单前可能有:
1 2 3 4 5 6 7
| 用户登录校验 商品状态校验 库存校验 拼团活动校验 用户参团资格校验 优惠券校验 风控校验
|
这些都可以拆成责任链节点。
十七、责任链模式的优点
责任链的优点主要有几个。
第一,减少大方法里的 if else。
原来所有逻辑都堆在一个方法里,现在拆成多个 Handler。
第二,职责更清晰。
每个 Handler 只做一件事。
第三,扩展更方便。
新增规则时,可以新增一个 Handler,而不是修改原来的大方法。
第四,主流程更干净。
订单服务只需要调用:
1
| orderHandlerChain.handle(context);
|
不需要关心里面具体有哪些校验。
第五,可以配合 Spring 自动装配。
新增节点后,只要加 @Component,实现接口,就能自动加入链路。
十八、责任链模式的缺点
责任链也不是万能的。
它也有缺点。
第一,链路太长时,不好排查问题。
比如一个请求经过十几个 Handler,最后失败了,就要靠日志判断到底卡在哪个节点。
第二,顺序很重要。
如果顺序配置错,可能导致业务逻辑异常。
比如应该先校验登录,再校验用户资格。如果顺序反了,可能会出现空指针或者错误判断。
第三,不适合所有场景。
如果只是两三个简单判断,而且以后基本不会变,直接写 if else 可能更简单。
所以责任链适合规则多、变化多、需要扩展的场景。
十九、责任链和策略模式的区别
很多人会把责任链模式和策略模式混在一起。
可以这样区分:
1 2
| 策略模式:多个策略中选择一个执行 责任链模式:多个处理器按顺序执行
|
比如优惠活动:
如果一次只选择其中一种优惠策略,这更像策略模式。
但是下单前校验:
1
| 登录校验 -> 库存校验 -> 风控校验 -> 优惠券校验
|
这是多个节点按顺序执行,更像责任链模式。
一句话区分:
二十、总结
责任链模式不是为了彻底消灭修改,也不是说用了它就一定比 if else 高级。
它真正解决的问题是:
1 2 3 4 5
| 把复杂流程拆成多个独立节点 让每个节点只负责自己的职责 让主业务流程保持简单 让新增规则尽量只新增类 让链路顺序可以统一管理
|
在 Spring 项目中,责任链常见写法就是:
1 2 3 4 5
| 定义统一 Handler 接口 多个 Handler 实现类加 @Component 在 Chain 类中注入 List<Handler> 按照 order 排序 for 循环依次执行
|
核心代码就是:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| @Component public class OrderHandlerChain {
private final List<OrderHandler> handlers;
public OrderHandlerChain(List<OrderHandler> handlers) { this.handlers = handlers.stream() .sorted(Comparator.comparingInt(OrderHandler::order)) .toList(); }
public void handle(OrderContext context) { for (OrderHandler handler : handlers) { handler.handle(context); } } }
|
这段代码的本质是:
1 2 3
| Spring 自动收集所有责任链节点 按照顺序排好 请求来了之后,一个一个执行
|
所以,责任链模式可以简单记成一句话:
责任链 = 多个处理器排成一条链,按顺序处理同一个请求。