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;
}
  1. getRunListeners 初始化 SpringApplicationRunListeners,也是通过 spring.factories 配置 EventPublishingRunListener。SpringApplicationRunListeners 监听了容器启动、环境准备等事件。

  2. prepareEnvironment 初始化环境变量 environment,根据是否是 web 程序启动不同的 Environment 实现。同时将 ①配置的默认数据源 defaultProperties;②main 方法的参数;③application.properties 配置文件当作 Environment 的数据源。

  3. createApplicationContext 实例化 ApplicationContext

  4. prepareContext 配置 ApplicationContext

  5. refreshContext 刷新 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]'}"

执行加载的过程如下:

  1. servletConfigInitParams 和 servletContextInitParams 是 web 项目独有,systemEnvironment 和 systemEnvironment 这些都在初始化 environment 注入。
  2. commandLineArgs 是 main 方法命令行参数,在 configureEnvironment 注入。
  3. 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 分析的更多相关文章

  1. spring boot启动原理步骤分析

    spring boot最重要的三个文件:1.启动类 2.pom.xml 3.application.yml配置文件 一.启动类->main方法 spring boot启动原理步骤分析 1.spr ...

  2. Spring Boot 启动原理分析

    https://yq.aliyun.com/articles/6056 转 在spring boot里,很吸引人的一个特性是可以直接把应用打包成为一个jar/war,然后这个jar/war是可以直接启 ...

  3. [Spring Boot] Spring Boot启动过程源码分析

    关于Spring Boot,已经有很多介绍其如何使用的文章了,本文从源代码(基于Spring-boot 1.5.6)的角度来看看Spring Boot的启动过程到底是怎么样的,为何以往纷繁复杂的配置到 ...

  4. Spring Boot启动过程源码分析--转

    https://blog.csdn.net/dm_vincent/article/details/76735888 关于Spring Boot,已经有很多介绍其如何使用的文章了,本文从源代码(基于Sp ...

  5. Spring Boot启动命令参数详解及源码分析

    使用过Spring Boot,我们都知道通过java -jar可以快速启动Spring Boot项目.同时,也可以通过在执行jar -jar时传递参数来进行配置.本文带大家系统的了解一下Spring ...

  6. Spring Boot启动流程分析

    引言 早在15年的时候就开始用spring boot进行开发了,然而一直就只是用用,并没有深入去了解spring boot是以什么原理怎样工作的,说来也惭愧.今天让我们从spring boot启动开始 ...

  7. Spring Boot 启动(四) EnvironmentPostProcessor

    Spring Boot 启动(四) EnvironmentPostProcessor Spring 系列目录(https://www.cnblogs.com/binarylei/p/10198698. ...

  8. Spring Boot 启动(二) 配置详解

    Spring Boot 启动(二) 配置详解 Spring 系列目录(https://www.cnblogs.com/binarylei/p/10198698.html) Spring Boot 配置 ...

  9. Spring Boot 启动(二) Environment 加载

    Spring Boot 启动(二) Environment 加载 Spring 系列目录(https://www.cnblogs.com/binarylei/p/10198698.html) 上一节中 ...

随机推荐

  1. mysql 远程备份

    #远程备份./innobackupex --defaults-file=/etc/my.cnf --no-timestamp --user xxx --host 192.168.1.123 \--pa ...

  2. vld for memory leak detector (release version)

    有没有这样的情况,无法静态的通过启动和退出来查找内存泄露,比如网络游戏,你总不能直接关游戏那玩家怎么办? 现在vld支持release,我们可以动态的找. 1.在release版本使用vld了.< ...

  3. 文件管理 - Ring3创建目录

    //多字符集 #include "stdafx.h" #include <Windows.h> #include <iostream> using name ...

  4. Python模块subprocess

    subprocess的常用用法 """ Description: Author:Nod Date: Record: #-------------------------- ...

  5. 知识点:synchronized 原理分析

    synchronized 原理分析 1. synchronized 介绍 在并发程序中,这个关键字可能是出现频率最高的一个字段,他可以避免多线程中的安全问题,对代码进行同步.同步的方式其实就是隐式的加 ...

  6. Python全栈开发记录_第二篇(文件操作及三级菜单栏增删改查)

    python3文件读写操作(本篇代码大约100行) f = open(xxx.txt, "r", encoding="utf-8") 不写“r”(只读)默认是只 ...

  7. JAVA多线程17个问题

    1.Thread 类中的start() 和 run() 方法有什么区别? Thread.start()方法(native)启动线程,使之进入就绪状态,当cpu分配时间该线程时,由JVM调度执行run( ...

  8. 国内+海外IDC资源合作

    主营业务:服务器租用.托管.机柜大带宽.安全防御.云主机.海外专线.海外托管.CDN加速.站群 资源覆盖: 华南:广东东莞.深圳.广州.湛江.福建厦门.泉州.福州 华北:北京.天津.山东 华东:江苏苏 ...

  9. Sphinx 与全文索引

    全文索引创建过程 第一步:将源文档传给分词组件(Tokenizer) 分词组件做了以下事情: 将文档分成一个一个的单词 去除标点符号 去除停词:英文(the / a / this / that ... ...

  10. 用ActiveX 创建自己的comboBox 控件(二)

    3.0 添加事件 3.1 添加OnSelChange 事件 当用户选中列表项的时候触发该事件.(不只是选择改变时触发,本次选择和上次相同时也触发): 添加完成后,在ActivexcomboBox.id ...