Spring事件機制詳解

帶你聊技術發表於2023-12-14

來源:江南一點雨


Spring 原始碼分析影片教程即將殺青,感興趣的小夥伴戳這裡:Spring原始碼應該怎麼學?。

今天我們來聊一聊 Spring 中的事件機制,從用法到原始碼分析,我們挨個過一遍。

1. Spring 事件機制

有的小夥伴可能會覺得 Spring 中的事件機制很神奇,一個地方發訊息,另一個地方收訊息,跟 MQ 一樣。其實,Spring 中的事件本質上就是觀察者模式的應用。事件有其便利的一面,但是用多了也容易導致混亂,所以在實際專案中,我們還是要謹慎選擇是否使用 Spring 事件。

2. 簡單實踐

先用一個簡單的案例,小夥伴們先了解一下 Spring 中事件的應用。

事件釋出流程中,有三個核心概念,他們之間的關係如下圖:

Spring事件機制詳解
  • 事件源(ApplicationEvent):這個就是你要釋出的事件物件。
  • 事件釋出器(ApplicationEventPublisher):這是事件的釋出工具。
  • 事件監聽器(ApplicationListener):這個相當於是事件的消費者。

以上三個要素,事件源和事件監聽器都可以有多個,事件釋出器(通常是由容器來扮演)一般來說只有一個。

接下來松哥透過一個簡單的案例來和小夥伴們演示一下 Spring 中事件的用法。

首先,我們需要自定義一個事件物件,自定義的事件繼承自 ApplicationEvent 類,如下:

public class MyEvent extends ApplicationEvent {
    private String name;
    public MyEvent(Object source, String name) {
        super(source);
        this.name = name;
    }

    @Override
    public String toString() {
        return "MyEvent{" +
                "name='" + name + '\'' +
                "} " + super.toString();
    }
}

這裡我只是額外定義了一個 name 屬性,如果大家在事件傳送的時候需要傳遞的資料比較多,那麼就可以在這裡定義更多的屬性。

在具體實踐中,事件源並非一定要繼承自 ApplicationEvent,事件源也可以是一個普通的 Java 類,如果是普通的 Java 類,系統會自動將之封裝為一個 PayloadApplicationEvent 物件去傳送。

接下來透過事件釋出器將事件釋出出去。Spring 中事件釋出器有專門的介面 ApplicationEventPublisher:

@FunctionalInterface
public interface ApplicationEventPublisher {
 default void publishEvent(ApplicationEvent event) {
  publishEvent((Object) event);
 }
 void publishEvent(Object event);
}

這裡就兩個方法,上面方法呼叫了下面方法,從這兩個方法的引數中也可以看出來,傳送時候的訊息型別可以分為兩種,一種是繼承自 ApplicationEvent 類,另一種則是普通的 Java 物件。

AbstractApplicationContext 實現了該介面並重寫了介面中的方法,所以我們平時使用的 AnnotationConfigApplicationContext 或者 ClassPathXmlApplicationContext,裡邊都是可以直接呼叫事件釋出方法的。

事件釋出方式如下:

AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(JavaConfig.class);
ctx.publishEvent(new MyEvent(new Demo(), "javaboy"));

事件釋出之後,我們還需要一個事件消費者去消費這個事件,或者也可以稱之為事件監聽器。

事件監聽器有兩種定義方式,第一種是自定義類實現 ApplicationListener 介面:

@Component
public class MyEventListener implements ApplicationListener<MyEvent{
    @Override
    public void onApplicationEvent(MyEvent event) {
        System.out.println("event = " + event);
    }
}

第二種方式則是透過註解去標記事件消費方法:

@Component
public class MyEventListener02 {
    @EventListener(value = MyEvent.class)
    public void hello(MyEvent event
{
        System.out.println("event02 = " + event);
    }
}

這樣,我們一個簡單的事件釋出訂閱就完成了,現在我們去釋出事件,事件監聽器中就可以接收到事件。

3. 原理分析

Spring 事件單純從用法上來說是非常容易的,上面松哥也都給大家演示了,原理即使我們不去看原始碼,大概也能猜出來個七七八八:當我們去釋出一個事件的時候,系統就會去找到所有合適的事件消費者,然後去呼叫這些事件消費者,就是這麼簡單。

3.1 ApplicationEventMulticaster

事件的這一切,我們得從 ApplicationEventMulticaster 開始說起,這是一個介面,從名字上可以看出來,這個叫做事件廣播器。

public interface ApplicationEventMulticaster {
 void addApplicationListener(ApplicationListener<?> listener);
 void addApplicationListenerBean(String listenerBeanName);
 void removeApplicationListener(ApplicationListener<?> listener);
 void removeApplicationListenerBean(String listenerBeanName);
 void removeApplicationListeners(Predicate<ApplicationListener<?>> predicate);
 void removeApplicationListenerBeans(Predicate<String> predicate);
 void removeAllListeners();
 void multicastEvent(ApplicationEvent event);
 void multicastEvent(ApplicationEvent event, @Nullable ResolvableType eventType);
}
  • addApplicationListener:這個方法是新增一個事件消費者進來,這個方法引數就是一個 ApplicationListener,無論我們透過何種方式註冊的事件消費者(繼承類或者註解的方式),最終都是呼叫這個方法把事件消費者新增進來的。
  • addApplicationListenerBean:這個方法是新增事件消費者的 bean 進來,這個專門處理繼承自 ApplicationListener 類的消費者,繼承自 ApplicationListener 類的消費者也是要註冊到 Spring 容器中去的,將來會透過這個方法記錄這些 bean 的名字。
  • removeApplicationListener:移除一個 ApplicationListener 監聽器。
  • removeApplicationListenerBean:根據 beanName 移除一個 ApplicationListener 監聽器。
  • removeApplicationListeners:根據條件移除 ApplicationListener 監聽器。
  • removeApplicationListenerBeans:根據條件移除 ApplicationListener 監聽器。
  • removeAllListeners:移除所有 ApplicationListener。
  • multicastEvent:這個方法有兩個過載的方法,這就是事件廣播方法了,其中一個過載方法多了一個 ResolvableType,這個是描述泛型資訊的。

ApplicationEventMulticaster 的繼承關係比較簡單,它也只有一個實現類,所以分析起來相對要容易一些:

Spring事件機制詳解

接下來我們的分析基本上都集中在 AbstractApplicationEventMulticaster 和 SimpleApplicationEventMulticaster 兩個類中。

3.2 收集監聽器

前面松哥和大家說了,監聽器的定義有兩種方式,要麼直接繼承自 ApplicationListener,要麼透過新增 @EventListener 註解,那麼接下來我們就來看下這兩種監聽器是如何載入到 Spring 容器中的。

3.2.1 類監聽器

類監聽器相對來說好處理一些,直接去 Spring 容器中查詢相關型別的 Bean 即可。

在初始化容器的 refresh 方法中,系統會呼叫到 registerListeners 方法,這個方法就是用來處理所有的類監聽器的,如下:

protected void registerListeners() {
 // Register statically specified listeners first.
 for (ApplicationListener<?> listener : getApplicationListeners()) {
  getApplicationEventMulticaster().addApplicationListener(listener);
 }
 // Do not initialize FactoryBeans here: We need to leave all regular beans
 // uninitialized to let post-processors apply to them!
 String[] listenerBeanNames = getBeanNamesForType(ApplicationListener.classtruefalse);
 for (String listenerBeanName : listenerBeanNames) {
  getApplicationEventMulticaster().addApplicationListenerBean(listenerBeanName);
 }
 // Publish early application events now that we finally have a multicaster...
 Set<ApplicationEvent> earlyEventsToProcess = this.earlyApplicationEvents;
 this.earlyApplicationEvents = null;
 if (!CollectionUtils.isEmpty(earlyEventsToProcess)) {
  for (ApplicationEvent earlyEvent : earlyEventsToProcess) {
   getApplicationEventMulticaster().multicastEvent(earlyEvent);
  }
 }
}

這個方法裡邊幹了三件事。

首先就是先處理所有的靜態監聽器,即不存在於 Spring 容器中的監聽器,可以直接呼叫 getApplicationListeners() 方法去獲取,對應的呼叫程式碼如下:

public class Demo {
    public static void main(String[] args) {
        AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
        ctx.addApplicationListener(new MyEventListener());
        ctx.register(JavaConfig.class);
        ctx.refresh();
        ctx.publishEvent(new MyEvent(new Demo(), "javaboy"));
    }
}

小夥伴們看到,這裡我手動呼叫容器的 addApplicationListener 方法新增了一個監聽器,這個手動加進來的監聽器可以不必存在於 Spring 容器中。

這種寫法大家作為了解即可,因為一般我們不會這樣做,比較麻煩且無必要。

registerListeners 方法乾的第二件事就是從 Spring 容器中查詢所有的 ApplicationListener 型別的 beanName,並將查詢的結果先存起來,將來廣播事件的時候使用。有小夥伴可能會說為什麼不直接到找到 ApplicationListener 物件存起來,一步到位多省事!注意,這個地方還拿不到物件,現在還是 Spring 容器的初始化階段,此時物件都還沒有初始化,要在 refresh 方法的倒數第二步進行 Bean 的初始化,所以現在只能先拿到 beanName 存起來。

registerListeners 方法乾的第三件事是檢查是否有需要提前釋出的事件,如果有就先將之廣播出去。

在具體的 addApplicationListener 方法中,會先檢查當前物件是否是代理物件,如果是,則先把代理物件提取出來,然後從監聽器集合中先移除再重新新增,防止一個監聽器以代理物件的方式被新增一次,又以被代理物件被新增一次;addApplicationListenerBean 方法則沒有這麼麻煩,直接新增到 Set 集合中即可,可以自動去重。

對於第二件事,由於這裡存的是 beanName,那麼這個 beanName 什麼時候會成為 bean 物件呢?有一個後置處理器 ApplicationListenerDetector,在該後置處理器的 postProcessAfterInitialization 方法中,會去挨個檢查建立出來的 bean 是否為一個 ApplicationListener,如果是,則將之新增到事件監聽器集合中:

@Override
public Object postProcessAfterInitialization(Object bean, String beanName) {
 if (bean instanceof ApplicationListener<?> applicationListener) {
  Boolean flag = this.singletonNames.get(beanName);
  if (Boolean.TRUE.equals(flag)) {
   this.applicationContext.addApplicationListener(applicationListener);
  }
  else if (Boolean.FALSE.equals(flag)) {
   this.singletonNames.remove(beanName);
  }
 }
 return bean;
}

以上就是監聽器類的收集過程。

3.2.2 註解監聽器

透過註解定義的事件監聽器則會比較特殊,因為註解標記的是方法,這些方法最終會被封裝為 ApplicationListenerMethodAdapter,ApplicationListenerMethodAdapter 也是 ApplicationListener 的一種,將來在執行的時候,無論是透過類定義的事件監聽器還是透過註解定義的事件監聽器,都可以統一對待處理。

對於這一類監聽器的處理是在 Spring 容器初始化的最後一步,即初始化各個 Bean,初始化完成之後,就會去處理這一類的 Bean,方法執行流程如下:

Spring事件機制詳解

所以最終就是在 EventListenerMethodProcessor#processBean 方法中處理透過註解配置的監聽器的:

private void processBean(final String beanName, final Class<?> targetType) {
 if (!this.nonAnnotatedClasses.contains(targetType) &&
   AnnotationUtils.isCandidateClass(targetType, EventListener.class) &&
   !isSpringContainerClass(targetType)) 
{
  Map<Method, EventListener> annotatedMethods = null;
  try {
   annotatedMethods = MethodIntrospector.selectMethods(targetType,
     (MethodIntrospector.MetadataLookup<EventListener>) method ->
       AnnotatedElementUtils.findMergedAnnotation(method, EventListener.class));
  }
  catch (Throwable ex) {
  }
  if (CollectionUtils.isEmpty(annotatedMethods)) {
   this.nonAnnotatedClasses.add(targetType);
  }
  else {
   // Non-empty set of methods
   ConfigurableApplicationContext context = this.applicationContext;
   List<EventListenerFactory> factories = this.eventListenerFactories;
   for (Method method : annotatedMethods.keySet()) {
    for (EventListenerFactory factory : factories) {
     if (factory.supportsMethod(method)) {
      Method methodToUse = AopUtils.selectInvocableMethod(method, context.getType(beanName));
      ApplicationListener<?> applicationListener =
        factory.createApplicationListener(beanName, targetType, methodToUse);
      if (applicationListener instanceof ApplicationListenerMethodAdapter alma) {
       alma.init(context, this.evaluator);
      }
      context.addApplicationListener(applicationListener);
      break;
     }
    }
   }
  }
 }
}

這段原始碼很好懂,首先呼叫 MethodIntrospector.selectMethods 方法,這個方法就是去查詢當前 Class 中所有被 @EventListener 註解標記的方法,將查詢的結果存入到 annotatedMethods 集合中。如果這個集合為空,那就意味著當前 Class 是沒有註解標記的 Class。否則就去遍歷 annotatedMethods 集合。

遍歷的時候,透過 EventListenerFactory 來建立 ApplicationListener 物件,EventListenerFactory 是一個介面,這裡說是在遍歷 factories 集合,但是這個集合中只有一個有效物件 DefaultEventListenerFactory,所以實際上就是由 DefaultEventListenerFactory 來建立 ApplicationListener,建立出來的就是我們前面所說的 ApplicationListenerMethodAdapter,最後將建立結果呼叫 addApplicationListener 方法新增到事件監聽器集合中(最後的 context 其實跟前面松哥案例中的 ctx 就是一個東西)。

對於監聽器的收集,主要就是如上兩種方式。

3.3 事件釋出

Spring 中的事件釋出介面是 ApplicationEventMulticaster,這個介面只有一個幹活的類就是 SimpleApplicationEventMulticaster,在 Spring 容器初始化的 refresh 方法中,會呼叫到 initApplicationEventMulticaster 方法,這個方法就是用來初始化事件廣播器的:

protected void initApplicationEventMulticaster() {
 ConfigurableListableBeanFactory beanFactory = getBeanFactory();
 if (beanFactory.containsLocalBean(APPLICATION_EVENT_MULTICASTER_BEAN_NAME)) {
  this.applicationEventMulticaster =
    beanFactory.getBean(APPLICATION_EVENT_MULTICASTER_BEAN_NAME, ApplicationEventMulticaster.class);
 }
 else {
  this.applicationEventMulticaster = new SimpleApplicationEventMulticaster(beanFactory);
  beanFactory.registerSingleton(APPLICATION_EVENT_MULTICASTER_BEAN_NAME, this.applicationEventMulticaster);
 }
}

這裡 APPLICATION_EVENT_MULTICASTER_BEAN_NAME 變數的名稱是 applicationEventMulticaster,如果容器中存在名為 applicationEventMulticaster 的 bean,則直接獲取,如果不存在,則直接 new 一個 SimpleApplicationEventMulticaster 並註冊到 Spring 容器中。

這段程式碼給我們的啟示是,如果想要自定義 ApplicationEventMulticaster,則自定義的 beanName 必須是 applicationEventMulticaster,否則自定義的 bean 不會生效。

接下來就是事件釋出了,事件釋出我們就從 publishEvent 方法開始看起。

@Override
public void publishEvent(ApplicationEvent event) {
 publishEvent(event, null);
}
protected void publishEvent(Object event, @Nullable ResolvableType typeHint) {
 ResolvableType eventType = null;
 // Decorate event as an ApplicationEvent if necessary
 ApplicationEvent applicationEvent;
 if (event instanceof ApplicationEvent applEvent) {
  applicationEvent = applEvent;
  eventType = typeHint;
 }
 else {
  ResolvableType payloadType = null;
  if (typeHint != null && ApplicationEvent.class.isAssignableFrom(typeHint.toClass())) {
   eventType = typeHint;
  }
  else {
   payloadType = typeHint;
  }
  applicationEvent = new PayloadApplicationEvent<>(this, event, payloadType);
 }
 // Determine event type only once (for multicast and parent publish)
 if (eventType == null) {
  eventType = ResolvableType.forInstance(applicationEvent);
  if (typeHint == null) {
   typeHint = eventType;
  }
 }
 // Multicast right now if possible - or lazily once the multicaster is initialized
 if (this.earlyApplicationEvents != null) {
  this.earlyApplicationEvents.add(applicationEvent);
 }
 else {
  getApplicationEventMulticaster().multicastEvent(applicationEvent, eventType);
 }
 // Publish event via parent context as well...
 if (this.parent != null) {
  if (this.parent instanceof AbstractApplicationContext abstractApplicationContext) {
   abstractApplicationContext.publishEvent(event, typeHint);
  }
  else {
   this.parent.publishEvent(event);
  }
 }
}

這個方法的邏輯我們來看下:

  1. 首先,如果傳入的事件型別就是 ApplicationEvent 型別,則直接賦值給 applicationEvent。
  2. 否則就說明這個事件可能是一個普通的 Java 物件,那麼就將這個 Java 物件封裝成一個 PayloadApplicationEvent 物件並賦值給 applicationEvent。
  3. 如果 eventType 為 null(預設情況下,此時 eventType 就是 null),那麼就從 applicationEvent 中提取出來要處理的事件物件:如果事件型別本身就是 ApplicationEvent,那麼 eventType 就是具體的事件物件;如果事件型別本身是普通的 Java 物件,那麼這裡提取到的 eventType 就是一個 ResolvableType,這個 ResolvableType 中標記了具體的事件物件。
  4. 接下來就是呼叫廣播器去廣播事件了,即 multicastEvent,這也是核心步驟。
  5. 最後還會再去判斷一下當前容器是否存在 parent,如果存在 parent,則在 parent 容器中也將當前事件廣播出去,這就會導致凡是註冊到 parent 容器中的監聽器,也會收到子容器釋出的事件。

這段程式碼邏輯並不難,接下來我們來看下 multicastEvent 方法:

@Override
public void multicastEvent(ApplicationEvent event, @Nullable ResolvableType eventType) {
 ResolvableType type = (eventType != null ? eventType : ResolvableType.forInstance(event));
 Executor executor = getTaskExecutor();
 for (ApplicationListener<?> listener : getApplicationListeners(event, type)) {
  if (executor != null) {
   executor.execute(() -> invokeListener(listener, event));
  }
  else {
   invokeListener(listener, event);
  }
 }
}

我們這裡看到的就是 SimpleApplicationEventMulticaster 的 multicastEvent 方法。

這個方法首先去分析出來事件的型別 type,然後獲取一個非同步處理器,接下來就根據事件 event 和 type 去找到合適的事件監聽器,然後遍歷事件監聽器,遍歷的時候,如果非同步處理器 executor 不為空,那麼就在這裡非同步處理器中呼叫事件監聽器,否則就直接在當前執行緒中呼叫事件監聽器。

從這裡大家可以看出來,如果我們提供了非同步處理器,那麼可以實現 Spring 事件的非同步處理,即非阻塞的效果,否則事件是阻塞的,即釋出者將事件釋出之後,必須等消費者將事件處理了,釋出者的程式碼才會繼續往下走。

如果我們想在 Spring 實現非阻塞的事件,那麼可以配置如下 Bean:

@Bean
SimpleApplicationEventMulticaster applicationEventMulticaster() {
    SimpleApplicationEventMulticaster multicaster = new SimpleApplicationEventMulticaster();
    ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
    //核心執行緒池數量
    executor.setCorePoolSize(Runtime.getRuntime().availableProcessors());
    //最大執行緒數量
    executor.setMaxPoolSize(Runtime.getRuntime().availableProcessors() * 5);
    //執行緒池的佇列容量
    executor.setQueueCapacity(Runtime.getRuntime().availableProcessors() * 2);
    //執行緒名稱的字首
    executor.setThreadNamePrefix("javaboy-async-executor-");
    executor.initialize();
    multicaster.setTaskExecutor(executor);
    return multicaster;
}

配置這個 Bean 的時候,注意 beanName 必須是 applicationEventMulticaster。

接下來再來看看 getApplicationListeners 方法是如何根據當前事件型別找到對應的事件處理器的:

protected Collection<ApplicationListener<?>> getApplicationListeners(
  ApplicationEvent event, ResolvableType eventType) {
 Object source = event.getSource();
 Class<?> sourceType = (source != null ? source.getClass() : null);
 ListenerCacheKey cacheKey = new ListenerCacheKey(eventType, sourceType);
 // Potential new retriever to populate
 CachedListenerRetriever newRetriever = null;
 // Quick check for existing entry on ConcurrentHashMap
 CachedListenerRetriever existingRetriever = this.retrieverCache.get(cacheKey);
 if (existingRetriever == null) {
  // Caching a new ListenerRetriever if possible
  if (this.beanClassLoader == null ||
    (ClassUtils.isCacheSafe(event.getClass(), this.beanClassLoader) &&
      (sourceType == null || ClassUtils.isCacheSafe(sourceType, this.beanClassLoader)))) {
   newRetriever = new CachedListenerRetriever();
   existingRetriever = this.retrieverCache.putIfAbsent(cacheKey, newRetriever);
   if (existingRetriever != null) {
    newRetriever = null;  // no need to populate it in retrieveApplicationListeners
   }
  }
 }
 if (existingRetriever != null) {
  Collection<ApplicationListener<?>> result = existingRetriever.getApplicationListeners();
  if (result != null) {
   return result;
  }
  // If result is null, the existing retriever is not fully populated yet by another thread.
  // Proceed like caching wasn't possible for this current local attempt.
 }
 return retrieveApplicationListeners(eventType, sourceType, newRetriever);
}

從這個方法中我們可以看到,這裡根據 eventType 和 sourceType 構建了一個快取的 key,也就是根據事件的型別和其所屬的 source,將與其對應的事件監聽器快取起來,快取的物件就是 CachedListenerRetriever,如果根據快取 key 能找到快取 value,那麼就從快取的 value 中提取出來監聽器,否則就呼叫 retrieveApplicationListeners 方法去查詢監聽器:

private Collection<ApplicationListener<?>> retrieveApplicationListeners(
  ResolvableType eventType, @Nullable Class<?> sourceType, @Nullable CachedListenerRetriever retriever) {
 List<ApplicationListener<?>> allListeners = new ArrayList<>();
 Set<ApplicationListener<?>> filteredListeners = (retriever != null ? new LinkedHashSet<>() : null);
 Set<String> filteredListenerBeans = (retriever != null ? new LinkedHashSet<>() : null);
 Set<ApplicationListener<?>> listeners;
 Set<String> listenerBeans;
 synchronized (this.defaultRetriever) {
  listeners = new LinkedHashSet<>(this.defaultRetriever.applicationListeners);
  listenerBeans = new LinkedHashSet<>(this.defaultRetriever.applicationListenerBeans);
 }
 // Add programmatically registered listeners, including ones coming
 // from ApplicationListenerDetector (singleton beans and inner beans).
 for (ApplicationListener<?> listener : listeners) {
  if (supportsEvent(listener, eventType, sourceType)) {
   if (retriever != null) {
    filteredListeners.add(listener);
   }
   allListeners.add(listener);
  }
 }
 // Add listeners by bean name, potentially overlapping with programmatically
 // registered listeners above - but here potentially with additional metadata.
 if (!listenerBeans.isEmpty()) {
  ConfigurableBeanFactory beanFactory = getBeanFactory();
  for (String listenerBeanName : listenerBeans) {
   try {
    if (supportsEvent(beanFactory, listenerBeanName, eventType)) {
     ApplicationListener<?> listener =
       beanFactory.getBean(listenerBeanName, ApplicationListener.class);
     if (!allListeners.contains(listener) && supportsEvent(listener, eventType, sourceType)) {
      if (retriever != null) {
       if (beanFactory.isSingleton(listenerBeanName)) {
        filteredListeners.add(listener);
       }
       else {
        filteredListenerBeans.add(listenerBeanName);
       }
      }
      allListeners.add(listener);
     }
    }
    else {
     // Remove non-matching listeners that originally came from
     // ApplicationListenerDetector, possibly ruled out by additional
     // BeanDefinition metadata (e.g. factory method generics) above.
     Object listener = beanFactory.getSingleton(listenerBeanName);
     if (retriever != null) {
      filteredListeners.remove(listener);
     }
     allListeners.remove(listener);
    }
   }
   catch (NoSuchBeanDefinitionException ex) {
    // Singleton listener instance (without backing bean definition) disappeared -
    // probably in the middle of the destruction phase
   }
  }
 }
 AnnotationAwareOrderComparator.sort(allListeners);
 if (retriever != null) {
  if (filteredListenerBeans.isEmpty()) {
   retriever.applicationListeners = new LinkedHashSet<>(allListeners);
   retriever.applicationListenerBeans = filteredListenerBeans;
  }
  else {
   retriever.applicationListeners = filteredListeners;
   retriever.applicationListenerBeans = filteredListenerBeans;
  }
 }
 return allListeners;
}

這段程式碼比較長,但是邏輯比較簡單。

首先遍歷之前收集到的所有 listener,呼叫 supportsEvent 方法去判斷該 listener 是否支援當前事件,如果支援,則將之存入到 allListeners 集合中,同時,如果快取物件 retriever 不為空,則往 filteredListeners 中也存一份監聽器。

接下來遍歷 listenerBeans,遍歷的時候根據 supportsEvent 方法去判斷該 listener 是否支援當前事件,如果支援,那麼就獲取到對應的 bean,如果這個 bean 是單例的,並且在存在快取物件的的情況下,那麼就將之存入到 filteredListeners 集合中,如果這個 bean 不是單例的,那麼就把 beanName 存入到 filteredListenerBeans 集合中。當然,最終拿到的監聽器物件也要存入到 allListeners 集合中。

最後還會做一個判斷,如果快取的 value 不為空,那麼當 filteredListenerBeans 為空就表示不存在非單例的監聽器,所有的監聽器都是單例的,即 allListeners 中不存在重複的 Bean,那麼直接將 allListeners 轉為 hashset 即可,否則說明有多例的監聽器,那麼就意味著 allListeners 集合中存在重複的 bean,此時就把 filteredListeners 集合賦值給快取物件的 applicationListeners 屬性即可。

這就是查詢匹配的監聽器的大致過程。這裡還涉及到一個比較重要的方法 supportsEvent,這個是判斷監聽器是否匹配的具體方法,這有三個過載的方法,前兩個過載方法都屬於初步校驗,如果校驗透過,第三個過載方法做最終校驗。

  1. supportsEvent(ConfigurableBeanFactory, String, ResolvableType):這個方法是根據 beanName 判斷某一個 bean 是否滿足條件,這只是一個初步判斷,並不是最終的判斷,判斷的邏輯就是如果當前 bean 是 GenericApplicationListener 或者 SmartApplicationListener 型別,就算是匹配的,否則就去判斷當前的事件型別和事件處理器上的泛型是否匹配。
  2. supportsEvent(Class<?>, ResolvableType):這個方法是判斷當前事件監聽器型別是否和事件型別相匹配。
  3. supportsEvent(ApplicationListener, ResolvableType, Class):這個是做最終的評估,這裡會把事件監聽器轉為 GenericApplicationListener 型別,然後分別判斷事件型別和 source 型別,滿足條件就是目標事件監聽器了。

一般來說,透過繼承類的方式開發的事件監聽器,要走前兩個方法,如果是透過註解定義的事件監聽器,直接走第三個方法。

好啦,這就是 Spring 中事件的玩法啦,感興趣的小夥伴可以自行 DEBUG 看下哦~

來自 “ ITPUB部落格 ” ,連結:https://blog.itpub.net/70024922/viewspace-3000043/,如需轉載,請註明出處,否則將追究法律責任。

相關文章