前言
日常開發中,我們經常會碰到這樣的業務場景:使用者註冊,註冊成功後需要傳送郵箱、簡訊提示使用者,通常我們都是這樣寫:
/** * 使用者註冊 */ @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使用事件釋出與監聽就暫時記錄到這,後續再進行補充。
程式碼開源
程式碼已經開源、託管到我的GitHub、碼雲:
GitHub:https://github.com/huanzi-qch/springBoot
碼雲:https://gitee.com/huanzi-qch/springBoot