SpringBoot监听器EventListener使用

对于部分审批流程或者多个表单数据处理需要回写数据,可以利用监听器处理

1.在方法上加入注解@EventListener

  "#processTaskEvent.flowCode.startsWith('leave')" 解释如下:

  processTaskEvent: 方法参数

  flowCode: processTaskEvent的属性

  startsWith: flowCode类型的方法(这里是String)

  

@org.springframework.context.event.EventListener(condition = "#processTaskEvent.flowCode.startsWith('leave')")
    public void processTaskHandler(ProcessTaskEvent processTaskEvent) {
        log.info("当前任务创建了{}", processTaskEvent.toString());
     // 这里写回调事件的数据处理 }

2.触发监听器

/**
     * 执行创建任务监听
     *
     * @param flowCode   流程定义编码
     * @param instance   实例数据
     * @param taskId     任务id
     */
    public void processTaskHandler(String flowCode, Instance instance, Long taskId) {
        String tenantId = SecurityUtils.getTenantId();
        
        ProcessTaskEvent processTaskEvent = new ProcessTaskEvent();
        processTaskEvent.setFlowCode(flowCode);
        // 执行监听
        SpringUtils.context().publishEvent(processTaskEvent);
    }

3.具体数据处理flowProcessEventHandler受Spring管理,中有触发监听器方法

// 具体的数据处理完成之后 发布事件
flowProcessEventHandler.processTaskHandler(definition.getFlowCode(), instance, task.getId());

 

具体执行: 3.数据处理 => 2.触发监听器 => 1.执行监听方法回调

 

@EventListener 与 @TransactionalEventListener 对比分析:基于事务提交与执行顺序

在 Spring 框架中,事件监听机制是实现松耦合架构的重要手段,而 @EventListener 和 @TransactionalEventListener 在处理事务相关事件时存在显著差异。以下从事务生命周期、执行顺序、数据一致性等维度展开对比分析。

一、核心机制对比

  
维度@EventListener@TransactionalEventListener
事务感知能力 不感知事务状态,默认在事务内执行 深度集成事务机制,可感知事务阶段
执行时机 事件发布时立即执行(可能在事务提交前) 可指定在事务提交后 / 前、回滚时执行
数据一致性保障 不保证事务提交后执行,可能读取未提交数据 确保在事务状态稳定后执行,数据一致性更强
底层实现 基于 ApplicationEventPublisher 直接发布 基于 TransactionSynchronization 机制监听事务阶段
参数配置 无事务相关参数 支持 phaserollbackFor 等事务参数

二、事务提交与执行顺序分析

1. 事务生命周期与事件触发点

    A[事务开始] --> B[业务操作]
    B --> C{事务是否成功?}
    C -- 是 --> D[事务提交前]
    C -- 否 --> E[事务回滚]
    D --> F[事务提交]
    F --> G[事务提交后]
    E --> H[事务回滚后]
    
    % 事件触发点
    D -. @EventListener(默认) .-> I[事件处理(可能失败导致事务回滚)]
    F -. @TransactionalEventListener(AFTER_COMMIT) .-> J[事件处理(数据已持久化)]
    E -. @TransactionalEventListener(AFTER_ROLLBACK) .-> K[事件处理(数据未变更)]
 
 
2. @EventListener 的执行时序问题
 
@Service
public class UserService {
    
    @Transactional
    public void updateUserAndPublishEvent(Long userId) {
        userRepository.updateStatus(userId, "INACTIVE");
        
        // 发布事件(此时事务未提交)
        eventPublisher.publishEvent(new UserStatusChangedEvent(userId));
        
        // 假设此处抛出异常,事务回滚
        if (shouldRollback()) {
            throw new RuntimeException("模拟事务回滚");
        }
    }
}

@Service
public class UserListener {
    
    @EventListener
    public void handleEvent(UserStatusChangedEvent event) {
        // 问题1:若事务回滚,此处理仍会执行(数据未持久化)
        logService.recordLog("用户状态已变更");
        
        // 问题2:若在此处查询数据库,可能读取到未提交的数据
        User user = userRepository.findById(event.getUserId()).get();
    }
}
 

 

  • 核心问题:
    • 事件处理可能在事务提交前执行,若事务回滚,事件处理逻辑与数据库状态不一致。
    • 若事件处理中包含数据库查询,可能读取到未提交的临时数据(脏读风险)。
3. @TransactionalEventListener 的事务阶段控制
 
@Service
public class UserListener {
    
    // 事务提交后执行(推荐场景:数据已持久化时处理)
    @TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT)
    public void handleAfterCommit(UserStatusChangedEvent event) {
        // 安全操作:事务已提交,数据一定存在
        sendNotification(event.getUserId());
    }
    
    // 事务提交前执行(谨慎使用:事务可能回滚)
    @TransactionalEventListener(phase = TransactionPhase.BEFORE_COMMIT)
    public void handleBeforeCommit(UserStatusChangedEvent event) {
        // 适用于需要在事务中联动的操作,但需承担回滚风险
    }
    
    // 事务回滚后执行(处理补偿逻辑)
    @TransactionalEventListener(phase = TransactionPhase.AFTER_ROLLBACK)
    public void handleAfterRollback(UserStatusChangedEvent event) {
        // 清理临时资源或记录回滚日志
    }
}
 

 

  • 执行顺序优势:
    • 通过 phase 参数精确控制事件在事务的哪个阶段执行,避免数据不一致。
    • AFTER_COMMIT 阶段确保事件处理时数据已持久化,后续操作安全。
    • AFTER_ROLLBACK 可用于处理事务失败后的补偿逻辑(如清理缓存)。

三、异步场景下的特殊考量

当结合 @Async 实现异步事件处理时,两者的行为差异更明显:

 

 
@Service
public class AsyncUserListener {
    
    // @EventListener + @Async 的风险场景
    @Async
    @EventListener
    public void asyncHandleEvent(UserDeletedEvent event) {
        // 异步线程中执行,原事务可能尚未提交
        // 若此时查询数据库,可能读到未删除的数据(因事务未提交)
        User user = userRepository.findById(event.getUserId()).orElse(null);
        // user 可能不为 null,导致逻辑错误
    }
    
    // @TransactionalEventListener + @Async 的正确实践
    @Async
    @TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT)
    public void safeAsyncHandleEvent(UserDeletedEvent event) {
        // 事务已提交,数据已删除,查询结果正确
        User user = userRepository.findById(event.getUserId()).orElse(null);
        // user 一定为 null
    }
}
 

 

  • 关键差异:
    • @EventListener + @Async 可能因事务未提交导致异步线程读取旧数据。
    • @TransactionalEventListener + @Async 会等待事务提交后再异步处理,确保数据可见性。

四、适用场景与最佳实践

场景类型推荐注解原因
普通业务事件(无事务依赖) @EventListener 无需感知事务状态,执行效率更高
事务相关事件(如数据持久化后通知) @TransactionalEventListener 确保事件在事务提交后执行,避免数据不一致
跨服务数据同步(最终一致性) @TransactionalEventListener 结合 AFTER_COMMIT 阶段,确保本地事务成功后再触发远程操作
事务回滚补偿逻辑 @TransactionalEventListener 通过 AFTER_ROLLBACK 阶段处理事务失败后的清理工作

五、总结:执行顺序与事务提交的关系

  1. @EventListener:
    • 事件发布时立即执行,可能在事务提交前执行。
    • 若事务回滚,事件处理结果与数据库状态不一致。
    • 适用于不依赖事务结果的轻量级事件。
  2. @TransactionalEventListener:
    • 可精确控制在事务提交后 / 前 / 回滚时执行。
    • AFTER_COMMIT 是最常用阶段,确保数据持久化后处理事件。
    • 适用于需要保证数据一致性的场景,如订单支付成功后发送通知。

 

通过合理选择事件监听方式,可在系统松耦合设计中同时保障事务一致性,避免因时序问题导致的数据不一致风险。

 

posted @ 2025-06-04 16:36  泡沫幻影  阅读(186)  评论(0)    收藏  举报