1. 程式人生 > >Springboot 內建ApplicationContextInitializer

Springboot 內建ApplicationContextInitializer

概述

在包org.springframework.context中提供了這樣一個介面ApplicationContextInitializer :

public interface ApplicationContextInitializer<C extends ConfigurableApplicationContext> {

	/**
	 * 初始化給定的 application context.
	 * @param applicationContext 需要被初始化的 application context
	 */
	void initialize(C applicationContext)
; }

這是一個用來初始化Spring ConfigurableApplicationContext應用上下文的回撥介面,設定的呼叫時機是在ConfigurableApplicationContext#refresh()呼叫之前。

該介面典型的應用場景是web應用中需要程式設計方式對應用上下文做初始化。比如,註冊屬性源(property sources)或者針對上下文的環境資訊environment啟用相應的profile

使用分析

在一個Springboot應用中,classpath上會包含很多jar包,有些jar包需要在ConfigurableApplicationContext#refresh()

呼叫之前對應用上下文做一些初始化動作,因此它們會提供自己的ApplicationContextInitializer實現類,然後放在自己的META-INF/spring.factories屬性檔案中,這樣相應的ApplicationContextInitializer實現類就會被SpringApplication#initialize發現:

// SpringApplication#initialize方法,在其建構函式內執行,從而確保在其run方法之前完成
private void initialize(Object[] sources) {
		if (sources != null &&
sources.length > 0) { this.sources.addAll(Arrays.asList(sources)); } this.webEnvironment = deduceWebEnvironment(); setInitializers((Collection) getSpringFactoriesInstances( ApplicationContextInitializer.class));// <=================== setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class)); this.mainApplicationClass = deduceMainApplicationClass(); }

然後在應用上下文建立之後,應用上下文重新整理(refresh)之前的準備階段被呼叫 :

	// SpringApplication#run方法
	public ConfigurableApplicationContext run(String... args) {
		StopWatch stopWatch = new StopWatch();
		stopWatch.start();
		ConfigurableApplicationContext context = null;
		FailureAnalyzers analyzers = null;
		configureHeadlessProperty();
		SpringApplicationRunListeners listeners = getRunListeners(args);
		listeners.starting();
		try {
			ApplicationArguments applicationArguments = new DefaultApplicationArguments(
					args);
			ConfigurableEnvironment environment = prepareEnvironment(listeners,
					applicationArguments);
			Banner printedBanner = printBanner(environment);
			context = createApplicationContext();
			analyzers = new FailureAnalyzers(context);
			prepareContext(context, environment, listeners, applicationArguments,
					printedBanner); // <===================
			refreshContext(context);
			afterRefresh(context, applicationArguments);
			listeners.finished(context, null);
			stopWatch.stop();
			if (this.logStartupInfo) {
				new StartupInfoLogger(this.mainApplicationClass)
						.logStarted(getApplicationLog(), stopWatch);
			}
			return context;
		}
		catch (Throwable ex) {
			handleRunFailure(context, listeners, analyzers, ex);
			throw new IllegalStateException(ex);
		}
	}
// SpringApplication#prepareContext方法
private void prepareContext(ConfigurableApplicationContext context,
			ConfigurableEnvironment environment, SpringApplicationRunListeners listeners,
			ApplicationArguments applicationArguments, Banner printedBanner) {
		context.setEnvironment(environment);
		postProcessApplicationContext(context);
		applyInitializers(context); // <==============
		listeners.contextPrepared(context);
		if (this.logStartupInfo) {
			logStartupInfo(context.getParent() == null);
			logStartupProfileInfo(context);
		}

		// Add boot specific singleton beans
		context.getBeanFactory().registerSingleton("springApplicationArguments",
				applicationArguments);
		if (printedBanner != null) {
			context.getBeanFactory().registerSingleton("springBootBanner", printedBanner);
		}

		// Load the sources
		Set<Object> sources = getSources();
		Assert.notEmpty(sources, "Sources must not be empty");
		load(context, sources.toArray(new Object[sources.size()]));
		listeners.contextLoaded(context);
	}
	protected void applyInitializers(ConfigurableApplicationContext context) {
		for (ApplicationContextInitializer initializer : getInitializers()) {
			Class<?> requiredType = GenericTypeResolver.resolveTypeArgument(
					initializer.getClass(), ApplicationContextInitializer.class);
			Assert.isInstanceOf(requiredType, context, "Unable to call initializer.");
			initializer.initialize(context);
		}
	}

常見的一些ApplicationContextInitializer實現

下面列出了一個使用預設配置的springboot web應用所使用到的ApplicationContextInitializer實現:

實現類 介紹
DelegatingApplicationContextInitializer org.sf.boot.context.config 使用環境屬性context.initializer.classes指定的初始化器(initializers)進行初始化工作,如果沒有指定則什麼都不做
ContextIdApplicationContextInitializer org.sf.boot.context 設定Spring應用上下文的ID,會參照環境屬性:
spring.application.name
vcap.application.name
spring.config.name
spring.application.index
vcap.application.instance_index
PORT
如果這些屬性都沒有,ID使用"application"。
ConfigurationWarningsApplicationContextInitializer org.sf.boot.context 對於一般配置錯誤在日誌中作出警告
ServerPortInfoApplicationContextInitializer org.sf.boot.context.embedded 將內建servlet容器實際使用的監聽埠寫入到Environment環境屬性中。這樣屬性"local.server.port"就可以直接通過@Value注入到測試中,或者通過環境屬性Environment獲取。
SharedMetadataReaderFactoryContextInitializer org.sf.boot.autoconfigure 建立一個SpringBoot和ConfigurationClassPostProcessor共用的CachingMetadataReaderFactory物件,實現類使用ConcurrentReferenceCachingMetadataReaderFactory
AutoConfigurationReportLoggingInitializer org.sf.boot.autoconfigure.logging ConditionEvaluationReport寫入日誌

注意 : 上表中包名中的 springframework 縮寫為 sf