Spring Boot 启动(一) SpringApplication 分析
Spring Boot 启动(一) SpringApplication 分析
Spring 系列目录(https://www.cnblogs.com/binarylei/p/10198698.html)
@SpringBootApplication
public class MyApplication {
    public static void main(String[] args) {
        SpringApplication.run(ProviderApplication.class);
    }
}
本节重点分析 Spring Boot(2.1.3) 的 SpringApplication#run 方法是如何启动 Spring 容器。run 方法最终调用 new SpringApplication(primarySources).run(args)。
一、SpringApplication 初始化
1.1 重要的属性说明
// 1. 配置类,primarySources 是 run 方法传入的
private Set<Class<?>> primarySources;
private Set<String> sources = new LinkedHashSet<>();
// 2. main 方法所在的启动类,日志输出用
private Class<?> mainApplicationClass;
// 3.environment 环境配置相关,addCommandLineProperties 添加 main 方法的命令行参数到 environment
private boolean addCommandLineProperties = true;
private boolean addConversionService = true;
private Map<String, Object> defaultProperties;
private Set<String> additionalProfiles = new HashSet<>();
private boolean isCustomEnvironment = false;
// 4. webmvc、webflux、非web 对应的 ApplicationContext 不同
private Class<? extends ConfigurableApplicationContext> applicationContextClass;
private WebApplicationType webApplicationType;
// 5. 通过 spring.factories 配置
private List<ApplicationContextInitializer<?>> initializers;
private List<ApplicationListener<?>> listeners;
1.2 SpringApplication 初始化
public SpringApplication(Class<?>... primarySources) {
	this(null, primarySources);
}
public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
	this.resourceLoader = resourceLoader;
	Assert.notNull(primarySources, "PrimarySources must not be null");
	// 1. primarySources 为配置类
	this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
	// 2. 根据加载的 jar 推断是 web、webflux、非web
	this.webApplicationType = WebApplicationType.deduceFromClasspath();
	// 3. 加载 ApplicationContextInitializer 到 initializers 中。 spring.factories
	setInitializers((Collection) getSpringFactoriesInstances(
			ApplicationContextInitializer.class));
	// 4. 加载监听器到 listeners 中。 spring.factories
	setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
	// 5. mainApplicationClass 启动类,即根据 new RuntimeException().getStackTrace()
	//    栈信息推断 main 方法所在的类,本例中即为 MyApplication,用于输出日志用
	this.mainApplicationClass = deduceMainApplicationClass();
}
在构造方法中重点关注第三步和第四步,通过 Spring 的 SPI 技术(类似 JDK 的 ServiceLoader,在 Spring 中为 SpringFactoriesLoader)向容器中注入在 spring.factories 中配置的组件:
第三步:注入 ApplicationContextInitializer 的配置类。Spring Boot 默认组装了 6 个实例,spring-boot-2.1.3.RELEASE.jar 下 4 个,spring-boot-autoconfigure-2.1.3.RELEASE.jar 下 2 个。
0 = {DelegatingApplicationContextInitializer@1866}
1 = {SharedMetadataReaderFactoryContextInitializer@1867}
2 = {ContextIdApplicationContextInitializer@1868}
3 = {ConfigurationWarningsApplicationContextInitializer@1869}
4 = {ServerPortInfoApplicationContextInitializer@1870}
5 = {ConditionEvaluationReportLoggingListener@1871}
第四步:注入监听器,Spring 是基于事件驱动的,如配置文件的加载。Spring Boot 默认组装了 10 个实例,spring-boot-2.1.3.RELEASE.jar 下 9 个,spring-boot-autoconfigure-2.1.3.RELEASE.jar 下 1 个。
0 = {ConfigFileApplicationListener@1771}
1 = {AnsiOutputApplicationListener@1772}
2 = {LoggingApplicationListener@1773}
3 = {ClasspathLoggingApplicationListener@1774}
4 = {BackgroundPreinitializer@1775}
5 = {DelegatingApplicationListener@1776}
6 = {ParentContextCloserApplicationListener@1777}
7 = {ClearCachesApplicationListener@1778}
8 = {FileEncodingApplicationListener@1779}
9 = {LiquibaseServiceLocatorApplicationListener@1780}
那 Spring 是如何保证这些组件的执行顺序的呢?在 getSpringFactoriesInstances 获取所有的实例后都会进行排序 AnnotationAwareOrderComparator.sort(instances)。 详见:https://www.cnblogs.com/binarylei/p/10426083.html
二、run 方法主要流程分析
run 方法主要是启动 ApplicationContext 容器,省略了一些非必须的代码。
public ConfigurableApplicationContext run(String... args) {
	ConfigurableApplicationContext context = null;
	// 1. listeners 用户监听容器的运行,默认实现为 EventPublishingRunListener
	SpringApplicationRunListeners listeners = getRunListeners(args);
	listeners.starting();
	try {
		ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
		// 2. 初始化环境变量 environment
		ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments);
		// 3. 仅仅实例化对应的 ApplicationContext,还没有任何配置
		context = createApplicationContext();
		// 4. 配置 context,为刷新容器做好准备
		prepareContext(context, environment, listeners, applicationArguments, printedBanner);
		// 5. AbstractApplicationContext#refresh
		refreshContext(context);
		afterRefresh(context, applicationArguments);
		listeners.started(context);
		callRunners(context, applicationArguments);
	} catch (Throwable ex) {
		throw new IllegalStateException(ex);
	}
	try {
		listeners.running(context);
	} catch (Throwable ex) {
		throw new IllegalStateException(ex);
	}
	return context;
}
getRunListeners初始化 SpringApplicationRunListeners,也是通过 spring.factories 配置 EventPublishingRunListener。SpringApplicationRunListeners 监听了容器启动、环境准备等事件。prepareEnvironment初始化环境变量 environment,根据是否是 web 程序启动不同的 Environment 实现。同时将 ①配置的默认数据源 defaultProperties;②main 方法的参数;③application.properties 配置文件当作 Environment 的数据源。createApplicationContext实例化 ApplicationContextprepareContext配置 ApplicationContextrefreshContext刷新 ApplicationContext
2.1 getRunListeners - 初始化 SpringApplicationRunListeners
SpringApplicationRunListeners 默认实现为 EventPublishingRunListener。注意之所以不用 ApplicationContext 直接触发事件,是因为只有到第 4 步 contextLoaded 之后容器的初始化工作才完成,此时才能用 context.publishEvent() 触发相应的事件。
public interface SpringApplicationRunListener {
	// 1. 调用 run 方法后首先触发 starting 事件
	void starting();
	// 2. prepareEnvironment。初始化 environment 时调用,
	void environmentPrepared(ConfigurableEnvironment environment);
	// 3. prepareContext 开始时调用
	void contextPrepared(ConfigurableApplicationContext context);
	// 4. prepareContext 完成时调用
	void contextLoaded(ConfigurableApplicationContext context);
	// 5. refreshContext 后调用
	void started(ConfigurableApplicationContext context);
	// 6. started 后调用,run 方法调用完成
	void running(ConfigurableApplicationContext context);
	void failed(ConfigurableApplicationContext context, Throwable exception);
}
2.2 prepareEnvironment - 初始化环境变量 environment
private ConfigurableEnvironment prepareEnvironment(SpringApplicationRunListeners listeners,
		ApplicationArguments applicationArguments) {
	// 1. 根据 webApplicationType 创建相应的 Environment
	ConfigurableEnvironment environment = getOrCreateEnvironment();
	// 2. 配置 Environment,主要有三点:一是 ConversionService;二是数据源,包括命令行参数;三是 Profiles
	configureEnvironment(environment, applicationArguments.getSourceArgs());
	// 3. 激活 environmentPrepared 事件,主要是加载 application.yml 等配置文件
	//    ConfigFileApplicationListener#ApplicationEnvironmentPreparedEvent
	listeners.environmentPrepared(environment);
	bindToSpringApplication(environment);
	if (!this.isCustomEnvironment) {
		environment = new EnvironmentConverter(getClassLoader())
				.convertEnvironmentIfNecessary(environment, deduceEnvironmentClass());
	}
	// ??? 以后再研究
	ConfigurationPropertySources.attach(environment);
	return environment;
}
加载后的数据源如下:
0 = {SimpleCommandLinePropertySource@2697} "SimpleCommandLinePropertySource {name='commandLineArgs'}"
1 = {PropertySource$StubPropertySource@2698} "StubPropertySource {name='servletConfigInitParams'}"
2 = {PropertySource$StubPropertySource@2699} "StubPropertySource {name='servletContextInitParams'}"
3 = {MapPropertySource@2700} "MapPropertySource {name='systemProperties'}"
4 = {SystemEnvironmentPropertySourceEnvironmentPostProcessor$OriginAwareSystemEnvironmentPropertySource@2701} "OriginAwareSystemEnvironmentPropertySource {name='systemEnvironment'}"
5 = {RandomValuePropertySource@2702} "RandomValuePropertySource {name='random'}"
6 = {OriginTrackedMapPropertySource@2703} "OriginTrackedMapPropertySource {name='applicationConfig: [classpath:/application.properties]'}"
执行加载的过程如下:
- servletConfigInitParams 和 servletContextInitParams 是 web 项目独有,systemEnvironment 和 systemEnvironment 这些都在初始化 environment 注入。
 - commandLineArgs 是 main 方法命令行参数,在 configureEnvironment 注入。
 - applicationConfig 是配置文件,在 environmentPrepared 时通过 ConfigFileApplicationListener#ApplicationEnvironmentPreparedEvent 事件注入。
 
2.3 createApplicationContext - 实例化 ApplicationContext
没什么可说的,略过
2.4 prepareContext- 配置 ApplicationContext
private void prepareContext(ConfigurableApplicationContext context,
		ConfigurableEnvironment environment, SpringApplicationRunListeners listeners,
		ApplicationArguments applicationArguments, Banner printedBanner) {
	// 1. 为 context 注入基本的组件
	context.setEnvironment(environment);
	postProcessApplicationContext(context);
	// 2. List<ApplicationContextInitializer<?>> initializers 在初始化的时候已经注入
	applyInitializers(context);
	// 3. 触发 contextPrepared 事件
	listeners.contextPrepared(context);
	// 4. 配置 beanFactory
	ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
	beanFactory.registerSingleton("springApplicationArguments", applicationArguments);
	if (beanFactory instanceof DefaultListableBeanFactory) {
		((DefaultListableBeanFactory) beanFactory)
				.setAllowBeanDefinitionOverriding(this.allowBeanDefinitionOverriding);
	}
	// 5. load 方法加载 BeanDefinition
	Set<Object> sources = getAllSources();
	Assert.notEmpty(sources, "Sources must not be empty");
	load(context, sources.toArray(new Object[0]));
	// 6. 触发 contextLoaded 事件,此时 context 准备工作已经完成
	listeners.contextLoaded(context);
}
prepareContext 主要是配置 context 和 beanFactory。其中最重要的方法是 load(context, sources.toArray(new Object[0])) 方法,向容器中加载 BeanDefinition。sources 指的是 Spring 的配置类,默认为 this.primarySources 即 SpringApplication.run(Application.class, args) 中的配置类 Application。
下面主要看一下 load 是如何加载 BeanDefinition 的。
protected void load(ApplicationContext context, Object[] sources) {
	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();
}
protected BeanDefinitionLoader createBeanDefinitionLoader(
		BeanDefinitionRegistry registry, Object[] sources) {
	return new BeanDefinitionLoader(registry, sources);
}
load 将加载 BeanDefinitionLoader 委托给了 BeanDefinitionLoader#load() 方法,其中 sources 即为配置类。
2.5 refreshContext - 刷新 ApplicationContext
没什么可说的,见 AbstractApplicationContext#refresh(https://www.cnblogs.com/binarylei/p/10423475.html)
每天用心记录一点点。内容也许不重要,但习惯很重要!
Spring Boot 启动(一) SpringApplication 分析的更多相关文章
- spring boot启动原理步骤分析
		
spring boot最重要的三个文件:1.启动类 2.pom.xml 3.application.yml配置文件 一.启动类->main方法 spring boot启动原理步骤分析 1.spr ...
 - Spring Boot 启动原理分析
		
https://yq.aliyun.com/articles/6056 转 在spring boot里,很吸引人的一个特性是可以直接把应用打包成为一个jar/war,然后这个jar/war是可以直接启 ...
 - [Spring Boot] Spring Boot启动过程源码分析
		
关于Spring Boot,已经有很多介绍其如何使用的文章了,本文从源代码(基于Spring-boot 1.5.6)的角度来看看Spring Boot的启动过程到底是怎么样的,为何以往纷繁复杂的配置到 ...
 - Spring Boot启动过程源码分析--转
		
https://blog.csdn.net/dm_vincent/article/details/76735888 关于Spring Boot,已经有很多介绍其如何使用的文章了,本文从源代码(基于Sp ...
 - Spring Boot启动命令参数详解及源码分析
		
使用过Spring Boot,我们都知道通过java -jar可以快速启动Spring Boot项目.同时,也可以通过在执行jar -jar时传递参数来进行配置.本文带大家系统的了解一下Spring ...
 - Spring Boot启动流程分析
		
引言 早在15年的时候就开始用spring boot进行开发了,然而一直就只是用用,并没有深入去了解spring boot是以什么原理怎样工作的,说来也惭愧.今天让我们从spring boot启动开始 ...
 - Spring Boot 启动(四) EnvironmentPostProcessor
		
Spring Boot 启动(四) EnvironmentPostProcessor Spring 系列目录(https://www.cnblogs.com/binarylei/p/10198698. ...
 - Spring Boot 启动(二) 配置详解
		
Spring Boot 启动(二) 配置详解 Spring 系列目录(https://www.cnblogs.com/binarylei/p/10198698.html) Spring Boot 配置 ...
 - Spring Boot 启动(二) Environment 加载
		
Spring Boot 启动(二) Environment 加载 Spring 系列目录(https://www.cnblogs.com/binarylei/p/10198698.html) 上一节中 ...
 
随机推荐
- SQL 第一范式、第二范式、第三范式、BCNF范式
			
一.第一范式 1NF 要求:每一个分量必须是不可分的数据项. 特点: 1)有主键,且主键不能为空. 2)字段不能再分. 示例:(以下例子 不满足 第一范式) /*学号 年龄 信 ...
 - java File处理
			
/**************************************************************************************工具类********** ...
 - Docker系列08:容器监控
			
1 监控解决方案 cadvisor+influxdb+grafana cAdvisor:Google开源的工具,用于监控Docker主机和容器系统资源,通过图形页面实时显示数据,但不存储:它通过宿主机 ...
 - solr字段压缩属性compressed新版本已经移除
			
solr字段压缩属性compressed新版本已经移除 可能是考虑到压缩意义不大还减少搜索效率,所以去掉了.而且好像没有替代属性.
 - pycharm 调试django项目时,debug断点没反应???
			
入门python.django框架时,使用pycharm断点调试时,发现打的断点没反应,不起作用!上网上稍微一查,90%的都差不多,需要新建一个python程序,重新配置一遍,的确可以成功! 操作链接 ...
 - How to solve “Dynamic Web Module 3.1 requires Java 1.7 or newer” in Eclipse
			
How to solve “Dynamic Web Module 3.1 requires Java 1.7 or newer” in Eclipse Last updated on June 20t ...
 - spring cloud整合 websocket 的那些事
			
我们知道, 现在很多浏览器都已经是直接支持 websocket 协议的了, 除此之外, sockjs, 也可以实现 websocket 功能.. 当然, 其原理是不同的. 一开始 websocket ...
 - Linux命令:unlias
			
语法 unalias [-a] name [name ...] 说明 取消别名. 可以一次取消多个别名,写几个取消几个.不写,取消所有别名. 参数 -a 取消所有别名,不论后面是否跟一个还是多个nam ...
 - nexus的安装和简介(2)
			
上传jar包到私服 1. 配置settings.xml 需要在客户端即部署dao工程的电脑上配置 maven环境,并修改 settings.xml 文件,配置连接私服的用户和密码 . 此用户名和密码用 ...
 - ps命令详解
			
1.简介: ps 命令有两种不同的语法风格 —— BSD 与 UNIX 两种风格.新手常常对这两种形式产生误解,因此我们有必要在这里作一个简单的说明: ps aux 与 ps -aux 是不同的,例如 ...