SpringBoot 系列:(一)SpringBoot 启动过程

本贴最后更新于 1901 天前,其中的信息可能已经时移俗易

在我当前的工作中,基本上所有的 Java 工程都是基于 SpringBoot 构建的。而在日常开发过程中,我们更多的关注于如何使用 SpringBoot,并没有太多的去了解 SpringBoot 的底层实现细节,或者对 SpringBoot 底层实现细节的了解处于一种零碎、分散的状态。因此,我想通过撰写系列文章来加深自己对于 SpringBoot 的理解,与大家一起加油进步。当然,由于自己也还是程序员届的一个小学生,如果文中有哪些错误之处,也请大家留言指出。

简介

这篇文章旨在通过分析 SpringBoot 的源码,了解 SpringBoot 的启动过程。读者在阅读该文时,最好能同时翻阅 SpringBoot 源码。

当然,在正式开始 SpringBoot 启动过程源码走读前,我们在脑海里需要有一个认知,即:SpringBoot 赋予我们的,是在基于约定的前提下,快速构建一个 Spring 容器的能力。

打个比方,在 SpringBoot 之前,如果要在 Spring 工程中通过 Druid 中间件连接数据库的话,需要通过 XML 文件的形式生成 DruidDataSource;在 XML 文件中,需要人工介入 DruidDataSource 的生成过程,比如指定数据库连接的 URL、用户名、密码等。某些情况下,这些配置显得十分繁琐,整个 XML 文件也变得十分复杂。那么,是否能以一种简单的方式来生成 DruidDataSource 呢?有,即 Druid 中间件的维护方提供一个自动配置类,Spring 在启动过程中通过某种方式感知到该自动配置类,并调用该自动配置类的某些特定方法。这些方法会读取工程的配置文件中某些配置值,利用这些配置值来初始化一个 DruidDataSource,并注册到 Spring 容器。这样,对于一个使用 Druid 中间件的开发者来说,他需要做的仅仅是通过 maven 或者 gradle 引入包含自动配置类的依赖包,并在配置文件中指定配置值而已,从而省去了编写 XML 文件的工作。

这就是 SpringBoot 帮我们做的事情,即在约定的基础上,帮助我们更快的构建 Spring 容器,简化开发过程。

SpringBoot 工程启动

SpringBoot 工程的启动非常简单,只需要运行 main 方法即可,main 方法中核心在于执行 SpringApplication 的 run 静态方法。代码如下:

WX20191008162847.png

生成 SpringApplication 对象

确定 webApplicationType

这一步的目的是确定当前的工程是否是 web 工程;如果是 web 工程的话,到底是普通的基于 servlet 的 web 工程还是 reactive web 工程。

其判断逻辑也比较简单,逻辑如下:
(1)如果类路径中存在 DispatcherHandler,但是不存在 DispatcherServlet,也不存在 ServletContainer,则为一个 reactive web 工程

(2)如果当前类路径下不存在 javax.servlet.Servlet 或者不存在 org.springframework.web.context.ConfigurableWebApplicationContext,则当前工程不是 web 工程

(3)否则,为一个普通的基于 servlet 的 web 工程
WX20191008163706.png

从 spring.factories 文件中读取并实例化 ApplicationContextInitializer 对象

这里代码很简单,核心在于调用 SpringFactoriesLoader 类的静态方法 loadFactoryNames,获取到 spring.factories 文件中的 ApplicationContextInitializer 配置,并初始化 ApplicationContextInitializer 对象。

ApplicationContextInitializer 是一个接口,内部只有一个 initialize 的方法声明。在 SpringBoot 启动的后续过程中(创建并配置 ConfigurableApplicationContext 对象时),会调用该方法,用来对进一步配置 ConfigurableApplicationContext。

public interface ApplicationContextInitializer<C extends ConfigurableApplicationContext> {
	void initialize(C applicationContext);
}

SpringFactoriesLoader 的 loadFactoryNames 方法会读取 META-INF 目录下 spring.factories。META-INF/spring.factories 可以有多个,分布于工程自身的资源目录下(运行时能从类路径根目录下获取到),或者在引入的依赖包根目录下。SpringBoot 包依赖中的一个 spring.factories 内容如以下截图所示。以该截图为例,本节标题“从 spring.factories 文件中读取并实例化 ApplicationContextInitializer 对象”,即指从 spring.factories 读取并实例化 ConfigurationWarningsApplicationContextInitializer/ContextIdApplicationContextInitializer/DelegatingApplicationContextInitializer/ServerPortInfoApplicationContextInitializer

image.png

从 spring.factories 文件中读取并实例化 ApplicationListener 对象

逻辑与“从 spring.factories 文件中读取并实例化 ApplicationContextInitializer 对象”类似,通过 SpringFactoriesLoader 读取 META-INF/spring.factories,并生成 ClearCachesApplicationListener/ParentContextCloserApplicationListener/FileEncodingApplicationListener/ConfigFileApplicationListener 等对象。这些对象会在 SpringBoot 启动的后续步骤中使用到(“从 spring.factories 文件中读取并实例化 SpringApplicationRunListener 对象” 步骤,会生成 EventPublishingRunListener 对象,该对象内部会持有这些 ApplicationListener 的引用)

ApplicationListener 接口定义如下。

public interface ApplicationListener<E extends ApplicationEvent> extends EventListener {
	void onApplicationEvent(E event);
}

在众多 ApplicationListener 中,需要特别注意 ConfigFileApplicationListener。ConfigFileApplicationListener 会被用来加载配置文件 application.properties、application.yml。

确定 main 方法所在的类

逻辑比较有意思,是通过异常的形式,从异常栈里面解析出 main 方法所属类的。
image.png

调用 SpringApplication 对象的 run 非静态方法

从 spring.factories 文件中读取并实例化 SpringApplicationRunListener 对象

过程与“从 spring.factories 文件中读取并实例化 ApplicationContextInitializer 对象”和“从 spring.factories 文件中读取并实例化 ApplicationListener 对象”类似,通过 SpringFactoriesLoader 读取 META-INF/spring.factories,并生成 SpringApplicationRunListener 接口实现类的实例。

SpringApplicationRunListener 接口定义如下,主要用于在 SpringApplication 启动过程各个阶段中,触发相应的事件。

public interface SpringApplicationRunListener {
	void starting();
	void environmentPrepared(ConfigurableEnvironment environment);
	void contextPrepared(ConfigurableApplicationContext context);
	void contextLoaded(ConfigurableApplicationContext context);
	void started(ConfigurableApplicationContext context);
	void running(ConfigurableApplicationContext context);
	void failed(ConfigurableApplicationContext context, Throwable exception);
}

从前面的 spring.factories 截图可知,这一步主要是生成 EventPublishingRunListener 对象。生成 EventPublishingRunListener 对象时,会将已读取 ApplicationListener 对象传入 EventPublishingRunListener 的构造函数,构造函数内部再将 ApplicationListener 的引用传入一个 SimpleApplicationEventMulticaster 对象。总而言之,EventPublishingRunListener 对象会间接的持有 ApplicationListener 对象的引用。

调用 SpringApplicationRunListener 对象的 starting 方法

这里,调用 SpringApplicationRunListener 对象的 starting 方法,主要是调用 EventPublishingRunListener 对象的 starting 方法。EventPublishingRunListener 对象的 starting 方法内部会通过 SimpleApplicationEventMulticaster 广播一个 ApplicationStartingEvent 事件,该事件会被相应的 ApplicationListener 处理。

生成并配置一个 ConfigurableEnvironment 对象

根据前面确定的 webApplicationType,生成一个 ConfigurableEnvironment 对象

webApplicationType 类型 ConfigurableEnvironment 对象类型
SERVLET StandardServletEnvironment
REACTIVE StandardReactiveWebEnvironment
其他 StandardEnvironment
private ConfigurableEnvironment prepareEnvironment(SpringApplicationRunListeners listeners,ApplicationArguments applicationArguments) {
	// ⑴. 得到环境对象ConfigurableEnvironment,没有则创建一个StandardServletEnvironment
	ConfigurableEnvironment environment = getOrCreateEnvironment();
	// ⑵. 配置环境信息(激活环境,通过从系统环境变量里取)
	configureEnvironment(environment, applicationArguments.getSourceArgs());
	// ⑶. 发布ApplicationEnvironmentPreparedEvent事件,加载配置文件
	listeners.environmentPrepared(environment);
	if (isWebEnvironment(environment) && !this.webEnvironment) {
		environment = convertToStandardEnvironment(environment);
	}
	return environment;
}

protected void configureEnvironment(ConfigurableEnvironment environment,String[] args) {
	configurePropertySources(environment, args);
	// 配置ConfigurableEnvironment中的激活属性
	configureProfiles(environment, args);
}
protected void configureProfiles(ConfigurableEnvironment environment, String[] args) {
	environment.getActiveProfiles(); // ensure they are initialized
	// additionalProfiles是项目启动时在main中SpringApplication.setAdditionalProfiles("")配置的
	Set<String> profiles = new LinkedHashSet<>(this.additionalProfiles);
	// 获取环境变量中设置的spring.profiles.active属性
	profiles.addAll(Arrays.asList(environment.getActiveProfiles()));
	// 赋值 activeProfiles
	environment.setActiveProfiles(StringUtils.toStringArray(profiles));
}

在这段代码里,会触发 ApplicationEnvironmentPreparedEvent。该事件会被 ConfigFileApplicationListener 处理(“从 spring.factories 文件中读取并实例化 ApplicationListener 对象”这一节有提及)。

ConfigFileApplicationListener 会加载不同优先级目录下的 application.properties、application.yml 文件。其处理 ApplicationEnvironmentPreparedEvent 事件的逻辑为:

public class ConfigFileApplicationListener implements EnvironmentPostProcessor, SmartApplicationListener, Ordered {
	@Override
	public void onApplicationEvent(ApplicationEvent event) {
		if (event instanceof ApplicationEnvironmentPreparedEvent) {
			onApplicationEnvironmentPreparedEvent(
				(ApplicationEnvironmentPreparedEvent) event);
		}
		//...
	}

	private void onApplicationEnvironmentPreparedEvent(ApplicationEnvironmentPreparedEvent event) {
		//从spring.factories文件中读取并实例化EnvironmentPostProcessor
		List<EnvironmentPostProcessor> postProcessors = loadPostProcessors();
		//ConfigFileApplicationListener 自身也是一个EnvironmentPostProcessor,添加到列表
		postProcessors.add(this);
		AnnotationAwareOrderComparator.sort(postProcessors);
		//依次执行EnvironmentPostProcessor的postProcessEnvironment方法
		for (EnvironmentPostProcessor postProcessor : postProcessors) {
			postProcessor.postProcessEnvironment(
					event.getEnvironment(),
 					event.getSpringApplication());
		}
	}

	@Override
	public void postProcessEnvironment(ConfigurableEnvironment environment, SpringApplication application) {
		addPropertySources(environment, application.getResourceLoader());
	}

	//按照一定的优先级来加载不同位置的配置文件application.properties、application.yml
	//优先级高的配置覆盖优先级底的配置
	//优先级从高到低为 file:./config/ > file:./ > classpath:config/ > classpath:
	protected void addPropertySources(ConfigurableEnvironment environment, ResourceLoader resourceLoader) {
		RandomValuePropertySource.addToEnvironment(environment);
		new Loader(environment, resourceLoader).load();
	}
}

生成并配置一个 ConfigurableApplicationContext 对象

根据前面确定的 webApplicationType,生成一个 ConfigurableApplicationContext 对象

webApplicationType 类型 ConfigurableApplicationContext 对象类型
SERVLET AnnotationConfigServletWebServerApplicationContext
REACTIVE AnnotationConfigReactiveWebServerApplicationContext
其他 AnnotationConfigApplicationContext

创建完毕 ConfigurableApplicationContext 后,对 ConfigurableApplicationContext 做一些初始化配置,包括调用 ApplicationContextInitializer 的 initialize 方法以及注册配置源。该过程中 hai 会广播 ApplicationContextInitializedEvent 和 ApplicationPreparedEvent 事件。

	private void prepareContext(
			ConfigurableApplicationContext context,
			ConfigurableEnvironment environment,
			SpringApplicationRunListeners listeners, 
			ApplicationArguments applicationArguments, 
			Banner printedBanner) {
		//... 省略一些代码
		//这里会调用前面从spring.factories文件中读取并实例化
		//ApplicationContextInitializer对象的initialize方法,
		//用于对ConfigurableApplicationContext进一步处理
		applyInitializers(context);
		//广播ApplicationContextInitializedEvent事件,被相应的ApplicationListener处理
		listeners.contextPrepared(context);
		//... 省略一些代码
		// Load the sources
		Set<Object> sources = getAllSources();
		Assert.notEmpty(sources, "Sources must not be empty");
		// 根据 main方法所在的类(一般为配置类),注册为一个配置源
		// 配置源用来配置一个spring容器,典型的配置源有xml文件和configuration类
		load(context, sources.toArray(new Object[0]));
		//广播ApplicationPreparedEvent事件,被相应的ApplicationListener处理
		listeners.contextLoaded(context);
	}

执行 ConfigurableApplicationContext 对象的 refresh 方法

ConfigurableApplicationContext 可能是 AnnotationConfigServletWebServerApplicationContext、AnnotationConfigReactiveWebServerApplicationContext 或者 AnnotationConfigApplicationContext。这里调用其 refresh 方法。

注意,这里是 spring 容器构建的重点,包含了 bean 的生成过程。后续会单开章节分析,本文暂不涉及。

执行 ConfigurableApplicationContext 对象的 refresh 方法完毕后,一个 spring 容器即构建完毕,可以被使用了。这一步的作用,相当于是在传统 spring 应用中执行 ApplicationContext context = new ClassPathXmlApplicationContext("classpath:application.xml")

public class App {
    public static void main(String[] args) {
        ApplicationContext context = new ClassPathXmlApplicationContext("classpath:application.xml");
	
	User user = context.getBean(User.class);
	System.out.println(user.getName());
    }
}

调用 SpringApplicationRunListener 对象的 started 方法

调用 SpringApplicationRunListener 对象的 started 方法,其内部调用 ConfigurableApplicationContext 的 publishEvent 方法,发布一个 ApplicationStartedEvent 事件

SpringApplicationRunListeners 对象代码
	public void started(ConfigurableApplicationContext context) {
		for (SpringApplicationRunListener listener : this.listeners) {
			listener.started(context);
		}
	}

EventPublishingRunListener代码
	@Override
	public void started(ConfigurableApplicationContext context) {
		context.publishEvent(new ApplicationStartedEvent(this.application, this.args, context));
	}

调用 SpringApplicationRunListener 对象的 running 方法

调用 SpringApplicationRunListener 对象的 running 方法,其内部调用 ConfigurableApplicationContext 的 publishEvent 方法,发布一个 ApplicationReadyEvent 事件

SpringApplicationRunListeners 对象代码
	public void running(ConfigurableApplicationContext context) {
		for (SpringApplicationRunListener listener : this.listeners) {
			listener.running(context);
		}
	}

EventPublishingRunListener代码
	@Override
	public void running(ConfigurableApplicationContext context) {
		context.publishEvent(new ApplicationReadyEvent(this.application, this.args, context));
	}

SpringBoot 启动过程中触发的事件

从上诉代码分析可知,SpringBoot 启动过程各步骤中会调用 SpringApplicationRunListener 的相应方法,触发相应事件。总结如下:

动作 事件
生成 SpringApplication 对象 确定 webApplicationType
从 spring.factories 文件中读取并实例化 ApplicationContextInitializer 对象
从 spring.factories 文件中读取并实例化 ApplicationListener 对象
确定 main 方法所在的类
调用 SpringApplication 对象的 run 非静态方法 从 spring.factories 文件中读取并实例化 SpringApplicationRunListener 对象
调用 SpringApplicationRunListener 对象的 starting 方法 SpringApplicationRunListener.starting 方法,广播 ApplicationStartingEvent 事件
生成并配置一个 ConfigurableEnvironment 对象 SpringApplicationRunListener.environmentPrepared 方法,广播 ApplicationEnvironmentPreparedEvent 事件,通过 ConfigFileApplicationListener 加载配置文件 application.properties、application.yml
生成并配置一个 ConfigurableApplicationContext 对象 SpringApplicationRunListener.contextPrepared 和 contextLoaded 方法,广播 ApplicationContextInitializedEvent 和 ApplicationPreparedEvent 事件
执行 ConfigurableApplicationContext 对象的 refresh 方法 以上事件,都是通过 EventPublishingRunListener 内部的 SimpleApplicationEventMulticaster 广播出去的。在执行完 refresh 之后,spring 容器构建成功,各个 bean 都实例化了,后面的事件则是通过 spring 容器发布的。
调用 SpringApplicationRunListener 对象的 started 方法 SpringApplicationRunListener.started 方法,调用 ConfigurableApplicationContext 发布 ApplicationStartedEvent 事件
调用 SpringApplicationRunListener 对象的 running 方法 SpringApplicationRunListener.running 方法,调用 ConfigurableApplicationContext 发布 ApplicationReadyEvent 事件

从表可知,将 ApplicationListener 注册到 spring.factories 文件中,则该 ApplicationListener 可以监听到任何它想监听的事件;而在 ApplicationListener 类加 Component 注解,则该 ApplicationListener 只能监听到 ApplicationStartedEvent、ApplicationReadyEvent 事件。

  • Spring

    Spring 是一个开源框架,是于 2003 年兴起的一个轻量级的 Java 开发框架,由 Rod Johnson 在其著作《Expert One-On-One J2EE Development and Design》中阐述的部分理念和原型衍生而来。它是为了解决企业应用开发的复杂性而创建的。框架的主要优势之一就是其分层架构,分层架构允许使用者选择使用哪一个组件,同时为 JavaEE 应用程序开发提供集成的框架。

    943 引用 • 1460 回帖 • 3 关注
  • 启动过程
    1 引用
  • 代码
    467 引用 • 586 回帖 • 9 关注
  • 事件
    5 引用 • 42 回帖

相关帖子

欢迎来到这里!

我们正在构建一个小众社区,大家在这里相互信任,以平等 • 自由 • 奔放的价值观进行分享交流。最终,希望大家能够找到与自己志同道合的伙伴,共同成长。

注册 关于
请输入回帖内容 ...

推荐标签 标签

  • Ruby

    Ruby 是一种开源的面向对象程序设计的服务器端脚本语言,在 20 世纪 90 年代中期由日本的松本行弘(まつもとゆきひろ/Yukihiro Matsumoto)设计并开发。在 Ruby 社区,松本也被称为马茨(Matz)。

    7 引用 • 31 回帖 • 216 关注
  • 以太坊

    以太坊(Ethereum)并不是一个机构,而是一款能够在区块链上实现智能合约、开源的底层系统。以太坊是一个平台和一种编程语言 Solidity,使开发人员能够建立和发布下一代去中心化应用。 以太坊可以用来编程、分散、担保和交易任何事物:投票、域名、金融交易所、众筹、公司管理、合同和知识产权等等。

    34 引用 • 367 回帖 • 1 关注
  • 爬虫

    网络爬虫(Spider、Crawler),是一种按照一定的规则,自动地抓取万维网信息的程序。

    106 引用 • 275 回帖 • 1 关注
  • SOHO

    为成为自由职业者在家办公而努力吧!

    7 引用 • 55 回帖 • 5 关注
  • CodeMirror
    1 引用 • 2 回帖 • 129 关注
  • Swagger

    Swagger 是一款非常流行的 API 开发工具,它遵循 OpenAPI Specification(这是一种通用的、和编程语言无关的 API 描述规范)。Swagger 贯穿整个 API 生命周期,如 API 的设计、编写文档、测试和部署。

    26 引用 • 35 回帖 • 5 关注
  • WiFiDog

    WiFiDog 是一套开源的无线热点认证管理工具,主要功能包括:位置相关的内容递送;用户认证和授权;集中式网络监控。

    1 引用 • 7 回帖 • 591 关注
  • 旅游

    希望你我能在旅途中找到人生的下一站。

    93 引用 • 899 回帖 • 3 关注
  • FlowUs

    FlowUs.息流 个人及团队的新一代生产力工具。

    让复杂的信息管理更轻松、自由、充满创意。

    1 引用
  • Mac

    Mac 是苹果公司自 1984 年起以“Macintosh”开始开发的个人消费型计算机,如:iMac、Mac mini、Macbook Air、Macbook Pro、Macbook、Mac Pro 等计算机。

    166 引用 • 595 回帖
  • 博客

    记录并分享人生的经历。

    273 引用 • 2388 回帖 • 1 关注
  • Angular

    AngularAngularJS 的新版本。

    26 引用 • 66 回帖 • 536 关注
  • QQ

    1999 年 2 月腾讯正式推出“腾讯 QQ”,在线用户由 1999 年的 2 人(马化腾和张志东)到现在已经发展到上亿用户了,在线人数超过一亿,是目前使用最广泛的聊天软件之一。

    45 引用 • 557 回帖 • 44 关注
  • Markdown

    Markdown 是一种轻量级标记语言,用户可使用纯文本编辑器来排版文档,最终通过 Markdown 引擎将文档转换为所需格式(比如 HTML、PDF 等)。

    167 引用 • 1520 回帖 • 1 关注
  • 黑曜石

    黑曜石是一款强大的知识库工具,支持本地 Markdown 文件编辑,支持双向链接和关系图。

    A second brain, for you, forever.

    16 引用 • 130 回帖
  • Bug

    Bug 本意是指臭虫、缺陷、损坏、犯贫、窃听器、小虫等。现在人们把在程序中一些缺陷或问题统称为 bug(漏洞)。

    76 引用 • 1737 回帖
  • 数据库

    据说 99% 的性能瓶颈都在数据库。

    343 引用 • 723 回帖
  • 又拍云

    又拍云是国内领先的 CDN 服务提供商,国家工信部认证通过的“可信云”,乌云众测平台认证的“安全云”,为移动时代的创业者提供新一代的 CDN 加速服务。

    21 引用 • 37 回帖 • 548 关注
  • LeetCode

    LeetCode(力扣)是一个全球极客挚爱的高质量技术成长平台,想要学习和提升专业能力从这里开始,充足技术干货等你来啃,轻松拿下 Dream Offer!

    209 引用 • 72 回帖
  • JWT

    JWT(JSON Web Token)是一种用于双方之间传递信息的简洁的、安全的表述性声明规范。JWT 作为一个开放的标准(RFC 7519),定义了一种简洁的,自包含的方法用于通信双方之间以 JSON 的形式安全的传递信息。

    20 引用 • 15 回帖 • 6 关注
  • 阿里巴巴

    阿里巴巴网络技术有限公司(简称:阿里巴巴集团)是以曾担任英语教师的马云为首的 18 人,于 1999 年在中国杭州创立,他们相信互联网能够创造公平的竞争环境,让小企业通过创新与科技扩展业务,并在参与国内或全球市场竞争时处于更有利的位置。

    43 引用 • 221 回帖 • 106 关注
  • Spring

    Spring 是一个开源框架,是于 2003 年兴起的一个轻量级的 Java 开发框架,由 Rod Johnson 在其著作《Expert One-On-One J2EE Development and Design》中阐述的部分理念和原型衍生而来。它是为了解决企业应用开发的复杂性而创建的。框架的主要优势之一就是其分层架构,分层架构允许使用者选择使用哪一个组件,同时为 JavaEE 应用程序开发提供集成的框架。

    943 引用 • 1460 回帖 • 3 关注
  • 微信

    腾讯公司 2011 年 1 月 21 日推出的一款手机通讯软件。用户可以通过摇一摇、搜索号码、扫描二维码等添加好友和关注公众平台,同时可以将自己看到的精彩内容分享到微信朋友圈。

    132 引用 • 795 回帖
  • 星云链

    星云链是一个开源公链,业内简单的将其称为区块链上的谷歌。其实它不仅仅是区块链搜索引擎,一个公链的所有功能,它基本都有,比如你可以用它来开发部署你的去中心化的 APP,你可以在上面编写智能合约,发送交易等等。3 分钟快速接入星云链 (NAS) 测试网

    3 引用 • 16 回帖 • 5 关注
  • App

    App(应用程序,Application 的缩写)一般指手机软件。

    91 引用 • 384 回帖 • 2 关注
  • Node.js

    Node.js 是一个基于 Chrome JavaScript 运行时建立的平台, 用于方便地搭建响应速度快、易于扩展的网络应用。Node.js 使用事件驱动, 非阻塞 I/O 模型而得以轻量和高效。

    139 引用 • 269 回帖 • 28 关注
  • Postman

    Postman 是一款简单好用的 HTTP API 调试工具。

    4 引用 • 3 回帖 • 7 关注