前言
日常開發中,我們經常會碰到這樣的業務場景:用戶注冊,注冊成功后需要發送郵箱、短信提示用戶,通常我們都是這樣寫:
/** * 用戶注冊 */ @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
