目录
[① 导读卡片](#① 导读卡片)
[② 背景与目标](#② 背景与目标)
[③ 核心概念与原理](#③ 核心概念与原理)
[3.1 责任链模式](#3.1 责任链模式)
[3.2 和策略模式的区别](#3.2 和策略模式的区别)
[④ 完整代码实现](#④ 完整代码实现)
[4.1 实体对象](#4.1 实体对象)
[4.2 抽象 Handler + 链式编排](#4.2 抽象 Handler + 链式编排)
[4.3 具体 Handler 实现](#4.3 具体 Handler 实现)
[4.4 Spring 自动编排链](#4.4 Spring 自动编排链)
[4.5 Service 层------提交报废审批](#4.5 Service 层——提交报废审批)
[4.6 Controller 层](#4.6 Controller 层)
[⑤ 执行流程全景](#⑤ 执行流程全景)
[⑥ 扩展场景](#⑥ 扩展场景)
[6.1 不同资产类别走不同审批链](#6.1 不同资产类别走不同审批链)
[6.2 并行审批(会签)](#6.2 并行审批(会签))
[6.3 动态审批金额(配置化)](#6.3 动态审批金额(配置化))
[⑦ 常见坑与最佳实践](#⑦ 常见坑与最佳实践)
[7.1 易错点清单](#7.1 易错点清单)
[7.2 自测问题](#7.2 自测问题)
[⑧ 总结](#⑧ 总结)
① 导读卡片
🧩 一句话读懂 :用责任链模式替代 if-else 实现报废审批规则,每个审批节点是独立 Handler,通过
@Order和 setNext 编排成链,金额范围自动路由到对应审批人 🎯 适合人群 :Java 后端开发者、涉及审批流 / 工作流设计的工程师、面试进阶选手 📊 难度等级 :★★★★☆(中等偏上,含完整代码) ⏱ 阅读时长 :约 10 分钟 💡 前置知识:Spring Boot、策略模式 / 职责链模式基本概念
② 背景与目标
为什么学?
报废审批需要根据金额范围路由到不同角色------≤5000 组长批,≤50000 经理批,以此类推。
用 if-else 写当然能跑:
java
if (amount <= 5000) { // 组长审批 }
else if (amount <= 50000) { // 经理审批 }
else if (amount <= 200000) { // 总监审批 }
else { // 总经理审批 }
但问题很快会出现:
-
金额区间调整了 → 改代码
-
新增审批角色 → 改代码
-
不同资产类别有不同的审批链 → 条件爆炸
-
以后加「会签」「转审」「加签」等复杂流程 → 根本没法改
责任链模式正好解决这个问题:每个审批节点是独立对象,链式组合,职责单一,扩展时新增一个 Handler 就行。
学完能怎样?
-
理解责任链模式在审批流中的完整用法
-
能写出包含实体、Handler、链式编排、Spring 管理的中等复杂度方案
-
知道怎么处理异常场景(越级、无需审批、并行审批)
-
能扩展到会签、加签、条件分支等复杂流程
③ 核心概念与原理
3.1 责任链模式
请求通过一条 Handler 链,每个 Handler 决定自己能不能处理:
┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐
│ 金额 ≤5k │ → │ ≤50k │ → │ ≤200k │ → │ >200k │
│ 组长审批 │ │ 经理审批 │ │ 总监审批 │ │ 总经理审批 │
└──────────┘ └──────────┘ └──────────┘ └──────────┘
│ │ │ │
└──── 能处理就停,不能就往下传
关键特征:
-
每个节点独立判断:能不能处理自己说了算
-
链式传递:处理不了就交下一个
-
职责单一:组长 Handler 只关心 5000 以内的逻辑
3.2 和策略模式的区别
| 维度 | 策略模式 | 责任链模式 |
|---|---|---|
| 路由方式 | Map 匹配 key 直接定位 | 链上逐节点命中 |
| 节点关系 | 独立,互不知晓 | 知道下一个节点 |
| 适用场景 | 精确匹配(如 1:1 的支付方式路由) | 区间/范围匹配(审批流) |
| 扩展性 | 加策略 + 改工厂 | 加 Handler + 插入链中 |
| 链式编排 | 不支持 | 天然支持 |
④ 完整代码实现
4.1 实体对象
java
/**
* 报废审批请求
*/
@Data
public class ScrapApprovalRequest {
/** 报废单 ID */
private Long scrapId;
/** 报废金额 */
private BigDecimal amount;
/** 资产类别(不同类别可能走不同审批链) */
private String assetCategory;
/** 提交人 */
private String submitterId;
/** 当前处理人角色(推进时设置) */
private String currentRole;
/** 审批意见 */
private String remark;
/** 审批结果 */
private ApprovalResult result;
}
java
/**
* 报废单实体
*/
@Data
@TableName("scrap_order")
public class ScrapOrder {
@TableId
private Long id;
/** 报废单号 */
private String orderNo;
/** 报废金额 */
private BigDecimal amount;
/** 资产编码 */
private String assetCode;
/** 资产类别 */
private String assetCategory;
/** 状态: 0-待提交 1-审批中 2-已通过 3-已驳回 */
private Integer status;
/** 当前审批角色 */
private String currentApproverRole;
/** 当前审批人 ID */
private String currentApproverId;
/** 创建人 */
private String createBy;
private LocalDateTime createTime;
private LocalDateTime updateTime;
}
java
/**
* 审批记录
*/
@Data
@TableName("approval_record")
public class ApprovalRecord {
@TableId
private Long id;
/** 关联报废单 ID */
private Long scrapId;
/** 审批角色 */
private String approverRole;
/** 审批人 */
private String approverId;
/** 审批动作: approve / reject */
private String action;
/** 审批意见 */
private String remark;
/** 审批顺序 */
private Integer sortOrder;
private LocalDateTime createTime;
}
4.2 抽象 Handler + 链式编排
java
/**
* 抽象审批节点
*/
public abstract class ScrapApprovalHandler {
/** 下一个审批节点 */
protected ScrapApprovalHandler next;
/** 设置下一个节点,返回 next 以支持链式调用 */
public ScrapApprovalHandler setNext(ScrapApprovalHandler next) {
this.next = next;
return next;
}
/**
* 模板方法:处理审批请求
*/
public final ApprovalResult handle(ScrapApprovalRequest request) {
// 1. 判断当前节点是否有权限处理
if (canApprove(request)) {
// 2. 执行本节点审批
ApprovalResult result = doApprove(request);
// 3. 如果通过且仍有金额未覆盖完,交给下一个节点
if (result.isApproved() && needContinue(request)) {
if (next != null) {
return next.handle(request);
}
}
// 4. 如果驳回,直接返回,不继续往下传
if (result.isRejected()) {
return result;
}
return result;
}
// 本节点无权限 → 交给下一个
if (next != null) {
return next.handle(request);
}
// 没人能处理(理论上不会发生)
throw new BizException("无匹配的审批节点,金额: " + request.getAmount());
}
/** 是否可以对当前请求进行审批 */
protected abstract boolean canApprove(ScrapApprovalRequest request);
/** 执行审批逻辑 */
protected abstract ApprovalResult doApprove(ScrapApprovalRequest request);
/** 审批通过后是否继续往下传(默认不传,交给更高级别审批时覆盖) */
protected boolean needContinue(ScrapApprovalRequest request) {
return false;
}
/** 获取本节点角色 */
public abstract String getRole();
}
java
/**
* 审批结果
*/
@Data
@AllArgsConstructor
public class ApprovalResult {
private boolean approved;
private boolean rejected;
private String approverRole;
private String message;
public static ApprovalResult approved(String role) {
return new ApprovalResult(true, false, role, role + "审批通过");
}
public static ApprovalResult rejected(String role, String reason) {
return new ApprovalResult(false, true, role, reason);
}
public static ApprovalResult passToNext(String role) {
return new ApprovalResult(true, false, role, role + "审批通过,转交下一级");
}
}
4.3 具体 Handler 实现
java
/**
* 组长审批节点------金额 ≤ 5,000
*/
@Component
@Order(1)
public class LeaderApprovalHandler extends ScrapApprovalHandler {
@Override
public String getRole() {
return "组长";
}
@Override
protected boolean canApprove(ScrapApprovalRequest request) {
return request.getAmount().compareTo(new BigDecimal("5000")) <= 0;
}
@Override
protected ApprovalResult doApprove(ScrapApprovalRequest request) {
// 实际场景:推送给对应的组长,等待组长审批
// 这里简化直接通过
log.info("组长审批通过,报废单: {}, 金额: {}",
request.getScrapId(), request.getAmount());
// 如需记录审批记录
// approvalRecordService.save(scrapId, "组长", "approve");
return ApprovalResult.approved("组长");
}
}
/**
* 经理审批节点------金额 5,001 ~ 50,000
* 注意:组长审核完,还需要经理审核
*/
@Component
@Order(2)
public class ManagerApprovalHandler extends ScrapApprovalHandler {
@Override
public String getRole() {
return "经理";
}
@Override
protected boolean canApprove(ScrapApprovalRequest request) {
return request.getAmount().compareTo(new BigDecimal("50000")) <= 0;
}
@Override
protected ApprovalResult doApprove(ScrapApprovalRequest request) {
log.info("经理审批通过,报废单: {}, 金额: {}",
request.getScrapId(), request.getAmount());
return ApprovalResult.approved("经理");
}
/** 金额超过 5000 的,经理审批完还要交给总监 */
@Override
protected boolean needContinue(ScrapApprovalRequest request) {
return request.getAmount().compareTo(new BigDecimal("5000")) > 0;
}
}
/**
* 总监审批节点------金额 50,001 ~ 200,000
*/
@Component
@Order(3)
public class DirectorApprovalHandler extends ScrapApprovalHandler {
@Override
public String getRole() {
return "总监";
}
@Override
protected boolean canApprove(ScrapApprovalRequest request) {
return request.getAmount().compareTo(new BigDecimal("200000")) <= 0;
}
@Override
protected ApprovalResult doApprove(ScrapApprovalRequest request) {
log.info("总监审批通过,报废单: {}, 金额: {}",
request.getScrapId(), request.getAmount());
return ApprovalResult.approved("总监");
}
/** 金额超过 50000 的转交总经理 */
@Override
protected boolean needContinue(ScrapApprovalRequest request) {
return request.getAmount().compareTo(new BigDecimal("50000")) > 0;
}
}
/**
* 总经理审批节点------金额 > 200,000
* 最后一个节点,不需要 needContinue
*/
@Component
@Order(4)
public class GmApprovalHandler extends ScrapApprovalHandler {
@Override
public String getRole() {
return "总经理";
}
@Override
protected boolean canApprove(ScrapApprovalRequest request) {
return true; // 兜底:只要到我这,就能批
}
@Override
protected ApprovalResult doApprove(ScrapApprovalRequest request) {
log.info("总经理审批通过,报废单: {}, 金额: {}",
request.getScrapId(), request.getAmount());
return ApprovalResult.approved("总经理");
}
}
4.4 Spring 自动编排链
关键设计:用 @Order 注解 + Spring 自动注入,Handler 自己不用关心链式关系。
java
/**
* 审批链工厂------自动编排 Handler 链
*/
@Component
public class ApprovalChainFactory {
/** Spring 自动注入所有 ScrapApprovalHandler,按 @Order 排序 */
@Autowired
private List<ScrapApprovalHandler> handlers;
/** 链头 */
private ScrapApprovalHandler chainHead;
@PostConstruct
public void initChain() {
if (handlers == null || handlers.isEmpty()) {
throw new IllegalStateException("未配置任何审批节点");
}
// 按 @Order 排序(Spring 已排序,再手动确保)
handlers.sort(AnnotationAwareOrderComparator.INSTANCE);
// 链式编排:Handler1 → Handler2 → Handler3 → Handler4
chainHead = handlers.get(0);
ScrapApprovalHandler current = chainHead;
for (int i = 1; i < handlers.size(); i++) {
current.setNext(handlers.get(i));
current = handlers.get(i);
}
log.info("审批链初始化完成,共 {} 个节点", handlers.size());
}
/**
* 处理审批请求
*/
public ApprovalResult process(ScrapApprovalRequest request) {
return chainHead.handle(request);
}
/**
* 获取当前链的所有节点角色(用于前端展示审批流程)
*/
public List<String> getChainRoles() {
return handlers.stream()
.map(ScrapApprovalHandler::getRole)
.collect(Collectors.toList());
}
}
4.5 Service 层------提交报废审批
java
/**
* 报废审批 Service
*/
@Service
@RequiredArgsConstructor
public class ScrapApprovalService {
private final ApprovalChainFactory chainFactory;
private final ScrapOrderMapper scrapOrderMapper;
private final ApprovalRecordMapper approvalRecordMapper;
/**
* 提交报废审批
*/
@Transactional(rollbackFor = Exception.class)
public void submitScrapApproval(ScrapApprovalRequest request) {
// 1. 校验报废单
ScrapOrder order = scrapOrderMapper.selectById(request.getScrapId());
if (order == null) {
throw new BizException("报废单不存在");
}
if (order.getStatus() != 0) {
throw new BizException("报废单状态不允许提交审批");
}
// 2. 注入金额
request.setAmount(order.getAmount());
// 3. 走审批链
ApprovalResult result = chainFactory.process(request);
// 4. 更新报废单状态
order.setStatus(result.isApproved() ? 2 : 3); // 2-通过 3-驳回
order.setCurrentApproverRole(result.getApproverRole());
order.setUpdateTime(LocalDateTime.now());
scrapOrderMapper.updateById(order);
// 5. 保存审批记录
ApprovalRecord record = new ApprovalRecord();
record.setScrapId(order.getId());
record.setApproverRole(result.getApproverRole());
record.setAction(result.isApproved() ? "approve" : "reject");
record.setRemark(request.getRemark());
record.setCreateTime(LocalDateTime.now());
approvalRecordMapper.insert(record);
}
}
4.6 Controller 层
java
@RestController
@RequestMapping("/scrap/approval")
@RequiredArgsConstructor
public class ScrapApprovalController {
private final ScrapApprovalService scrapApprovalService;
@PostMapping("/submit")
public R<Void> submitApproval(@RequestBody ScrapApprovalRequest request) {
scrapApprovalService.submitScrapApproval(request);
return R.ok("审批提交成功");
}
@GetMapping("/chain")
public R<List<String>> getApprovalChain() {
// 前端用来展示:当前报废单需要经过哪些角色审批
return R.ok(chainFactory.getChainRoles());
}
}
⑤ 执行流程全景
以金额 80,000 的报废单为例,完整的执行路径:
请求: amount=80000, scrapId=1001
│
▼
LeaderApprovalHandler.canApprove(80000)
├─ 80000 <= 5000? → false
└─ → next.handle()
│
▼
ManagerApprovalHandler.canApprove(80000)
├─ 80000 <= 50000? → false
└─ → next.handle()
│
▼
DirectorApprovalHandler.canApprove(80000)
├─ 80000 <= 200000? → true ✅
├─ doApprove → 总监审批通过
├─ needContinue(80000)?
│ └─ 80000 > 50000? → true → next.handle()
│ │
│ ▼
│ GmApprovalHandler.canApprove(80000)
│ ├─ true(兜底)
│ ├─ doApprove → 总经理审批通过
│ └─ needContinue → false(末节点)
│ │
│ ▼
└─ 返回最终结果: 已通过
各金额范围的审批路径:
| 金额 | 审批路径 |
|---|---|
| ¥3,000 | 组长 ✅ → 结束 |
| ¥20,000 | 组长 ✅ → 经理 ✅ → 结束 |
| ¥80,000 | 组长 ✅ → 经理 ✅ → 总监 ✅ → 总经理 ✅ → 结束 |
| ¥300,000 | 组长 ✅ → 经理 ✅ → 总监 ✅ → 总经理 ✅ → 结束 |
⑥ 扩展场景
6.1 不同资产类别走不同审批链
java
@Component
public class ApprovalChainFactory {
/** key=assetCategory, value=审批链头 */
private Map<String, ScrapApprovalHandler> chainMap = new HashMap<>();
@PostConstruct
public void initChains() {
// 生产设备:组长 → 经理 → 总监
chainMap.put("production", buildChain(
leader, manager, director
));
// 办公设备:组长 → 经理(简化流程)
chainMap.put("office", buildChain(
leader, manager
));
// IT 设备:组长 → IT 主管 → 经理
chainMap.put("it", buildChain(
leader, itManager, manager
));
}
public ApprovalResult process(ScrapApprovalRequest request) {
ScrapApprovalHandler chain = chainMap.get(request.getAssetCategory());
if (chain == null) {
chain = defaultChain; // 默认链
}
return chain.handle(request);
}
}
6.2 并行审批(会签)
当需要"总监和财务总监同时审批"时,引入并行节点:
java
/**
* 并行审批节点------所有子节点都通过才算通过
*/
public class ParallelApprovalHandler extends ScrapApprovalHandler {
private List<ScrapApprovalHandler> parallelHandlers;
@Override
protected ApprovalResult doApprove(ScrapApprovalRequest request) {
for (ScrapApprovalHandler handler : parallelHandlers) {
ApprovalResult result = handler.handle(request);
if (result.isRejected()) {
return result; // 一个驳回就是驳回
}
}
return ApprovalResult.approved("会签");
}
}
6.3 动态审批金额(配置化)
把金额阈值放到数据库或配置中心,不用改代码:
java
// 数据库表 approval_rule
// id | role | min_amount | max_amount | sort_order
@Component
public class DynamicApprovalHandler extends ScrapApprovalHandler {
@Autowired
private ApprovalRuleMapper ruleMapper;
@Override
protected boolean canApprove(ScrapApprovalRequest request) {
ApprovalRule rule = ruleMapper.selectByRole(getRole());
return request.getAmount().compareTo(rule.getMinAmount()) >= 0
&& request.getAmount().compareTo(rule.getMaxAmount()) <= 0;
}
}
⑦ 常见坑与最佳实践
7.1 易错点清单
| # | 坑点 | 现象 | ✅ 避坑方案 |
|---|---|---|---|
| 1 | Handler 用 @Component 但未加 @Order | 链顺序随机 | 全部加上 @Order 明确顺序 |
| 2 | 金额比较用 BigDecimal 的 equals | 2.0 和 2.00 比较失败 | 用 compareTo() 而不是 equals() |
| 3 | 末节点 canApprove 没兜底 | 超出金额范围抛异常 | 最末节点设 canApprove = true |
| 4 | needContinue 逻辑遗漏 | 金额>5000的只走了组长/经理就结束了 | 每个节点检查有没有更高审批层级 |
| 5 | 事务范围覆盖整个链 | 审批链长时连接长时间占用 | 审批链只做逻辑判断,事务放在 Service 层 |
7.2 自测问题
Q:你们审批流怎么设计的?
用责任链模式,每个审批节点是一个独立的 Handler 类。Handler 的判断逻辑是金额区间------比如组长只能批 5000 以内的,经理批 50000 以内。超出自己范围就交链上下一个 Handler。所有 Handler 通过 Spring 自动注入 + @Order 排序,由工厂类@PostConstruct编排成链,Service 层调chainHead.handle()即可。
Q:不用 if-else 而用责任链,好处是什么?
主要有三点。一是职责单一,每个 Handler 只关心自己的逻辑,组长 Handler 不会看到经理的代码。二是扩展方便,新加一个审批角色只需新增一个 Handler 类,插入到链中就行,不用改任何已有代码。三是不同资产类别可以自由组合不同的链,生产设备走 4 级审批,办公设备走 2 级,通过工厂配置就行。
Q:金额超过 5000 需要组长+经理双重审批,这个在责任链里怎么实现的?
Handler 有一个needContinue()方法。比如组长节点:金额 ≤5000 就自己批完并结束;金额 >5000 自己先批,但返回passToNext把请求继续往下传。这样金额 30000 的单子就会依次经过组长→经理,各自做审批记录,最终谁驳回就终止。
Q:如果以后规则复杂了,责任链还够用吗?
纯金额区间 + 角色是标配场景,责任链足够了。如果后面加了维度(部门归属、资产类别、历史报废次数等)组合成复杂表达式,可以升级成规则引擎(如 Drools、QLExpress),把规则存到数据库,Handler 的 canApprove 里调用规则引擎判断。
⑧ 总结
核心要点速览
| 维度 | 要点 | 一句话记住 |
|---|---|---|
| 核心模式 | 责任链 | 每个 Handler 独立判断,能批就批,不能就传 |
| 编排方式 | @Order + Spring 注入 | 不改代码调顺序 |
| 金额比较 | compareTo() |
不是 equals,是 compareTo |
| 多维度 | Map 维护多链 | 不同资产走不同链 |
| 兜底 | 末节点 canApprove=true | 不漏单 |
自检清单
- 我能写出责任链的抽象 Handler 基类
- 我知道 needContinue 的用法和什么时候需要它
- 我能用 Spring @Order + List 注入自动编排链
- 我知道怎么扩展到不同资产类别走不同审批链
- 我知道金额比较要用 compareTo 而不是 equals
目录结构
com.example.scrap.approval
├── entity
│ ├── ScrapOrder.java # 报废单实体
│ ├── ApprovalRecord.java # 审批记录
│ └── ScrapApprovalRequest.java # 审批请求 DTO
├── handler
│ ├── ScrapApprovalHandler.java # 抽象 Handler
│ ├── LeaderApprovalHandler.java # 组长
│ ├── ManagerApprovalHandler.java# 经理
│ ├── DirectorApprovalHandler.java# 总监
│ └── GmApprovalHandler.java # 总经理
├── factory
│ └── ApprovalChainFactory.java # 链工厂
├── service
│ └── ScrapApprovalService.java # 审批 Service
└── controller
└── ScrapApprovalController.java