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

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

在我当前的工作中,基本上所有的 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 静态方法。代码如下:

package com.example.demo;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class DemoApplication {
	public static void main(String[] args) {
		SpringApplication.run(DemoApplication.class, args);
	}
}

深入到 SpringApplication 类的 run 静态方法,发现整个过程分为以下几个步骤:

  • 生成 SpringApplication 对象
    • 确定 webApplicationType
    • 从 spring.factories 文件中读取并实例化 ApplicationContextInitializer 对象
    • 从 spring.factories 文件中读取并实例化 ApplicationListener 对象
    • 确定 main 方法所在的类
  • 调用 SpringApplication 对象的 run 非静态方法
    • 从 spring.factories 文件中读取并实例化 SpringApplicationRunListener 对象
    • 调用 SpringApplicationRunListener 对象的 starting 方法
    • 生成并配置一个 ConfigurableEnvironment 对象
    • 生成并配置一个 ConfigurableApplicationContext 对象
    • 执行 ConfigurableApplicationContext 对象的 refresh 方法
    • 调用 SpringApplicationRunListener 对象的 started 方法
    • 调用 SpringApplicationRunListener 对象的 running 方法

以上步骤的代码如下:
WX20191008162820.png

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 应用程序开发提供集成的框架。

    938 引用 • 1456 回帖 • 163 关注
  • 启动过程
    1 引用
  • 代码
    459 引用 • 591 回帖 • 8 关注
  • 事件
    5 引用 • 42 回帖

相关帖子

欢迎来到这里!

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

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