Spring Boot 自定義事件及監(jiān)聽
事件及監(jiān)聽并不是 SpringBoot 的新功能,Spring 框架早已提供了完善的事件監(jiān)聽機制,在 Spring 框架中實現(xiàn)事件監(jiān)聽的流程如下:
自定義事件,繼承 org.springframework.context.ApplicationEvent 抽象類
定義事件監(jiān)聽器,實現(xiàn) org.springframework.context.ApplicationListener 接口
在 Spring 容器中發(fā)布事件
實現(xiàn)自定義事件及監(jiān)聽
定義事件
1 //自定義事件
2 public class ApplicationEventTest extends ApplicationEvent {
3
4 public ApplicationEventTest(Object source) {
5 super(source);
6 }
7
8 /**
9 * 事件處理事項
10 * @param msg
11 */
12 public void printMsg(String msg)
13 {
14 System.out.println("監(jiān)聽到事件:"+ApplicationEventTest.class);
15 }
16 }
定義監(jiān)聽器
1 //自定義事件監(jiān)聽器
2 //@Component
3 public class ApplicationListenerTest implements ApplicationListener<ApplicationEventTest> {
4
5 @Override
6 public void onApplicationEvent(ApplicationEventTest event) {
7
8 event.printMsg(null);
9 }
10 }
在 Spring 容器中發(fā)布事件
1 public static void main(String[] args) {
2
3 SpringApplication application = new SpringApplication(SpringbootdemoApplication.class);
4 //需要把監(jiān)聽器加入到spring容器中
5 application.addListeners(new ApplicationListenerTest());
6 Set<ApplicationListener<?>> listeners = application.getListeners();
7 ConfigurableApplicationContext context = application.run(args);
8 //發(fā)布事件
9 context.publishEvent(new ApplicationEventTest(new Object()));
10
11 context.close();
12 }
上面的示例是在 SpringBoot 應用中簡單的測試一下。
實際開發(fā)中實現(xiàn)監(jiān)聽還有其他的方式,在 Spring 框架中提供了兩種事件監(jiān)聽的方式:
編程式:通過實現(xiàn) ApplicationListener 接口來監(jiān)聽指定類型的事件
注解式:通過在方法上加 @EventListener 注解的方式監(jiān)聽指定參數(shù)類型的事件,寫該類需要托管到 Spring 容器中
在 SpringBoot 應用中還可以通過配置的方式實現(xiàn)監(jiān)聽:
3. 通過 application.properties 中配置 context.listener.classes 屬性指定監(jiān)聽器
下面分別分析一下這三種監(jiān)聽方式
編程式實現(xiàn)監(jiān)聽
實現(xiàn) ApplicationListenser 接口:
1 @Component
2 public class ApplicationListenerTest implements ApplicationListener<ApplicationEventTest> {
3
4 @Override
5 public void onApplicationEvent(ApplicationEventTest event) {
6
7 event.printMsg(null);
8 }
9 }
控制臺輸出測試:
1 public static void main(String[] args) {
2
3 SpringApplication application = new SpringApplication(SpringbootdemoApplication.class);
4 //需要把監(jiān)聽器加入到spring容器中
5 //application.addListeners(new ApplicationListenerTest());
6 //Set<ApplicationListener<?>> listeners = application.getListeners();
7
8 ConfigurableApplicationContext context = application.run(args);
9 //發(fā)布事件
10 context.publishEvent(new ApplicationEventTest(new Object()));
11 }
那么我們跟蹤一下源碼,看一下事件是如何發(fā)布出去的,又是如何被監(jiān)聽到的呢?
AbstractApplicationContext.java 中截取部分代碼
1 protected void publishEvent(Object event, @Nullable ResolvableType eventType) {
2 Assert.notNull(event, "Event must not be null");
3 if (logger.isTraceEnabled()) {
4 logger.trace("Publishing event in " + getDisplayName() + ": " + event);
5 }
6
7 // Decorate event as an ApplicationEvent if necessary
8 /將object轉成ApplicationEvent
9 ApplicationEvent applicationEvent;
10 if (event instanceof ApplicationEvent) {
11 applicationEvent = (ApplicationEvent) event;
12 }
13 else {
14 applicationEvent = new PayloadApplicationEvent<>(this, event);
15 if (eventType == null) {
16 eventType = ((PayloadApplicationEvent) applicationEvent).getResolvableType();
17 }
18 }
19
20 // Multicast right now if possible - or lazily once the multicaster is initialized
22 if (this.earlyApplicationEvents != null) {
23 this.earlyApplicationEvents.add(applicationEvent);
24 }
25 else {
26 // SimpleApplicationEventMulticaster 獲取事件發(fā)布器,發(fā)布事件
27 getApplicationEventMulticaster().multicastEvent(applicationEvent, eventType);
28 }
29
30 // Publish event via parent context as well...
31 if (this.parent != null) {
32 if (this.parent instanceof AbstractApplicationContext) {
33 ((AbstractApplicationContext) this.parent).publishEvent(event, eventType);
34 }
35 else {
36 this.parent.publishEvent(event);
37 }
38 }
39 }
查看一下 ApplicationContext 類結構圖可以發(fā)現(xiàn):應用上下文 AbstractApplicationContext 實際還是通過繼承 ApplicationEventPublisher 接口,實現(xiàn)了其中的事件發(fā)布的方法,使得 Spring 應用上下文有了發(fā)布事件的功能,在 AbstractApplicationContext 內部通過 SimpleApplicationEventMulticaster 事件發(fā)布類,將具體事件 ApplicationEvent 發(fā)布出去。
那么事件發(fā)布出去后又是如何被監(jiān)聽到的呢?下面看一下具 Spring 中負責處理事件發(fā)布類 SimpleApplicationEventMulticaster 中 multicastEvent 方法具體實現(xiàn)過程
SimpleApplicationEventMulticaster.java 部分代碼,實際嘗試將當前事件逐個廣播到指定類型的監(jiān)聽器中(listeners 已經根據(jù)當前事件類型過濾了)
1 @Override
2 public void multicastEvent(final ApplicationEvent event, @Nullable ResolvableType eventType) {
3 ResolvableType type = (eventType != null ? eventType : resolveDefaultEventType(event));
4 // getApplicationListeners(event, type) 篩選監(jiān)聽器,在context.publish(ApplicationEvent event)中已經將事件傳入,getApplicationListeners中將可以根據(jù)這個event類型從Spring容器中檢索出符合條件的監(jiān)聽器
5
6 for (final ApplicationListener<?> listener : getApplicationListeners(event, type)) {
7 Executor executor = getTaskExecutor();
8 if (executor != null) {
9 executor.execute(() -> invokeListener(listener, event));
10 }
11 else {
12 //嘗試逐個向監(jiān)聽器廣播
13 invokeListener(listener, event);
14 }
15 }
16 }
@EventListener 注解方式實現(xiàn)
定義注解方法
@Component
public class MyEventHandleTest {
/**
* 參數(shù)為Object類型時,所有事件都會監(jiān)聽到
* 參數(shù)為指定類型事件時,該參數(shù)類型事件或者其子事件(子類)都可以接收到
*/
@EventListener
public void event(ApplicationEventTest event){
event.printMsg(null);
}
}
實現(xiàn)過程分析:
@EventListener 注解主要通過 EventListenerMethodProcessor 掃描出所有帶有 @EventListener 注解的方法,然后動態(tài)構造事件監(jiān)聽器,并將監(jiān)聽器托管到 Spring 應用上文中。
1 protected void processBean(
2 final List<EventListenerFactory> factories, final String beanName, final Class<?> targetType) {
3
4 if (!this.nonAnnotatedClasses.contains(targetType)) {
5 Map<Method, EventListener> annotatedMethods = null;
6 try {
7 //查找含有@EventListener注解的所有方法
8 annotatedMethods = MethodIntrospector.selectMethods(targetType,
9 (MethodIntrospector.MetadataLookup<EventListener>) method ->
10 AnnotatedElementUtils.findMergedAnnotation(method, EventListener.class));
11 }
12 catch (Throwable ex) {
13 // An unresolvable type in a method signature, probably from a lazy bean - let's ignore it.
14 if (logger.isDebugEnabled()) {
15 logger.debug("Could not resolve methods for bean with name '" + beanName + "'", ex);
16 }
17 }
18 if (CollectionUtils.isEmpty(annotatedMethods)) {
19 this.nonAnnotatedClasses.add(targetType);
20 if (logger.isTraceEnabled()) {
21 logger.trace("No @EventListener annotations found on bean class: " + targetType.getName());
22 }
23 }
24 else {
25 // Non-empty set of methods
26 ConfigurableApplicationContext context = getApplicationContext();
27 //遍歷含有@EventListener注解的方法
28 for (Method method : annotatedMethods.keySet()) {
29 for (EventListenerFactory factory : factories) {
30 if (factory.supportsMethod(method)) {
31 Method methodToUse = AopUtils.selectInvocableMethod(method, context.getType(beanName));
32 //動態(tài)構造相對應的事件監(jiān)聽器
33 ApplicationListener<?> applicationListener =
34 factory.createApplicationListener(beanName, targetType, methodToUse);
35 if (applicationListener instanceof ApplicationListenerMethodAdapter) {
36 ((ApplicationListenerMethodAdapter) applicationListener).init(context, this.evaluator);
37 }
38 //將監(jiān)聽器添加的Spring應用上下文中托管
39 context.addApplicationListener(applicationListener);
40 break;
41 }
42 }
43 }
44 if (logger.isDebugEnabled()) {
45 logger.debug(annotatedMethods.size() + " @EventListener methods processed on bean '" +
46 beanName + "': " + annotatedMethods);
47 }
48 }
49 }
50 }
在 application.properties 中配置 context.listener.classes
添加如下配置:
context.listener.classes=com.sl.springbootdemo.Listeners.ApplicationListenerTest
查看一下 DelegatingApplicationListener 類中實現(xiàn)邏輯:
1 public class DelegatingApplicationListener
2 implements ApplicationListener<ApplicationEvent>, Ordered {
3
4 private static final String PROPERTY_NAME = "context.listener.classes";
5
6 private int order = 0;
7 //Spring framework提供的負責處理發(fā)布事件的類,前面說的Spring應用上下文中也是通過這個類發(fā)布事件的
8 private SimpleApplicationEventMulticaster multicaster;
9
10 @Override
11 public void onApplicationEvent(ApplicationEvent event) {
12 if (event instanceof ApplicationEnvironmentPreparedEvent) {
13 // getListeners內部實現(xiàn)讀取context.listener.classes配置的監(jiān)聽器
14 List<ApplicationListener<ApplicationEvent>> delegates = getListeners(
15 ((ApplicationEnvironmentPreparedEvent) event).getEnvironment());
16 if (delegates.isEmpty()) {
17 return;
18 }
19 this.multicaster = new SimpleApplicationEventMulticaster();
20 for (ApplicationListener<ApplicationEvent> listener : delegates) {
21 this.multicaster.addApplicationListener(listener);
22 }
23 }
24 //發(fā)布事件
25 if (this.multicaster != null) {
26 this.multicaster.multicastEvent(event);
27 }
28 }
Spring-boot-{version}.jar 包中提供一個類 DelegatingApplicationListener,該類的作用是從 application.properties 中讀取配置 context.listener.classes,并將事件廣播給這些配置的監(jiān)聽器。通過前面一章對 SpringBoot 啟動流程分析,我們已經了解到 SpringBoot 啟動時會從 META-INF/spring.factories 中讀取 key 為 org.springframework.context.ApplicationListener 的所有監(jiān)聽器。DelegatingApplicationListener 的功能可以讓我們不需要創(chuàng)建 META-INF/spring.factories,直接在 application.properties 中配置即可。
作者:仍是少年
來源鏈接:
https://www.cnblogs.com/ashleyboy/p/9566579.html
獲取更多優(yōu)質文章,點擊關注
??????


