做业务异步解耦、消息削峰、本地事务可靠异步,绝大多数开发者第一选择是 RabbitMQ、RocketMQ、Kafka 等中间件。但中小型项目、内部后台、单体应用、低流量业务引入独立消息队列会带来大量额外成本:
-
1. 额外部署运维 Broker,服务器资源占用、集群维护、版本升级; -
2. 增加中间件依赖,本地开发需要本地启动MQ,环境搭建繁琐; -
3. 多一套组件故障点,MQ宕机直接阻断异步流程,需要额外做重试、死信、监控; -
4. 简单本地异步场景过度设计,学习成本高,小业务完全没必要。
很多人不知道:Spring 原生内置两套轻量异步能力,无需任何第三方中间件,分别是 @Async内存异步、Spring 内置 ApplicationEvent事件驱动,其中「事务同步事件(TransactionalApplicationListener)」是极易被忽略的隐藏高级特性——支持事务提交后才发送异步消息、事务回滚自动丢弃消息,轻量零依赖,完全替代MQ处理本地异步场景,性能开销极低。
一、三种异步方案对比
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@Async内存异步 |
|
|
|
|
|
|
|
|
|
|
|
|
核心结论:单体内部、不需要跨服务、要求本地事务可靠的异步场景,Spring事务事件比MQ轻量100%,零运维成本,无需额外组件。
二、普通事件 vs 事务同步事件
2.1 普通 ApplicationEvent 致命缺陷
常规事件发布 applicationContext.publishEvent(),事件会同步立即执行,或者@Async异步执行,但不受数据库事务控制:
-
1. 事务还未提交,异步任务已经执行; -
2. 业务代码抛异常事务回滚,但异步任务已经执行完成,出现数据不一致;
示例:创建订单发布发券事件,订单事务回滚,优惠券依旧发放,产生脏数据。
2.2 事务同步事件核心能力(Spring隐藏特性)
Spring 提供 @TransactionalEventListener注解,内置三种执行时机,完全绑定当前数据库事务生命周期:
-
1. TransactionPhase.BEFORE_COMMIT:事务提交前执行; -
2. TransactionPhase.AFTER_COMMIT(最常用):事务成功提交后才执行事件逻辑; -
3. TransactionPhase.AFTER_ROLLBACK:事务回滚时执行; -
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(64) NOT 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进阶专栏!
微信赞赏
支付宝扫码领红包

