spring boot原理分析(七):spring boot運行時事件的監聽

前言

    在原理分析(六)介紹spring boot啓動流程中涉及到的組件或者模塊的準備,事件監聽器就是其中的一塊。事件監聽器的運行主要包括三個部分。首先第一部分是用來處理事件的監聽器的初始化,這是在SpringApplication的構造函數中完成的。

public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
  ......
  setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
  ......
}

第二部分是構造並初始化了事件監聽器的管理器列表,這一步是在SpringApplication的run方法調用getRunListeners實現。

public ConfigurableApplicationContext run(String... args) {
  ......
  SpringApplicationRunListeners listeners = getRunListeners(args);
  listeners.starting();
  try {
    ......
    ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments);
    ......
    prepareContext(context, environment, listeners, applicationArguments, printedBanner);
    refreshContext(context);
    ......
    listeners.started(context);
    callRunners(context, applicationArguments);
  }
  catch (Throwable ex) {
    handleRunFailure(context, ex, exceptionReporters, listeners);
    throw new IllegalStateException(ex);
  }
    ......
    listeners.running(context);
    ......
  return context;
}

最後一個部分是事件監聽器的管理器發送各種事件,這些操作是分佈在run方法和其他run方法中調用的傳入listeners的方法中。但是spring boot的事件監聽不僅僅侷限在SpringApplication中SpringApplicationRunListeners。無論是環境environment還是上下文context的準備(prepare)方法中都只是調用卻沒有持有listeners,這是因爲context自己初始化和構造了另一套事件監聽系統。雖然這部分內容屬於上下文context的相關部分,但是也會在補充介紹一些。

事件

事件基礎定義

//SpringApplicationEvent.java
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;
	}
}
//ApplicationEvent.java
public abstract class ApplicationEvent extends EventObject {
	private final long timestamp;

	public ApplicationEvent(Object source) {
		super(source);
		this.timestamp = System.currentTimeMillis();
	}

	public final long getTimestamp() {
		return this.timestamp;
	}
}

    spring boot中事件都是繼承自SpringApplicationEvent的抽象類,命名顯示這類事件主要是反饋spring boot應用運行時的狀態的改變。SpringApplicationEvent持有了一個字符串用來存儲應用傳入的參數,除此之外,就是繼承了ApplicationEvent抽象類。ApplicationEvent持有一個時間戳,存儲事件對象被構造的時間。構造方法傳入的Object source代表事件被髮出的類,這個Object對象是由父類EventObject持有的。需要注意的是,ApplicationEvent是由spring-context jar包提供的,這意味着這種事件處理的模式是spring-context提供的能力。

事件的類型

    事件的類型可以由ApplicationEvent的子類例舉出來,當然上文提到SpringApplicationEvent也是子類之一。
spring boot運行時事件的監聽-ApplicationEvent.png
    上圖展示了ApplicationEvent一些子類。雖有些並不是處於spring boot包,這也無傷大雅。根據圖片可以很容易將這些子類分門別類,有些子類的名字也比較露骨,幾乎一眼能識別出主要在什麼情況下發揮作用。

  • SpringApplicationEvent
        SpringApplicationEvent被使用在SpringApplication類中,用來反饋應用啓動狀態變化的系列事件。當spring boot在啓動過程中,應用狀態發生變化時,相應的子類Event就會被髮出,開發者可以通過編寫事件處理器,在合適的階段插入自己的代碼。比如我有一篇文章講了使用bytebuddy解決spring AOP嵌套方法不生效的問題,代碼注入需要在上下文準備之前,所以需要監聽ApplicationEnvironmentPreparedEvent。
        下面依次介紹每個事件拋出具體場景。在上面run方法中,其實已經出現了大部分的事件。listeners.starting()方法拋出了ApplicationStartingEvent事件,代表應用正在啓動中;prepareEnvironment方法中傳入了listeners,拋出了ApplicationEnvironmentPreparedEvent,代表環境已經準備就緒。prepareContext方法中拋出了兩個事件,
private void prepareContext(ConfigurableApplicationContext context, ConfigurableEnvironment environment,
    SpringApplicationRunListeners listeners, ApplicationArguments applicationArguments, Banner printedBanner) {
  context.setEnvironment(environment);
  postProcessApplicationContext(context);
  applyInitializers(context);
  listeners.contextPrepared(context);
  ......
  listeners.contextLoaded(context);
}

其中listeners.contextPrepared拋出了ApplicationContextInitializedEvent代表上下文已經初始化完成,listeners.contextLoaded拋出了ApplicationPreparedEvent代表上下文加載完成,應用準備完畢;
接着run方法中調用listeners.started拋出ApplicationStartedEvent,代表應用啓動準備完成;在try catch中的handleRunFailure方法傳入了listeners,內部調用了listeners.failed方法,這個毫無疑問是拋出了ApplicationFailedEvent事件,代表應用啓動失敗;最後run方法中調用listeners.running方法,拋出了ApplicationReadyEvent事件,代表應用啓動成功。

  • ApplicationContextEvent
        ApplicationContextEvent系列的事件是用來反饋上下文加載過程中狀態的變化,主要是在AbstractApplicationContext上下文抽象類中被子類調用併發送事件。同理,根據事件的名稱也容易判斷事件什麼時候被拋出,具體內容還是放在context相關的文章中詳細解釋。

  • ParentContextAvailableEvent
        這個事件用來反饋存在父上下文並且已經準備就緒。

  • WebServerInitializedEvent
        這個系列的兩個事件,看了在原理分析(六)應該有印象,這裏分別代表SERVLET、REACTIVE的web服務已經初始化完成。

  • 其他
        RequestHandledEvent系列是處理請求完成後會發成的事件。
        DataSourceSchemaCreatedEvent是和數據庫相關的,當DataSourceSchema創建完成後會拋出這個事件。
        ExitCodeEvent是當應用退出時,產生了退出碼之後會拋出的事件,可以根據退出碼判斷應用退出的原因。
        JobExecutionEvent是job執行發出的事件,這裏的JobExcution是spring batch中提供的。
        PayloadApplicationEvent不是在特定情況下被髮出,只是當需要傳遞一個負載信息(Payload)給特定處理器時,可以使用這個事件。PayloadApplicationEvent具有比較強的通用性。

事件的處理

註冊一個事件監聽的處理器

public class AppEnvListener implements ApplicationListener<ApplicationEnvironmentPreparedEvent> {
    @Override
    public void onApplicationEvent(ApplicationEnvironmentPreparedEvent applicationEnvironmentPreparedEvent) {
    }
}

    ApplicationListener是一個接口模板,泛型參數需要傳入這個監聽器監聽的事件,比如這裏監聽ApplicationEnvironmentPreparedEvent。ApplicationListener接口中就只有一個方法onApplicationEvent,在相應監聽事件被髮出時,就會調用子類的onApplicationEvent方法,傳入對應的事件實例。在實現onApplicationEvent方法時,可以使用事件實例中包含的參數,比如ApplicationEnvironmentPreparedEvent就會包含環境Environment的實例。
    最後,不要忘記,要使這個監聽器生效,需要在spring.factories中註冊。

org.springframework.context.ApplicationListener=\
com.xiaojukeji.epower.aegis.utils.context.AppEnvListener

關於spring.factories文件中的配置能夠生效的原理已經在原理分析(三)原理分析(四)中介紹過了。

事件監聽的處理器如何處理事件

    事件的發送和處理是由ApplicationEventMulticaster來實現的,這個組件同樣也是由spring-context包提供的。

public interface ApplicationEventMulticaster {

	void addApplicationListener(ApplicationListener<?> listener);

	void addApplicationListenerBean(String listenerBeanName);

	void removeApplicationListener(ApplicationListener<?> listener);


	void removeApplicationListenerBean(String listenerBeanName);

	void removeAllListeners();

	void multicastEvent(ApplicationEvent event);

	void multicastEvent(ApplicationEvent event, @Nullable ResolvableType eventType);

}

    由接口定義的方法可以看出,ApplicationEventMulticaster子類中需要維護註冊的ApplicationListener的集合,當拋出事件時使用multicastEvent方法傳入事件。其子類AbstractApplicationEventMulticaster也確實是這樣做的,只是使用ListenerRetriever對ApplicationListener進行了封裝。

public abstract class AbstractApplicationEventMulticaster
		implements ApplicationEventMulticaster, BeanClassLoaderAware, BeanFactoryAware {
        .....
	private final ListenerRetriever defaultRetriever = new ListenerRetriever(false);

	final Map<ListenerCacheKey, ListenerRetriever> retrieverCache = new ConcurrentHashMap<>(64);
        .....
}

    ApplicationEventMulticaster類中包含一個默認的ListenerRetriever和一個鍵值是ListenerCacheKey的ListenerRetriever的map。先看一下ListenerRetriever的定義。

private class ListenerRetriever {

  public final Set<ApplicationListener<?>> applicationListeners = new LinkedHashSet<>();

  public final Set<String> applicationListenerBeans = new LinkedHashSet<>();

  private final boolean preFiltered;

  public ListenerRetriever(boolean preFiltered) {
    this.preFiltered = preFiltered;
  }

  public Collection<ApplicationListener<?>> getApplicationListeners() {
    ......
    return allListeners;
  }
}

ListenerRetriever比較簡單,就是封裝了ApplicationListener和ApplicationListener bean name的集合,實現getApplicationListeners方法能夠返回所有的ApplicationListener。那麼ListenerCacheKey是什麼呢?這個key其實就是對ListenerRetriever進行分類,生成標準是事件的類型和事件產生的類的類型(event type and source type),這樣在處理事件時,可以加快搜索對應的ApplicationListener。
    最後spring boot中使用的ApplicationEventMulticaster的實現類是SimpleApplicationEventMulticaster,

public class SimpleApplicationEventMulticaster extends AbstractApplicationEventMulticaster {

	@Nullable
	private Executor taskExecutor;

	@Nullable
	private ErrorHandler errorHandler;
        ......
	@Override
	public void multicastEvent(ApplicationEvent event) {
		multicastEvent(event, resolveDefaultEventType(event));
	}

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

其允許傳入Executor taskExecutor和ErrorHandler errorHandler用來異步執行Listener對事件的響應和錯誤的處理。multicastEvent就是用來拋出事件,並調用相應的Listener來處理事件的方法。

ApplicationContextEvent事件監聽實例

    ApplicationContextEvent的事件監聽處理器Listeners在SpringApplication的構造函數獲取並初始化,這在前言中已經說過。接下來在run方法中,事件的發送和處理借用的是SpringApplicationRunListeners。

class SpringApplicationRunListeners {
        ......
	private final List<SpringApplicationRunListener> listeners;
        ......
}

SpringApplicationRunListeners其實只是對SpringApplicationRunListener的列表封裝了一下,作爲代理所有操作還是遍歷每一個SpringApplicationRunListener來處理的。

public class EventPublishingRunListener implements SpringApplicationRunListener, Ordered {

	private final SpringApplication application;

	private final String[] args;

	private final SimpleApplicationEventMulticaster initialMulticaster;

	public EventPublishingRunListener(SpringApplication application, String[] args) {
		this.application = application;
		this.args = args;
		this.initialMulticaster = new SimpleApplicationEventMulticaster();
		for (ApplicationListener<?> listener : application.getListeners()) {
			this.initialMulticaster.addApplicationListener(listener);
		}
	}
  
	@Override
	public void starting() {
		this.initialMulticaster.multicastEvent(new ApplicationStartingEvent(this.application, this.args));
	}
         ......
}

    事實上,默認情況下,繼承自SpringApplicationRunListener的子類只有一個,就是EventPublishingRunListener。也是在這個子類中,構造了SimpleApplicationEventMulticaster的實例,並將註冊的事件監聽處理器Listener傳了進去,用來發送和處理事件。比如對應run方法中的starting()方法,這裏就直接使用initialMulticaster.multicastEvent處理了ApplicationStartingEvent的事件。對應environmentPrepared、contextPrepared、started等方法也是同樣的道理。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章