做业务异步解耦、消息削峰、本地事务可靠异步,绝大多数开发者第一选择是 RabbitMQ、RocketMQ、Kafka 等中间件。但中小型项目、内部后台、单体应用、低流量业务引入独立消息队列会带来大量额外成本:

  1. 1. 额外部署运维 Broker,服务器资源占用、集群维护、版本升级;
  2. 2. 增加中间件依赖,本地开发需要本地启动MQ,环境搭建繁琐;
  3. 3. 多一套组件故障点,MQ宕机直接阻断异步流程,需要额外做重试、死信、监控;
  4. 4. 简单本地异步场景过度设计,学习成本高,小业务完全没必要。

很多人不知道:Spring 原生内置两套轻量异步能力,无需任何第三方中间件,分别是 @Async内存异步、Spring 内置 ApplicationEvent事件驱动,其中「事务同步事件(TransactionalApplicationListener)」是极易被忽略的隐藏高级特性——支持事务提交后才发送异步消息、事务回滚自动丢弃消息,轻量零依赖,完全替代MQ处理本地异步场景,性能开销极低。

一、三种异步方案对比

方案
依赖
事务一致性
宕机消息持久化
集群多实例
适用场景
RabbitMQ/Kafka
独立中间件,额外部署
支持分布式事务/本地事务消息
磁盘持久化,宕机不丢
天然集群分发
跨服务、高吞吐、海量消息、分布式异步
普通 @Async内存异步
仅Spring,无第三方
不支持,事务未提交消息已执行
内存消息,服务重启全部丢失
单机内存隔离,集群无法互通
无事务要求、非核心日志、无关紧要异步任务
Spring 事务同步事件(TransactionalEvent)
Spring内置,零额外依赖
强事务绑定:事务成功才执行异步,回滚自动丢弃消息
可扩展本地文件/数据库持久化兜底
单机内异步,集群需配合DB表
单体/中台本地业务异步:订单通知、库存扣减后发券、日志记录、本地解耦

核心结论:单体内部、不需要跨服务、要求本地事务可靠的异步场景,Spring事务事件比MQ轻量100%,零运维成本,无需额外组件

二、普通事件 vs 事务同步事件

2.1 普通 ApplicationEvent 致命缺陷

常规事件发布 applicationContext.publishEvent(),事件会同步立即执行,或者@Async异步执行,但不受数据库事务控制:

  1. 1. 事务还未提交,异步任务已经执行;
  2. 2. 业务代码抛异常事务回滚,但异步任务已经执行完成,出现数据不一致;
    示例:创建订单发布发券事件,订单事务回滚,优惠券依旧发放,产生脏数据。

2.2 事务同步事件核心能力(Spring隐藏特性)

Spring 提供 @TransactionalEventListener注解,内置三种执行时机,完全绑定当前数据库事务生命周期:

  1. 1. TransactionPhase.BEFORE_COMMIT:事务提交前执行;
  2. 2. TransactionPhase.AFTER_COMMIT(最常用):事务成功提交后才执行事件逻辑
  3. 3. TransactionPhase.AFTER_ROLLBACK:事务回滚时执行;
  4. 4. TransactionPhase.AFTER_COMPLETION:无论提交/回滚,事务结束后执行。

底层原理:
事件不会立即分发,Spring 将事件缓存至当前事务绑定的本地线程缓存;只有事务正常 commit,才会触发事件监听执行;一旦事务回滚,缓存事件直接丢弃,从根源杜绝事务不一致问题。
搭配 @Async可实现事务可靠 + 异步执行双重能力,完美替代本地MQ异步场景。

三、代码示例

3.1 基础环境依赖

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-aop</artifactId>
</dependency>

3.2 开启异步支持

启动类添加 @EnableAsync,开启异步线程池能力:

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.scheduling.annotation.EnableAsync;

@SpringBootApplication
@EnableAsync
public class AsyncEventApplication {
    public static void main(String[] args) {
        SpringApplication.run(AsyncEventApplication.class, args);
    }
}

3.3 自定义业务事件实体(订单创建事件示例)

事件实体承载业务参数,POJO无特殊要求:

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.springframework.context.ApplicationEvent;
import java.math.BigDecimal;

@Data
@NoArgsConstructor
@AllArgsConstructor
public class OrderCreateEvent extends ApplicationEvent {
    // 订单号
    private String orderNo;
    // 用户ID
    private Long userId;
    // 订单金额
    private BigDecimal amount;

    public OrderCreateEvent(Object source, String orderNo, Long userId, BigDecimal amount) {
        super(source);
        this.orderNo = orderNo;
        this.userId = userId;
        this.amount = amount;
    }
}

3.4 业务发布事件(带事务的业务Service)

import lombok.RequiredArgsConstructor;
import org.springframework.context.ApplicationContext;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.math.BigDecimal;
import java.util.UUID;

@Service
@RequiredArgsConstructor
public class OrderService {
    private final ApplicationContext context;

    @Transactional(rollbackFor = Exception.class)
    public void createOrder(Long userId, BigDecimal amount) {
        // 1. 数据库创建订单记录
        String orderNo = "ORD" + UUID.randomUUID().toString().substring(0,16);
        // orderMapper.insert(order);

        // 2. 发布事务同步事件,不会立刻执行,等待事务提交
        context.publishEvent(new OrderCreateEvent(this, orderNo, userId, amount));

        // 模拟异常测试回滚:抛出异常后,上面发布的事件会直接丢弃,监听器不会执行
        // int a = 1 / 0;
    }
}

3.5 事务异步监听器实现(

使用 @TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT)@Async

  • • 事务提交成功后才触发;
  • • 异步线程执行,不阻塞主业务流程;
import lombok.extern.slf4j.Slf4j;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Component;
import org.springframework.transaction.event.TransactionPhase;
import org.springframework.transaction.event.TransactionalEventListener;

@Slf4j
@Component
public class OrderEventListener {

    /**
     * 订单创建成功后异步执行:发优惠券、推送消息、统计销量
     * AFTER_COMMIT:事务提交才执行,回滚直接丢弃事件
     */
    @Async
    @TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT)
    public void handleOrderCreateEvent(OrderCreateEvent event) {
        log.info("异步事件开始执行,订单号:{}", event.getOrderNo());
        // 业务1:发放新人优惠券
        sendCoupon(event.getUserId());
        // 业务2:推送站内通知
        sendUserNotice(event.getUserId(), event.getOrderNo());
        // 业务3:统计商品销量
        statOrderSales(event.getOrderNo(), event.getAmount());
        log.info("订单异步事件处理完成");
    }

    private void sendCoupon(Long userId) {
        // 发放优惠券逻辑
    }

    private void sendUserNotice(Long userId, String orderNo) {
        // 站内消息推送
    }

    private void statOrderSales(String orderNo, BigDecimal amount) {
        // 销量统计
    }

    // 事务回滚监听(可选)
    @Async
    @TransactionalEventListener(phase = TransactionPhase.AFTER_ROLLBACK)
    public void handleOrderRollback(OrderCreateEvent event) {
        log.warn("订单事务回滚,丢弃异步任务,订单号:{}", event.getOrderNo());
    }
}

四、解决原生内存事件短板

原生事件存在短板:服务重启内存事件丢失、单机内有效、异步执行失败无重试。下面提供4套生产级增强方案。

4.1 自定义异步线程池,防止线程耗尽

默认@Async使用无界线程池,高并发容易OOM,自定义线程池统一管控:

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import java.util.concurrent.Executor;
import java.util.concurrent.ThreadPoolExecutor;

@Configuration
@EnableAsync
public class AsyncThreadPoolConfig {
    @Bean("eventTaskExecutor")
    public Executor eventTaskExecutor() {
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        // 核心线程
        executor.setCorePoolSize(8);
        // 最大线程
        executor.setMaxPoolSize(32);
        // 队列容量
        executor.setQueueCapacity(200);
        // 线程前缀
        executor.setThreadNamePrefix("event-async-thread-");
        // 拒绝策略:丢弃最旧任务
        executor.setRejectedExecutionHandler(new ThreadPoolExecutor.DiscardOldestPolicy());
        executor.initialize();
        return executor;
    }
}

指定线程池执行:

@Async("eventTaskExecutor")
@TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT)
public void handleOrderCreateEvent(OrderCreateEvent event) {}

4.2 异步执行失败重试+死信持久化(解决内存丢失)

原生事件执行异常直接丢失,我们增加AOP捕获异常,将失败事件存入本地数据库「事件死信表」,定时任务重试补偿。

1)事件死信表

CREATE TABLE event_dead_letter (
    id BIGINT AUTO_INCREMENT PRIMARY KEY,
    event_type VARCHAR(64NOT NULL COMMENT '事件类型',
    event_json TEXT NOT NULL COMMENT '事件序列化内容',
    retry_count INT DEFAULT 0 COMMENT '重试次数',
    max_retry INT DEFAULT 3 COMMENT '最大重试次数',
    create_time DATETIME DEFAULT NOW(),
    next_retry_time DATETIME DEFAULT NOW(),
    INDEX idx_next_retry(next_retry_time)
);

2)异常捕获存储死信 + 定时重试任务

通过AOP环绕监听器方法,执行失败则序列化事件存入死信表;定时任务扫描未达最大重试次数的事件重新发布。

4.3 集群多实例同步方案

单机事件无法同步其他服务实例,如果需要集群全局异步:
方案1:事件落地DB后,通过定时任务拉取分发(轻量无MQ);
方案2:业务跨服务异步场景再引入MQ,本地事务事件处理单机内部逻辑。

4.4 事件去重防重复消费

订单号、唯一业务标识存入Redis,处理前判断是否已消费,防止定时重试重复执行。

五、普通@Async 为什么不推荐做事务异步?

错误示例

@Service
@RequiredArgsConstructor
public class WrongOrderService {
    private final AsyncTaskService asyncTaskService;

    @Transactional(rollbackFor = Exception.class)
    public void createOrder(Long userId) {
        // 1. 插入订单
        // 2. 直接调用异步方法
        asyncTaskService.sendCoupon(userId);
        // 模拟异常回滚
        int i = 1/0;
    }
}

@Service
class AsyncTaskService {
    @Async
    public void sendCoupon(Long userId) {
        // 事务已经回滚,优惠券依然发放,数据错乱
    }
}

问题根源:@Async异步线程独立于事务线程,主线程事务未提交,异步任务已经执行,回滚无法撤销异步操作。
而 @TransactionalEventListener由Spring事务管理器统一管控,彻底规避该问题。

六、全文总结

绝大多数开发者只知道Spring的@Async异步,却忽略了**事务同步事件@TransactionalEventListener**这个内置轻量异步能力。
它无需RabbitMQ/RocketMQ等中间件,零部署、零运维、零额外依赖,天然绑定数据库事务,保证「事务成功才执行异步,回滚自动丢弃消息」,完美解决单体本地异步解耦需求。
对比重型MQ,它轻量化、上手简单、环境搭建无成本;对比普通@Async,它解决了事务不一致的致命缺陷。
配合自定义线程池、死信持久化、定时重试,完全可以满足生产级本地异步业务,只有分布式、高吞吐场景才需要引入专业消息队列。


Spring内置事件驱动、AOP、异步线程池是轻量解耦核心方案,很多中小团队盲目引入MQ增加运维负担。后续持续更新Spring进阶实战:事件总线封装、分布式本地事件、事务消息兼容方案、异步线程池监控全套源码干货。
喜欢Spring底层轻量化架构、零中间件解决方案,欢迎点赞、收藏、关注,持续跟进SpringBoot进阶专栏!

扫码领红包

微信赞赏支付宝扫码领红包

发表回复

后才能评论