Spring Boot事件(Events)及監聽器(Listeners)

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發佈於博客園

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章