天天看點

Spring容器啟動過程中釋出的核心事件及事件處理機制詳解

作者:Spring全家桶實戰案例

環境:Spring5.3.23

核心事件

ApplicationContext中的事件處理是通過ApplicationEvent類和ApplicationListener接口提供的。如果将實作一個Bean實作了ApplicationListener接口,那麼每當ApplicationEvent釋出到ApplicationContext時,就會通知該bean。本質上,這是标準的觀察者設計模式。

到Spring 4.2為止,事件基礎設施得到了顯著改進,提供了基于注釋的模型以及釋出任意事件的能力(也就是說,不一定是從ApplicationEvent擴充的對象)。當這樣的對象被釋出時,我們将它包裝在一個事件中。

下表列出了Spring提供的标準事件:

Event Explanation
ContextRefreshedEvent 在ApplicationContext被初始化或重新整理時釋出(例如,通過使用ConfigurableApplicationContext接口上的refresh()方法)。這裡的“初始化”意味着加載了所有bean,檢測并激活了後處理器bean,預執行個體化了單例,并且ApplicationContext對象已經準備好可以使用。隻要上下文沒有關閉,就可以多次觸發重新整理,前提是所選擇的ApplicationContext實際上支援這種“熱”重新整理。
ContextStartedEvent 通過使用ConfigurableApplicationContext接口上的start()方法啟動ApplicationContext時釋出。在這裡,“started”意味着所有生命周期bean都接收一個顯式的開始信号。通常,該信号用于在顯式停止之後重新啟動bean,但也可以用于啟動未配置為自動啟動的元件(例如,在初始化時尚未啟動的元件)。
ContextStoppedEvent 當通過使用ConfigurableApplicationContext接口上的stop()方法停止ApplicationContext時釋出。在這裡,“stopped”意味着所有生命周期bean都接收一個明确的停止信号。停止的上下文可以通過start()調用重新啟動。
ContextClosedEvent 使用ConfigurableApplicationContext接口上的close()方法或通過JVM關閉挂鈎關閉ApplicationContext時釋出。在這裡,“closed”意味着所有的單例bean都會被銷毀。一旦上下文被關閉,它就會到達生命的終點,并且不能被重新整理或重新開機。
RequestHandledEvent 一個特定于web的事件,告訴所有bean一個HTTP請求已經得到了服務。此事件在請求完成後釋出。這個事件隻适用于使用Spring的DispatcherServlet的web應用程式。
ServletRequestHandledEvent RequestHandledEvent的一個子類,用于添加特定于servlet的上下文資訊。

以上事件釋出時機:

  • ContextRefreshedEvent
public abstract class AbstractApplicationContext extends DefaultResourceLoader implements ConfigurableApplicationContext {
  public void refresh() {
    // ...
    finishRefresh();
  }
}           
  • ContextStartedEvent
public abstract class AbstractApplicationContext extends DefaultResourceLoader implements ConfigurableApplicationContext {
  public void refresh() {
    // ...
    finishRefresh();
  }
  protected void finishRefresh() {
    // 初始化LifecycleProcessor(DefaultLifecycleProcessor)
    initLifecycleProcessor();
    getLifecycleProcessor().onRefresh();
  }
  public void start() {
    getLifecycleProcessor().start();
    publishEvent(new ContextStartedEvent(this));
  }
}
public class DefaultLifecycleProcessor implements LifecycleProcessor, BeanFactoryAware {
  public void start() {
    startBeans(false);
    this.running = true;
  }
}           
  • ContextStoppedEvent

該事件與上面的started是對應的

public abstract class AbstractApplicationContext extends DefaultResourceLoader implements ConfigurableApplicationContext {
  public void stop() {
    getLifecycleProcessor().stop();
		publishEvent(new ContextStoppedEvent(this));
  }
}
public class DefaultLifecycleProcessor implements LifecycleProcessor, BeanFactoryAware {
  public void stop() {
    stopBeans();
    this.running = false;
  }
}           
  • ContextClosedEvent
public abstract class AbstractApplicationContext extends DefaultResourceLoader implements ConfigurableApplicationContext {
  public void close() {
    synchronized (this.startupShutdownMonitor) {
      doClose();
    }
  }
  protected void doClose() {
    publishEvent(new ContextClosedEvent(this));
  }
}           
  • ServletRequestHandledEvent
public abstract class FrameworkServlet {
  protected final void processRequest(HttpServletRequest request, HttpServletResponse response) {
    publishRequestHandledEvent(request, response, startTime, failureCause);
  }
  private void publishRequestHandledEvent(HttpServletRequest request, HttpServletResponse response, long startTime, @Nullable Throwable failureCause) {
    if (this.publishEvents && this.webApplicationContext != null) {
    // Whether or not we succeeded, publish an event.
    long processingTime = System.currentTimeMillis() - startTime;
    this.webApplicationContext.publishEvent(
      new ServletRequestHandledEvent(this,
              request.getRequestURI(), request.getRemoteAddr(),
              request.getMethod(), getServletConfig().getServletName(),
              WebUtils.getSessionId(request), getUsernameForRequest(request),
              processingTime, failureCause, response.getStatus()));
    }
  }
}           

你還可以建立和釋出自己的自定義事件。下面的例子展示了一個簡單的類,它擴充了Spring的ApplicationEvent基類:

public class BlockedListEvent extends ApplicationEvent {

  private final String address;
  private final String content;

  public BlockedListEvent(Object source, String address, String content) {
    super(source);
    this.address = address;
    this.content = content;
  }
}           

要釋出自定義的ApplicationEvent,需要調用ApplicationEventPublisher的publishEvent()方法。通常,這是通過建立一個實作ApplicationEventPublisherAware的類并将其注冊為Spring bean來完成的。下面的例子展示了這樣一個類:

public class EmailService implements ApplicationEventPublisherAware {

  private List<String> blockedList;
  private ApplicationEventPublisher publisher;

  public void setBlockedList(List<String> blockedList) {
    this.blockedList = blockedList;
  }

  public void setApplicationEventPublisher(ApplicationEventPublisher publisher) {
    this.publisher = publisher;
  }

  public void sendEmail(String address, String content) {
    if (blockedList.contains(address)) {
      publisher.publishEvent(new BlockedListEvent(this, address, content));
      return;
    }
  }
}           

在配置時,Spring容器檢測到EmailService實作了ApplicationEventPublisherAware,并自動調用setApplicationEventPublisher()。實際上,傳入的參數是Spring容器本身。通過ApplicationEventPublisher接口與應用程式上下文進行互動。

要接收自定義的ApplicationEvent,可以建立一個實作ApplicationListener的類,并将其注冊為Spring bean。下面的例子展示了這樣一個類:

public class BlockedListNotifier implements ApplicationListener<BlockedListEvent> {

  private String notificationAddress;

  public void setNotificationAddress(String notificationAddress) {
    this.notificationAddress = notificationAddress;
  }

  public void onApplicationEvent(BlockedListEvent event) {
  }
}           

請注意,ApplicationListener通常參數化為自定義事件的類型。這意味着onApplicationEvent()方法可以保持類型安全,避免任何向下轉換的需要。你可以注冊任意數量的事件監聽器,但請注意,預設情況下,事件監聽器是同步接收事件的。這意味着publishEvent()方法會阻塞,直到所有監聽器都完成事件處理。這種同步和單線程方法的一個優點是,當偵聽器接收到事件時,如果事務上下文可用,它将在釋出者的事務上下文内操作。

通過注解監聽事件

可以使用@EventListener注解在托管bean的任何方法上注冊事件監聽器。可以将BlockedListNotifier重寫為:

public class BlockedListNotifier {

  private String notificationAddress;

  public void setNotificationAddress(String notificationAddress) {
    this.notificationAddress = notificationAddress;
  }

  @EventListener
  public void processBlockedListEvent(BlockedListEvent event) {
  }
}           

同時監聽多個事件

@EventListener({ContextStartedEvent.class, ContextRefreshedEvent.class})
public void handleContextStart() {
}           

異步事件

如果希望特定的監聽器異步處理事件,可以重用正常的@Async支援。如下面的例子所示:

@EventListener
@Async
public void processBlockedListEvent(BlockedListEvent event) {
}           

在使用異步事件時,請注意以下限制:

  1. 如果異步事件監聽器抛出異常,則異常不會傳播到調用者。
  2. 異步事件監聽器方法無法通過傳回值釋出後續事件。如果你需要釋出另一個事件作為處理的結果,注入一個ApplicationEventPublisher來手動釋出事件。

事件監聽順序

如果需要在調用另一個監聽器之前調用一個監聽器,可以在方法聲明中添加@Order注解,如下面的例子所示:

@EventListener
@Order(1)
public void processBlockedListEvent(BlockedListEvent event) {
}           

通用的事件

你還可以使用泛型來進一步定義事件的結構。考慮使用EntityCreatedEvent<T>,其中T是實際建立的實體的類型。例如,你可以建立以下監聽器定義,隻接收Person的EntityCreatedEvent:

@EventListener
public void onPersonCreated(EntityCreatedEvent<Person> event) {
}           

由于類型擦除,隻有當觸發的事件解析了事件監聽器過濾的泛型參數(即類似于PersonCreatedEvent類擴充EntityCreatedEvent<Person>{…})時,才會起作用。

事件觸發原理

方式1:ApplicationEventPublisher

AbstractApplicationContext實作了ApplicationEventPublisher接口,那麼隻要ApplicationContext繼承自AbstractApplicationContext都可以直接釋出事件:

public abstract class AbstractApplicationContext {
  protected void publishEvent(Object event, @Nullable ResolvableType eventType) {
    ApplicationEvent applicationEvent;
    if (event instanceof ApplicationEvent) {
      applicationEvent = (ApplicationEvent) event;
    } else {
      applicationEvent = new PayloadApplicationEvent<>(this, event);
      if (eventType == null) {
        eventType = ((PayloadApplicationEvent<?>) applicationEvent).getResolvableType();
      }
    }
    if (this.earlyApplicationEvents != null) {
      this.earlyApplicationEvents.add(applicationEvent);
    } else {
      getApplicationEventMulticaster().multicastEvent(applicationEvent, eventType);
    }
    if (this.parent != null) {
      if (this.parent instanceof AbstractApplicationContext) {
        ((AbstractApplicationContext) this.parent).publishEvent(event, eventType);
      } else {
        this.parent.publishEvent(event);
      }
    }
  }
}           

方式2:通過@EventListener注解

該注解是由EventListenerMethodProcessor處理器處理的。

public class EventListenerMethodProcessor implements SmartInitializingSingleton, ApplicationContextAware, BeanFactoryPostProcessor {
  public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) {
    // 擷取容器中注冊的事件監聽工廠,可以有多個
    Map<String, EventListenerFactory> beans = beanFactory.getBeansOfType(EventListenerFactory.class, false, false);
  }
  public void afterSingletonsInstantiated() {
    String[] beanNames = beanFactory.getBeanNamesForType(Object.class);
    for (String beanName : beanNames) {
      processBean(beanName, type);    
    }
  }
  private void processBean(final String beanName, final Class<?> targetType) {
    Map<Method, EventListener> annotatedMethods = null;
    try {
      // 取得Bean内帶有@EventListener注解的方法
      annotatedMethods = MethodIntrospector.selectMethods(targetType, (MethodIntrospector.MetadataLookup<EventListener>) method -> 
        AnnotatedElementUtils.findMergedAnnotation(method, EventListener.class));
    }
    // 周遊找到的所有@EventListener注解的方法
    for (Method method : annotatedMethods.keySet()) {
      // 周遊所有的EventListenerFactory
      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) {
            ((ApplicationListenerMethodAdapter) applicationListener).init(context, this.evaluator);
          }
          context.addApplicationListener(applicationListener);
          break;
        }
      }
    }
  }
}           

完畢!!!

給個關注

Spring Cloud Gateway核心全局過濾器

完全自定義實作SpringMVC核心元件

Spring AOP之切入點Pointcut API詳細介紹及使用

Spring容器啟動過程中釋出的核心事件及事件處理機制詳解
Spring容器啟動過程中釋出的核心事件及事件處理機制詳解