jjzjj

《SpringBoot启动流程三》:两万+字图文带你debug源码分析SpringApplication准备阶段(含配置文件加载时机、日志系统初始化时机)

秃秃爱健身 2023-04-18 原文

文章目录

一、前言

上文聊了 SpringBoot中SpringApplication是如何构建的(《SpringBoot启动流程二》:七千字源码分析SpringApplication构造阶段)?从这篇文章开始,进入到SpringApplication的运行阶段(核心过程),我们分三个部分来讨论,分别为:SpringApplication准备阶段、ApplicationContext启动阶段、ApplicationContext启动后阶段。

其中SpringApplication的准备阶段是从run(String…)方法调用开始 到 refreshContext(ConfigurableApplicationContext调用前)。

注:Spring Boot版本:2.3.7

二、SpringApplication准备阶段

除初始化StopWatch等少数无足轻重的对象外,该过程会依次准备核心对象:SpringApplicationRunListeners、ApplicationArguments、ConfigurableEnvironment、Banner、ConfigurableApplicationContext 和 SpringBootExceptionReporter集合。其中ConfigurableApplicationContext 可能我们最熟悉,为了加深对它们的理解,下面逐一讨论。

我在看源码的时候喜欢将各个逻辑按步骤,一步步拆分再汇总。针对Spring Boot运行阶段我大致分为十五步,其中SpringApplication准备阶段占了十步。

整体流程如下:

1、准备一些无伤大雅的对象

整体流程图的第一步、第二步、第六步准备了一些无伤大雅的对象,比如:开启计时器StopWatch、设置一些系统属性。

1)第一步:开启计时器StopWatch

第一步只是开启计时器,并设置程序启动的开始时间

StopWatch stopWatch = new StopWatch();
stopWatch.start();

代码的整体调用如下,其中taskName默认为空字符串"",记录程序启动的时间为运行stopWatch.start()方法的当前时间。

就整个程序的启动时间而言,其实并不精确,因为构建SpringApplication的时间并没有算进去,其中也包含了一次从spring.factories文件中读取信息的IO操作。

2)第二步:设置系统属性java.awt.headless

第二步只是设置一个系统属性java.awt.headless,默认为true;用于运行headless服务器,进行简单的图像处理;此外,其多用于在缺少显示屏、键盘或者鼠标时的系统配置,很多监控工具如jconsole,需要将该值设置为true。

configureHeadlessProperty();

整体代码执行流程:

如果设置了java.awt.headless参数,则将其赋值给java.awt.headless系统属性,否者将true赋值给java.awt.headless系统属性。

private static final String SYSTEM_PROPERTY_JAVA_AWT_HEADLESS = "java.awt.headless";

3)第六步:设置系统属性spring.beaninfo.ignore

第六步也只是设置一个系统属性spring.beaninfo.ignore,保证某些bean不会添加到准备的环境中。

configureIgnoreBeanInfo(environment);

整体代码执行流程:

默认设置spring.beaninfo.ignore系统属性为true(一般不会该这个,无需关注)

下面,我们进入一些核心对象、和核心执行流程的讨论。

2、第三步:加载运行时监听器SpringApplicationRunListeners

加载运行时监听器SpringApplicationRunListeners主要做两个操作:

  1. 从所有依赖jar包的META-INF/spring.factories文件中获取SpringApplicationRunListener接口所有的实现类。
  2. 将获取到的实现类通过构造函数赋值到SpringApplicationRunListeners类的List类型的成员变量listeners上。
SpringApplicationRunListeners listeners = getRunListeners(args);

从代码逻辑上来看,SpringApplicationRunListeners是由getRunListeners(String[])方法创建的。

其中SpringApplicationRunListener 属于 组合模式的实现,其内部关联了SpringApplicationRunListner集合。


getSpringFactoriesInstances(SpringApplicationRunListener.class, types, this, args)方法只会获取到一个EventPublishingRunListeners对象,此对象会贯穿整个应用程序启动的过程,用于发布各种事件给到各个Spring事件监听器。

针对getSpringFactoriesInstance()方法是如何从spring.factories文件中获取到执行SpringApplicationRunListener接口的所有实现类的,我们在博文:《SpringBoot启动流程一》:万字debug梳理SpringBoot如何加载并处理META-INF/spring.factories文件中的信息中详细聊过;

1)浅谈SpringApplicationRunListner

上面说道,我们会把所有SpringApplicationRunListner的实现类赋值到SpringApplicationRunListeners类的List类型的成员变量listeners上。那么SpringApplicationRunListner有什么用?

SpringApplicationRunListner是Spring Boot应用运行时监听器,而不是Spring Boot事件监听器;其监听方法被SpringApplicationRunListeners阶段性的执行,在SpringApplication的运行阶段涉及的方法如下:

具体每个方法对应哪些Spring Boot事件、哪些Spring Boot事件监听器会执行,放在<SpringBoot事件和事件监听器在整个SpringBoot启动流程中具体是如何运作的?(具体到每个事件对应的每个事件监听器都做了什么)>一文中详细讨论,敬请期待。后期写完自动补充到这里(todo)。

2)第三.2步:发布应用启动事件ApplicationStartingEvent

在加载完运行时监听器SpringApplicationRunListeners之后,紧接着会通过其发布一个starting事件;

按F7 debug往里跟:

这里可以注意到,starting()方法中直接遍历this.listeners,而listeners是在初始化运行时监听器SpringApplicationRunListeners赋值的,并且其中只有一个对象:EventPublishingRunListeners
所以我们接下来进入EventPublishingRunListeners#starting()方法。

multicastEvent的命名推测,EventPublishingRunListeners通过其内部的initialMulticaster成员广播事件。

1> 那么initialMulticaster成员是什么初始化的?


在初始化EventPublishingRunListener(即在调用EventPublishingRunListener构造函数)的同时会初始化initialMulticaster,并将SpringApplication中的11个Spring事件监听器添加到initialMulticaster中。

再看SimpleApplicationEventMulticaster的类图:

其继承自AbstractApplicationEventMulticaster,而其自身并没有addApplicationListener(ApplicationListener)方法,但其父类AbstractApplicationEventMulticaster有,所以进入到AbstractApplicationEventMulticaster#addApplicationListener(ApplicationListener)方法:

方法中先对defaultRetriever成员变量加锁,保证可以线程安全的操作defaultRetriever变量,其次将传入的Spring事件监听器listener添加到defaultRetriever对象的List类型的applicationListeners成员中。

public abstract class AbstractApplicationEventMulticaster
		implements ApplicationEventMulticaster, BeanClassLoaderAware, BeanFactoryAware {

	private final DefaultListenerRetriever defaultRetriever = new DefaultListenerRetriever();

    ....

	private class DefaultListenerRetriever {

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

        ....
    }

    ....
}

2> 回到this.initialMulticaster.multicastEvent()继续看是如何发布事件的?


EventPublishingRunListener将Spring事件的发布委托给它SimpleApplicationEventMulticaster类型的成员initialMulticaster。即:SimpleApplicationEventMulticaster出自 Spring Framework,其中关联了ApplicationListener,并负责广播ApplicationEvent。

3> ApplicationEvent对应哪些ApplicationListener?

ApplicationStartingEvent事件为例,发布这个事件之后,应该通知哪些Spring事件监听器ApplicationListener?AbstractApplicationEventMulticaster#getApplicationListeners(ApplicationEvent,ResolvableType)方法给了我们答案。

进入到retrieveApplicationListeners()方法,主要逻辑如下:

  1. 对AbstractApplicationEventMulticaster类的defaultRetriever对象加锁,防止期间别的线程对defaultRetriever对象修改。加锁成功后,将defaultRetriever.applicationListeners赋值到listeners变量上。而defaultRetriever.applicationListeners我们在上文中提到过:其是在EventPublishingRunListener初始化的时候赋值的,里面包含11个监听器。
  2. 遍历listeners,使用supportsEvent()方法判断每个监听器是否可以监听当前事件,将可以监听当前事件的监听器添加到allListeners List集合中,排序后返回。

我们再看一下supportsEvent()方法是如何判断某个Spring事件监听器是否可以处理某个Spring事件的?

以这里的ApplicationStartingEvent事件和CloudFoundryVcapEnvironmentPostProcessor事件监听器为例;

<1> 首先将Spring事件监听器封装为GenericApplicationListenerAdapter对象,然后调用GenericApplicationListenerAdapter对象的supportsSourceType(Class)方法判断是否支持监听传入的事件类型。


<2> 如果当前事件监听器的类型是SmartApplicationListener,则直接调用当前事件监听器的supportsEventType()方法,否则进一步调用declaredEventType.isAssignableFrom()方法判断。

看到这里也就够了,大家不要再深入了。里面太细了,博主会放在<SpringBoot事件和事件监听器在整个SpringBoot启动流程中具体是如何运作的?(具体到每个事件对应的每个事件监听器都做了什么)>一文中详细讨论,敬请期待。后期写完自动补充到这里(todo)。后续文章中牵扯到的Spring事件细节都放在另一博文中详细讨论,次博文只给出结论。

说了这么多,发布应用启动事件ApplicationStartingEvent之后,其实只有LoggingApplicationListener 和 BackgroundPreinitializer做了事情; 其中:

  1. LoggingApplicationListener 中获取了日志系统loggingSystem,并做了日志系统的beforeInitialize()操作。
  2. BackgroundPreinitializer 中做了一些后台功能的初始化,比如:转换服务、校验服务、Jackson、Charset字符集初始化。

3、第四步:加载并解析命令行的参数到ApplicationArguments对象中

当执行完SpringApplicationRunListeners#starting()方法后,SpringApplication进入到装配ApplicationArguments逻辑:

ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);

ApplicationArguments的实现类DefaultApplicationArguments的底层实现是基于Spring Framework中的命令行配置源SimpleCommandLinePropertySource

代码整体执行流程如下:

SimpleCommandLinePropertySource将命令行参数分为两组,分别为:

  1. “选项参数”,选项参数必须以“--”为前缀。
  2. “非选项参数”,非选项参数是未包含“--”前缀的命令行参数。

而命令行参数的解析由SimpleCommandLineArgsParser来完成,具体体现在其parse()方法中:

class SimpleCommandLineArgsParser {

	/**
	 * Parse the given {@code String} array based on the rules described {@linkplain
	 * SimpleCommandLineArgsParser above}, returning a fully-populated
	 * {@link CommandLineArgs} object.
	 * @param args command line arguments, typically from a {@code main()} method
	 */
	public CommandLineArgs parse(String... args) {
		CommandLineArgs commandLineArgs = new CommandLineArgs();
		// 1. 选项参数
		for (String arg : args) {
			if (arg.startsWith("--")) {
				String optionText = arg.substring(2);
				String optionName;
				String optionValue = null;
				int indexOfEqualsSign = optionText.indexOf('=');
				if (indexOfEqualsSign > -1) {
				    // 1.1 从字符串中截取出key
					optionName = optionText.substring(0, indexOfEqualsSign);
					// 1.2 从字符串中截取出value
					optionValue = optionText.substring(indexOfEqualsSign + 1);
				}
				else {
					optionName = optionText;
				}
				if (optionName.isEmpty()) {
					throw new IllegalArgumentException("Invalid argument syntax: " + arg);
				}
				// 1.3 以key-value对的形式将参数 和 其对应的值添加到CommandLineArgs对象的Map类型成员optionArgs中。
				commandLineArgs.addOptionArg(optionName, optionValue);
			}
			// 2. 非选项参数
			else {
				commandLineArgs.addNonOptionArg(arg);
			}
		}
		return commandLineArgs;
	}

}

根据以上规则,命令行参数--server.port=8088SimpleCommandLineArgsParser解析为“server.port : 8088”键值属性,加入到CommandLineArgs中。

这里看完,给人的感觉只是将参数解析为key:value键值形式添加到DefaultApplicationArguments对象中,具体什么时候会用呢?

4、第五步:准备当前应用程序的环境ConfigurableEnvironment

ApplicationArguments实例准备完毕后,SpringApplication进入到准备应用程序环境ConfigurableEnvironment的阶段。

ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments);

prepareEnvironment()方法中的内容比较多,大致可以分为6步:

  1. 获取当前环境,如果不存在,则根据应用类型来创建对应的环境。
    比如:SERVLET应用类型创建StandardServletEnvironment、REACTIVE应用类型创建StandardReactiveWebEnvironment,否者创建StandardEnvironment。另外:StandardReactiveWebEnvironment 继承自 StandardEnvironment,两者内容完全一样,只是StandardReactiveWebEnvironment又实现了ConfigurableReactiveWebEnvironment接口,虽然其中没做方法的重写。
  2. 配置当前环境
    将类型转换器和格式化器添加到环境中、将命令行参数内容(SimpleCommandLinePropertySource {name=‘commandLineArgs’})添加到环境的propertySources成员变量中、给环境设置activeProfiles。
  3. 如果propertySources中没有configurationProperties则将ConfigurationPropertySourcesPropertySource {name='configurationProperties'}加入到propertySources中,有的话先移除,然后再加。
  4. ,广播ApplicationEnvironmentPreparedEvent事件,通知监听器,当前引用环境准备好了。在这里ConfigFileApplicationListener会解析我们的外部配置文件xx.properties、xxx.yml
  5. 绑定应用环境到spring.main,将应用环境ConfigurableEnvironment转换为相应应用类型的环境。
  6. 防止对环境转换时有问题,这里再重新配置当前环境。
private ConfigurableEnvironment prepareEnvironment(SpringApplicationRunListeners listeners,
		ApplicationArguments applicationArguments) {
	// 1、获取或者创建应用环境(创建时会根据应用类型判断)
	ConfigurableEnvironment environment = getOrCreateEnvironment();
	// 2、配置应用环境,配置propertySource和activeProfiles
	configureEnvironment(environment, applicationArguments.getSourceArgs());
	// 如果propertySources中没有`configurationProperties`则将`ConfigurationPropertySourcesPropertySource {name='configurationProperties'}`加入到propertySources中。
	// 有的话先移除,然后再加。
	ConfigurationPropertySources.attach(environment);
	// 3、监听器环境准备,广播ApplicationEnvironmentPreparedEvent事件
	//   这里会处理我们自定义的配置文件内容--见ConfigFileApplicationListener
	listeners.environmentPrepared(environment);
	// 4、绑定应用环境,不用往里深跟
	bindToSpringApplication(environment);
	if (!this.isCustomEnvironment) {
		// 将应用环境转换为相应应用类型的环境,比如:StandardServletEnvironment、StandardReactiveWebEnvironment、StandardEnvironment
		environment = new EnvironmentConverter(getClassLoader()).convertEnvironmentIfNecessary(environment,
				deduceEnvironmentClass());
	}
	// 5、防止对环境转换时有问题,这里再重新配置propertySource和activeProfiles
	ConfigurationPropertySources.attach(environment);
	return environment;
}

下面,我们将这6步拆开细看一下。

1)获取或者创建应用环境getOrCreateEnvironment()

private ConfigurableEnvironment getOrCreateEnvironment() {
	if (this.environment != null) {
		return this.environment;
	}
	switch (this.webApplicationType) {
	case SERVLET:
		return new StandardServletEnvironment();
	case REACTIVE:
		return new StandardReactiveWebEnvironment();
	default:
		return new StandardEnvironment();
	}
}

从方法的命名来看,这里的意思是获取或创建环境。

  1. 首先尝试获取应用环境,如果环境不存在,则根据应用类型来创建对应的环境。
  2. SERVLET应用类型创建StandardServletEnvironment、
  3. REACTIVE应用类型创建StandardReactiveWebEnvironment,
  4. 否者创建StandardEnvironment。

另外:StandardReactiveWebEnvironment 继承自 StandardEnvironment,两者内容完全一样,只是StandardReactiveWebEnvironment又实现了ConfigurableReactiveWebEnvironment接口,虽然其中没做方法的重写。

2)配置应用环境configureEnvironment()

configureEnvironment(environment, applicationArguments.getSourceArgs());
protected void configureEnvironment(ConfigurableEnvironment environment, String[] args) {

	// 加载转换器和格式化器
	if (this.addConversionService) {
		ConversionService conversionService = ApplicationConversionService.getSharedInstance();
		environment.setConversionService((ConfigurableConversionService) conversionService);
	}
	// 配置property sources, 将“SimpleCommandLinePropertySource {name='commandLineArgs'}”添加到ConfigurableEnvironment的propertySourceList。
	configurePropertySources(environment, args);
	// 配置profiles
	configureProfiles(environment, args);
}

configureEnvironment()方法中会做三件事:加载类型转换器和初始化器、配置propertySources、配置profiles。

1> 加载类型转换器和初始化器

默认会将ConversionService赋值到environment中。

2> 配置propertySources

如果存在命令行参数,则将命令行参数封装为SimpleCommandLinePropertySource添加到环境的propertySources成员变量中。这里和第四步:加载并解析命令行的参数到ApplicationArguments对象中是一样的操作。

3> 给环境设置activeProfiles

这里就是单纯的获取所有的additionalProfiles和当前环境active的Profile,最后合并添加到environment的activeProfiles成员变量上。

3)环境的propertySources中添加configurationProperties


走到这里时,从sources中获取到的configurationProperties为null,所以会初始化一个ConfigurationPropertySourcesPropertySource并添加到environment的propertySourceList中。

此时,可以看到environment的propertySources中有6个对象,即比最初多个两个对象(commandLineArgs、configurationProperties):

4)发布事件listeners.environmentPrepared()

当应用程序的环境准备之后,EventPublishingRunListener发布一个ApplicationEnvironmentPreparedEvent事件。

EventPublishingRunListener将Spring事件的发布委托给它SimpleApplicationEventMulticaster类型的成员initialMulticaster。具体细节和上文聊的第三.2步:发布应用启动事件ApplicationStartingEvent是一样的,参考其往里追即可。

在这个阶段,ConfigFileApplicationListener事件监听器会进行yaml/properties配置文件的加载;LoggingApplicationListener事件监听器会进行日志系统的初始化;细节另出博文总结。

此时,再看environment的propertySources中有8个对象,即比上一阶段6个对象多个两个对象(random、applicationConfig:[classpath:/application.yml]):

5)绑定应用环境到spring.main


绑定应用环境到spring.main

将应用环境ConfigurableEnvironment转换为相应应用类型的环境;

6)再次向环境的propertySources中添加configurationProperties


先将configurationProperties从environment的propertySources中移除,然后再将其添加到propertySources的头部。

至此,ConfigurationEnvironment准备完毕,后面日志中开始输出banner信息。

5、第七步:打印banner

Banner printedBanner = printBanner(environment);
private Banner printBanner(ConfigurableEnvironment environment) {
	// bannerMode默认为CONSOLE
	if (this.bannerMode == Banner.Mode.OFF) {
		return null;
	}
	// 获取resourceLoader,默认为:DefaultResourceLoader
	ResourceLoader resourceLoader = (this.resourceLoader != null) ? this.resourceLoader
			: new DefaultResourceLoader(getClassLoader());
	SpringApplicationBannerPrinter bannerPrinter = new SpringApplicationBannerPrinter(resourceLoader, this.banner);
	if (this.bannerMode == Mode.LOG) {
		return bannerPrinter.print(environment, this.mainApplicationClass, logger);
	}
	// 打印Banner日志,然后返回
	return bannerPrinter.print(environment, this.mainApplicationClass, System.out);
}

具体流程如下:

获取banner的逻辑如下:

走完banner.printBanner(environment, sourceClass, out);逻辑之后,Console日志输出如下:

这里的Banner是默认的,博主没有添加任何自定义的banner;

自定义Banner可以参考博文:趣味篇:SpringBoot自定义Banner

至此,banner打印完毕,进入创建Spring应用上下文阶段。

6、第八步:创建Spring应用上下文

根据应用类型利用反射创建Spring应用上下文,可以理解为创建一个容器;就SERVLET而言:实例化AnnotationConfigServletWebServerApplicationContext。

7、第九步:加载异常报告器SpringBootExceptionReporter

加载异常报告器SpringBootExceptionReporter,用来支持报告关于启动的错误

exceptionReporters = getSpringFactoriesInstances(SpringBootExceptionReporter.class,
					new Class[] { ConfigurableApplicationContext.class }, context);

**具体逻辑:**从"META-INF/spring.factories"文件中读取SpringBootExceptionReporter类的实例名称集合,然后进行Set去重、利用反射实例化对象,最后按照Order排序。排序后的Set集合赋值到Collection类型的exceptionReporters对象上。

getSpringFactoriesInstances(Class)详细过程,见博文:《SpringBoot启动流程一》:万字debug梳理SpringBoot如何加载并处理META-INF/spring.factories文件中的信息

8、第十步:Spring应用上下文运行前准备

Spring应用上下文运行前的准备工作由SpringApplication#prepareContext方法完成,根据SpringApplicationRunListener的生命周期回调又分为“Spring应用上下文准备阶段”“Spring应用上下文装载阶段”

prepareContext(context, environment, listeners, applicationArguments, printedBanner);
private void prepareContext(ConfigurableApplicationContext context, ConfigurableEnvironment environment,
		SpringApplicationRunListeners listeners, ApplicationArguments applicationArguments, Banner printedBanner) {
	// 设置应用上下文的environment
	context.setEnvironment(environment);
	// 应用上下文后置处理,默认只会将ConversionService添加到context的beanFactory中
	postProcessApplicationContext(context);
	// 应用一些初始化器,执行ApplicationContextInitializer,将所有的初始化对象放到context对象中。
	applyInitializers(context);
	// 发布 应用上下文初始化完成事件ApplicationContextInitializedEvent
	listeners.contextPrepared(context);
	if (this.logStartupInfo) {
		// 打印启动信息
		logStartupInfo(context.getParent() == null);
		// 打印active profile的信息
		logStartupProfileInfo(context);
	}
	// Add boot specific singleton beans
	// 注册启动参数bean,将容器指定的参数封装成bean,注入容器
	ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
	beanFactory.registerSingleton("springApplicationArguments", applicationArguments);
	// 设置banner
	if (printedBanner != null) {
		beanFactory.registerSingleton("springBootBanner", printedBanner);
	}
	if (beanFactory instanceof DefaultListableBeanFactory) {
		((DefaultListableBeanFactory) beanFactory)
				.setAllowBeanDefinitionOverriding(this.allowBeanDefinitionOverriding);
	}
	if (this.lazyInitialization) {
		context.addBeanFactoryPostProcessor(new LazyInitializationBeanFactoryPostProcessor());
	}
	// Load the sources
	// 加载所有资源(指的是启动器指定的参数)
	Set<Object> sources = getAllSources();
	Assert.notEmpty(sources, "Sources must not be empty");
	// 核心所在!!将我们自己的启动类bean、自动装配的内容  加载到上下文中
	load(context, sources.toArray(new Object[0]));
	// 发布 应用已准备好事件ApplicationPreparedEvent
	listeners.contextLoaded(context);
}

1)Spring应用上下文准备阶段

本阶段的执行从prepareContext()方法开始,到SpringApplicationRunListener#contextPrepared()运行截止;

该阶段,
1> 首先context.setEnvironment(environment);将environment设置到Spring应用上下文中;

2> 其次postProcessApplicationContext(context);会做一些上下文后置处理,默认只会将ConversionService添加到context的beanFactory中

3> 接着applyInitializers(context);应用一些初始化器ApplicationContextInitializer,将所有的初始化器对象的相关内容放到Spring上下文context对象中。

其中getInitializers()方法直接从SpringApplication的initializers成员中获取7个初始化器,而initializers的初始化发生在SpringApplication构建阶段,参考博文:《SpringBoot启动流程二》:七千字源码分析SpringApplication构造阶段

每个初始化器做的具体工作如下:

4> 接着listeners.contextPrepared(context);发布Spring上下文已经初始化完成事件ApplicationContextInitializedEvent

此处只有BackgroundPreinitializer和DelegatingApplicationListener两个事件监听器会处理ApplicationContextInitializedEvent事件,然而它俩处理逻辑中什么都没做。

至此,Spring应用上下文准备阶段内容全部结束,紧接着进入到Spring应用上下文装载阶段

2)Spring应用上下文装载阶段

prepareContext()方法中的剩余逻辑全部为Spring应用上下文装载阶段

本阶段又可划分为四个过程,分别为:“注册Spring Boot Bean”、“合并Spring应用上下文配置源”、“加载Spring应用上下文配置源” 和 “发布应用已准备好但未刷新事件ApplicationPreparedEvent”。

1> 注册Spring Boot Bean

SpringApplication#preparedContext()方法会将之前创建的ApplicationArguments对象和可能存在的Banner实例注册为Spring 单例Bean。

2> 合并Spring应用上下文配置源

合并Spring应用上下文配置源的操作由getAllSources()方法实现。

Set<Object> sources = getAllSources();
public Set<Object> getAllSources() {
	Set<Object> allSources = new LinkedHashSet<>();
	// primarySources来自于SpringApplication构造器参数
	if (!CollectionUtils.isEmpty(this.primarySources)) {
		allSources.addAll(this.primarySources);
	}
	// sources来自于setSource(Set<Object>)方法
	if (!CollectionUtils.isEmpty(this.sources)) {
		allSources.addAll(this.sources);
	}
	return Collections.unmodifiableSet(allSources);
}

getAllASources()方法返回值是一个只读Set,它由两个子集合组成:属性primarySources和sources;前者来自来自于SpringApplication构造器参数、后者来自setSource(Set)方法。

3> 加载Spring应用上下文配置源

load(ApplicationContext, Object[])方法将承担加载Spring应用上下文配置源的职责:

load(context, sources.toArray(new Object[0]));
protected void load(ApplicationContext context, Object[] sources) {
	if (logger.isDebugEnabled()) {
		logger.debug("Loading source " + StringUtils.arrayToCommaDelimitedString(sources));
	}
	// 获取bean对象定义的加载器
	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);
	}
	// 实际执行load的是BeanDefinitionLoader中的load方法
	loader.load();
}

Spring应用上下文 Bean的装载任务SpringApplication委托给了BeanDefinitionLoader来完成。进入BeanDefinitionLoader#load()方法。

AnnotatedBeanDefinitionReaderClassPathBeanDefinitionScanner配合使用,形成AnnotationConfigApplicationContext扫描和注册配置类的基础,并将配置类解析为Bean定义BeanDefinition。

我们接着跟AnnotatedBeanDefinitionReader#register()方法:

最后进入到doRegisterBean(Class,String,Class,Supplier,BeanDefinitionCustomizer[])方法,里面有些东西我们会很熟悉。

ConditionEvaluator.shouldSkip(AnnotatedTypeMetadata)方法会判断当前要注册的类有没有被@Conditional注解标注?是否应该跳过它的注册逻辑?

BeanDefinitionReaderUtils.registerBeanDefinition(BeanDefinitionHolder, BeanDefinitionRegistry);方法中真正将当前启动类注册为BeanDefinition,并且其中会涉及到Alias别名的处理。

4> 发布应用已准备好但未刷新事件ApplicationPreparedEvent

listeners.contextLoaded(context);

当应用上下文准备之后,EventPublishingRunListener发布一个ApplicationPreparedEvent事件。

和前面发布事件的方式一样EventPublishingRunListener将Spring事件的发布委托给它SimpleApplicationEventMulticaster类型的成员initialMulticaster。具体细节和上文聊的第三.2步:发布应用启动事件ApplicationStartingEvent是一样的,参考其往里追即可。

在这个阶段,各个监听器做的内容如下:

三、next

当SpringApplicationRunListener#contextLoaded()方法执行完成后,Spring应用上下文(ConfigurableApplicationContext)运行前准备的各个操作都执行完毕。写一篇博文,我们把Spring应用上下文启动阶段、和启动后阶段做一个讨论。

有关《SpringBoot启动流程三》:两万+字图文带你debug源码分析SpringApplication准备阶段(含配置文件加载时机、日志系统初始化时机)的更多相关文章

  1. ruby-on-rails - 未初始化的常量 Psych::Syck (NameError) - 2

    在我的gem中,我需要yaml并且在我的本地计算机上运行良好。但是在将我的gem推送到ruby​​gems.org之后,当我尝试使用我的gem时,我收到一条错误消息=>"uninitializedconstantPsych::Syck(NameError)"谁能帮我解决这个问题?附言RubyVersion=>ruby1.9.2,GemVersion=>1.6.2,Bundlerversion=>1.0.15 最佳答案 经过几个小时的研究,我发现=>“YAML使用未维护的Syck库,而Psych使用现代的LibYAML”因此,为了解决

  2. ruby-on-rails - 独立 ruby​​ 脚本的配置文件 - 2

    我有一个在Linux服务器上运行的ruby​​脚本。它不使用rails或任何东西。它基本上是一个命令行ruby​​脚本,可以像这样传递参数:./ruby_script.rbarg1arg2如何将参数抽象到配置文件(例如yaml文件或其他文件)中?您能否举例说明如何做到这一点?提前谢谢你。 最佳答案 首先,您可以运行一个写入YAML配置文件的独立脚本:require"yaml"File.write("path_to_yaml_file",[arg1,arg2].to_yaml)然后,在您的应用中阅读它:require"yaml"arg

  3. ruby-on-rails - 未在 Ruby 中初始化的对象 - 2

    我在Rails工作并有以下类(class):classPlayer当我运行时bundleexecrailsconsole然后尝试:a=Player.new("me",5.0,"UCLA")我回来了:=>#我不知道为什么Player对象不会在这里初始化。关于可能导致此问题的操作/解释的任何建议?谢谢,马里奥格 最佳答案 havenoideawhythePlayerobjectwouldn'tbeinitializedhere它没有初始化很简单,因为你还没有初始化它!您已经覆盖了ActiveRecord::Base初始化方法,但您没有调

  4. Ruby Sinatra 配置用于生产和开发 - 2

    我已经在Sinatra上创建了应用程序,它代表了一个简单的API。我想在生产和开发上进行部署。我想在部署时选择,是开发还是生产,一些方法的逻辑应该改变,这取决于部署类型。是否有任何想法,如何完成以及解决此问题的一些示例。例子:我有代码get'/api/test'doreturn"Itisdev"end但是在部署到生产环境之后我想在运行/api/test之后看到ItisPROD如何实现? 最佳答案 根据SinatraDocumentation:EnvironmentscanbesetthroughtheRACK_ENVenvironm

  5. ruby-on-rails - ActionController::RoutingError: 未初始化常量 Api::V1::ApiController - 2

    我有用于控制用户任务的Rails5API项目,我有以下错误,但并非总是针对相同的Controller和路由。ActionController::RoutingError:uninitializedconstantApi::V1::ApiController我向您描述了一些我的项目,以更详细地解释错误。应用结构路线scopemodule:'api'donamespace:v1do#=>Loginroutesscopemodule:'login'domatch'login',to:'sessions#login',as:'login',via::postend#=>Teamroutessc

  6. ruby - 这两个 Ruby 类初始化定义有什么区别? - 2

    我正在阅读一本关于Ruby的书,作者在编写类初始化定义时使用的形式与他在本书前几节中使用的形式略有不同。它看起来像这样:classTicketattr_accessor:venue,:datedefinitialize(venue,date)self.venue=venueself.date=dateendend在本书的前几节中,它的定义如下:classTicketattr_accessor:venue,:datedefinitialize(venue,date)@venue=venue@date=dateendend在第一个示例中使用setter方法与在第二个示例中使用实例变量之间是

  7. 电脑0x0000001A蓝屏错误怎么U盘重装系统教学 - 2

      电脑0x0000001A蓝屏错误怎么U盘重装系统教学分享。有用户电脑开机之后遇到了系统蓝屏的情况。系统蓝屏问题很多时候都是系统bug,只有通过重装系统来进行解决。那么蓝屏问题如何通过U盘重装新系统来解决呢?来看看以下的详细操作方法教学吧。  准备工作:  1、U盘一个(尽量使用8G以上的U盘)。  2、一台正常联网可使用的电脑。  3、ghost或ISO系统镜像文件(Win10系统下载_Win10专业版_windows10正式版下载-系统之家)。  4、在本页面下载U盘启动盘制作工具:系统之家U盘启动工具。  U盘启动盘制作步骤:  注意:制作期间,U盘会被格式化,因此U盘中的重要文件请注

  8. 【鸿蒙应用开发系列】- 获取系统设备信息以及版本API兼容调用方式 - 2

    在应用开发中,有时候我们需要获取系统的设备信息,用于数据上报和行为分析。那在鸿蒙系统中,我们应该怎么去获取设备的系统信息呢,比如说获取手机的系统版本号、手机的制造商、手机型号等数据。1、获取方式这里分为两种情况,一种是设备信息的获取,一种是系统信息的获取。1.1、获取设备信息获取设备信息,鸿蒙的SDK包为我们提供了DeviceInfo类,通过该类的一些静态方法,可以获取设备信息,DeviceInfo类的包路径为:ohos.system.DeviceInfo.具体的方法如下:ModifierandTypeMethodDescriptionstatic StringgetAbiList​()Obt

  9. UE4 源码阅读:从引擎启动到Receive Begin Play - 2

    一、引擎主循环UE版本:4.27一、引擎主循环的位置:Launch.cpp:GuardedMain函数二、、GuardedMain函数执行逻辑:1、EnginePreInit:加载大多数模块int32ErrorLevel=EnginePreInit(CmdLine);PreInit模块加载顺序:模块加载过程:(1)注册模块中定义的UObject,同时为每个类构造一个类默认对象(CDO,记录类的默认状态,作为模板用于子类实例创建)(2)调用模块的StartUpModule方法2、FEngineLoop::Init()1、检查Engine的配置文件找出使用了哪一个GameEngine类(UGame

  10. Vscode+Cmake配置并运行opencv环境(Windows和Ubuntu大同小异) - 2

    之前在培训新生的时候,windows环境下配置opencv环境一直教的都是网上主流的vsstudio配置属性表,但是这个似乎对新生来说难度略高(虽然个人觉得完全是他们自己的问题),加之暑假之后对cmake实在是爱不释手,且这样配置确实十分简单(其实都不需要配置),故斗胆妄言vscode下配置CV之法。其实极为简单,图比较多所以很长。如果你看此文还配不好,你应该思考一下是不是自己的问题。闲话少说,直接开始。0.CMkae简介有的人到大二了都不知道cmake是什么,我不说是谁。CMake是一个开源免费并且跨平台的构建工具,可以用简单的语句来描述所有平台的编译过程。它能够根据当前所在平台输出对应的m

随机推荐