设计模式—责任链模式(完整工程面试详解)
本文目录如下:
1. 为什么需要责任链模式
1.1 责任链模式解决的核心痛点
1.2 典型使用场景
1.3 不适合责任链模式的场景
2. 责任链模式核心体系与角色
2.1 核心特点
2.2 三大标准角色
3. 责任链标准实战实现(同步基础版)
3.1 业务场景
3.2 第一步:封装请求实体
3.3 第二步:抽象处理器
3.4 第三步:多个具体处理器
3.5 客户端组装链条并调用
3.6 代码核心分析
4. 企业级成熟方案:SpringBoot 责任链(容器自动装配、可配置顺序)
4.1 业务标识常量
4.2 请求载体
4.3 抽象处理器(增加所属业务标识、执行顺序)
4.4 具体处理器
4.5 链路工厂:按业务类型分组、自动构建对应链条
4.6 业务 Service 调用
4.7 测试示例
5. 责任链模式优缺点
5.1 优点
5.2 缺点
6. 工程常见痛点与解决方案
6.1 忘记调用下一级处理器,链条断裂
6.2 链条节点过多,串行执行RT过高
6.3 手动组装链条,维护麻烦
6.4 链路日志缺失,线上难以定位拦截节点
6.5 循环引用、死链问题
7. 高频面试:易混模式核心区分
7.1 责任链 vs 装饰器模式
7.2 责任链 vs 模板方法模式
7.3 责任链 vs 观察者模式
7.4 责任链 vs 策略模式
8. 主流框架落地(面试高频)
8.1 Servlet Filter 过滤器
8.2 SpringMVC Interceptor 拦截器
8.3 Mybatis Interceptor 插件链
8.4 Spring Security 安全过滤器链
9. 核心总结
1. 为什么需要责任链模式
责任链模式是行为型设计模式。
核心目标:将多个处理逻辑封装为独立处理器,串联成一条处理链条;请求依次经过链条上所有处理器,每个处理器自主判断是否处理、是否放行请求,实现请求发送者与请求处理者完全解耦。
核心设计思想:单一职责、链式流转、按需拦截、分层处理。每个处理器只专注自身单一校验 / 处理逻辑,互不耦合;新增、删除、调整处理顺序无需修改原有业务代码。
换句话讲:业务存在多层连续校验、多级处理流程,且处理节点需要灵活增删、调整执行顺序时,避免多层 if-else 嵌套、硬编码串行调用,把每一层逻辑抽成独立处理器,通过链条统一流转请求,彻底实现流程分层解耦。
1.1 责任链模式解决的核心痛点
a.多层校验逻辑嵌套臃肿:参数校验、权限校验、风控校验、签名校验层层 if 嵌套,主流程代码庞大,可读性极差;
b.新增处理节点需修改核心流程:每增加一层校验,就要改动主业务方法,违反开闭原则;
c.处理逻辑高度耦合:各层级处理代码写在同一方法内,无法单独复用、单独单元测试;
d.处理顺序固化难调整:硬编码串行执行,如需调换校验顺序需要大面积改动代码;
e.职责混乱,定位问题困难:多层逻辑混在一起,出现异常难以定位是哪一层拦截失败。
1.2 典型使用场景
a.多级请求校验场景:接口入参校验、登录权限校验、接口签名校验、风控黑名单校验;
b.多层业务处理流程:订单创建前置校验、审批流(普通审批→主管审批→总经理审批);
c.框架拦截过滤场景:Servlet Filter、Spring Interceptor、Mybatis 拦截器;
d.日志、报文多级加工场景:报文解密、参数脱敏、日志埋点、报文加密链式处理; e. 可灵活增减处理节点的业务:活动规则校验、支付前置安全校验。
1.3 不适合责任链模式的场景
a.处理节点永久固定、无需调整顺序:仅固定 2-3 层逻辑,无拓展需求,直接顺序调用更简洁;
b.节点之间强依赖、执行顺序不可变更且永久不变;
c.单次处理、无分层校验,逻辑极简;
d.需要并行处理所有节点:责任链默认串行流转,不适合并发并行场景。
2. 责任链模式核心体系与角色
2.1 核心特点
- 链式串联:每个处理器持有下一个处理器引用,形成完整处理链路;
- 自主控制流转:处理器可选择处理请求后放行、处理后直接终止链条;
- 解耦发送与处理:发起请求的客户端完全不感知有多少处理器、处理顺序;
- 高度可拓展:新增处理器只需新增类,调整链路仅修改组装逻辑,不改动业务处理代码。
2.2 三大标准角色
- 请求实体(Request):封装本次处理的全部业务数据,在整条链条中流转;
- 抽象处理器(Handler):定义统一处理接口,持有下一级处理器引用,提供设置后继处理器的方法;
- 具体处理器(ConcreteHandler):实现抽象处理方法,完成自身专属逻辑,自主决定放行或终止链条。
3. 责任链标准实战实现(同步基础版)
3.1 业务场景
下单接口前置三层校验:参数校验 → 登录权限校验 → 风控黑名单校验,任意一层校验失败直接拦截,终止链条;全部校验通过才允许创建订单。
3.2 第一步:封装请求实体
/** * 请求实体:链条流转的数据载体 */ public class OrderRequest { // 用户ID private Long userId; // 商品ID private Long goodsId; // 订单金额 private BigDecimal amount; // 校验结果信息 private String msg; // 是否校验通过 private boolean pass = true; // getter/setter public Long getUserId() { return userId; } public void setUserId(Long userId) { this.userId = userId; } public Long getGoodsId() { return goodsId; } public void setGoodsId(Long goodsId) { this.goodsId = goodsId; } public BigDecimal getAmount() { return amount; } public void setAmount(BigDecimal amount) { this.amount = amount; } public String getMsg() { return msg; } public void setMsg(String msg) { this.msg = msg; } public boolean isPass() { return pass; } public void setPass(boolean pass) { this.pass = pass; } }3.3 第二步:抽象处理器
/** * 抽象处理器 */ public abstract class AbstractOrderHandler { // 持有下一个处理器,形成链条 protected AbstractOrderHandler nextHandler; // 设置后继处理器 public void setNextHandler(AbstractOrderHandler nextHandler) { this.nextHandler = nextHandler; } // 统一处理方法 public abstract void handle(OrderRequest request); }3.4 第三步:多个具体处理器
/** * 第一层:参数校验处理器 */ public class ParamHandler extends AbstractOrderHandler { @Override public void handle(OrderRequest request) { // 自身处理逻辑 if (request.getUserId() == null || request.getGoodsId() == null || request.getAmount() == null) { request.setPass(false); request.setMsg("参数缺失,下单失败"); // 校验失败,直接终止链条,不执行下一级 return; } System.out.println("参数校验通过,流转下一层"); // 校验通过,存在下一级则继续执行 if (nextHandler != null) { nextHandler.handle(request); } } } /** * 第二层:登录权限校验 */ public class LoginAuthHandler extends AbstractOrderHandler { @Override public void handle(OrderRequest request) { // 模拟未登录用户ID=0 if (request.getUserId().equals(0L)) { request.setPass(false); request.setMsg("用户未登录,请先登录"); return; } System.out.println("登录权限校验通过,流转下一层"); if (nextHandler != null) { nextHandler.handle(request); } } } /** * 第三层:风控黑名单校验 */ public class RiskBlackHandler extends AbstractOrderHandler { @Override public void handle(OrderRequest request) { // 模拟黑名单用户ID=999 if (request.getUserId().equals(999L)) { request.setPass(false); request.setMsg("当前用户存在风控限制,禁止下单"); return; } System.out.println("风控校验通过,校验全部完成"); if (nextHandler != null) { nextHandler.handle(request); } } }3.5 客户端组装链条并调用
public class ChainClient { public static void main(String[] args) { // 1. 创建所有处理器 AbstractOrderHandler paramHandler = new ParamHandler(); AbstractOrderHandler authHandler = new LoginAuthHandler(); AbstractOrderHandler riskHandler = new RiskBlackHandler(); // 2. 组装链条:参数→权限→风控 paramHandler.setNextHandler(authHandler); authHandler.setNextHandler(riskHandler); // 3. 构造请求 OrderRequest request = new OrderRequest(); request.setUserId(1001L); request.setGoodsId(10001L); request.setAmount(new BigDecimal("99.9")); // 4. 从链条头部发起请求 paramHandler.handle(request); // 5. 输出结果 if (request.isPass()) { System.out.println("全部校验通过,执行下单逻辑"); } else { System.out.println("下单拦截:" + request.getMsg()); } } }3.6 代码核心分析
- 每个处理器仅负责单一校验逻辑,单一职责,可独立修改、单独测试;
- 链条组装与业务处理完全分离,调换执行顺序仅修改组装代码;
- 校验失败可直接 return 终止链条,减少无效执行;
- 新增校验层只需新增 Handler,无需修改原有任何处理器,符合开闭原则。
4. 企业级成熟方案:SpringBoot 责任链(容器自动装配、可配置顺序)
基础手写链条存在缺陷:手动组装处理器,项目庞大后组装代码杂乱;无法动态控制执行顺序。企业项目结合 Spring IOC 自动注入所有处理器,通过注解排序自动构建链路,无需手动 setNext。
4.1 业务标识常量
public class BizConstant { // 下单业务链路 public static final String BIZ_ORDER = "ORDER"; // 退款业务链路 public static final String BIZ_REFUND = "REFUND"; }4.2 请求载体
import java.math.BigDecimal; public class BizRequest { // 业务标识,用于匹配链路 private String bizType; private Long userId; private Long orderId; private BigDecimal amount; private boolean pass = true; private String msg; // getter/setter public String getBizType() { return bizType; } public void setBizType(String bizType) { this.bizType = bizType; } public Long getUserId() { return userId; } public void setUserId(Long userId) { this.userId = userId; } public Long getOrderId() { return orderId; } public void setOrderId(Long orderId) { this.orderId = orderId; } public BigDecimal getAmount() { return amount; } public void setAmount(BigDecimal amount) { this.amount = amount; } public boolean isPass() { return pass; } public void setPass(boolean pass) { this.pass = pass; } public String getMsg() { return msg; } public void setMsg(String msg) { this.msg = msg; } }4.3 抽象处理器(增加所属业务标识、执行顺序)
public abstract class AbstractBizHandler { // 下一个处理器 protected AbstractBizHandler nextHandler; public void setNextHandler(AbstractBizHandler nextHandler) { this.nextHandler = nextHandler; } // 处理逻辑 public abstract void handle(BizRequest request); // 存储业务类型,集合的方式是为了做到复用,也就是说一个处理器可能被多个业务使用到 public abstract Set<String> supportBizTypeSet(); // 执行顺序,数字越小越先执行 public abstract int getOrder(); }4.4 具体处理器
通用参数校验(下单、退款都需要)
import org.springframework.stereotype.Component; import java.util.HashSet; import java.util.Set; @Component public class ParamCheckHandler extends AbstractBizHandler { @Override public void handle(BizRequest request) { if (request.getUserId() == null || request.getAmount() == null) { request.setPass(false); request.setMsg("参数不能为空"); return; } if (nextHandler != null) { nextHandler.handle(request); } } @Override public Set<String> supportBizTypeSet() { Set<String> set = new HashSet<>(); set.add(BizConstant.BIZ_ORDER); set.add(BizConstant.BIZ_REFUND); return set; } @Override public int getOrder() { return 1; } }下单专属风控校验
import org.springframework.stereotype.Component; import java.util.HashSet; import java.util.Set; @Component public class OrderRiskHandler extends AbstractBizHandler { @Override public void handle(BizRequest request) { if (request.getUserId().equals(999L)) { request.setPass(false); request.setMsg("下单风控拦截"); return; } if (nextHandler != null) { nextHandler.handle(request); } } @Override public Set<String> supportBizTypeSet() { Set<String> set = new HashSet<>(); set.add(BizConstant.BIZ_ORDER); return set; } @Override public int getOrder() { return 2; } }退款专属余额校验
import org.springframework.stereotype.Component; import java.math.BigDecimal; import java.util.HashSet; import java.util.Set; @Component public class RefundBalanceHandler extends AbstractBizHandler { @Override public void handle(BizRequest request) { if (request.getAmount().compareTo(new BigDecimal("1000")) > 0) { request.setPass(false); request.setMsg("退款金额超限"); return; } if (nextHandler != null) { nextHandler.handle(request); } } @Override public Set<String> supportBizTypeSet() { Set<String> set = new HashSet<>(); set.add(BizConstant.BIZ_REFUND); return set; } @Override public int getOrder() { return 2; } }4.5 链路工厂:按业务类型分组、自动构建对应链条
import org.springframework.stereotype.Component; import javax.annotation.Resource; import java.util.Comparator; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.stream.Collectors; @Component public class BizChainFactory { @Resource private List<AbstractBizHandler> allHandlerList; // 缓存各业务链路头节点 private final Map<String, AbstractBizHandler> bizChainCache = new HashMap<>(); // 根据业务类型获取对应链路头部处理器 public AbstractBizHandler getChainByBizType(String bizType) { // 缓存命中直接返回 if (bizChainCache.containsKey(bizType)) { return bizChainCache.get(bizType); } // 使用HashSet精准匹配,无模糊匹配问题,查询效率O(1) List<AbstractBizHandler> matchHandlers = allHandlerList.stream() .filter(h -> h.supportBizTypeSet().contains(bizType)) // 按顺序升序 .sorted(Comparator.comparingInt(AbstractBizHandler::getOrder)) .collect(Collectors.toList()); if (matchHandlers.isEmpty()) { throw new RuntimeException("无对应业务处理链路:" + bizType); } // 串联处理器 for (int i = 0; i < matchHandlers.size() - 1; i++) { AbstractBizHandler curr = matchHandlers.get(i); AbstractBizHandler next = matchHandlers.get(i + 1); curr.setNextHandler(next); } AbstractBizHandler head = matchHandlers.get(0); bizChainCache.put(bizType, head); return head; } }4.6 业务 Service 调用
import org.springframework.stereotype.Service; import javax.annotation.Resource; @Service public class BizOrderService { @Resource private BizChainFactory chainFactory; public String doBiz(BizRequest request) { // 传入业务标识,自动匹配专属链路 AbstractBizHandler chainHead = chainFactory.getChainByBizType(request.getBizType()); chainHead.handle(request); if (!request.isPass()) { return "处理失败:" + request.getMsg(); } return "业务处理完成"; } }4.7 测试示例
public class TestMain { public static void main(String[] args) { BizOrderService service = new BizOrderService(); // 下单链路 BizRequest orderReq = new BizRequest(); orderReq.setBizType(BizConstant.BIZ_ORDER); orderReq.setUserId(1001L); orderReq.setAmount(new BigDecimal("99")); System.out.println(service.doBiz(orderReq)); // 退款链路 BizRequest refundReq = new BizRequest(); refundReq.setBizType(BizConstant.BIZ_REFUND); refundReq.setUserId(1001L); refundReq.setAmount(new BigDecimal("500")); System.out.println(service.doBiz(refundReq)); } }5. 责任链模式优缺点
5.1 优点
- 分层解耦,单一职责:每一个处理器只处理一件事,代码清晰易维护;
- 完美符合开闭原则:新增校验 / 处理节点仅新增 Handler,原有代码零改动;
- 灵活调整链路:修改执行顺序、临时移除节点仅改动链条组装逻辑;
- 请求与处理分离:发起请求方不用关心内部有多少处理步骤;
- 支持中断流程:任意节点校验失败可直接终止链条,减少无效执行。
5.2 缺点
- 链条过长时性能损耗:串行逐层执行,链路节点极多会拉长接口 RT;
- 排查链路问题困难:多层流转,异常需要逐层打印日志定位;
- 存在链条断裂风险:处理器忘记调用 nextHandler.handle () 会导致后续逻辑不执行;
- 会产生大量处理器类:每一层逻辑对应一个类,项目类文件增多。
6. 工程常见痛点与解决方案
6.1 忘记调用下一级处理器,链条断裂
解决方案:抽象层封装统一流转逻辑,模板方法固定流程,子类只重写业务逻辑,统一后置放行。
6.2 链条节点过多,串行执行RT过高
解决方案:无依赖的独立节点抽取异步线程池并行执行;强依赖节点保留串行。
6.3 手动组装链条,维护麻烦
解决方案:结合 Spring 容器自动注入,通过排序注解自动构建链路,无需硬编码串联。
6.4 链路日志缺失,线上难以定位拦截节点
解决方案:抽象处理器统一打印进入、退出日志,记录当前处理器名称与拦截信息。
6.5 循环引用、死链问题
解决方案:统一在工厂构建链条,禁止运行时动态修改 nextHandler,避免循环持有。
7. 高频面试:易混模式核心区分
7.1 责任链 vs 装饰器模式
责任链:多条独立处理节点串行流转请求,可中途终止链条,各节点职责独立无包装关系;
装饰器:层层包装同一对象,持续叠加功能,无法中途终止,必须完整执行内层逻辑。
7.2 责任链 vs 模板方法模式
责任链:多个独立类分层处理,可灵活增删、调整顺序;
模板方法:单一父类固定完整流程骨架,步骤不可随意移除、顺序固化。
7.3 责任链 vs 观察者模式
责任链:单个请求串行依次处理,可中断,流程有先后顺序;
观察者:一对多并行通知,无先后强制顺序,无法中途终止其他订阅者执行。
7.4 责任链 vs 策略模式
责任链:多层依次校验处理,链式流转;
策略模式:单一算法动态切换,一次仅执行一套逻辑,无分层流转。
8. 主流框架落地(面试高频)
8.1 Servlet Filter 过滤器
标准责任链实现,FilterChain 串联多个 Filter,doFilter 放行进入下一层,可拦截终止请求。
8.2 SpringMVC Interceptor 拦截器
多个拦截器有序执行,preHandle 返回 false 直接终止链路,典型责任链思想。
8.3 Mybatis Interceptor 插件链
多个拦截器串联处理 SQL 执行,依次流转,可拦截修改 SQL 逻辑。
8.4 Spring Security 安全过滤器链
大量校验过滤器串联组成责任链,权限、登录、跨域分层校验,可自由新增过滤器。
9. 核心总结
责任链模式核心本质:拆分分层处理逻辑,串联成可灵活调整的执行链路,请求逐层流转,分层拦截解耦,是接口校验、审批流程、框架拦截体系最常用的行为型模式。
工程落地选型规范:
- 多层连续校验、需要灵活增删处理节点,优先使用责任链;
- 小型项目可手写组装链条,中大型 Spring 项目采用容器自动装配排序;
- 每个处理器严格遵循单一职责,不混合多层校验逻辑;
- 控制链条长度,节点过多拆分异步执行,避免接口 RT 升高;
- 区分装饰器、模板方法,不要混淆链式流转与功能增强、固定流程的设计目标。
