SpringBoot系列——事件发布与监听
前言
日常开发中,我们经常会碰到这样的业务场景:用户注册,注册成功后需要发送邮箱、短信提示用户,通常我们都是这样写:
/** * 用户注册 */ @GetMapping("/userRegister") public String userRegister(UserVo userVo) { //校验参数 //存库 //发送邮件 //发送短信 //API返回结果 return "操作成功!"; }
可以发现,用户注册与信息推送强耦合,用户注册其实到存库成功,就已经算是完成了,后面的信息推送都是额外的操作,甚至信息推送失败报错,还会影响API接口的结果,如果在同一事务,报错信息不捕获,还会导致事务回滚,存库失败。
官方文档相关介绍:https://docs.spring.io/spring-boot/docs/2.1.0.RELEASE/reference/htmlsingle/#boot-features-application-events-and-listeners
本文记录springboot使用@EventListener监听事件、ApplicationEventPublisher.publishEvent发布事件实现业务解耦。
代码
项目结构
默认情况下,事件的发布和监听操作是同步执行的,我们先配置一下async,优雅多线程异步任务,详情请戳:SpringBoot系列——@Async优雅的异步调用
启动类添加@EnableAsync注解
/** * 异步任务线程池的配置 */ @Configuration public class AsyncConfig { private static final int MAX_POOL_SIZE = 50; private static final int CORE_POOL_SIZE = 20; @Bean("asyncTaskExecutor") public AsyncTaskExecutor asyncTaskExecutor() { ThreadPoolTaskExecutor asyncTaskExecutor = new ThreadPoolTaskExecutor(); asyncTaskExecutor.setMaxPoolSize(MAX_POOL_SIZE); asyncTaskExecutor.setCorePoolSize(CORE_POOL_SIZE); asyncTaskExecutor.setThreadNamePrefix("async-task-"); asyncTaskExecutor.initialize(); return asyncTaskExecutor; } }
多数情况下的业务操作都会涉及数据库事务,可以使用@TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT)注解开启事务监听,确保数据入库后再进行异步任务操作。
定义事件源
先定义两个事件源,继承ApplicationEvent
/** * 用户Vo */ @Data public class UserVo { private Integer id; private String username; } /** * 用户事件源 */ @Getter @Setter public class UserEventSource extends ApplicationEvent { private UserVo userVo; UserEventSource(UserVo userVo) { super(userVo); this.userVo = userVo; } }
/** * 业务工单Vo */ @Data public class WorkOrderVo { private Integer id; private String WorkOrderName; } /** * 业务工单事件源 */ @Getter @Setter public class WorkOrderEventSource extends ApplicationEvent { private cn.huanzi.qch.springbooteventsandlisteners.pojo.WorkOrderVo WorkOrderVo; WorkOrderEventSource(WorkOrderVo WorkOrderVo) { super(WorkOrderVo); this.WorkOrderVo = WorkOrderVo; } }
监听事件
监听用户注册事件、监听业务工单发起事件
/** * 事件监听 */ @Slf4j @Component public class EventListenerList { /** * 用户注册事件监听 */ @Async("asyncTaskExecutor") @EventListener @Order(1)//一个事件多个事监听,在同步的情况下,使用@order值越小,执行顺序优先 public void userRegisterListener(UserEventSource eventSourceEvent){ log.info("用户注册事件监听1:"+eventSourceEvent.getUserVo()); //开展其他业务,例如发送邮件、短信等 } /** * 用户注册事件监听 */ @Async("asyncTaskExecutor") @EventListener @Order(2)//一个事件多个事监听,在同步的情况下,使用@order值越小,执行顺序优先 public void userRegisterListener2(UserEventSource eventSourceEvent){ log.info("用户注册事件监听2:"+eventSourceEvent.getUserVo()); //开展其他业务,例如发送邮件、短信等 } /** * 业务工单发起事件监听 */ @Async("asyncTaskExecutor") @EventListener public void workOrderStartListener(WorkOrderEventSource eventSourceEvent){ log.info("业务工单发起事件:"+eventSourceEvent.getWorkOrderVo()); //开展其他业务,例如发送邮件、短信等 } }
发布事件
创建一个controller,新增两个测试接口
/** * 事件发布 */ @Slf4j @RestController @RequestMapping("/eventPublish/") public class EventPublish { @Autowired private ApplicationEventPublisher applicationEventPublisher; /** * 用户注册 */ @GetMapping("userRegister") public String userRegister(UserVo userVo) { log.info("用户注册!"); //发布 用户注册事件 applicationEventPublisher.publishEvent(new UserEventSource(userVo)); return "操作成功!"; } /** * 业务工单发起 */ @GetMapping("workOrderStart") public String workOrderStart(WorkOrderVo workOrderVo) { log.info("业务工单发起!"); //发布 业务工单发起事件 applicationEventPublisher.publishEvent(new WorkOrderEventSource(workOrderVo)); return "操作成功!"; } }
效果
用户注册
http://localhost:10010/eventPublish/userRegister?id=1&username=张三
API返回
后台异步任务执行
工单发起
http://localhost:10010/eventPublish/workOrderStart?id=1&workOrderName=设备出入申请单
API返回
后台异步任务执行
后记
springboot使用事件发布与监听就暂时记录到这,后续再进行补充。
更新
2021-08-12更新
有同学反馈说,都SpringBoot了,不需要继承ApplicationEvent,直接发布Vo就行,今天一试果然如此!
监听
直接监听Vo
/** * 事件监听 */ @Slf4j @Component public class EventListenerList { /** * 用户注册事件监听 */ @Async("asyncTaskExecutor") @EventListener @Order(1)//一个事件多个事监听,同步的情况下,使用@order值越小,执行顺序优先 public void userRegisterListener(UserVo userVo){ log.info("用户注册事件监听1:"+userVo); //开展其他业务,例如发送邮件、短信等 } /** * 用户注册事件监听 */ @Async("asyncTaskExecutor") @EventListener @Order(2)//一个事件多个事监听,同步的情况下,使用@order值越小,执行顺序优先 public void userRegisterListener2(UserVo userVo){ log.info("用户注册事件监听2:"+userVo); //开展其他业务,例如发送邮件、短信等 } /** * 业务工单发起事件监听 */ @Async("asyncTaskExecutor") @EventListener public void workOrderStartListener(WorkOrderVo workOrderVo){ log.info("业务工单发起事件:"+workOrderVo); //开展其他业务,例如发送邮件、短信等 } }
发布
直接发布Vo
/** * 事件发布 */ @Slf4j @RestController @RequestMapping("/eventPublish/") public class EventPublish { @Autowired private ApplicationEventPublisher applicationEventPublisher; /** * 用户注册 */ @GetMapping("userRegister") public String userRegister(UserVo userVo) { log.info("用户注册!"); //发布 用户注册事件 applicationEventPublisher.publishEvent(userVo); return "操作成功!"; } /** * 业务工单发起 */ @GetMapping("workOrderStart") public String workOrderStart(WorkOrderVo workOrderVo) { log.info("业务工单发起!"); //发布 业务工单发起事件 applicationEventPublisher.publishEvent(workOrderVo); return "操作成功!"; } }
效果
代码开源
代码已经开源、托管到我的GitHub、码云:
GitHub:https://github.com/huanzi-qch/springBoot
码云:https://gitee.com/huanzi-qch/springBoot
版权声明
捐献、打赏

支付宝

微信
交流群


【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· go语言实现终端里的倒计时
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 使用C#创建一个MCP客户端
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列1:轻松3步本地部署deepseek,普通电脑可用
· 按钮权限的设计及实现