跳到主要内容

Flowable 自定义监听事件与触发

Flowable 内置事件已经覆盖了引擎运行的大部分场景,但业务上总有一些"引擎不知道"的语义需要对外广播。典型例子是审批流的驳回与改派:驳回本质上是删除流程实例加记录评论,改派只是修改任务办理人,引擎只会派发 PROCESS_CANCELLED、ENTITY_UPDATED 这类通用事件,无法表达"这是一次业务驳回"或"任务被重新指派"。这时就需要自定义事件。

自定义事件的关键在于两点:

  1. 自定义事件类型:实现 FlowableEventType 接口,声明业务事件名称;
  2. 自定义事件对象:实现 FlowableEvent(或其子接口),携带业务数据;
  3. 主动触发:通过 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 解析时也会按枚举各自的值域校验。

参考资料​