JAVA 8
Spring Boot 2.5.3
---
序章
Spring Boot 可以通過 【發佈事件、監聽事件、執行業務】 來進行通信。ben發佈於博客園
在官方文檔的 “SpringApplication 》 Application Events and Listeners”一節可以看到詳情,本文參考該文檔進行演示。
關鍵類或接口:
// 事件
public abstract class ApplicationEvent extends EventObject {
}
// 事件發佈者
@FunctionalInterface
public interface ApplicationEventPublisher {
}
// 事件發佈者Aware
public interface ApplicationEventPublisherAware extends Aware {
}
// 事件監聽器
@FunctionalInterface
public interface ApplicationListener<E extends ApplicationEvent> extends EventListener {
}
以上接口或類來自 spring-context 包中,其實,這是 spring framework 提供的功能。ben發佈於博客園
ApplicationEvent 的子類:
ApplicationEventPublisher 的子類:ben發佈於博客園
其它類或接口的層次關係,請自行在IDE中查看。
Spring Boot 啓動事件
這些事件都是 ApplicationEvent 的子類的子類,比如,ApplicationReadyEvent。ben發佈於博客園
自定義事件
除了框架中的事件,也可以通過繼承 ApplicationEvent 創建自己業務需要的事件。
比如,前面圖中的 MyEvent(也是本文測試的 事件):
public class MyEvent extends ApplicationEvent {
public String getContent() {
return content;
}
private String content;
public MyEvent(Object source, String content) {
super(source);
this.content = content;
}
}
官文中提到的 添加事件監聽器的方式:ben發佈於博客園
SpringApplication.addListeners(…)
SpringApplicationBuilder.listeners(…)
META-INF/spring.factories 中 org.springframework.context.ApplicationListener
試驗:發佈事件並處理
使用前面的 MyEvent。
實現 ApplicationEventPublisherAware 接口 創建 事件發佈者。
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.ApplicationEvent;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.context.ApplicationEventPublisherAware;
import org.springframework.stereotype.Component;
// 1、事件發佈者組件
@Component
@Slf4j
public class AppEventPublisher implements ApplicationEventPublisherAware {
private ApplicationEventPublisher applicationEventPublisher;
@Override
public void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher) {
this.applicationEventPublisher = applicationEventPublisher;
log.info("applicationEventPublisher 設置完畢");
}
// 2、發佈事件
public boolean sendEvent(ApplicationEvent event) {
applicationEventPublisher.publishEvent(event);
return true;
}
}
實現 ApplicationListener 接口 創建 事件監聽器:
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.ApplicationListener;
@Slf4j
public class MyEventListener implements ApplicationListener<MyEvent> {
@Override
public void onApplicationEvent(MyEvent event) {
log.info("MyEventListener 處理事件 MyEvent event={}, source={}, content={}",
event, event.getSource(), event.getContent());
}
}
建立 第二個 事件監聽器:ben發佈於博客園
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.ApplicationListener;
@Slf4j
public class MyEventListener2 implements ApplicationListener<MyEvent> {
@Override
public void onApplicationEvent(MyEvent event) {
log.info("MyEventListener2 處理事件 MyEvent event={}, source={}, content={}",
event, event.getSource(), event.getContent());
}
}
項目啓動時,註冊 事件監聽器:
public static void main(String[] args) {
SpringApplication app = new SpringApplication(BootwebApplication.class);
app.setBannerMode(Banner.Mode.OFF);
// 1、單個監聽器
app.addListeners(new MyEventListener());
// 2、多個監聽器:相同類型
// app.addListeners(new MyEventListener(), new MyEventListener());
// 3、多個監聽器:不同類型
// app.addListeners(new MyEventListener(), new MyEventListener2());
ConfigurableApplicationContext ctx = app.run(args);
log.info("----BootwebApplication已啓動----");
}
對 上面的 1、2、3 進行測試。ben發佈於博客園
測試接口:/api/appEvent/send
@RestController
@RequestMapping(value = "/api/appEvent")
@RequiredArgsConstructor
@Slf4j
public class AppEventController {
private final AppEventPublisher appEventPublisher;
@GetMapping(value = "send")
public boolean sendEvent(@RequestParam String content) {
appEventPublisher.sendEvent(new MyEvent(this.appEventPublisher, content));
log.info("MyEvent 發送完成:content={}", content);
return true;
}
}
測試方式:
啓動後,調用接口發送事件。ben發佈於博客園
測試1:單個監聽器
結果1:
2023-02-06 19:50:09.257 INFO 21072 --- [nio-8080-exec-2] com.lib.bootweb.events.MyEventListener :
MyEventListener 處理事件 MyEvent event=com.lib.bootweb.events.MyEvent[source=com.lib.bootweb.events.AppEventPublisher@1483bdad],
source=com.lib.bootweb.events.AppEventPublisher@1483bdad, content=abcde
2023-02-06 19:50:09.258 INFO 21072 --- [nio-8080-exec-2] com.lib.bootweb.api.AppEventController :
MyEvent 發送完成:content=abcde
測試2:多個相同類型監聽器
結果2:
2023-02-06 19:52:32.834 INFO 31280 --- [nio-8080-exec-1] com.lib.bootweb.events.MyEventListener :
MyEventListener 處理事件 MyEvent event=com.lib.bootweb.events.MyEvent[source=com.lib.bootweb.events.AppEventPublisher@2915626e],
source=com.lib.bootweb.events.AppEventPublisher@2915626e, content=dddddd
2023-02-06 19:52:32.835 INFO 31280 --- [nio-8080-exec-1] com.lib.bootweb.events.MyEventListener :
MyEventListener 處理事件 MyEvent event=com.lib.bootweb.events.MyEvent[source=com.lib.bootweb.events.AppEventPublisher@2915626e],
source=com.lib.bootweb.events.AppEventPublisher@2915626e, content=dddddd
2023-02-06 19:52:32.835 INFO 31280 --- [nio-8080-exec-1] com.lib.bootweb.api.AppEventController :
MyEvent 發送完成:content=dddddd
測試3:多個不同類型監聽器
結果3:ben發佈於博客園
2023-02-06 19:53:38.173 INFO 11068 --- [nio-8080-exec-2] com.lib.bootweb.events.MyEventListener :
MyEventListener 處理事件 MyEvent event=com.lib.bootweb.events.MyEvent[source=com.lib.bootweb.events.AppEventPublisher@1e9b970e],
source=com.lib.bootweb.events.AppEventPublisher@1e9b970e, content=zzzzz
2023-02-06 19:53:38.174 INFO 11068 --- [nio-8080-exec-2] com.lib.bootweb.events.MyEventListener2 :
MyEventListener2 處理事件 MyEvent event=com.lib.bootweb.events.MyEvent[source=com.lib.bootweb.events.AppEventPublisher@1e9b970e],
source=com.lib.bootweb.events.AppEventPublisher@1e9b970e, content=zzzzz
2023-02-06 19:53:38.174 INFO 11068 --- [nio-8080-exec-2] com.lib.bootweb.api.AppEventController : MyEvent 發送完成:content=zzzzz
小結
三種方式發送事件,事件都被正確處理了。
不過,這裏存在一個問題:接口調用和事件處理是同步進行的,因爲 AppEventPublisher#sendEvent 有返回值——boolean。ben發佈於博客園
添加無返回指定 AppEventPublisher#sendEventAsync (使用 @EnableAsync、@Async 註解)即可實現異步處理事件——接口調用完即返回:
@Async
public void sendEventAsync(ApplicationEvent event) {
applicationEventPublisher.publishEvent(event);
log.info("事件發送完畢:event={}", event);
}
在接口中調用 sendEventAsync:
// 異步
appEventPublisher.sendEventAsync(new MyEvent(this.appEventPublisher, content));
測試結果:接口第一時間返回了,線程名稱不同了,事件被異步處理了
2023-02-06 19:59:07.033 INFO 23592 --- [nio-8080-exec-1] com.lib.bootweb.api.AppEventController :
MyEvent 發送完成:content=async
2023-02-06 19:59:07.044 INFO 23592 --- [ task-1] com.lib.bootweb.events.MyEventListener :
MyEventListener 處理事件 MyEvent event=com.lib.bootweb.events.MyEvent[source=com.lib.bootweb.events.AppEventPublisher@7052f2b2],
source=com.lib.bootweb.events.AppEventPublisher@7052f2b2, content=async
2023-02-06 19:59:07.044 INFO 23592 --- [ task-1] com.lib.bootweb.events.MyEventListener2 :
MyEventListener2 處理事件 MyEvent event=com.lib.bootweb.events.MyEvent[source=com.lib.bootweb.events.AppEventPublisher@7052f2b2],
source=com.lib.bootweb.events.AppEventPublisher@7052f2b2, content=async
2023-02-06 19:59:07.045 INFO 23592 --- [ task-1] c.lib.bootweb.events.AppEventPublisher :
事件發送完畢:event=com.lib.bootweb.events.MyEvent[source=com.lib.bootweb.events.AppEventPublisher@7052f2b2]
試驗:使用 @EventListener 註解監聽並處理事件
監聽自定義 MyEvent
處理上面的 MyEvent 事件:
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.event.EventListener;
// 1、@Configuration 必須
@Configuration
@Slf4j
public class AppEventConfig {
// 2、定義事件監聽器
@EventListener
public void listenMyEvent(MyEvent myEvent) {
log.info("在 AppEventConfig#listenMyEvent 方法處理事件:myEvent={}, {}",
myEvent.getSource(), myEvent.getContent());
}
}
註釋掉 啓動類中使用 addListeners 方法 添加的監聽器,執行測試——調用接口。ben發佈於博客園
測試結果:“@EventListener” 爲 請求參數 content 的值
2023-02-06 20:08:57.176 INFO 3020 --- [nio-8080-exec-3] com.lib.bootweb.api.AppEventController :
MyEvent 發送完成:content=@EventListener
2023-02-06 20:08:57.185 INFO 3020 --- [ task-1] com.lib.bootweb.events.AppEventConfig :
在 AppEventConfig#listenMyEvent 方法處理事件:myEvent=com.lib.bootweb.events.AppEventPublisher@41dc5ca0, @EventListener
2023-02-06 20:08:57.185 INFO 3020 --- [ task-1] c.lib.bootweb.events.AppEventPublisher :
事件發送完畢:event=com.lib.bootweb.events.MyEvent[source=com.lib.bootweb.events.AppEventPublisher@41dc5ca0]
監聽更多事件
@EventListener 註解源碼 有一些參數,其中,value(同 classes) 可以 設置多個 監聽事件:ben發佈於博客園
注,還有其它屬性 condition、id。
測試代碼:
@EventListener(value = {ApplicationStartingEvent.class,
ApplicationEnvironmentPreparedEvent.class,
ApplicationPreparedEvent.class,
ApplicationStartedEvent.class,
ApplicationReadyEvent.class,
ApplicationFailedEvent.class,
WebServerInitializedEvent.class,
ContextRefreshedEvent.class,
MyEvent.class
})
public void listenMyEvent2(ApplicationEvent event) {
log.info("在 AppEventConfig#listenMyEvent2 方法處理事件:event={}, class={}",
event.getSource(), event.getClass());
}
測試結果:ben發佈於博客園
// 啓動Web應用:ServletWebServerInitializedEvent、ContextRefreshedEvent、ApplicationStartedEvent、ApplicationReadyEvent
[ main] com.lib.bootweb.events.AppEventConfig : 在 AppEventConfig#listenMyEvent2 方法處理事件:
event=org.springframework.boot.web.embedded.tomcat.TomcatWebServer@78010562,
class=class org.springframework.boot.web.servlet.context.ServletWebServerInitializedEvent
[ main] com.lib.bootweb.events.AppEventConfig : 在 AppEventConfig#listenMyEvent2 方法處理事件:
event=org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext@65f095f8,
started on Tue Feb 07 20:20:55 CST 2023,
class=class org.springframework.context.event.ContextRefreshedEvent
[ main] com.lib.bootweb.BootwebApplication : Started BootwebApplication in 3.471 seconds (JVM running for 3.883)
[ main] com.lib.bootweb.events.AppEventConfig : 在 AppEventConfig#listenMyEvent2 方法處理事件:
event=org.springframework.boot.SpringApplication@35eb4a3b, class=class org.springframework.boot.context.event.ApplicationStartedEvent
[ main] c.l.b.config.ModifyObjectMapperRunner : ModifyObjectMapperRunner END
[ main] com.lib.bootweb.events.AppEventConfig : 在 AppEventConfig#listenMyEvent2 方法處理事件:
event=org.springframework.boot.SpringApplication@35eb4a3b, class=class org.springframework.boot.context.event.ApplicationReadyEvent
[ main] com.lib.bootweb.BootwebApplication : ----BootwebApplication已啓動----
[nio-8080-exec-1] o.a.c.c.C.[Tomcat].[localhost].[/] : Initializing Spring DispatcherServlet 'dispatcherServlet'
[nio-8080-exec-1] o.s.web.servlet.DispatcherServlet : Initializing Servlet 'dispatcherServlet'
[nio-8080-exec-1] o.s.web.servlet.DispatcherServlet : Completed initialization in 1 ms
// 調用接口
[nio-8080-exec-1] com.lib.bootweb.api.AppEventController : MyEvent 發送完成:content=listenMyEvent2
[ task-1] com.lib.bootweb.events.AppEventConfig : 在 AppEventConfig#listenMyEvent 方法處理事件:
myEvent=com.lib.bootweb.events.AppEventPublisher@1140a495, listenMyEvent2
[ task-1] com.lib.bootweb.events.AppEventConfig : 在 AppEventConfig#listenMyEvent2 方法處理事件:
event=com.lib.bootweb.events.AppEventPublisher@1140a495, class=class com.lib.bootweb.events.MyEvent
[ task-1] c.lib.bootweb.events.AppEventPublisher : 事件發送完畢:
event=com.lib.bootweb.events.MyEvent[source=com.lib.bootweb.events.AppEventPublisher@1140a495]
小結
從這裏可以看到,使用 @EventListener 監聽事件會更方便。
AppEventConfig 除了使用 @Configuration 註解,也可以使用 @Component 註解。
ApplicationEvent 來自 Spring框架!
一直奇怪 @EventListener 在 spring boot 的文檔中 怎麼沒有詳細說明,原來,它們是來自 spring 框架。
在 spring framework 官文的 core 部分可以找到相關內容:
在 該官文中,有關於 事件 的詳盡描述,包括 @EventListener註解的使用。
https://spring.io/projects/spring-framework
下圖是 官文的 html版本,可以下載其 pdf版本:
https://docs.spring.io/spring-framework/docs/current/reference/
注,本文作者未看完。
注意 spring framework 中 ApplicationContextEvent 的幾個子類:做一些監聽會很有用。
比如,應該關閉時調用 ContextClosedEvent 事件。
---END---
本文鏈接:
https://www.cnblogs.com/luo630/p/17090167.html
ben發佈於博客園
參考資料
1、Spring中的Aware解析理解
by Young丶
於 2020-09-09 14:33:05 發佈
原文鏈接:https://blog.csdn.net/agonie201218/article/details/108489141
關於Spring中 Aware 的使用,還需探究——用處頗大。
2、
ben發佈於博客園