应继承 runtimeexception 设计自定义异常,如 approvaldeniedexception,通过 reasoncode 等字段携带拒绝原因、审批人等上下文,并在服务层捕获后显式调用流程引擎 api 跳转至拒绝结束节点,避免穿透引发重试。

在 Java 工作流审批场景中,为“拒绝”操作设计自定义异常,核心目标不是把拒绝当成错误,而是**明确表达业务语义上的非失败性中断**——即:流程被主动终止,但系统运行正常。因此,这类异常通常应继承 RuntimeException,不强制调用方捕获,同时通过命名和文档清晰传达其用途。
命名要体现业务意图,避免歧义
不要叫 RejectException 或 ApprovalRejectedException 这类模糊名称,容易让人误以为是系统出错。推荐使用带动词+上下文的命名:
-
WorkflowRejectionSignal—— 强调这是流程中的一个“信号”,非错误 -
ApprovalDeniedException—— “Denied” 比 “Rejected” 更贴近权限/策略语境(如规则不满足、权限不足) -
BusinessRuleNotMetException—— 若拒绝由具体业务规则触发(如金额超限、申请人岗位不符),可更精准
构造函数支持多种业务信息携带
拒绝常需附带原因、审批人、节点ID、规则ID等,方便日志追踪和前端展示。建议提供多参构造器,并内置一个标准字段(如 reasonCode)用于分类处理:
public class ApprovalDeniedException extends RuntimeException {
private final String reasonCode;
private final String approverId;
private final String nodeId;
public ApprovalDeniedException(String message, String reasonCode) {
super(message);
this.reasonCode = reasonCode;
this.approverId = null;
this.nodeId = null;
}
public ApprovalDeniedException(String message, String reasonCode,
String approverId, String nodeId) {
super(message);
this.reasonCode = reasonCode;
this.approverId = approverId;
this.nodeId = nodeId;
}
// getter 省略
}
配合流程引擎做语义化处理
如果使用 Flowable / Camunda 等引擎,不要让异常直接穿透到引擎底层(否则可能触发任务重试或进入错误事件)。应在服务层捕获该异常,并显式调用引擎 API 跳转到“已拒绝”结束节点:
- 抛出自定义异常前,先记录审计日志(含 reasonCode 和上下文)
- 在 Service 方法中 try-catch 该异常,然后调用
runtimeService.createProcessInstanceModification(...).startBeforeActivity("rejectEnd").execute() - 避免在监听器(ExecutionListener)中抛出——监听器无权决定流程走向,应只负责副作用(如发消息)
前端与日志友好:统一 reasonCode + 可读消息
后端抛出异常时,message 应面向开发(如 "Credit limit exceeded for applicant: ABC123"),而 reasonCode(如 CREDIT_LIMIT_EXCEEDED)供前端映射成用户语言(如“申请人授信额度不足”)。日志框架(如 Logback)可配置 MDC 插入 reasonCode 和 processInstanceId,便于问题归因。
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











