Spring Boot(六)自定義事件及監聽

事件及監聽並非SpringBoot的新功能,Spring框架早已提供了完善的事件監聽機制,在Spring框架中實現事件監聽的流程以下:java

  1. 自定義事件,繼承org.springframework.context.ApplicationEvent抽象類spring

  2. 定義事件監聽器,實現org.springframework.context.ApplicationListener接口編程

  3. 在Spring容器中發佈事件springboot

實現自定義事件及監聽

  • 定義事件
 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("監聽到事件:"+ApplicationEventTest.class);
15     }
16 }
  • 定義監聽器
 1 //自定義事件監聽器
 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容器中發佈事件
 1 public static void main(String[] args) {
 2 
 3    SpringApplication application = new SpringApplication(SpringbootdemoApplication.class);
 4    //須要把監聽器加入到spring容器中
 5    application.addListeners(new ApplicationListenerTest());
 6    Set<ApplicationListener<?>> listeners = application.getListeners();
 7    ConfigurableApplicationContext context =  application.run(args);
 8    //發佈事件
 9    context.publishEvent(new ApplicationEventTest(new Object()));
10 
11    context.close();
12 }

上面的示例是在SpringBoot應用中簡單的測試一下。app

實際開發中實現監聽還有其餘的方式,在Spring框架中提供了兩種事件監聽的方式:框架

  1. 編程式:經過實現ApplicationListener接口來監聽指定類型的事件ide

  2. 註解式:經過在方法上加@EventListener註解的方式監聽指定參數類型的事件,寫該類須要託管到Spring容器中測試

 在SpringBoot應用中還能夠經過配置的方式實現監聽:this

   3. 經過application.properties中配置context.listener.classes屬性指定監聽器lua

下面分別分析一下這三種監聽方式

編程式實現監聽

實現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    //須要把監聽器加入到spring容器中
 5    //application.addListeners(new ApplicationListenerTest());
 6    //Set<ApplicationListener<?>> listeners = application.getListeners();
 7 
 8    ConfigurableApplicationContext context =  application.run(args);
 9    //發佈事件
10    context.publishEvent(new ApplicationEventTest(new Object()));
11 }

那麼咱們跟蹤一下源碼,看一下事件是如何發佈出去的,又是如何被監聽到的呢?

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 獲取事件發佈器,發佈事件
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類結構圖能夠發現:應用上下文AbstractApplicationContext實際仍是經過繼承ApplicationEventPublisher接口,實現了其中的事件發佈的方法,使得Spring應用上下文有了發佈事件的功能,在AbstractApplicationContext內部經過SimpleApplicationEventMulticaster事件發佈類,將具體事件ApplicationEvent發佈出去。

那麼事件發佈出去後又是如何被監聽到的呢?下面看一下具Spring中負責處理事件發佈類SimpleApplicationEventMulticaster 中multicastEvent方法具體實現過程

SimpleApplicationEventMulticaster.java部分代碼,實際嘗試將當前事件逐個廣播到指定類型的監聽器中(listeners已經根據當前事件類型過濾了)

 1 @Override
 2 public void multicastEvent(final ApplicationEvent event, @Nullable ResolvableType eventType) {
 3    ResolvableType type = (eventType != null ? eventType : resolveDefaultEventType(event));
 4     // getApplicationListeners(event, type) 篩選監聽器,在context.publish(ApplicationEvent event)中已經將事件傳入,getApplicationListeners中將能夠根據這個event類型從Spring容器中檢索出符合條件的監聽器
 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     //嘗試逐個向監聽器廣播
13          invokeListener(listener, event);
14       }
15    }
16 }

@EventListener註解方式實現

 定義註解方法

@Component
public class MyEventHandleTest {

    /**
     * 參數爲Object類型時,全部事件都會監聽到
     * 參數爲指定類型事件時,該參數類型事件或者其子事件(子類)均可以接收到
     */
    @EventListener
    public void event(ApplicationEventTest event){

        event.printMsg(null);
    }

}

實現過程分析:

@EventListener註解主要經過EventListenerMethodProcessor掃描出全部帶有@EventListener註解的方法,而後動態構造事件監聽器,並將監聽器託管到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           //動態構造相對應的事件監聽器
33                   ApplicationListener<?> applicationListener =
34                         factory.createApplicationListener(beanName, targetType, methodToUse);
35                   if (applicationListener instanceof ApplicationListenerMethodAdapter) {
36                      ((ApplicationListenerMethodAdapter) applicationListener).init(context, this.evaluator);
37                   }
38           //將監聽器添加的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類中實現邏輯:

 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提供的負責處理髮布事件的類,前面說的Spring應用上下文中也是經過這個類發佈事件的
 8    private SimpleApplicationEventMulticaster multicaster;
 9 
10    @Override
11    public void onApplicationEvent(ApplicationEvent event) {
12       if (event instanceof ApplicationEnvironmentPreparedEvent) {
13         // getListeners內部實現讀取context.listener.classes配置的監聽器
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     //發佈事件
25       if (this.multicaster != null) {
26          this.multicaster.multicastEvent(event);
27       }
28    }

  Spring-boot-{version}.jar包中提供一個類DelegatingApplicationListener,該類的做用是從application.properties中讀取配置context.listener.classes,並將事件廣播給這些配置的監聽器。經過前面一章對SpringBoot啓動流程分析,咱們已經瞭解到SpringBoot啓動時會從META-INF/spring.factories中讀取key爲org.springframework.context.ApplicationListener的全部監聽器。DelegatingApplicationListener的功能能夠讓咱們不須要建立META-INF/spring.factories,直接在application.properties中配置便可。

相關文章
相關標籤/搜索