39-InterceptorChainService:数据库配置的拦截器链
哪个流程的哪个节点挂什么拦截器——不写在代码里、不写在BPMN里,写在TASK_INTERCEPTOR_CONFIG表里。68行的InterceptorChainService用ApplicationContextAware收集所有拦截器Bean,按“流程key+节点key”查表执行。这是已发布《数据库配拦截器,不用改BPMN》的实现篇。
文章目录
- 39-InterceptorChainService:数据库配置的拦截器链
-
- 一、拦截器接口:两个方法四个参数
- 二、启动期:ApplicationContextAware收集全部实现
- 三、运行期:查表执行
- 四、调用侧:completeTask的两处埋点
- 五、拦截器与BPMN监听器的对比
- 六、before里能做什么:variables的妙用
源码:browise-workflow/src/main/java/com/browise/workflow/service/InterceptorChainService.java(68行) 接口:interceptor/TaskInterceptor.java 实现:LogTaskInterceptor.java、CmsPublishInterceptor.java 配置表:TASK_INTERCEPTOR_CONFIG
一、拦截器接口:两个方法四个参数
public interface TaskInterceptor {
String getId(); // 拦截器标识——配置表里引用的名字
void before(String taskId, String processInstanceId,
String taskDefKey, Map<String, Object> variables);
void after(String taskId, String processInstanceId,
String taskDefKey, Map<String, Object> variables);
}
一个接口同时承担before和after——不拆成两个接口(BeforeInterceptor/AfterInterceptor)——因为业务拦截器往往前后都要做(before锁数据/after写结果),拆开导致一个业务逻辑注册两个Bean。
getId()是自注册的钥匙——Bean的标识由实现自己声明,不依赖Spring的Bean名(@Component(“log”)的bean名与业务id可能不一致—— getId返回"cmsPublish"这样的业务语义名)。
二、启动期:ApplicationContextAware收集全部实现
@Service
public class InterceptorChainService implements ApplicationContextAware {
private final Map<String, TaskInterceptor> interceptorBeans = new HashMap<>();
@Override
public void setApplicationContext(ApplicationContext ctx) {
Map<String, TaskInterceptor> beans = ctx.getBeansOfType(TaskInterceptor.class);
for (TaskInterceptor i : beans.values()) {
interceptorBeans.put(i.getId(), i); // id → Bean实例
}
log.info("Loaded {} task interceptors: {}", ...);
}
}
getBeansOfType(TaskInterceptor.class)——容器里所有实现此接口的Bean全捞出来——新拦截器=实现接口+@Component,零配置自动注册。这就是SPI的Spring版(notify模块用META-INF/services的Java SPI——两种机制并存,各有场景)。
收集时机是setApplicationContext回调——容器刷新完成前——比@PostConstruct更早,保证任何completeTask发生前注册表已就绪。
当前注册的两个实现:
| log | LogTaskInterceptor | 任务操作日志 |
| cmsPublish | CmsPublishInterceptor | CMS内容审核通过后发布 |
三、运行期:查表执行
public void executeBefore(String processKey, String taskDefKey,
String taskId, String processInstanceId, Map<String, Object> variables) {
// ①查配置:这个流程的这个节点配了什么前置拦截器
TaskInterceptorConfig config =
interceptorMapper.selectByKeyAndTaskDefKey(processKey, taskDefKey);
if (config == null || config.getBeforeId() == null) return; // 没配→直接返回
// ②拆id串:before_id = "log,cmsPublish"
String[] ids = config.getBeforeId().split(",");
// ③按id取Bean逐个执行
for (String id : ids) {
TaskInterceptor interceptor = interceptorBeans.get(id.trim());
if (interceptor != null) {
interceptor.before(taskId, processInstanceId, taskDefKey, variables);
}
}
}
配置表结构(推TASK_INTERCEPTOR_CONFIG):
PROCESS_KEY TASK_DEF_KEY BEFORE_ID AFTER_ID
apply–review review log log,cmsPublish
apply–review approve log log
逗号分隔的id串——一行配置挂多个拦截器,按配置顺序执行(split保序)。加新拦截器=改配置表一行——不用改BPMN重新部署、不用改Java代码。
id查不到Bean时的null跳过——配置表写了"xxx"但没这个实现(拼错/模块没引入)——不抛异常静默跳过+debug日志。拦截器缺失不该阻断审批——降级运行(该记的日志没记是问题,但审批业务不能停)。
四、调用侧:completeTask的两处埋点
第36篇的completeTask:
// complete前
interceptorChainService.executeBefore(procDefKey, taskDefKey, taskId, procInstId, variables);
taskService.complete(taskId, variables);
// complete后
interceptorChainService.executeAfter(procDefKey, taskDefKey, taskId, procInstId, variables);
两次查表——before和after各查一次selectByKeyAndTaskDefKey。可以优化为查一次传config对象——但配置表可能在before执行期间被改(极端场景),两次查各自拿到当下最新——顺带保持方法无状态。
每次complete两次SQL——审批是低频操作(人点按钮),这个查询开销可忽略。如果流程引擎被程序化高频调用(批量审批),加一层“processKey+taskDefKey→config”的ConcurrentHashMap缓存+管理界面保存时失效即可——当前规模不做。
五、拦截器与BPMN监听器的对比
Flowable原生的扩展点——BPMN里配监听器:
<userTask id="review">
<extensionElements>
<flowable:taskListener event="create"
class="com.browise.workflow.MyListener" />
</extensionElements>
</userTask>
| 挂载方式 | XML里写类名 | 配置表里写id |
| 变更成本 | 改BPMN+重新部署 | 改表即刻生效 |
| 顺序控制 | 多listener执行顺序不直观 | id串顺序明确 |
| 可发现性 | 要看每个节点的XML | 一张表看全部 |
| 引擎耦合 | 实现Flowable的接口 | 纯自己的接口 |
browise选数据库配置的核心原因——变更成本。政务系统的流程调整要走审批——改BPMN重新部署是“系统变更”,改配置表是“参数调整”——运维等级完全不同。这就是已发布《数据库配拦截器》讲的“不改流程定义”的落地。
六、before里能做什么:variables的妙用
variables参数是引用传递——before拦截器可以往里塞变量:
public class MyInterceptor implements TaskInterceptor {
public void before(taskId, procInstId, taskDefKey, Map<String, Object> variables) {
// 从业务库查数据塞进流程变量
String manager = userService.findManager(variables.get("applyUser"));
variables.put("nextAssignee", manager); // complete时带给引擎
}
}
completeTask的variables最终传给taskService.complete——拦截器成为“动态计算流程参数”的钩子——比如按申请金额动态决定下一审批层级(金额>10万加签分管领导)。业务规则进了流程变量,BPMN网关按变量路由——规则与流程图解耦。
✅ 亮点:ApplicationContextAware的getBeansOfType自注册、getId业务语义名与Bean名分离、逗号id串的有序执行、id查不到静默降级不阻断审批、与BPMN监听器的六维对比(变更成本是核心)、before塞variables实现动态路由。与已发布拦截器文章闭环。扩展方向:第36篇completeTask、第35篇配置分层、第56篇通知渠道。
网硕互联帮助中心

评论前必须登录!
注册