(四)SpringBoot啟動過程的分析-預處理ApplicationContext


-- 以下內容均基於2.1.8.RELEASE版本

緊接着上一篇(三)SpringBoot啟動過程的分析-創建應用程序上下文,本文將分析上下文創建完畢之后的下一步操作:預處理上下文容器。

預處理上下文容器

預處理上下文容器由prepareContext()方法完成,本篇內容全部都是基於這個方法所涉及的內容進行分析。

// SpringApplication.java

private void prepareContext(ConfigurableApplicationContext context, ConfigurableEnvironment environment, SpringApplicationRunListeners listeners, ApplicationArguments applicationArguments, Banner printedBanner) {

	// 設置環境對象,傳入的對象是解析完畢profiles的對象,Context內部則是不完整的對象
	context.setEnvironment(environment);
	// 設置上下文參數
	postProcessApplicationContext(context);
	// 加載ApplicationContextInitializers
	applyInitializers(context);
	// 觸發開始准備上下文事件
	listeners.contextPrepared(context);
	if (this.logStartupInfo) {
		logStartupInfo(context.getParent() == null);
		logStartupProfileInfo(context);
	}
	// 將啟動參數包裝為名為springApplicationArguments的DefaultApplicationArguments對象,並以單例模式注冊
	ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
	beanFactory.registerSingleton("springApplicationArguments", applicationArguments);
	
	// 設置打印的Banner
	if (printedBanner != null) {
		beanFactory.registerSingleton("springBootBanner", printedBanner);
	}
	
	// 設置是否允許覆蓋BeanDefinition
	if (beanFactory instanceof DefaultListableBeanFactory) {
		((DefaultListableBeanFactory) beanFactory).setAllowBeanDefinitionOverriding(this.allowBeanDefinitionOverriding);
	}
	
	// 加載資源
	Set<Object> sources = getAllSources();
	Assert.notEmpty(sources, "Sources must not be empty");
	load(context, sources.toArray(new Object[0]));
	
	// 觸發上下文加載完畢事件
	listeners.contextLoaded(context);
}

設置上下文參數

// SpringApplication.java

protected void postProcessApplicationContext(ConfigurableApplicationContext context) {

	// 以單例模式注冊beanNameGenerator
	if (this.beanNameGenerator != null) {
		context.getBeanFactory().registerSingleton(AnnotationConfigUtils.CONFIGURATION_BEAN_NAME_GENERATOR, this.beanNameGenerator);
	}
	
	// 為上下文設置資源加載器和類加載器
	if (this.resourceLoader != null) {
		if (context instanceof GenericApplicationContext) {
			((GenericApplicationContext) context).setResourceLoader(this.resourceLoader);
		}
		if (context instanceof DefaultResourceLoader) {
			((DefaultResourceLoader) context).setClassLoader(this.resourceLoader.getClassLoader());
		}
	}
	
	// 為上下文設置轉換服務
	if (this.addConversionService) {
		context.getBeanFactory().setConversionService(ApplicationConversionService.getSharedInstance());
	}
}

將前面步驟初始化的屬性賦值給上下文容器,代碼中的this代表的是SpringApplication。

加載ApplicationContextInitializers

在SpringApplication.run()的一開始它就通過SPI獲取到所有的ApplicationContextInitializers,在這里他們將被執行。

protected void applyInitializers(ConfigurableApplicationContext context) {
	// 調用每一個實現類的initialize方法
	for (ApplicationContextInitializer initializer : getInitializers()) {
		Class<?> requiredType = GenericTypeResolver.resolveTypeArgument(initializer.getClass(), ApplicationContextInitializer.class);
		Assert.isInstanceOf(requiredType, context, "Unable to call initializer.");
		initializer.initialize(context);
	}
}

// 將之前獲取到的集合進行排序並返回只讀集合
public Set<ApplicationContextInitializer<?>> getInitializers() {
	return asUnmodifiableOrderedSet(this.initializers);
}

DelegatingApplicationContextInitializer

用於初始化配置文件(屬性名:context.initializer.classes)中指定的ApplicationContextInitializer實現類

public void initialize(ConfigurableApplicationContext context) {
	// 獲取環境對象
	ConfigurableEnvironment environment = context.getEnvironment();
	// 獲取context.initializer.classes屬性指定的實現類
	List<Class<?>> initializerClasses = getInitializerClasses(environment);
	if (!initializerClasses.isEmpty()) {
		// 調用其initialize()方法
		applyInitializerClasses(context, initializerClasses);
	}
}

private List<Class<?>> getInitializerClasses(ConfigurableEnvironment env) {
	// 通過指定屬性去獲取,此處常量屬性值為context.initializer.classes
	String classNames = env.getProperty(PROPERTY_NAME);
	List<Class<?>> classes = new ArrayList<>();
	if (StringUtils.hasLength(classNames)) {
		// 根據代碼可以推斷出,context.initializer.classes的值可以用逗號拼接
		for (String className : StringUtils.tokenizeToStringArray(classNames, ",")) {
			classes.add(getInitializerClass(className));
		}
	}
	return classes;
}

這里看起來好像很奇怪,本身當前的類就是一個ApplicationContextInitializer, 它已經被上游代碼調用了initializer()方法,在initializer()方法中它又去獲取ApplicationContextInitializer,然后接着調用initializer(),好像很繞。不過它的類描述已經說明了問題,它用於加載從配置文件指定
的那些ApplicationContextInitializer。如果閱讀過第一篇概覽SpringApplication.java #構造方法的話就會明白,當前對象就是在SpringApplication類的構造方法中通過SPI方式獲取到的,而當前方法則是通過配置文件指定的方式
來獲取。由此就可以得出一個結論:在SpringBoot中實現ApplicationContextInitializer並確保其被加載有三種方法,一是通過SpringApplication公開的addInitializers()方法直接添加,二是以SPI方式配置,三是以配置文件方式配置。

在SpringBoot中三種配置ApplicationContextInitializer的方法:

  1. 直接以代碼方式添加
    new SpringApplication(Example.class).addInitializers(new 實現類());
  1. 以SPI方式
    在/META-INF/spring.factories文件中配置org.springframework.context.ApplicationContextInitializer=實現類A, 實現類B
  1. 以配置文件方式(語法取決於你使用什么樣的配置文件properties or xml or yml or yaml or 配置中心)
    context.initializer.classes = 實現類A, 實現類B

SharedMetadataReaderFactoryContextInitializer

在ConfigurationClassPostProcessor和Spring Boot之間創建共享的CachingMetadataReaderFactory, 干啥的我也不知道,后續debug在研究。

ContextIdApplicationContextInitializer

用於設置SpringApplication.getId()的值,如果配置文件中指定了spring.application.name,則本類不起作用,反之。

ConfigurationWarningsApplicationContextInitializer

添加BeanFactoryPostProcessor:ConfigurationWarningsPostProcessor用於打印配置錯誤的日志

ServerPortInfoApplicationContextInitializer

用於設置server.ports屬性的值,它是Web服務器實際監聽的應用程序端口。同時實現了ApplicationListener接口用於監聽WebServerInitializedEvent事件,WebServer初始化完畢之后設置端口。

// ServerPortInfoApplicationContextInitializer.java

// ①
public void initialize(ConfigurableApplicationContext applicationContext) {
	applicationContext.addApplicationListener(this);
}

// ②
public void onApplicationEvent(WebServerInitializedEvent event) {
	String propertyName = "local." + getName(event.getApplicationContext()) + ".port";
	setPortProperty(event.getApplicationContext(), propertyName, event.getWebServer().getPort());
}

// ③
private String getName(WebServerApplicationContext context) {
	String name = context.getServerNamespace();
	return StringUtils.hasText(name) ? name : "server";
}

// ④
private void setPortProperty(ApplicationContext context, String propertyName, int port) {
	if (context instanceof ConfigurableApplicationContext) {
		setPortProperty(((ConfigurableApplicationContext) context).getEnvironment(), propertyName, port);
	}
	if (context.getParent() != null) {
		setPortProperty(context.getParent(), propertyName, port);
	}
}

@SuppressWarnings("unchecked")
private void setPortProperty(ConfigurableEnvironment environment, String propertyName, int port) {
	MutablePropertySources sources = environment.getPropertySources();
	PropertySource<?> source = sources.get("server.ports");
	if (source == null) {
		source = new MapPropertySource("server.ports", new HashMap<>());
		sources.addFirst(source);
	}
	((Map<String, Object>) source.getSource()).put(propertyName, port);
}

① - 向上下文監聽器列表中添加當前類(作為監聽器加入)
② - 在監聽事件中設置端口,默認屬性為local.server.port
③ - 若設置了服務器Namespace,則屬性值為local.Namespace的值.port
④ - 將監聽端口信息填入server.ports屬性下

ConditionEvaluationReportLoggingListener

用於設置ConditionEvaluationReportListener監聽器,此監聽器監聽ContextRefreshedEvent和ApplicationFailedEvent,分別打印出上下文容器成功刷新和失敗的日志報告

在這一章節中,可以看到ApplicationContextInitializer擴展接口的實際應用。通過Spring框架內置的一些initializer可以實現框架功能,同理我們也可以利用這一特性在上下文容器還未刷新之前做一些擴展功能。

發布ApplicationContextInitializedEvent事件

由SpringApplicationRunListeners.contextPrepared(ConfigurableApplicationContext context)方法觸發。

當上下文容器加載完畢所有的ApplicationContextInitializer之后,觸發該事件,通知那些關注了此事件的監聽器進行下一步操作。每次發布事件的時候可能已經有新的監聽器被加進來,在上一章節中我們就看到會在ApplicationContextInitializer里面添加監聽器
但我們只需要關注那些監聽了當前事件的監聽器即可。這里僅僅作為一個提示,監聽器可能在任何地方被加進來。這里並未發現有監聽器監聽此事件。

記錄活動的Profiles日志

日志打印的:The following profiles are active: dev,test 這一句就來自於此。代碼比較簡單一看便知。

if (this.logStartupInfo) {
	logStartupInfo(context.getParent() == null);
	logStartupProfileInfo(context);
}

protected void logStartupProfileInfo(ConfigurableApplicationContext context) {
	Log log = getApplicationLog();
	if (log.isInfoEnabled()) {
		String[] activeProfiles = context.getEnvironment().getActiveProfiles();
		if (ObjectUtils.isEmpty(activeProfiles)) {
			String[] defaultProfiles = context.getEnvironment().getDefaultProfiles();
			log.info("No active profile set, falling back to default profiles: " + StringUtils.arrayToCommaDelimitedString(defaultProfiles));
		}
		else {
			log.info("The following profiles are active: "+ StringUtils.arrayToCommaDelimitedString(activeProfiles));
		}
	}
}

注冊啟動SpringApplication時的參數

將參數對象注冊為單例模式

// SpringApplication.java

ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
beanFactory.registerSingleton("springApplicationArguments", applicationArguments);

是否允許覆蓋Bean定義

這個是和spring.main.allow-bean-definition-overriding參數有關,默認情況下在DefaultListableBeanFactory中是true,但是SpringBoot在此處給設置成了false。

if (beanFactory instanceof DefaultListableBeanFactory) {
	((DefaultListableBeanFactory) beanFactory).setAllowBeanDefinitionOverriding(this.allowBeanDefinitionOverriding);
}

加載Bean到上下文中

加載需要兩個必備條件:誰來加載Bean,從哪加載Bean;此處加載BeanDefinition是由BeanDefinitionLoader來完成。

// SpringApplication.java#prepareContext()

// 獲取當前應用要加載的資源
Set<Object> sources = getAllSources();
Assert.notEmpty(sources, "Sources must not be empty");
// 加載Bean
load(context, sources.toArray(new Object[0]));

// 創建Bean加載器,並加載
protected void load(ApplicationContext context, Object[] sources) {
	if (logger.isDebugEnabled()) {
		logger.debug("Loading source " + StringUtils.arrayToCommaDelimitedString(sources));
	}
	// 創建BeanDefinitionLoader
	BeanDefinitionLoader loader = createBeanDefinitionLoader(getBeanDefinitionRegistry(context), sources);
	if (this.beanNameGenerator != null) {
		loader.setBeanNameGenerator(this.beanNameGenerator);
	}
	if (this.resourceLoader != null) {
		loader.setResourceLoader(this.resourceLoader);
	}
	if (this.environment != null) {
		loader.setEnvironment(this.environment);
	}
	loader.load();
}



初始化BeanDefinitionLoader

用於創建BeanDefinitionLoader,並從source加載類,它是對加載Bean的整體功能做了封裝,內部由不同的資源加載類來完成不同類型的資源加載,例如從基於注解的類來開始加載,從xml文件開始加載

// SpringApplication.java

protected BeanDefinitionLoader createBeanDefinitionLoader(BeanDefinitionRegistry registry, Object[] sources) {
	return new BeanDefinitionLoader(registry, sources);
}

class BeanDefinitionLoader {
	
	// 類的來源
	private final Object[] sources;

	// JavaConfig類讀取器
	private final AnnotatedBeanDefinitionReader annotatedReader;

	// xml文件類讀取器
	private final XmlBeanDefinitionReader xmlReader;

	// groovy類讀取器
	private BeanDefinitionReader groovyReader;

	// classpath類讀取器
	private final ClassPathBeanDefinitionScanner scanner;

	// 資源加載器
	private ResourceLoader resourceLoader;

	BeanDefinitionLoader(BeanDefinitionRegistry registry, Object... sources) {
		Assert.notNull(registry, "Registry must not be null");
		Assert.notEmpty(sources, "Sources must not be empty");
		this.sources = sources;
		this.annotatedReader = new AnnotatedBeanDefinitionReader(registry);
		this.xmlReader = new XmlBeanDefinitionReader(registry);
		if (isGroovyPresent()) {
			this.groovyReader = new GroovyBeanDefinitionReader(registry);
		}
		this.scanner = new ClassPathBeanDefinitionScanner(registry);
		this.scanner.addExcludeFilter(new ClassExcludeFilter(sources));
	}
	// ...省略部分代碼
}

通過觀察它的內部屬性和構造方法可以看出,它支持加載基於編程方式配置的類,支持xml文件配置的類,支持groovy和xml混合定義的類的加載。它使用門面模式封裝了這些具體的加載器。此處只需要先知道BeanDefinitionLoader用於加載Bean,且它內部封裝了多個類讀取器即可
不必深入。先了解大體的功能性即可。后續會開單獨篇章介紹。

使用BeanDefinitionLoader加載Bean

// BeanDefinitionLoader.java

// ①
public int load() {
	int count = 0;
	for (Object source : this.sources) {
		count += load(source);
	}
	return count;
}

// ②
private int load(Object source) {
	Assert.notNull(source, "Source must not be null");
	if (source instanceof Class<?>) {
		return load((Class<?>) source);
	}
	if (source instanceof Resource) {
		return load((Resource) source);
	}
	if (source instanceof Package) {
		return load((Package) source);
	}
	if (source instanceof CharSequence) {
		return load((CharSequence) source);
	}
	throw new IllegalArgumentException("Invalid source type " + source.getClass());
}

① - 記錄加載的資源數量
② - 根據傳入的資源類型選擇不同的加載方式

此處我們選擇SpringBoot典型的資源加載方式Class方式來分析,在應用程序啟動入口我們使用的是SpringApplication.run(Example.class, args);而Example.class是我們的main函數所在類,以它作為資源來加載。

// BeanDefinitionLoader.java

private int load(Class<?> source) {
	// ①
	if (isGroovyPresent() && GroovyBeanDefinitionSource.class.isAssignableFrom(source)) {
		// Any GroovyLoaders added in beans{} DSL can contribute beans here
		GroovyBeanDefinitionSource loader = BeanUtils.instantiateClass(source, GroovyBeanDefinitionSource.class);
		load(loader);
	}
	// ②
	if (isComponent(source)) {
		this.annotatedReader.register(source);
		return 1;
	}
	return 0;
}

① - 判斷是否支持Groovy
② - 判斷是否包含@Component注解,如果包含才開始注冊

// BeanDefinitionLoader.java

private boolean isComponent(Class<?> type) {

	if (AnnotationUtils.findAnnotation(type, Component.class) != null) {
		return true;
	}
	if (type.getName().matches(".*\\$_.*closure.*") || type.isAnonymousClass() || type.getConstructors() == null || type.getConstructors().length == 0) {
		return false;
	}
	return true;
}

// AnnotationUtils.java

public static <A extends Annotation> A findAnnotation(Class<?> clazz, @Nullable Class<A> annotationType) {
		return findAnnotation(clazz, annotationType, true);
}


@SuppressWarnings("unchecked")
@Nullable
private static <A extends Annotation> A findAnnotation(Class<?> clazz, @Nullable Class<A> annotationType, boolean synthesize) {

	Assert.notNull(clazz, "Class must not be null");
	if (annotationType == null) {
		return null;
	}
	
	// ①
	AnnotationCacheKey cacheKey = new AnnotationCacheKey(clazz, annotationType);
	A result = (A) findAnnotationCache.get(cacheKey);
	if (result == null) {
		result = findAnnotation(clazz, annotationType, new HashSet<>());
		if (result != null && synthesize) {
			result = synthesizeAnnotation(result, clazz);
			findAnnotationCache.put(cacheKey, result);
		}
	}
	return result;
}

① - 從緩存獲取被加載的資源類是否有@Component注解的緩存

默認情況下是沒有緩存,此時將會去查找Example.class是否包含@Component注解。那在默認情況下我們的啟動類並未直接標明這個注解, 一個典型的SpringBoot Web應用如下:


@RestController
@SpringBootApplication
public class Example {

	@RequestMapping("/")
	String home() {
		return "Hello World!";
	}

	public static void main(String[] args) {
		SpringApplication.run(Example.class, args);

	}

}

實際上@RestController和@SpringBootApplication兩個注解都包含了@Component注解。因此Example.class自然也就具有@Component注解,感興趣的可以親自點進去看看這兩個注解內部。更多細節將來會單獨分析。在確定了它支持@Component注解之后,開始加載Bean。

// BeanDefinitionLoader#load(Class<?> source)

this.annotatedReader.register(source);

// AnnotatedBeanDefinitionReader.java

<T> void doRegisterBean(Class<T> annotatedClass, @Nullable Supplier<T> instanceSupplier, @Nullable String name,
			@Nullable Class<? extends Annotation>[] qualifiers, BeanDefinitionCustomizer... definitionCustomizers) {

	// ①
	AnnotatedGenericBeanDefinition abd = new AnnotatedGenericBeanDefinition(annotatedClass);
	// ②
	if (this.conditionEvaluator.shouldSkip(abd.getMetadata())) {
		return;
	}

	// ③
	abd.setInstanceSupplier(instanceSupplier);
	// ④
	ScopeMetadata scopeMetadata = this.scopeMetadataResolver.resolveScopeMetadata(abd);
	abd.setScope(scopeMetadata.getScopeName());
	String beanName = (name != null ? name : this.beanNameGenerator.generateBeanName(abd, this.registry));
	// ⑤
	AnnotationConfigUtils.processCommonDefinitionAnnotations(abd);
	// ⑥
	if (qualifiers != null) {
		for (Class<? extends Annotation> qualifier : qualifiers) {
			if (Primary.class == qualifier) {
				abd.setPrimary(true);
			}
			else if (Lazy.class == qualifier) {
				abd.setLazyInit(true);
			}
			else {
				abd.addQualifier(new AutowireCandidateQualifier(qualifier));
			}
		}
	}
	// ⑦
	for (BeanDefinitionCustomizer customizer : definitionCustomizers) {
		customizer.customize(abd);
	}

	// ⑧
	BeanDefinitionHolder definitionHolder = new BeanDefinitionHolder(abd, beanName);
	definitionHolder = AnnotationConfigUtils.applyScopedProxyMode(scopeMetadata, definitionHolder, this.registry);
	BeanDefinitionReaderUtils.registerBeanDefinition(definitionHolder, this.registry);
}

① - 創建一個帶注解元數據的類定義。
② - 判斷是否有@Conditional注解,以確定是否滿足條件需要排除。
③ - 設置一個創建Bean的回調,基本都是空值。
④ - 獲取當前Bean的作用域元數據,判斷依據是是否包含@Scope注解。ScopeMetadata包含類的作用域描述(singleton | prototype)和代理模式描述ScopedProxyMode,默認為單例模式,不使用代理創建。
⑤ - 處理常用注解,包括@Lazy、@Primary、@Role、@Description、@DependsOn,他們將被設置到BeanDefinition的屬性當中。
⑥ - 檢查傳入的Qualifiers。
⑦ - 暫時不知道干啥的,以后再說
⑧ - 確認好當前Bean的代理模式,並注冊。

在真正將一個類以BeanDefinition來注冊的時候需要把可能涉及到的一些特性全部都檢查一遍。此處只加載了我們的示例類Example.class,並未加載其他類。

發布ApplicationPreparedEvent事件

由EventPublishingRunListener.contextLoaded(ConfigurableApplicationContext context)方法觸發。

在真正觸發事件之前,它處理了ApplicationContextAware實現,為其設置了上下文。

為ApplicationContextAware擴展接口設置上下文

public void contextLoaded(ConfigurableApplicationContext context) {
	for (ApplicationListener<?> listener : this.application.getListeners()) {
		if (listener instanceof ApplicationContextAware) {
			((ApplicationContextAware) listener).setApplicationContext(context);
		}
		// 將所有監聽器賦值給上下文
		context.addApplicationListener(listener);
	}
	this.initialMulticaster.multicastEvent(new ApplicationPreparedEvent(this.application, this.args, context));
}

ConfigFileApplicationListener

添加了一個BeanFactoryPostProcessor:PropertySourceOrderingPostProcessor,用於重排序defaultProperties

LoggingApplicationListener

以單例模式注冊注冊當前日志系統

總結

在預處理上下文時將先前加載的一些屬性賦值給context,執行了ApplicationContextInitializers的實現類,為ApplicationContextAware接口填充context對象。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM