本文基于spring-boot版本2.1.4.RELEASE
首先使用spring-boot-starter-web构建一个web项目,编写代码如下:
1 |
|
入口程序执行的方法SpringApplication.run(Application.class, args)是SpringApplication类的静态run方法
1 | 代码摘自:org.springframework.boot.SpringApplication |
第一个静态run函数实际上是将单个的source构造成数组,然后调用了第二个静态run函数。第二个函数创建了SpringApplication对象,并调用该对象的非静态run函数(有三个run函数)
因此,我们也可以将前面程序主类的启动过程修改为:
1 | public class SBApplication { |
如此一来,我们可以使用到SpringApplication提供的一系列实例方法对其进行配置。
从上面代码看,应用的启动过程分为两部分:首先创建一个SpringApplication对象;然后执行其对象方法run。构造函数中实际业务逻辑都放在了initialize方法中。下面我们分别分析这两部分都干了什么。
1. 创建SpringApplication对象
1 | 代码摘自:org.springframework.boot.SpringApplication |
将Application类当做主配置类传给第一个构造函数,然后调用第二个构造函数,ResourceLoader默认值为null。
构造过程如下:
- 将传进来的配置类参数set进this.primarySources,该参数代表了SpringBoot启动时指定的Configuration类(可多个)。
- 设置this.webApplicationType,改参数表示此应用是Servlet或Reactive或者是None
- 设置Initializers
- 设置Listeners
- 设置this.mainApplicationClass,改参数记录了入口类的类对象实例。
1.1 webApplicationType
通过判断当前是否含有:
1 | 代码摘自:org.springframework.boot.WebApplicationType |
WebApplicationType是一个枚举类型,有三种类型的常量:SERVLET、REACTIVE和NONE
枚举类型内部通过判断类路径上存在哪些类型从而判断应用属于那种类型,具体可看源码,简明易懂
1.2 初始化initializer和listener
通过两个函数getSpringFactoriesInstances(ApplicationContextInitializer.class)、getSpringFactoriesInstances(ApplicationListener.class)得到以SpringFactoriesLoader扩展方案注册的ApplicationContextInitializer和ApplicationListener类型的实例,并设置到当前SpringApplication的对象中。在这两个函数都调用了:
1 | private <T> Collection<? extends T> getSpringFactoriesInstances(Class<T> type, |
可以看到,该方法首先调用SpringFactoriesLoader.loadFactoryNames(type, classLoader))方法获取type类型的组件的名称,再调用createSpringFactoriesInstances方法根据读取到的类名创建对象,最后将所有创建好的对象排序并返回。
从loadFactoryNames方法中看出是从一个名字叫spring.factories的资源文件中读取的类名,spring.factories的部分内容如下:
1 | 代码摘自:spring-boot-2.1.4.RELEASE.jar!/META-INF/spring.factories:10 |
所以在我们的例子中,SpringApplication对象的成员变量initalizers就被初始化为,ConfigurationWarningsApplicationContextInitializer,ContextIdApplicationContextInitializer,DelegatingApplicationContextInitializer,ServerPortInfoApplicationContextInitializer这四个类的对象组成的list。
下图画出了加载的ApplicationContextInitializer,并说明了他们的作用,后文将分析何时应用他们。未命名文件
listener最终会被初始化为ClearCachesApplicationListener,ParentContextCloserApplicationListener,FileEncodingApplicationListener,AnsiOutputApplicationListener,ConfigFileApplicationListener,DelegatingApplicationListener,LiquibaseServiceLocatorApplicationListener,ClasspathLoggingApplicationListener,LoggingApplicationListener这几个类的对象组成的list。
下图画出了加载的ApplicationListener,并说明了他们的作用后文将解释何时使用他们。Listener
1.3 设置mainApplicationClass
1 | private Class<?> deduceMainApplicationClass() { |
通过new RuntimeException().getStackTrace()获取运行时方法栈,遍历栈找到main方法,继而找到main方法所在的类对象。
2. 实际启动过程run
SpringApplication将SpringBoot应用启动流程模板化,并在启动过程的不同时机定义了一系列不同类型的的扩展点,方便我们对其进行定制。下面对整个启动过程代码进行分析:
1 | //代码引用自org.springframework.boot.SpringApplication |
- 可变个数参数args即是我们整个应用程序的入口main方法的参数,在本文的例子中,参数个数为零。
- StopWatch是来自org.springframework.util的工具类,可以用来方便的记录程序的运行时间。
- 设置Headless实际上是就是设置系统属性java.awt.headless,在我们的例子中该属性会被设置为true,因为我们开发的是服务器程序,一般运行在没有显示器和键盘的环境。
2.1 配置运行监听器
1 | SpringApplicationRunListeners listeners = getRunListeners(args);//1 |
1 | 摘自资源文件META-INF/spring.factories |
通过SpringFactoriesLoader来获取定义在spring.factories中的SpringApplicationRunListener,SpringBoot框架默认只定义了一个EventPublishingRunListener,其中维护了一个SimpleApplicationEventMulticaster,并将上节初始化的ApplicationListener实例注册进去。然后调用其starting()方法,给所有的SpringApplicationRunListener发送一个start事件,然后EventPublishingRunListener给注册在其中的所有ApplicationListener发送ApplicationStartedEvent。
此处包含了两个扩展点:
- 可以自定义SpringApplicationRunListener以扩展SpringBoot程序启动过程。
- 可以自定义ApplicationListener以扩展EventPublishingRunListener。
在启动的不同阶段,会发送不同的事件给SpringApplicationRunListeners,listeners通知相应的ApplicationListeners处理事件。
1 | listeners.starting(); |
- LoggingApplicationListener响应此事件,会根据classpath中的类情况创建相应的日志系统对象,并执行一些初始化之前的操作;
1
2
3
4
5
6
7
8
9
10
11
12
public void onApplicationEvent(ApplicationEvent event) {
if (event instanceof ApplicationStartedEvent) {
onApplicationStartedEvent((ApplicationStartedEvent) event);
}
...
}
private void onApplicationStartedEvent(ApplicationStartedEvent event) {
this.loggingSystem = LoggingSystem
.get(event.getSpringApplication().getClassLoader());
this.loggingSystem.beforeInitialize();
}
本文例子中,创建的是org.springframework.boot.logging.logback.LogbackLoggingSystem类的对象,Logback是SpringBoot默认采用的日志系统。
- LiquibaseServiceLocatorApplicationListener响应此事件,会检查classpath中是否有liquibase.servicelocator.ServiceLocator并做相应操作,本文的例子中classpath中不存在liquibase,所以不执行任何操作。
1
2
3
4
5
6
7
public void onApplicationEvent(ApplicationStartingEvent event) {
if (ClassUtils.isPresent("liquibase.servicelocator.CustomResolverServiceLocator",
event.getSpringApplication().getClassLoader())) {
new LiquibasePresent().replaceServiceLocator();
}
}
2.2 包装参数
1 | ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);//2 |
将参数包装为ApplicationArguments,DefaultApplicationArguments是用来维护命令行参数的,例如可以方便的将命令行参数中的options和non options区分开,以及获得某option的值等。
DefaultApplicationArguments将String[] args中的参数解析包装成 Source类型,Source类的继承关系如下:
1 | public class SimpleCommandLinePropertySource extends CommandLinePropertySource<CommandLineArgs> |
这里的关键是泛型类型变量CommandLineArgs,这个类型中的两个成员变量:
1 | private final Map<String, List<String>> optionArgs = new HashMap<>(); |
就是用来存放从args中解析出来的optionArgs和nonOptionArgs。
2.3 准备应用环境
1 | ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments);//3 |
通过ApplicationArguments来准备应用环境Environment, Environment包含了两个层面的信息:属性(properties)和轮廓(profiles)。 profiles用来描述哪些bean definition是可用的, properties用来描述系统的配置,其来源可能是配置文件、jvm属性文件、操作系统环境变量等等。
配置属性源(propertySource),关于Environment中的属性来源分散在启动的若干个阶段,并且按照特定的优先级顺序,也就是说一个属性值可以在不同的地方配置,但是优先级高的值会覆盖优先级低的值。
配置轮廓(profile)可以认为是程序的运行环境,典型的环境比如有开发环境(Develop)、生产环境(Production)、测试环境(Test)等等。我们可以定义某个Bean在特定的环境中才生效,这样就可以通过指定profile来方便的切换运行环境。可通过SpringApplication.setAdditionalProfiles()来设置轮廓,environment内通过activeProfiles来维护生效的轮廓(可不止一个)。
prepareEnvironment方法的代码如下:
1 | private ConfigurableEnvironment prepareEnvironment(SpringApplicationRunListeners listeners, |
在getOrCreateEnvironment()方法中通过上节初始化的webApplicationType判断是否为web应用创建一个StandardServletEnvironment或StandardEnvironment。
接着执行configureEnvironment函数:
1 | 以下代码摘自:org.springframework.boot.SpringApplication |
configurePropertySources首先查看SpringApplication对象的成员变量defaultProperties,如果该变量非null且内容非空,则将其加入到Environment的PropertySource列表的最后。然后查看SpringApplication对象的成员变量addCommandLineProperties和main函数的参数args,如果设置了addCommandLineProperties=true,且args个数大于0,那么就构造一个由main函数的参数组成的PropertySource放到Environment的PropertySource列表的最前面(这就能保证,我们通过main函数的参数来做的配置是最优先的,可以覆盖其他配置)。在我们的例子中,由于没有配置defaultProperties且main函数的参数args个数为0,所以这个函数什么也不做。
configureProfiles首先会读取Properties中key为spring.profiles.active的配置项,配置到Environment,然后再将SpringApplication对象的成员变量additionalProfiles加入到Environment的active profiles配置中。在我们的例子中,配置文件里没有spring.profiles.active的配置项,而SpringApplication对象的成员变量additionalProfiles也是一个空的集合,所以这个函数没有配置任何active profile。
在环境配置完毕后,执行所有SpringApplicationRunListeners的environmentPrepared函数,然后EventPublishingRunListener给所有注册其中的ApplicationListeners发送一个“环境准备好了”ApplicationEnvironmentPreparedEvent事件:
1 | listeners.environmentPrepared(environment); |
- FileEncodingApplicationListener响应该事件,检查file.encoding配置是否与spring.mandatory_file_encoding一致,在本文的例子中,因为没有spring.mandatory_file_encoding的配置,所以这个响应方法什么都不做。
- AnsiOutputApplicationListener响应该事件,根据spring.output.ansi.enabled和spring.output.ansi.console-available对AnsiOutput类做相应配置,本文的例子中,这两项配置都是空的,所以这个响应方法什么都不做。
- ConfigFileApplicationListener加载该事件,从一些约定的位置加载一些配置文件,而且这些位置是可配置的。
- DelegatingApplicationListener响应该事件,将配置文件中key为context.listener.classes的配置项,加载在成员变量multicaster中
- LoggingApplicationListener响应该事件,并对在ApplicationStarted时加载的LoggingSystem做一些初始化工作
2.4 创建ApplicationContext
关于ApplicationContext:ApplicationContext用于扩展BeanFactory中的功能,ApplicationContext拥有BeanFactory对于Bean的管理维护的所有功能,并且提供了更多的扩展功能,实际上ApplicationContext的实现在内部持有一个BeanFactory的实现来完成BeanFactory的工作。AbstractApplicationContext是ApplicationContext的第一个抽象实现类,其中使用模板方法模式定义了springcontext的核心扩展流程refresh,并提供几个抽象函数供具体子类去实现。其直接子类有AbstractRefreshableApplicationContext和GenericApplicationContext两种。
这两个子类的不同之处在于对内部的DefaultListableBeanFactory的管理:AbstractRefreshableApplicationContext允许多次调用其refreshBeanFactory()函数,每次调用时都会重新创建一个DefaultListableBeanFactory,并将已有的销毁;而GenericApplicationContext不允许刷新beanFactory,只能调用refreshBeanFactory()一次,当多次调用时会抛出异常。
无论AnnotationConfigApplicationContext还是AnnotationConfigServletWebServerApplicationContext,它们都是GenericApplicationContext的子类。因此其内部持有的BeanFactory是不可刷新的,并且从初始化开始就一直持有一个唯一的BeanFactory。
1 | context = createApplicationContext();//4 |
根据前面判断的是web应用还是普通应用决定创建什么类型的ApplicationContext,createApplicationContext方法代码如下:
1 | public static final String DEFAULT_SERVLET_WEB_CONTEXT_CLASS = "org.springframework.boot.web.servlet.context. |
2.5 代码5-配置异常分析器
借助SpringFactoriesLoader获得spring.factories中注册的FailureAnalyzers以供当运行过程中出现异常时进行分析:
1 | exceptionReporters = getSpringFactoriesInstances( |
2.6 代码6-准备context
接着就是SpringBoot启动过程中的最核心流程,对第4步创建的ApplicationContext进行准备:
1 | prepareContext(context, environment, listeners, applicationArguments,printedBanner); |
详细内容见下节。
2.7 代码7
调用ApplicationContext的refresh函数,开启spring context的核心流程,就是根据配置加载bean(spring beans核心功能)以及在各个时机开放的不同扩展机制(spring context):
1 | refreshContext(context); |
详细内容见下节。
2.8 代码8
获取所有的ApplicationRunner和CommandLineRunner并执行:
1 | afterRefresh(context, applicationArguments); |
此时由于context已经refresh完毕,因此bean都已经加载完毕了。所以这两个类型的runner都是直接从context中获取的:
1 | protected void afterRefresh(ConfigurableApplicationContext context,ApplicationArguments args) { |
两者的执行时机是完全一样的,唯一的区别在于一个接受ApplicationArguments,一个接受String[]类型的原始命令行参数。而ApplicationArguments也只是对原始命令行参数的一个封装,因此本质上是一样的。
此处又定义了两个扩展机制,我们可以自定义ApplicationRunner或CommandLineRunner并将其配置为Bean,便可以在context refresh完毕后执行。
2.9 代码9
spring-context refresh过程完毕后执行所有SpringApplicationRunListeners的finished函数,然后EventPublishingRunListener给所有注册其中的ApplicationListeners发送一个“应用启动完毕”ApplicationReadyEvent事件:
1 | listeners.finished(context, null); |
2.10 代码10-异常处理
当运行时出现异常时,向context发送退出码事件ExitCodeEvent,供其内部listener执行退出前的操作;并使用前面第5步获得的analyzers来打印可能的原因:
1 | handleRunFailure(context, listeners, analyzers, ex); |
另外,就算运行异常,也会向SpringApplication中的listeners发送“应用启动完毕”的事件,代码如下:
1 | private void handleRunFailure(ConfigurableApplicationContext context, |
此时,EventPublishingRunListener发送给注册其中的ApplicationListeners的事件成了“应用启动异常”ApplicationFailedEvent。
至此,SpringApplication的run函数,也就是SpringBoot应用的启动过程就执行完毕了。可以看出,SpringBoot的启动过程是对Spring context启动过程的扩展,在其中定义了若干的扩展点并提供了不同的扩展机制。并提供了默认配置,我们可以什么都不配,也可以进行功能非常强大的配置和扩展。这也正是SpringBoot的优势所在。
3 核心过程prepareContext
顾名思义,该函数的功能就是对前面创建的ApplicationContext进行准备,其执行步骤如下:
3.1 将environment设置到context中
1 | context.setEnvironment(environment); |
environment是我们在run过程的第3步创建的。
3.2
对ApplicationContext应用相关的后处理,子类可以重写该方法来添加任意的后处理功能:
1 | postProcessApplicationContext(context); |
该方法代码如下:
1 | protected void postProcessApplicationContext(ConfigurableApplicationContext context) { |
如果SpringApplication设置了beanNameGenerator,则将其注册为singleton类型的bean,并命名为: org.springframework.context.annotation.internalConfigurationBeanNameGenerator 另外,若SpringApplication设置了resourceLoader,则设置进context中。
3.3 使用Initializer修改context
对initialize阶段得到的通过spring.factories注册进来的所有ApplicationContextInitializer,逐个执行其initialize方法来修改context,并在执行之前对其进行校验:
1 | protected void applyInitializers(ConfigurableApplicationContext context) { |
此处定义了一个扩展点,可以自定义并通过spring.factories注册ApplicationContextInitializer,这些ApplicationContextInitializer可在ApplicationContext准备完毕后对其进行维护修改,例如可以改变其定义的activeProfiles以改变应用环境。
3.4 发布contextPrepared事件
执行所有SpringApplicationRunListeners的contextPrepared函数,注意EventPublishingRunListener并没有给所有注册其中的ApplicationListeners发送对应的事件:
1 | listeners.contextPrepared(context); |
此时的listeners可以获得context作为参数,从而对context进行修改。
3.5 注册applicationArguments和printBanner
将applicationArguments注册进context.getBeanFactory()中,名字为”SpringApplicationArguments”、若printBanner不为空,将printBanner注册到context.getBeanFactory()中,名字为”SpringBootBanner”:
1 | context.getBeanFactory().registerSingleton("springApplicationArguments", |
3.6 通过sources加载配置类
得到所有的sources(可通过SpringApplication的run函数、构造函数和setSources函数指定,代表了一个或多个Configuration类),然后执行load(context, sources)函数:
1 | Set<Object> sources = getSources(); |
load函数中会创建一个BeanDefinitionLoader并设置其beanNameGenerator, resourceLoader, environment等属性,然后委托其执行具体的load动作,代码如下:
1 | protected void load(ApplicationContext context, Object[] sources) { |
其中对于每一个source根据其类型不同执行不同的load逻辑:class, Resource, Package, CharSequence等。将解析出来的所有bean的BeanDefinition注册到BeanDefinitionRegistry中(注意,只是source本身,并不包括其内部定义的@Bean方法):
1 | public int load() { |
由于我们的source是class类,所以load某一个具体source的行为是委托给了AnnotatedBeanDefinitionReader的register方法:
1 | public void register(Class<?>... annotatedClasses) { |
此处已是spring context的功能了,将通过注释定义的Configuration类的BeanDefinition注册到BeanDefinitionRegistry中。(此时尚不解析Configuration类内部定义的@Bean方法)
3.7 发布context加载完毕事件
执行所有SpringApplicationRunListeners的contextLoaded函数,然后EventPublishingRunListener给所有注册其中的ApplicationListeners发送一个“应用上下文准备完毕”ApplicationPreparedEvent事件,另外还将所有注册在自身的ApplicationListener注册到context之中: listeners.contextLoaded(context); 其中调用到EventPublishingRunListener的contextLoaded函数:
1 | public void contextLoaded(ConfigurableApplicationContext context) { |