云计算百科
云计算领域专业知识百科平台

39-数据库配置拦截器链

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发生前注册表已就绪。

当前注册的两个实现:

id类功能
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
applyreview review log log,cmsPublish
applyreview 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>

BPMN监听器数据库拦截器
挂载方式 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篇通知渠道。

赞(0)
未经允许不得转载:网硕互联帮助中心 » 39-数据库配置拦截器链
分享到: 更多 (0)

评论 抢沙发

评论前必须登录!