Flowable 自定义监听事件与触发
Flowable 内置事件已经覆盖了引擎运行的大部分场景,但业务上总有一些"引擎不知道"的语义需要对外广播。典型例子是审批流的驳回与改派:驳回本质上是删除流程实例加记录评论,改派只是修改任务办理人,引擎只会派发 PROCESS_CANCELLED、ENTITY_UPDATED 这类通用事件,无法表达"这是一次业务驳回"或"任务被重新指派"。这时就需要自定义事件。
自定义事件的关键在于两点:
- 自定义事件类型:实现
FlowableEventType接口,声明业务事件名称; - 自定义事件对象:实现
FlowableEvent(或其子接口),携带业务数据; - 主动触发:通过
RuntimeService#dispatchEvent把事件派发到引擎事件分发器,注册了该类型的事件监听器即可收到通知。
第一步:定义自定义事件类型
事件类型本质上是 FlowableEventType 的一个实现。最简单的做法是定义一个实现该接口的枚举。darkblue 项目中的 CustomTaskEventTypes 定义了两个任务级自定义事件:
public enum CustomTaskEventTypes implements FlowableEventType {
TASK_CHANGE_ASSIGNED,
TASK_REJECT
}
TASK_CHANGE_ASSIGNED:任务被重新指派;TASK_REJECT:任务被驳回。
枚举天然是单例、可序列化,且 name() 即事件名,与内置枚举 FlowableEngineEventType 的用法完全一致。该枚举还提供了与内置枚举相同的 getTypesFromString 工具方法,支持从逗号分隔字符串解析事件类型:
public static FlowableEventType[] getTypesFromString(String string) {
List<FlowableEventType> result = new ArrayList<>();
if (string != null && !string.isEmpty()) {
String[] split = StringUtils.split(string, ",");
for (String typeName : split) {
boolean found = false;
for (CustomTaskEventTypes type : values()) {
if (typeName.equals(type.name())) {
result.add(type);
found = true;
break;
}
}
if (!found) {
throw new FlowableIllegalArgumentException("Invalid event-type: " + typeName);
}
}
}
return result.toArray(EMPTY_ARRAY);
}
第二步:定义自定义事件对象
事件对象需要实现 FlowableEvent 的某个子接口。若希望监听器能像处理引擎事件一样拿到流程上下文(流程实例 ID、执行实例 ID、scope 等),应实现 FlowableEngineEntityEvent 或 FlowableEntityEvent。
darkblue 项目中的 CustomTaskEntityEvent 实现 FlowableEngineEntityEvent,把任务实体作为事件的 entity,并复用了 TaskEntity 自带的流程上下文信息:
@Getter
public class CustomTaskEntityEvent implements FlowableEngineEntityEvent {
private final TaskEntity taskEntity;
private final FlowableEventType type;
public CustomTaskEntityEvent(TaskEntity taskEntity, FlowableEventType type) {
this.taskEntity = taskEntity;
this.type = type;
}
@Override
public String getExecutionId() {
return this.taskEntity.getExecutionId();
}
@Override
public String getProcessInstanceId() {
return this.taskEntity.getProcessInstanceId();
}
@Override
public String getProcessDefinitionId() {
return this.taskEntity.getProcessDefinitionId();
}
@Override
public String getScopeType() {
return this.taskEntity.getScopeType();
}
@Override
public String getScopeId() {
return this.taskEntity.getScopeId();
}
@Override
public String getSubScopeId() {
return this.taskEntity.getSubScopeId();
}
@Override
public String getScopeDefinitionId() {
return this.taskEntity.getScopeDefinitionId();
}
@Override
public Object getEntity() {
return this.taskEntity;
}
}
要点:
- 通过构造器同时传入任务实体与事件类型,
getType()返回构造时指定的事件类型; - 流程上下文方法全部委托给
TaskEntity,监听器拿到事件后可直接使用这些信息,无需再查库; - 监听器可通过
getEntity()直接获取任务实体。
第三步:触发自定义事件
触发自定义事件只需调用 RuntimeService#dispatchEvent(FlowableEvent)。引擎会把它当作普通事件交给 FlowableEventDispatcher 分发,与内置事件的派发路径完全一致。
darkblue 项目在 TaskRejectServiceImpl#reject 中触发 TASK_REJECT 事件,完整流程如下:
@Service
public class TaskRejectServiceImpl extends AbstractTaskService implements TaskRejectService {
private final RuntimeService runtimeService;
@Override
public void reject(TaskRejectDto rejectDto) {
ServiceAssert.notBlank(rejectDto.getComment(), TaskError.TASK_COMMENT_IS_NOT_BLANK);
Authentication.setAuthenticatedUserId(SecurityUtils.getUserId());
TaskEntity taskEntity = (TaskEntity) this.taskService.createTaskQuery()
.taskId(rejectDto.getTaskId())
.singleResult();
ServiceAssert.notNull(taskEntity, TaskError.TASK_IS_NOT_FOUND);
this.taskService.setAssignee(rejectDto.getTaskId(), SecurityUtils.getUserId());
this.saveComment(taskEntity, rejectDto.getComment());
this.saveAttachment(taskEntity, rejectDto.getAttachmentList());
taskService.setVariableLocal(taskEntity.getId(), CommonVariableNames.STATE, TaskState.REJECT);
this.runtimeService.setVariable(taskEntity.getProcessInstanceId(), CommonVariableNames.STATE, ProcessInstanceState.REJECT);
// 删除流程实例
ProcessInstance processInstance = runtimeService.createProcessInstanceQuery()
.processInstanceId(taskEntity.getProcessInstanceId()).singleResult();
if (processInstance != null) {
runtimeService.deleteProcessInstance(taskEntity.getProcessInstanceId(), rejectDto.getComment());
}
Authentication.setAuthenticatedUserId(null);
// 触发自定义的任务审批拒绝事件
FlowableEngineEntityEvent event = new CustomTaskEntityEvent(taskEntity, CustomTaskEventTypes.TASK_REJECT);
this.runtimeService.dispatchEvent(event);
}
}
触发环节的三个要点:
- 在业务命令完成后触发:先完成变量设置、流程实例删除等引擎操作,再派发事件,保证监听器看到的是最终状态;
- 通过
dispatchEvent派发:dispatchEvent走的就是引擎事件分发器,因此监听器无需特殊处理即可收到事件; - 事件类型区分:同一个自定义事件对象可以携带不同的事件类型,一个事件类服务多个业务语义。
第四步:监听自定义事件
监听器编写方式与内置事件完全一致:继承监听器基类,在 getTypes() 中返回自定义事件类型,在业务处理方法中强转并处理。darkblue 项目的 TaskRejectedEventListener:
@Slf4j
@Service
public class TaskRejectedEventListener extends AbstractFlowableEngineEventListener<CustomTaskEntityEvent> {
private final ApplicationEventPublisher applicationEventPublisher;
private final MessageSender messageSender;
@Override
protected void onEngineEvent(CustomTaskEntityEvent engineEvent) {
TaskEntity taskEntity = engineEvent.getTaskEntity();
log.info("触发任务驳回事件,流程实例ID:{},任务ID:{}", taskEntity.getProcessInstanceId(), taskEntity.getId());
HistoricProcessInstance processInstance = this.getHistoricProcessInstance(engineEvent.getProcessInstanceId());
String serviceMark = this.getBusinessMark(processInstance.getProcessVariables());
// 发送任务驳回消息到业务服务
TaskEventMessage message = new TaskEventMessage();
message.setTaskId(taskEntity.getId());
message.setTaskName(taskEntity.getName());
message.setProcessInstanceId(taskEntity.getProcessInstanceId());
message.setBusinessKey(processInstance.getBusinessKey());
message.setBusinessMark(serviceMark);
message.setState(TaskState.REJECT);
message.setTaskDefinitionKey(taskEntity.getTaskDefinitionKey());
Topic topic = new Topic(Topics.TASK_EVENT_TOPIC, serviceMark);
messageSender.send(topic, taskEntity.getId(), message, this.getUserContext(processInstance, taskEntity));
// 发布待办驳回事件,驱动业务侧待办状态
TaskRejectTodoEvent taskRejectTodoEvent = new TaskRejectTodoEvent(
this, taskEntity, processInstance, taskEntity.getAssignee());
this.applicationEventPublisher.publishEvent(taskRejectTodoEvent);
}
@Override
public Collection<? extends FlowableEventType> getTypes() {
return Set.of(CustomTaskEventTypes.TASK_REJECT);
}
}
同样,TaskAssignedEventListener 通过 getTypes() 返回 CustomTaskEventTypes.TASK_CHANGE_ASSIGNED 来监听任务改派事件。监听器在应用启动时由 FlowableListenerConfiguration 统一注册到 RuntimeService(见监听配置与示例),因此自定义事件与内置事件使用同一套注册、分发、过滤机制。
完整调用时序
以"驳回任务"为例,从业务操作到监听器处理的完整时序:
注意事项
- 事件携带的数据:自定义事件对象里放什么、不放什么要克制。任务实体、流程实例 ID 这类监听器可以自行查询的数据,不必全部塞进事件;事件越轻量,序列化与排查越简单。
- 触发位置:
dispatchEvent在引擎事务内执行,监听器逻辑默认与触发代码同事务。若监听器抛异常且isFailOnException()返回true,触发方的操作会一并回滚,需要评估是否符合预期。 - 与 Spring 事件的关系:自定义 Flowable 事件走引擎事件分发器,面向 Flowable 监听器;如果只是应用内部解耦,直接用
ApplicationEventPublisher发布 Spring 事件即可。darkblue 项目同时使用两者:Flowable 事件负责"引擎 侧语义",Spring 事件负责"应用侧待办驱动"。 - 幂等性:
dispatchEvent可能被重复调用(如接口重试),监听逻辑应幂等,避免重复发消息、重复改状态。 - 事件类型命名:自定义事件类型应避免与
FlowableEngineEventType等内置枚举的常量同名,防止类型混淆;getTypesFromString解析时也会按枚举各自的值域校验。