您好,登錄后才能下訂單哦!
這篇文章主要介紹了Spring事件發布與監聽機制的示例分析,具有一定借鑒價值,感興趣的朋友可以參考下,希望大家閱讀完這篇文章之后大有收獲,下面讓小編帶著大家一起了解一下。
Spring 提供了 ApplicationContext
事件機制,可以發布和監聽事件,這個特性非常有用。
Spring 內置了一些事件和監聽器,例如在 Spring 容器啟動前,Spring 容器啟動后,應用啟動失敗后等事件發生后,監聽在這些事件上的監聽器會做出相應的響應處理。
當然,我們也可以自定義監聽器,監聽 Spring 原有的事件。或者自定義我們自己的事件和監聽器,在必要的時間點發布事件,然后監聽器監聽到事件就做出響應處理。
ApplicationContext 事件機制采用觀察者設計模式來實現,通過 ApplicationEvent
事件類和 ApplicationListener
監聽器接口,可以實現 ApplicationContext
事件發布與處理。
每當 ApplicationContext 發布 ApplicationEvent 時,如果 Spring 容器中有 ApplicationListener bean,則監聽器會被觸發執行相應的處理。當然,ApplicationEvent 事件的發布需要顯示觸發,要么 Spring 顯示觸發,要么我們顯示觸發。
定義應用監聽器需要實現的接口。此接口繼承了 JDK 標準的事件監聽器接口 EventListener
,EventListener 接口是一個空的標記接口,推薦所有事件監聽器必須要繼承它。
package org.springframework.context; import java.util.EventListener; @FunctionalInterface public interface ApplicationListener<E extends ApplicationEvent> extends EventListener { /** * 處理應用事件 */ void onApplicationEvent(E event); }
package java.util; public interface EventListener { }
ApplicationListener 是個泛型接口,我們自定義此接口的實現類時,如果指定了泛型的具體事件類,那么只會監聽此事件。如果不指定具體的泛型,則會監聽 ApplicationEvent 抽象類的所有子類事件。
如下我們定義一個監聽器,監聽具體的事件,例如監聽 ApplicationStartedEvent 事件。
package com.chenpi; import lombok.extern.slf4j.Slf4j; import org.springframework.boot.context.event.ApplicationStartedEvent; import org.springframework.context.ApplicationListener; import org.springframework.stereotype.Component; /** * @Description * @Author 陳皮 * @Date 2021/6/26 * @Version 1.0 */ @Slf4j @Component public class MyApplicationListener implements ApplicationListener<ApplicationStartedEvent> { @Override public void onApplicationEvent(ApplicationStartedEvent event) { log.info(">>> MyApplicationListener:{}", event); } }
啟動服務,會發現在服務啟動后,此監聽器被觸發了。
如果不指定具體的泛型類,則會監聽 ApplicationEvent 抽象類的所有子類事件。如下所示:
package com.chenpi; import lombok.extern.slf4j.Slf4j; import org.springframework.context.ApplicationEvent; import org.springframework.context.ApplicationListener; import org.springframework.stereotype.Component; /** * @Description * @Author 陳皮 * @Date 2021/6/26 * @Version 1.0 */ @Slf4j @Component public class MyApplicationListener implements ApplicationListener { @Override public void onApplicationEvent(ApplicationEvent event) { log.info(">>> MyApplicationListener:{}", event); } }
注意,監聽器類的 bean 要注入到 Spring 容器中,不然不會生效。一種是使用注解注入,例如 @Component
。另外可以使用 SpringApplicationBuilder.listeners()
方法添加,不過這兩種方式有區別的,看以下示例。
首先我們使用 @Component 注解方式,服務啟動時,監視到了2個事件:
ApplicationStartedEvent
ApplicationReadyEvent
package com.chenpi; import lombok.extern.slf4j.Slf4j; import org.springframework.boot.context.event.SpringApplicationEvent; import org.springframework.context.ApplicationListener; import org.springframework.stereotype.Component; /** * @Description * @Author 陳皮 * @Date 2021/6/26 * @Version 1.0 */ @Slf4j @Component public class MyApplicationListener implements ApplicationListener<SpringApplicationEvent> { @Override public void onApplicationEvent(SpringApplicationEvent event) { log.info(">>> MyApplicationListener:{}", event); } }
而使用 SpringApplicationBuilder.listeners()
方法添加監聽器,服務啟動時,監聽到了5個事件:
ApplicationEnvironmentPreparedEvent
ApplicationContextInitializedEvent
ApplicationPreparedEvent
ApplicationStartedEvent
ApplicationReadyEvent
package com.chenpi; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.boot.builder.SpringApplicationBuilder; import org.springframework.context.ConfigurableApplicationContext; @SpringBootApplication public class Application { public static void main(String[] args) { // SpringApplication.run(Application.class, args); SpringApplication app = new SpringApplicationBuilder(Application.class) .listeners(new MyApplicationListener()).build(); app.run(args); } }
其實這是和監聽器 bean 注冊的時機有關,@component 注解的監聽器 bean 只有在 bean 初始化注冊完后才能使用;而通過 SpringApplicationBuilder.listeners() 添加的監聽器 bean 是在容器啟動前,所以監聽到的事件比較多。但是注意,這兩個不要同時使用,不然監聽器會重復執行兩遍。
如果你想在監聽器 bean 中注入其他 bean(例如 @Autowired),那最好是使用注解形式,因為如果太早發布監聽器,可能其他 bean 還未初始化完成,可能會報錯。
ApplicationEvent 是所有應用事件需要繼承的抽象類。它繼承了 EventObject
類,EventObject 是所有事件的根類,這個類有個 Object 類型的對象 source,代表事件源。所有繼承它的類的構造函數都必須要顯示傳遞這個事件源。
package org.springframework.context; import java.util.EventObject; public abstract class ApplicationEvent extends EventObject { private static final long serialVersionUID = 7099057708183571937L; // 發布事件的系統時間 private final long timestamp; public ApplicationEvent(Object source) { super(source); this.timestamp = System.currentTimeMillis(); } public final long getTimestamp() { return this.timestamp; } }
package java.util; public class EventObject implements java.io.Serializable { private static final long serialVersionUID = 5516075349620653480L; protected transient Object source; public EventObject(Object source) { if (source == null) throw new IllegalArgumentException("null source"); this.source = source; } public Object getSource() { return source; } public String toString() { return getClass().getName() + "[source=" + source + "]"; } }
在 Spring 中,比較重要的事件類是 SpringApplicationEvent
。Spring 有一些內置的事件,當完成某種操作時會觸發某些事件。這些內置事件繼承 SpringApplicationEvent
抽象類。SpringApplicationEvent 繼承 ApplicationEvent 并增加了字符串數組參數字段 args。
/** * Base class for {@link ApplicationEvent} related to a {@link SpringApplication}. * * @author Phillip Webb * @since 1.0.0 */ @SuppressWarnings("serial") public abstract class SpringApplicationEvent extends ApplicationEvent { private final String[] args; public SpringApplicationEvent(SpringApplication application, String[] args) { super(application); this.args = args; } public SpringApplication getSpringApplication() { return (SpringApplication) getSource(); } public final String[] getArgs() { return this.args; } }
我們可以編寫自己的監聽器,然后監聽這些事件,實現自己的業務邏輯。例如編寫 ApplicationListener 接口的實現類,監聽 ContextStartedEvent 事件,當應用容器 ApplicationContext 啟動時,會發布該事件,所以我們編寫的監聽器會被觸發。
ContextRefreshedEvent:ApplicationContext 被初始化或刷新時,事件被發布。ConfigurableApplicationContext接口中的 refresh() 方法被調用也會觸發事件發布。初始化是指所有的 Bean 被成功裝載,后處理 Bean 被檢測并激活,所有單例 Bean 被預實例化,ApplicationContext 容器已就緒可用。
ContextStartedEvent:應用程序上下文被刷新后,但在任何 ApplicationRunner 和 CommandLineRunner 被調用之前,發布此事件。
ApplicationReadyEvent:此事件會盡可能晚地被發布,以表明應用程序已準備好為請求提供服務。事件源是SpringApplication 本身,但是要注意修改它的內部狀態,因為到那時所有初始化步驟都已經完成了。
ContextStoppedEvent:ConfigurableApplicationContext 接口的 stop() 被調用停止 ApplicationContext 時,事件被發布。
ContextClosedEvent:ConfigurableApplicationContext 接口的 close() 被調用關閉 ApplicationContext 時,事件被發布。注意,一個已關閉的上下文到達生命周期末端后,它不能被刷新或重啟。
ApplicationFailedEvent:當應用啟動失敗后發布事件。
ApplicationEnvironmentPreparedEvent:事件是在 SpringApplication 啟動時發布的,并且首次檢查和修改 Environment 時,此時上 ApplicationContext 還沒有創建。
ApplicationPreparedEvent:事件發布時,SpringApplication 正在啟動,ApplicationContext 已經完全準備好,但沒有刷新。在這個階段,將加載 bean definitions 并準備使用 Environment。
RequestHandledEvent:這是一個 web 事件,只能應用于使用 DispatcherServlet 的 Web 應用。在使用 Spring 作為前端的 MVC 控制器時,當 Spring 處理用戶請求結束后,系統會自動觸發該事件。
前面介紹了自定義監聽器,然后監聽 Spring 原有的事件。下面介紹自定義事件和自定義監聽器,然后在程序中發布事件,觸發監聽器執行,實現自己的業務邏輯。
首先自定義事件,繼承 ApplicationEvent
,當然事件可以自定義自己的屬性。
package com.chenpi; import lombok.Getter; import lombok.Setter; import lombok.ToString; import org.springframework.context.ApplicationEvent; /** * @Description 自定義事件 * @Author 陳皮 * @Date 2021/6/26 * @Version 1.0 */ @Getter @Setter public class MyApplicationEvent extends ApplicationEvent { // 事件可以增加自己的屬性 private String myField; public MyApplicationEvent(Object source, String myField) { // 綁定事件源 super(source); this.myField = myField; } @Override public String toString() { return "MyApplicationEvent{" + "myField='" + myField + '\'' + ", source=" + source + '}'; } }
然后自定義監聽器,監聽我們自定義的事件。
package com.chenpi; import lombok.extern.slf4j.Slf4j; import org.springframework.context.ApplicationListener; /** * @Description 自定義監聽器 * @Author 陳皮 * @Date 2021/6/26 * @Version 1.0 */ @Slf4j public class MyApplicationListener implements ApplicationListener<MyApplicationEvent> { @Override public void onApplicationEvent(MyApplicationEvent event) { log.info(">>> MyApplicationListener:{}", event); } }
注冊監聽器和發布事件。注冊監聽器上面講解了有兩種方式。事件的發布可以通過 ApplicationEventPublisher.publishEvent()
方法。此處演示直接用 configurableApplicationContext 發布,它實現了 ApplicationEventPublisher 接口。
package com.chenpi; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.boot.builder.SpringApplicationBuilder; import org.springframework.context.ConfigurableApplicationContext; @SpringBootApplication public class Application { public static void main(String[] args) { // SpringApplication.run(Application.class, args); // 注冊監聽器 SpringApplication app = new SpringApplicationBuilder(Application.class) .listeners(new MyApplicationListener()).build(); ConfigurableApplicationContext configurableApplicationContext = app.run(args); // 方便演示,在項目啟動后發布事件,當然也可以在其他操作和其他時間點發布事件 configurableApplicationContext .publishEvent(new MyApplicationEvent("我是事件源,項目啟動成功后發布事件", "我是自定義事件屬性")); } }
啟動服務,結果顯示確實監聽到發布的事件了。
2021-06-26 16:15:09.584 INFO 10992 --- [ main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat started on port(s): 8081 (http) with context path '' 2021-06-26 16:15:09.601 INFO 10992 --- [ main] com.chenpi.Application : Started Application in 2.563 seconds (JVM running for 4.012) 2021-06-26 16:15:09.606 INFO 10992 --- [ main] com.chenpi.MyApplicationListener : >>> MyApplicationListener:MyApplicationEvent{myField='我是自定義事件屬性', source=我是事件源,項目啟動成功后發布事件 }
事件監聽機制能達到分發,解耦效果。例如可以在業務類中發布事件,讓監聽在此事件的監聽器執行自己的業務處理。例如:
package com.chenpi; import org.springframework.context.ApplicationEventPublisher; import org.springframework.context.ApplicationEventPublisherAware; import org.springframework.stereotype.Service; /** * @Description * @Author 陳皮 * @Date 2021/6/26 * @Version 1.0 */ @Service public class MyService implements ApplicationEventPublisherAware { private ApplicationEventPublisher applicationEventPublisher; @Override public void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher) { this.applicationEventPublisher = applicationEventPublisher; } public void testEvent() { applicationEventPublisher .publishEvent(new MyApplicationEvent("我是事件源", "我是自定義事件屬性")); } }
除了實現 ApplicationListener 接口創建監聽器外,Spring 還提供了注解 @EventListener
來創建監聽器。
package com.chenpi; import org.springframework.context.event.EventListener; import org.springframework.stereotype.Component; import lombok.extern.slf4j.Slf4j; /** * @Description 自定義監聽器 * @Author 陳皮 * @Date 2021/6/26 * @Version 1.0 */ @Slf4j @Component public class MyApplicationListener01 { @EventListener public void onApplicationEvent(MyApplicationEvent event) { log.info(">>> MyApplicationListener:{}", event); } }
而且注解還可以通過條件過濾只監聽指定條件的事件。例如事件的 myField 屬性的值等于"陳皮"的事件。
package com.chenpi; import org.springframework.context.event.EventListener; import org.springframework.stereotype.Component; import lombok.extern.slf4j.Slf4j; /** * @Description 自定義監聽器 * @Author 陳皮 * @Date 2021/6/26 * @Version 1.0 */ @Slf4j @Component public class MyApplicationListener01 { @EventListener(condition = "#event.myField.equals('陳皮')") public void onApplicationEvent(MyApplicationEvent event) { log.info(">>> MyApplicationListener:{}", event); } }
還可以在同一個類中定義多個監聽,對同一個事件的不同監聽還可以指定順序。order 值越小越先執行。
package com.chenpi; import org.springframework.context.event.EventListener; import org.springframework.core.annotation.Order; import org.springframework.stereotype.Component; import lombok.extern.slf4j.Slf4j; /** * @Description 自定義監聽器 * @Author 陳皮 * @Date 2021/6/26 * @Version 1.0 */ @Slf4j @Component public class MyApplicationListener01 { @Order(2) @EventListener public void onApplicationEvent(MyApplicationEvent event) { log.info(">>> onApplicationEvent order=2:{}", event); } @Order(1) @EventListener public void onApplicationEvent01(MyApplicationEvent event) { log.info(">>> onApplicationEvent order=1:{}", event); } @EventListener public void otherEvent(YourApplicationEvent event) { log.info(">>> otherEvent:{}", event); } }
執行結果如下:
>>> onApplicationEvent order=1:MyApplicationEvent{myField='陳皮', source=我是事件源}
>>> onApplicationEvent order=2:MyApplicationEvent{myField='陳皮', source=我是事件源}
>>> otherEvent:MyApplicationEvent{myField='我是自定義事件屬性01', source=我是事件源01}
事件的監聽處理是同步的,如下:
package com.chenpi; import lombok.extern.slf4j.Slf4j; import org.springframework.context.ApplicationEventPublisher; import org.springframework.context.ApplicationEventPublisherAware; import org.springframework.stereotype.Service; /** * @Description * @Author 陳皮 * @Date 2021/6/26 * @Version 1.0 */ @Service @Slf4j public class MyService implements ApplicationEventPublisherAware { private ApplicationEventPublisher applicationEventPublisher; @Override public void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher) { this.applicationEventPublisher = applicationEventPublisher; } public void testEvent() { log.info(">>> testEvent begin"); applicationEventPublisher.publishEvent(new MyApplicationEvent("我是事件源", "陳皮")); applicationEventPublisher.publishEvent(new YourApplicationEvent("我是事件源01", "我是自定義事件屬性01")); log.info(">>> testEvent end"); } }
執行結果如下:
2021-06-26 20:34:27.990 INFO 12936 --- [nio-8081-exec-1] com.chenpi.MyService : >>> testEvent begin
2021-06-26 20:34:27.990 INFO 12936 --- [nio-8081-exec-1] com.chenpi.MyApplicationListener01 : >>> onApplicationEvent order=1:MyApplicationEvent{myField='陳皮', source=我是事件源}
2021-06-26 20:34:27.991 INFO 12936 --- [nio-8081-exec-1] com.chenpi.MyApplicationListener01 : >>> onApplicationEvent order=2:MyApplicationEvent{myField='陳皮', source=我是事件源}
2021-06-26 20:34:27.992 INFO 12936 --- [nio-8081-exec-1] com.chenpi.MyApplicationListener01 : >>> otherEvent:MyApplicationEvent{myField='我是自定義事件屬性01', source=我是事件源01}
2021-06-26 20:34:27.992 INFO 12936 --- [nio-8081-exec-1] com.chenpi.MyService : >>> testEvent end
不過,我們也可以顯示指定異步方式去執行監聽器,記得在服務添加 @EnableAsync
注解開啟異步注解。
package com.chenpi; import org.springframework.context.event.EventListener; import org.springframework.core.annotation.Order; import org.springframework.scheduling.annotation.Async; import org.springframework.stereotype.Component; import lombok.extern.slf4j.Slf4j; /** * @Description 自定義監聽器 * @Author 陳皮 * @Date 2021/6/26 * @Version 1.0 */ @Slf4j @Component public class MyApplicationListener01 { @Async @Order(2) @EventListener public void onApplicationEvent(MyApplicationEvent event) { log.info(">>> onApplicationEvent order=2:{}", event); } @Order(1) @EventListener public void onApplicationEvent01(MyApplicationEvent event) { log.info(">>> onApplicationEvent order=1:{}", event); } @Async @EventListener public void otherEvent(YourApplicationEvent event) { log.info(">>> otherEvent:{}", event); } }
執行結果如下,注意打印的線程名。
2021-06-26 20:37:04.807 INFO 9092 --- [nio-8081-exec-1] com.chenpi.MyService : >>> testEvent begin 2021-06-26 20:37:04.819 INFO 9092 --- [nio-8081-exec-1] com.chenpi.MyApplicationListener01 : >>> onApplicationEvent order=1:MyApplicationEvent{myField='陳皮', source=我是事件源} 2021-06-26 20:37:04.831 INFO 9092 --- [ task-1] com.chenpi.MyApplicationListener01 : >>> onApplicationEvent order=2:MyApplicationEvent{myField='陳皮', source=我是事件源} 2021-06-26 20:37:04.831 INFO 9092 --- [nio-8081-exec-1] com.chenpi.MyService : >>> testEvent end 2021-06-26 20:37:04.831 INFO 9092 --- [ task-2] com.chenpi.MyApplicationListener01 : >>> otherEvent:MyApplicationEvent{myField='我是自定義事件屬性01', source=我是事件源01 }
感謝你能夠認真閱讀完這篇文章,希望小編分享的“Spring事件發布與監聽機制的示例分析”這篇文章對大家有幫助,同時也希望大家多多支持億速云,關注億速云行業資訊頻道,更多相關知識等著你來學習!
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。