编码之路

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

2019.10.08

在我当前的工作中,基本上所有的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方法

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

image.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工程
image.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对象类型
SERVLETStandardServletEnvironment
REACTIVEStandardReactiveWebEnvironment
其他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对象类型
SERVLETAnnotationConfigServletWebServerApplicationContext
REACTIVEAnnotationConfigReactiveWebServerApplicationContext
其他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事件。