springboot?publish?event?事件機制demo分享
1. 使用ApplicationEventPublisher 發(fā)布事件
復(fù)制下面全部代碼,右鍵包名,粘貼即可生成java類,執(zhí)行即可看到效果。
事件機制
- 需要自定義一個事件類繼承ApplicationEvent;
- 需要自定義一個監(jiān)聽器類實現(xiàn)ApplicationListener接口,或普通類的方法中使用@EventListener注解;
- 使用默認發(fā)布器ApplicationEventPublisher發(fā)布即可;
- 事件類不需要注入到IOC;監(jiān)聽器需要注入到IOC;ApplicationEventPublisher用Autowired注入進來即可;
- 默認情況下,事件發(fā)布與執(zhí)行是同步的,事件執(zhí)行完畢,發(fā)布者才會執(zhí)行下面的邏輯;
package com.example.controller; import com.alibaba.fastjson.JSON; import com.example.SpringbootRedisApplication; import lombok.Data; import lombok.SneakyThrows; import lombok.extern.slf4j.Slf4j; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.context.ApplicationEvent; import org.springframework.context.ApplicationEventPublisher; import org.springframework.context.ApplicationListener; import org.springframework.context.event.EventListener; import org.springframework.scheduling.annotation.Async; import org.springframework.scheduling.annotation.EnableAsync; import org.springframework.stereotype.Component; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; import java.util.Objects; @RunWith(SpringJUnit4ClassRunner.class) @SpringBootTest(classes = SpringbootRedisApplication.class) @Slf4j @EnableAsync public class PublishEventDemo { /** * 事件機制: * 需要自定義一個事件類繼承ApplicationEvent; * 需要自定義一個監(jiān)聽器類實現(xiàn)ApplicationListener接口,或普通類的方法中使用@EventListener注解; * 使用默認發(fā)布器ApplicationEventPublisher發(fā)布即可; * 事件類不需要注入到IOC;監(jiān)聽器需要注入到IOC;ApplicationEventPublisher用Autowired注入進來即可; */ /** * 事件發(fā)布器 */ @Autowired private ApplicationEventPublisher eventPublisher; /** * 測試類 */ @Test public void publishTest() throws InterruptedException { Task task = new Task(); task.setId(1L); task.setTaskName("測試任務(wù)"); task.setTaskContext("任務(wù)內(nèi)容"); task.setFinish(false); MyEvent event = new MyEvent(task); log.info("開始發(fā)布任務(wù)"); eventPublisher.publishEvent(event); //applicationContext.publishEvent(event); log.info("結(jié)束發(fā)布任務(wù)"); Thread.sleep(10000); } } /** * 任務(wù)類 * 任務(wù)就是一個普通的類,用來保存你要發(fā)布事件的內(nèi)容。這個沒有特殊限制,可以根據(jù)自己業(yè)務(wù)隨意設(shè)置。 */ @Data class Task { private Long id; private String taskName; private String taskContext; private boolean finish; } /** * 事件類 * 事件類需要繼承org.springframework.context.ApplicationEvent,這樣發(fā)布的事件才能被Spring所識別 */ @Slf4j class MyEvent<T> extends ApplicationEvent { private Task task; public MyEvent(Task task) { super(task); this.task = task; } public Task getTask() { return task; } } /** * 事件監(jiān)聽類 * 事件的監(jiān)聽器需要實現(xiàn)org.springframework.context.ApplicationListener,并且需要注入到容器之中。 */ @Component @Slf4j class MyEventListenerA implements ApplicationListener<MyEvent> { /** * 監(jiān)聽方式1:實現(xiàn)ApplicationListener接口,重寫onApplicationEvent方法 * 需要使用Component注入IOC */ @SneakyThrows @Async @Override public void onApplicationEvent(MyEvent MyEvent) { Thread.sleep(5000); if (Objects.isNull(MyEvent)) { return; } Task task = MyEvent.getTask(); log.info("監(jiān)聽器A接收任務(wù):{}", JSON.toJSONString(task)); task.setFinish(true); log.info("監(jiān)聽器A此時完成任務(wù)"); } } @Component @Slf4j class MyEventListenerB implements ApplicationListener<MyEvent> { /** * 監(jiān)聽方式2:接口和注解混合使用 * 但此時 @EventListener不能與注解@Async在同一個類中使用,會報錯,至于為什么,不知道; * 需要使用Component注入IOC */ //@Async//加上這個,@EventListener的方法就會報java.lang.IllegalStateException: Failed to load ApplicationContext @SneakyThrows @Override public void onApplicationEvent(MyEvent MyEvent) { Thread.sleep(1000); if (Objects.isNull(MyEvent)) { return; } Task task = MyEvent.getTask(); log.info("監(jiān)聽器B接收任務(wù):{}", JSON.toJSONString(task)); task.setFinish(true); log.info("監(jiān)聽器B此時完成任務(wù)"); } @EventListener public void someMethod(MyEvent event) throws InterruptedException { Thread.sleep(1000); log.info("監(jiān)聽器@EventListenerB收到={}", event.getTask()); } } @Component @Slf4j class MyEventListennerC { /** * 監(jiān)聽方式3:注解@EventListener的監(jiān)聽器不需要實現(xiàn)任何接口 * 需要使用Component注入IOC */ @EventListener public void someMethod(MyEvent event) throws InterruptedException { Thread.sleep(1000); log.info("監(jiān)聽器@EventListenerC收到={}", event.getTask()); } }
運行日志:
2020-11-30 18:13:56.238 INFO 19776 --- [ main] com.example.controller.PublishEventDemo : Started PublishEventDemo in 11.098 seconds (JVM running for 13.737)
2020-11-30 18:13:56.902 INFO 19776 --- [ main] com.example.controller.PublishEventDemo : 開始發(fā)布任務(wù)
2020-11-30 18:13:57.904 INFO 19776 --- [ main] com.example.controller.MyEventListenerB : 監(jiān)聽器@EventListenerB收到=Task(id=1, taskName=測試任務(wù), taskContext=任務(wù)內(nèi)容, finish=false)
2020-11-30 18:13:58.905 INFO 19776 --- [ main] c.example.controller.MyEventListennerC : 監(jiān)聽器@EventListenerC收到=Task(id=1, taskName=測試任務(wù), taskContext=任務(wù)內(nèi)容, finish=false)
2020-11-30 18:13:59.920 INFO 19776 --- [ main] com.example.controller.MyEventListenerB : 監(jiān)聽器B接收任務(wù):{"finish":false,"id":1,"taskContext":"任務(wù)內(nèi)容","taskName":"測試任務(wù)"}
2020-11-30 18:13:59.921 INFO 19776 --- [ main] com.example.controller.MyEventListenerB : 監(jiān)聽器B此時完成任務(wù)
2020-11-30 18:13:59.921 INFO 19776 --- [ main] com.example.controller.PublishEventDemo : 結(jié)束發(fā)布任務(wù)
2020-11-30 18:14:03.913 INFO 19776 --- [ task-1] com.example.controller.MyEventListenerA : 監(jiān)聽器A接收任務(wù):{"finish":true,"id":1,"taskContext":"任務(wù)內(nèi)容","taskName":"測試任務(wù)"}
2020-11-30 18:14:03.913 INFO 19776 --- [ task-1] com.example.controller.MyEventListenerA : 監(jiān)聽器A此時完成任務(wù)
2020-11-30 18:14:09.958 INFO 19776 --- [ Thread-3] o.s.s.concurrent.ThreadPoolTaskExecutor : Shutting down ExecutorService 'applicationTaskExecutor'
事件發(fā)送后,會等待事件執(zhí)行完畢,因此他們是同步的。若想異步執(zhí)行事件,可以把@Async加到監(jiān)聽方法上;
2. 使用ApplicationContext發(fā)布事件
與上例不同之處
- 使用ApplicationContext發(fā)布事件,ApplicationContext實現(xiàn)了ApplicationEventPublisher接口;
- 使用ApplicationContextEvent 定義事件,ApplicationContextEvent 繼承了ApplicationEvent類;
demo代碼:
package com.example.controller; import ch.qos.logback.classic.Logger; import com.example.SpringbootRedisApplication; import lombok.ToString; import lombok.extern.slf4j.Slf4j; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationListener; import org.springframework.context.event.ApplicationContextEvent; import org.springframework.context.event.EventListener; import org.springframework.stereotype.Component; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; import org.springframework.web.bind.annotation.RestController; @RunWith(SpringJUnit4ClassRunner.class) @SpringBootTest(classes = SpringbootRedisApplication.class) @RestController @Slf4j public class PublishEventDemo2 { /** * 使用ApplicationContext發(fā)布事件 */ @Autowired ApplicationContext applicationContext; @Test public void send() { MyEvent2 myEvent2 = new MyEvent2(applicationContext); myEvent2.setData("數(shù)據(jù)"); log.info("開始發(fā)送事件"); applicationContext.publishEvent(myEvent2); log.info("結(jié)束發(fā)送事件"); } } /** * 監(jiān)聽器類 */ @Slf4j @Component class MyEventListener2 implements ApplicationListener<MyEvent2> { @Override public void onApplicationEvent(MyEvent2 event) { log.info("監(jiān)聽器MyEventListener2收到={}", event); } @EventListener public void someMethod(MyEvent2 event) { log.info("監(jiān)聽器MyEventListener2@EventListener收到={}", event); } } /** * 監(jiān)聽器類 */ @Slf4j @Component class MyEventListenner1 { @EventListener public void someMethod(MyEvent2 event) { log.info("監(jiān)聽器MyEventListenner1收到={}", event); } } /** * 事件類 * * @param <T> */ @ToString class MyEvent2<T> extends ApplicationContextEvent { private T data; public MyEvent2(ApplicationContext source) { super(source); } public T getData() { return data; } public void setData(T data) { this.data = data; } }
運行日志:
2020-11-30 18:03:38.638 INFO 15792 --- [ main] c.example.controller.PublishEventDemo2 : Started PublishEventDemo2 in 9.571 seconds (JVM running for 12.677)
2020-11-30 18:03:39.355 INFO 15792 --- [ main] c.example.controller.PublishEventDemo2 : 開始發(fā)送事件
2020-11-30 18:03:39.358 INFO 15792 --- [ main] com.example.controller.MyEventListener2 : 監(jiān)聽器MyEventListener2@EventListener收到=MyEvent2(data=數(shù)據(jù))
2020-11-30 18:03:39.359 INFO 15792 --- [ main] c.example.controller.MyEventListenner1 : 監(jiān)聽器MyEventListenner1收到=MyEvent2(data=數(shù)據(jù))
2020-11-30 18:03:39.359 INFO 15792 --- [ main] com.example.controller.MyEventListener2 : 監(jiān)聽器MyEventListener2收到=MyEvent2(data=數(shù)據(jù))
2020-11-30 18:03:39.359 INFO 15792 --- [ main] c.example.controller.PublishEventDemo2 : 結(jié)束發(fā)送事件
2020-11-30 18:03:39.435 INFO 15792 --- [ Thread-3] o.s.s.concurrent.ThreadPoolTaskExecutor : Shutting down ExecutorService 'applicationTaskExecutor'
事件發(fā)送后,會等待事件執(zhí)行完畢,因此他們是同步的。若想異步執(zhí)行事件,可以把@Async加到監(jiān)聽方法上;
以上為個人經(jīng)驗,希望能給大家一個參考,也希望大家多多支持腳本之家。
相關(guān)文章
IDEA配置靜態(tài)資源熱加載操作(Springboot修改靜態(tài)資源不重啟)
這篇文章主要介紹了IDEA配置靜態(tài)資源熱加載操作(Springboot修改靜態(tài)資源不重啟),具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧2020-10-10spring啟動錯誤Singleton bean creation not al
本文主要介紹了spring啟動錯誤Singleton bean creation not allowed while the singletons of this factory are indestruction,文中通過示例代碼介紹的非常詳細,對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2023-07-07springboot業(yè)務(wù)功能實戰(zhàn)之告別輪詢websocket的集成使用
WebSocket使得客戶端和服務(wù)器之間的數(shù)據(jù)交換變得更加簡單,允許服務(wù)端主動向客戶端推送數(shù)據(jù),下面這篇文章主要給大家介紹了關(guān)于springboot業(yè)務(wù)功能實戰(zhàn)之告別輪詢websocket的集成使用,需要的朋友可以參考下2022-10-10Java并發(fā)編程之顯示鎖ReentrantLock和ReadWriteLock讀寫鎖
這篇文章主要介紹了Java并發(fā)編程之顯示鎖ReentrantLock和ReadWriteLock讀寫鎖,本文講解了ReentrantLock概況、Lock接口、Lock使用、輪詢鎖的和定時鎖、公平性、可中斷獲鎖獲取操作等內(nèi)容,需要的朋友可以參考下2015-04-04SpringBoot中的JPA(Java?Persistence?API)詳解
這篇文章主要介紹了SpringBoot中的JPA(Java?Persistence?API)詳解,JPA用于將?Java?對象映射到關(guān)系型數(shù)據(jù)庫中,它提供了一種面向?qū)ο蟮姆绞絹聿僮鲾?shù)據(jù)庫,使得開發(fā)者可以更加方便地進行數(shù)據(jù)持久化操作,需要的朋友可以參考下2023-07-07SpringBoot多數(shù)據(jù)源配置并通過注解實現(xiàn)動態(tài)切換數(shù)據(jù)源
本文主要介紹了SpringBoot多數(shù)據(jù)源配置并通過注解實現(xiàn)動態(tài)切換數(shù)據(jù)源,文中通過示例代碼介紹的非常詳細,對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2022-08-08