全面解析 Spring Event 的使用方法与真实业务落地技巧,涵盖同步监听、异步处理、事务后触发等场景,助你彻底掌握这套事件解耦神器。内容实战导向,结构清晰,点进来看不亏!
Spring Event 业务解耦神器,详细教程
一、啥玩意叫 Spring Event?为啥用它?
兄弟你听我一句劝,要是你项目里头还老是把业务逻辑都堆一块,A方法里头又调B,又调C,写着写着脑袋就跟泡面似的打结,那你就该好好看看Spring Event这玩意了,真是个解耦的利器。
你看啊,正常咱写业务代码是不是经常有这种情况:
- 用户下单了要发个短信
- 下完单还得给运营发个钉钉消息
- 再顺手给用户打个积分
乍一听没毛病,但你真都写在一个方法里头,就跟炖了一锅大杂烩,后面谁要是改个短信文案,那不是把整锅都得翻?所以咱今天讲的Spring Event,就是把这锅饭给你分着装,干净利索,风味各异还互不打扰。
咱废话不多说,直接上第一个案例,我告诉你它咋发布事件,咋监听事件,先学个基本操作,别一上来就学那啥异步广播、事务后监听,慢点整,别噎着哈。
二、第一个案例:发布一个事件,监听器来接活儿干
先给你整清楚一个概念:
- 事件对象:就是咱干的活,用来传东西。
- 事件发布器:负责广播通知,“活来了兄弟们干活啦!”
- 事件监听器:这些是真正的搬砖人,谁来监听这个活,谁干。
1. 创建事件对象(自定义事件)
// 这是我们自定义的事件对象
public class UserRegisterEvent extends ApplicationEvent {
private String username;
public UserRegisterEvent(Object source, String username) {
super(source); // source 是事件源,随便传个 this
this.username = username;
}
public String getUsername() {
return username;
}
}
这玩意儿干啥用?就像咱建了一个“任务单”,告诉系统:嘿,有个叫xxx的用户注册了,快安排后续的活。
2. 写个监听器,专门盯着这个事件
@Component
public class WelcomeEmailListener {
@EventListener
public void handleUserRegister(UserRegisterEvent event) {
// 这玩意一监听到事件就开始干活
System.out.println("用户注册成功,给他发个欢迎邮件吧!用户名是:" + event.getUsername());
}
}
注意这个
@EventListener
注解哈,Spring 看到这玩意,自动就把它安排成了事件监听器。
3. 发布事件(也就是发通知)
@RestController
@RequestMapping("/user")
public class UserController {
@Autowired
private ApplicationEventPublisher publisher; // 这就是发布器,Spring自己给装配的
@PostMapping("/register")
public String register(@RequestParam String username) {
// 模拟注册逻辑
System.out.println("用户 " + username + " 注册成功");
// 注册成功之后发个事件通知
publisher.publishEvent(new UserRegisterEvent(this, username));
return "注册成功";
}
}
来了,重头戏来了,这个
publishEvent
就是发布事件的钩子,一执行,所有监听这个事件的搬砖人立马动起来。
4. 控制台输出效果
你访问下 /user/register?username=zhangsan
,控制台输出大概长这样:
用户 zhangsan 注册成功
用户注册成功,给他发个欢迎邮件吧!用户名是:zhangsan
你看看你看看,活儿一安排,全自动就干了,不用你一层层 if else 去判断。整这套下来代码分工明确,扩展性贼好,后面要加发积分、推消息、扔MQ,随便加,不用改原逻辑一丁点,谁来谁注册个监听器不就完了?
好嘞伙计,刚才咱不是讲完了基础版“发个事件,监听一下”的那套事儿嘛,这回咱上点猛料的,整异步事件监听,主线程不等你,监听器自己慢慢磨刀干活去!
这玩意儿在哪有用?你想啊,注册完用户后你发个邮件、打个积分、通知个钉钉啥的,这要是都在主线程一股脑干,那你用户得等你磨完豆腐才能返回。用户一着急,系统一忙活,这体验不就下水道了嘛?
所以,整异步事件监听,就成了业务解耦+性能提升的王炸组合!
三、案例二:异步事件监听,干活别挡道
这回的结构和前面一样,还是三件套:
- 自定义事件对象(不变)
- 监听器标注为异步(重点)
- 主程序开线程池支持(必要配置)
咱一件一件抠。
1. 自定义事件还是老配方,不变
public class UserRegisterEvent extends ApplicationEvent {
private String username;
public UserRegisterEvent(Object source, String username) {
super(source);
this.username = username;
}
public String getUsername() {
return username;
}
}
这段没啥可说的,你要整复杂点也行,传个手机号、IP、注册时间啥的都行,反正你用得着就往里塞。
2. 监听器这次要加个猛料:@Async
@Component
public class WelcomeEmailListener {
@Async // 这就是灵魂,搞成异步
@EventListener
public void handleUserRegister(UserRegisterEvent event) {
try {
// 模拟干活儿时间长
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("【异步】欢迎邮件发出去了:用户名是 " + event.getUsername());
}
}
看到了吧,加了个
@Async
,监听事件的这坨逻辑就自动丢到线程池里去了,主线程爱谁谁,管你呢!
3. 主程序必须启用异步支持
你别忘了,Spring 默认是不开 @Async
的,你得手动开下:
@SpringBootApplication
@EnableAsync // 重点来了,不开这个啥也白搭
public class SpringEventDemoApplication {
public static void main(String[] args) {
SpringApplication.run(SpringEventDemoApplication.class, args);
}
}
就这一句话,别忘了!你没这玩意儿,
@Async
就跟贴个标签似的,系统看都不看你。
4. 搞个 REST 接口,触发事件
@RestController
@RequestMapping("/user")
public class UserController {
@Autowired
private ApplicationEventPublisher publisher;
@PostMapping("/register")
public String register(@RequestParam String username) {
System.out.println("注册流程完成,主线程收工:" + username);
publisher.publishEvent(new UserRegisterEvent(this, username));
return "用户注册完成,后续异步处理中...";
}
}
5. 控制台输出(重点!你看看时间差)
注册流程完成,主线程收工:zhangsan
【异步】欢迎邮件发出去了:用户名是 zhangsan
你仔细瞅,前面那行是立马返回的,后面这行大概3秒之后才出,说明咱异步成功了!主线程没等监听器干完活就先走了,用户体验嘎嘎拉满!
6. 要不要自定义线程池?(进阶小贴士)
要是你业务多,监听器多,系统又复杂,那建议你再加个线程池配置,不然用的是 Spring 默认的,扛不住大流量。
@Configuration
@EnableAsync
public class AsyncConfig {
@Bean(name = "eventExecutor")
public Executor eventExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(5); // 核心线程数
executor.setMaxPoolSize(10); // 最大线程数
executor.setQueueCapacity(50); // 队列容量
executor.setThreadNamePrefix("event-"); // 线程名前缀
executor.initialize();
return executor;
}
}
然后监听器里头用上这个线程池:
@Async("eventExecutor")
@EventListener
public void handleUserRegister(UserRegisterEvent event) {
...
}
你瞅这套配置,妥妥地给你系统撑起一片天。线程池一上线,再多监听器都能排队有序,不至于撑爆主线程。
好了哥几个,异步监听器这块咱也抠完了,下一场更猛——事务提交后再执行监听器逻辑,比如数据库事务没提交成功,我监听器那边就不该动手,这才是真正跟业务状态走的那种“听劝”型监听。
刚才说了同步监听、异步监听,那咱这最后一板斧——事务提交后再触发监听器逻辑——来了!
为啥这个东西值钱?为啥这招是收官杀招?
你就想,咱注册个用户,数据库一插,监听器那边立马开始发邮件、发积分、通知大妈来跳广场舞了……结果数据库那边回滚了,哎哟我去,那不是白忙活一场,还闹笑话?
所以嘛,咱得学会一种叫事务成功后再广播事件的骚操作,也就是你那数据库真正提交成功了,我这边监听器再慢慢开始干活,不然一切都免谈。
四、案例三:事务提交后,再触发事件监听
这一块你要是搞不明白,后期系统出了脏数据,你哭都来不及,跟你讲。
1. 还是老熟人,自定义事件对象(没变)
public class UserRegisterEvent extends ApplicationEvent {
private String username;
public UserRegisterEvent(Object source, String username) {
super(source);
this.username = username;
}
public String getUsername() {
return username;
}
}
2. 自定义一个事务提交后再发布事件的方法
Spring 的事件广播是即发即送的,咱得稍微绕个弯——利用 TransactionSynchronizationManager
来钩住事务提交之后的那一刻,偷偷发事件。
整一个工具类,来来来:
@Component
public class TransactionalEventPublisher {
@Autowired
private ApplicationEventPublisher applicationEventPublisher;
// 延迟发布事件,等事务提交后再发
public void publishEventAfterCommit(ApplicationEvent event) {
if (TransactionSynchronizationManager.isSynchronizationActive()) {
// 注册一个事务同步回调
TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronizationAdapter() {
@Override
public void afterCommit() {
applicationEventPublisher.publishEvent(event);
}
});
} else {
// 没有事务就直接发布
applicationEventPublisher.publishEvent(event);
}
}
}
这个类,你就记住一句话:保你监听器不再抢跑,等数据库把事儿办明白了再动手。
3. Controller 调用这个发布器,保证“有事儿再说”
@RestController
@RequestMapping("/user")
public class UserController {
@Autowired
private TransactionalEventPublisher transactionalEventPublisher;
@Autowired
private UserRepository userRepository;
@PostMapping("/registerTx")
@Transactional
public String register(@RequestParam String username) {
System.out.println("注册开始:" + username);
// 插入用户
User user = new User();
user.setUsername(username);
userRepository.save(user);
// 发布事件(等事务提交后再发)
transactionalEventPublisher.publishEventAfterCommit(new UserRegisterEvent(this, username));
System.out.println("注册结束:" + username);
return "注册成功";
}
}
注意看哦,这个方法上标了 @Transactional
,也就是说只要数据库操作没问题,咱才会走到 afterCommit 那一步。
4. 监听器不用变,之前的继续用就行
@Component
public class WelcomeEmailListener {
@Async
@EventListener
public void handleUserRegister(UserRegisterEvent event) {
System.out.println("【事务后】欢迎邮件发出去了,用户:" + event.getUsername());
}
}
5. 验证效果:搞个事务失败试试?
你可以故意在保存后面整点异常试试:
userRepository.save(user);
int i = 1 / 0; // 手动造个除零异常
你去请求 /user/registerTx?username=zhangsan
,看看是不是监听器根本就没响应,控制台一句屁都没打印?
说明咱这招管用,监听器真懂事,事务失败就不瞎凑热闹!
五、总结:Spring Event 真不是花架子,是真香工具
兄弟姐妹们,我知道你可能一开始觉得这Spring Event听着像什么不着调的玩意儿,实际上它真是一套“隐藏技能树”。
你看看这一套下来:
- 同步监听器:简单好用,适合轻量逻辑
- 异步监听器:不阻塞主线程,用户体验嗖嗖的
- 事务后触发:不瞎跑,不犯错,配合数据库操作稳的一批
这些东西单拎出来都不值钱,组合起来才叫“业务解耦三件套”。咱一个接口方法里不再塞一堆业务细节,谁该干啥就自己监听,自己处理,符合开闭原则、提升性能、保证事务一致性——这可比天天堆 if else、搞一堆 service 嵌套要高级得多!
不是所有事件都值得监听,但值得监听的事件,必须干干净净地分出来。