程序设计的所有原则和方法论都是追求一件事——简单——功能简单、依赖简单、修改简单、理解简单。因为只有简单才好用,简单才好维护。因此,不应该以评论艺术品的眼光来评价程序设计是否优秀,程序设计的艺术不在于有多复杂多深沉,而在于能用多简单的方式实现多复杂的业务需求;不在于只有少数人能理解,而在于能让更多人理解。
概要
Spring boot为spring集成开发带来很大的遍历,降低了spring中bean的配置工作,几乎0配置即可开发一个spring应用。本篇主要介绍spring boot在启动过程中都做了哪些工作,重点介绍在spring容器ApplicationContext刷新前都做了哪些准备,本篇以源码解释为主,示例为辅进行梳理其过程。
Spring boot整个启动过程分为3部分,分别是
- 准备环境配置
- 准备Spring容器并刷新容器
- 容器刷新后处理
每个部分又有如下图所示的步骤(图片比较宽,左右滑动查看):
#mermaid-svg-8vUJIbqX7O55K2fG {font-family:”trebuchet ms”,verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg-8vUJIbqX7O55K2fG .error-icon{fill:#552222;}#mermaid-svg-8vUJIbqX7O55K2fG .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-8vUJIbqX7O55K2fG .edge-thickness-normal{stroke-width:2px;}#mermaid-svg-8vUJIbqX7O55K2fG .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-8vUJIbqX7O55K2fG .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-8vUJIbqX7O55K2fG .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-8vUJIbqX7O55K2fG .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-8vUJIbqX7O55K2fG .marker{fill:#333333;stroke:#333333;}#mermaid-svg-8vUJIbqX7O55K2fG .marker.cross{stroke:#333333;}#mermaid-svg-8vUJIbqX7O55K2fG svg{font-family:”trebuchet ms”,verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-8vUJIbqX7O55K2fG .label{font-family:”trebuchet ms”,verdana,arial,sans-serif;color:#333;}#mermaid-svg-8vUJIbqX7O55K2fG .cluster-label text{fill:#333;}#mermaid-svg-8vUJIbqX7O55K2fG .cluster-label span{color:#333;}#mermaid-svg-8vUJIbqX7O55K2fG .label text,#mermaid-svg-8vUJIbqX7O55K2fG span{fill:#333;color:#333;}#mermaid-svg-8vUJIbqX7O55K2fG .node rect,#mermaid-svg-8vUJIbqX7O55K2fG .node circle,#mermaid-svg-8vUJIbqX7O55K2fG .node ellipse,#mermaid-svg-8vUJIbqX7O55K2fG .node polygon,#mermaid-svg-8vUJIbqX7O55K2fG .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-8vUJIbqX7O55K2fG .node .label{text-align:center;}#mermaid-svg-8vUJIbqX7O55K2fG .node.clickable{cursor:pointer;}#mermaid-svg-8vUJIbqX7O55K2fG .arrowheadPath{fill:#333333;}#mermaid-svg-8vUJIbqX7O55K2fG .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-8vUJIbqX7O55K2fG .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-8vUJIbqX7O55K2fG .edgeLabel{background-color:#e8e8e8;text-align:center;}#mermaid-svg-8vUJIbqX7O55K2fG .edgeLabel rect{opacity:0.5;background-color:#e8e8e8;fill:#e8e8e8;}#mermaid-svg-8vUJIbqX7O55K2fG .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-8vUJIbqX7O55K2fG .cluster text{fill:#333;}#mermaid-svg-8vUJIbqX7O55K2fG .cluster span{color:#333;}#mermaid-svg-8vUJIbqX7O55K2fG div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:”trebuchet ms”,verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-8vUJIbqX7O55K2fG :root{–mermaid-font-family:”trebuchet ms”,verdana,arial,sans-serif;}
以上步骤过程主要在SpringApplication
对象的`run方法中执行,该方法是一个模板模式的应用,定义了spring boot应用的启动过程,源码如下,注意阅读注释
// spring boot SpringApplication源码
public ConfigurableApplicationContext run(String... args) {
Startup startup = Startup.create();
if (this.registerShutdownHook) { // 默认为true
SpringApplication.shutdownHook.enableShutdownHookAddition();
}
// 1.2 创建DefaultBootstrapContext并执行BootstrapRegistryInitializer
DefaultBootstrapContext bootstrapContext = createBootstrapContext();
ConfigurableApplicationContext context = null;
configureHeadlessProperty();
//1.3从spring.factories文件加载所有SpringApplicationRunListeners
SpringApplicationRunListeners listeners = getRunListeners(args);
// 1.4发布ApplicationStartingEvent事件标识正在启动应用
listeners.starting(bootstrapContext, this.mainApplicationClass);
try {
// 命令行参数对象化
ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
// 1.5和1.6 加载环境配置和发布ApplicationEnvironmentPreparedEvent事件
ConfigurableEnvironment environment = prepareEnvironment(listeners, bootstrapContext, applicationArguments);
// 打印spring banner
Banner printedBanner = printBanner(environment);
// 2.1创建Spring容器ConfigurableApplicationContext
context = createApplicationContext();
context.setApplicationStartup(this.applicationStartup);
// 2.2-2.6
prepareContext(bootstrapContext, context, environment, listeners, applicationArguments, printedBanner);
// 2.7刷新容器
refreshContext(context);
// 刷新后处理器,钩子函数,空实现,留给子类实现
afterRefresh(context, applicationArguments);
startup.started();
if (this.logStartupInfo) { // 默认为true
// 打印启动耗时
new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), startup);
}
// 3.1发布ApplicationStartedEvent事件
listeners.started(context, startup.timeTakenToStarted());
// 3.2执行ApplicationRunner和CommandLineRunner
callRunners(context, applicationArguments);
} catch (Throwable ex) {
throw handleRunFailure(context, ex, listeners);
}
try {
if (context.isRunning()) {
// 3.3发布ApplicationReadyEvent事件
listeners.ready(context, startup.ready());
}
} catch (Throwable ex) {
throw handleRunFailure(context, ex, null);
}
return context;
}
下面根据上面代码注释上的编号逐一梳理,注释前的编号与下面的章节号对应,本篇代码中的注释前编号都是与之对应的章节号。
1. 环境配置准备
1.1 创建SpringApplication并执行其run方法
1.1.1 创建SpringApplication
SpringApplication
类大家都不陌生,它是spring boot提供的外观类,屏蔽了spring庞杂的内部结构,通过它只需要一行代码即可启动一个spring
boot应用。它在启动时有3中创建方式:
- 执行其静态方法run方法,这是我们经常使用的方式。
// 示例代码
@SpringBootApplication
public class MyApplication {
public static void main(String[] args) {
SpringApplication.run(MyApplication.class, args);
// 等同于
// new SpringApplication(MyApplication.class).run(args);
}
}
- 通过
new
关键字直接创建
// 示例代码
@SpringBootApplication
public class MyApplication {
public static void main(String[] args) {
SpringApplication application = new SpringApplication(MyApplication.class);
application.setAllowCircularReferences(true);
application.setAllowBeanDefinitionOverriding(true);
application.setLogStartupInfo(true);
application.setWebApplicationType(WebApplicationType.SERVLET);
application.run(args);
}
}
- 使用
SpringApplicationBuilder
// 示例代码
@SpringBootApplication
public class MyApplication {
public static void main(String[] args) {
new SpringApplicationBuilder(MyApplication.class)
.allowCircularReferences(true)
.logStartupInfo(true)
.web(WebApplicationType.REACTIVE)
.run();
}
}
3种方式简要说明:
- 第一种方式通过执行静态方法,非常简单,也是我们经常使用的方式
- 后面两种给予我们除了配置文件外,还可以通过代码来控制启动时需要的配置。如果你喜欢使用链式编程风格,你可以选择第三种方式。
1.1.2 创建SpringApplication
下面是SpringApplication
构造函数源码,注意阅读注释,后面根据注释的编号做进一步分析。
// spring boot SpringApplication源码
public SpringApplication(ResourceLoader resourceLoader, Class?>... primarySources) {
// 用于加载资源,默认为null
this.resourceLoader = resourceLoader;
Assert.notNull(primarySources, "PrimarySources must not be null");
// 即被@SpringBootApplication注解的类,以它为根进行扫描所有bean定义
this.primarySources = new LinkedHashSet>(Arrays.asList(primarySources));
// 1.1.2.1 判断应用类型,分别是SERVLET|REACTIVE|NONE。后面会根据应用类型创建Spring容器
this.webApplicationType = WebApplicationType.deduceFromClasspath();
// 1.1.2.2 加载spring.factories中配置的扩展实现类
this.bootstrapRegistryInitializers = new ArrayList>(
getSpringFactoriesInstances(BootstrapRegistryInitializer.class));
setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
// 1.1.2.3 main方法所在类,后面用于日志显示
this.mainApplicationClass = deduceMainApplicationClass();
}
代码说明见下面3节
1.1.2.1 判断应用类型
应用类型WebApplicationType
有3个枚举值,分别是SERVLET|REACTIVE|NONE。不同类型会选择相应的抽象工厂ApplicationContextFactory
的
工厂方法创建spring容器对象。下面表格是抽象工厂各实现提供的容器类和环境对象类。
枚举 | Factory实现类 | AOT编译时的容器类 | 非AOT时的容器类 | 提供的Environment实现类 |
---|---|---|---|---|
SERVLET | ServletWebServerApplicationContextFactory |
ServletWebServerApplicationContext |
AnnotationConfigServletWebServerApplicationContext |
ApplicationServletEnvironment |
REACTIVE | ReactiveWebServerApplicationContextFactory |
ReactiveWebServerApplicationContext |
AnnotationConfigReactiveWebServerApplicationContext |
ApplicationReactiveWebEnvironment |
NONE | DefaultApplicationContextFactory |
GenericApplicationContext |
AnnotationConfigApplicationContext |
ApplicationEnvironment |
spring boot从3.0开始支持GraalVM Native编译即AOT编译,AOT编译要求所有代码都是可达、静态的,通过注解在运行时动态加载具体类型违背AOT的原则,
Spring boot为支持AOT编译需要提前为通过注解配置的bean生成代码和AOP时的字节码,比如下面为示例的启动类MyApplication
生成的代码。
因此AOT的容器类不需支持加载注解配置,而只需要执行java代码就可以装载bean。
// 示例代码
@Generated
public class MyApplication__BeanDefinitions {
/**
* Get the bean definition for 'MyApplication'.
*/
public static BeanDefinition getMyApplicationBeanDefinition() {
RootBeanDefinition beanDefinition = new RootBeanDefinition(MyApplication.class);
beanDefinition.setTargetType(MyApplication.class);
ConfigurationClassUtils.initializeConfigurationClass(MyApplication.class);
beanDefinition.setInstanceSupplier(MyApplication$$SpringCGLIB$$0::new);
return beanDefinition;
}
}
1.1.2.2 加载spring.factories中配置的扩展实现类
-
BootstrapRegistryInitializer
: 用于向BootstrapRegistry
中注册对象,在创建BootstrapRegistry
后就会执行,见后面1.2章节 -
ApplicationContextInitializer
: 用于向ConfigurableApplicationContext
spring容器添加对象,在执行refresh()
方法前触发,见2.2 执行ApplicationContextInitializer初始化Spring容器。 -
ApplicationListener
: 这里注册的监听器可以监听spring boot应用的整个过程的事件
1.1.2.3 其它
- resourceLoader默认为null,
- primarySources为spring加载业务代码bean的起点,通常为被
@SpringBootApplication
注解的类,也可以是其它spring注解的类。 - mainApplicationClass为
main
方法所在类
1.2 创建DefaultBootstrapContext并执行BootstrapRegistryInitializer
SpringApplication
源码如下:
// spring boot SpringApplication源码
private DefaultBootstrapContext createBootstrapContext() {
// 1.2.1 创建启动上下文
DefaultBootstrapContext bootstrapContext = new DefaultBootstrapContext();
// 1.2.2 执行BootstrapRegistryInitializer
this.bootstrapRegistryInitializers.forEach((initializer) -> initializer.initialize(bootstrapContext));
return bootstrapContext;
}
1.2.1 创建启动上下文
DefaultBootstrapContext
实现了BootstrapRegistry
和BootstrapContext
两个接口,前者用于注册启动相关的类对象,后者用于获取注册的类对象。
1.2.2 执行BootstrapRegistryInitializer
BootstrapRegistryInitializer
通过spring.factories
文件注册,一般用于往BootstrapRegistry
即DefaultBootstrapContext
注册自定义框架的对象,用在后面某些步骤中发挥作用。
1.3从spring.factories文件加载所有SpringApplicationRunListener
SpringApplicationRunListener
用于监听SpringApplication
启动过程的特定步骤,它定义的方法与启动步骤一一对应。
SpringApplication
中对应的源码如下,注意阅读注释,后面根据注释的编号做进一步分析。
// spring boot SpringApplication源码
private SpringApplicationRunListeners getRunListeners(String[] args) {
// 1.3.1 从spring.factories加载SpringApplicationRunListener
ArgumentResolver argumentResolver = ArgumentResolver.of(SpringApplication.class, this);
argumentResolver = argumentResolver.and(String[].class, args);
ListSpringApplicationRunListener> listeners = getSpringFactoriesInstances(SpringApplicationRunListener.class,
argumentResolver);
// 1.3.2 从ThreadLocal中获取应用钩子
SpringApplicationHook hook = applicationHook.get();
SpringApplicationRunListener hookListener = (hook != null) ? hook.getRunListener(this) : null;
if (hookListener != null) {
// 把钩子提供的监听器执行器加入listeners
listeners = new ArrayList>(listeners);
listeners.add(hookListener);
}
// 1.3.3 创建一个外观对象来编排各个监听执行器的执行
return new SpringApplicationRunListeners(logger, listeners, this.applicationStartup);
}
1.3.1 从spring.factories加载SpringApplicationRunListener
在上面源码中有2个点要注意:
- spring提供的默认实现为
EventPublishingRunListener
,spring boot启动过程中的各类ApplicationEvent
事件(如ApplicationStartingEvent
事件)都由它告知所有ApplicationListener
对象。 -
SpringApplicationRunListener
实现类构造函数可选参数有:无参、SpringApplication
,命令行参数String[] args
,如下面代码
// 示例代码
public class MySpringApplicationRunListener implements SpringApplicationRunListener {
private final SpringApplication application;
public MySpringApplicationRunListener(SpringApplication application) {
this.application = application;
}
// 其它代码...
}
关于SpringApplicationRunListener
需说明如下:
SpringApplicationRunListener
与ApplicationListener
一样都属于监听器(观察者),不过它用于观察spring boot的启动过程,
SpringApplication
在各个阶段通过其管理器SpringApplicationRunListeners
来执行各个SpringApplicationRunListener
对象的阶段函数
(如starting
、started
、ready
)。
1.3.2 从ThreadLocal中获取应用钩子
springboot3.0提供了启动应用程序可以设置钩子,由这个钩子提供一个SpringApplicationRunListener
对象,比如下面代码
// 示例代码
@SpringBootApplication
public class MyApplication {
public static void main(String[] args) {
SpringApplication.withHook(new MySpringApplicationHook(), () -> {
SpringApplication.run(MyApplication.class, args);
});
}
}
public class MySpringApplicationHook implements SpringApplicationHook {
@Override
public SpringApplicationRunListener getRunListener(SpringApplication springApplication) {
return new MySpringApplicationRunListener(springApplication);
}
}
spring boot提供这个钩子功能的作用:
-
AOT编译使用该钩子在spring容器刷新前抛出异常来终止动态类扫描,从而做到不启动应用。因此使用AOT编译的程序,不可在spring容器刷新前使用
非守护
线程做长时间处理,否则影响编译时间。 -
多
SpringApplication
应用监听器隔离
1.3.3 创建一个外观对象来编排各个监听执行器的执行
SpringApplicationRunListeners
是个外观类,它提供了与SpringApplicationRunListener
一致的方法,在发布事件时会做两件事:
- 对所有监听执行器做迭代执行
- 记录执行步骤
见下面SpringApplicationRunListeners
关于spring boot开始时发布第一个事件的源码
// spring boot源码
void starting(ConfigurableBootstrapContext bootstrapContext, Class?> mainApplicationClass) {
doWithListeners("spring.boot.application.starting", (listener) -> listener.starting(bootstrapContext),
(step) -> {
if (mainApplicationClass != null) {
step.tag("mainApplicationClass", mainApplicationClass.getName());
}
});
}
private void doWithListeners(String stepName, ConsumerSpringApplicationRunListener> listenerAction,
ConsumerStartupStep> stepAction) {
StartupStep step = this.applicationStartup.start(stepName);
// 执行所有监听执行器
this.listeners.forEach(listenerAction);
if (stepAction != null) {
stepAction.accept(step);
}
step.end();
}
1.4发布ApplicationStartingEvent事件标识正在启动应用
接上面SpringApplicationRunListeners
关于spring
boot开始时发布第一个事件的源码,接下来执行默认的springboot事件发布监听器EventPublishingRunListener
,
方法调用顺序:SpringApplicationRunListeners#starting
–> EventPublishingRunListener#starting
,
下面是其源码
// spring boot EventPublishingRunListener源码
EventPublishingRunListener(SpringApplication application, String[] args) {
this.application = application;
this.args = args;
this.initialMulticaster = new SimpleApplicationEventMulticaster();
}
public void starting(ConfigurableBootstrapContext bootstrapContext) {
multicastInitialEvent(new ApplicationStartingEvent(bootstrapContext, this.application, this.args));
}
private void multicastInitialEvent(ApplicationEvent event) {
// 1.4.1 每次发布事件都更新监听器列表,保障每步中新加监听器能收到事件
refreshApplicationListeners();
// 1.4.2 使用多播器分发事件
this.initialMulticaster.multicastEvent(event);
}
private void refreshApplicationListeners() {
this.application.getListeners().forEach(this.initialMulticaster::addApplicationListener);
}
1.4.1 更新ApplicationListener
监听器列表
在spring boot的启动各阶段,都有可能往SpringApplication
添加监听器,为了保障新加监听器能收到事件,每次都要刷新多播器中的监听器列表。
如下面的示例代码,如果应用类型为REACTIVE
则添加一个监听器:
// 示例代码
public class MySpringApplicationRunListener implements SpringApplicationRunListener {
private final SpringApplication application;
public MySpringApplicationRunListener(SpringApplication application) {
this.application = application;
}
@Override
public void starting(ConfigurableBootstrapContext bootstrapContext) {
if (application.getWebApplicationType().equals(WebApplicationType.REACTIVE)) {
application.addListeners(event -> {
System.out.println("REACTIVE APP trigger event: " + event);
});
}
}
}
1.4.2 使用多播器分发事件
这里使用的多播器和Spring容器默认使用的多播器属于同一个类即SimpleApplicationEventMulticaster
,该类在实现上不单是维护一个监听器列表。
在Spring事件机制的实现中,ApplicationEventMulticaster
多播器是事件机制的外观类,可以根据ApplicationEvent事件对象匹配适合的监听器,从而实现监听器之间的独立性。
1.5创建ConfigurableEnvironment加载环境配置如环境变量和系统配置以及命令行参数
准备环境配置的流程如下,注意阅读注释,后面根据注释的编号做进一步分析。
// spring boot SpringApplication源码
private ConfigurableEnvironment prepareEnvironment(SpringApplicationRunListeners listeners,
DefaultBootstrapContext bootstrapContext, ApplicationArguments applicationArguments) {
// 1.5.1 创建环境对象
ConfigurableEnvironment environment = getOrCreateEnvironment();
// 1.5.2 配置环境对象
configureEnvironment(environment, applicationArguments.getSourceArgs());
// 把所有source使用ConfigurationPropertySourcesPropertySource来管理,
ConfigurationPropertySources.attach(environment);
// 1.5.3 通知监听器环境对象以准备好
listeners.environmentPrepared(bootstrapContext, environment);
// 把默认配置(名称为:defaultProperties)移到最后,表示被使用的优先级最低
DefaultPropertiesPropertySource.moveToEnd(environment);
Assert.state(!environment.containsProperty("spring.main.environment-prefix"),
"Environment prefix cannot be set via properties.");
// 1.5.4 通过设置以spring.main为前缀的配置来设置SpringnativeApplication对象的属性
bindToSpringApplication(environment);
if (!this.isCustomEnvironment) {
// 1.5.5 如果是自定义环境类型,则转为应用类型相符的环境类型
EnvironmentConverter environmentConverter = new EnvironmentConverter(getClassLoader());
// deduceEnvironmentClass方法根据应用类型返回对应的环境类型
// 把environment转换为应用对应的环境类型对象
environment = environmentConverter.convertEnvironmentIfNecessary(environment, deduceEnvironmentClass());
}
// 把所有source使用ConfigurationPropertySourcesPropertySource来管理,
ConfigurationPropertySources.attach(environment);
return environment;
}
1.5.1 创建环境对象
// spring boot SpringApplication源码
private ConfigurableEnvironment getOrCreateEnvironment() {
if (this.environment != null) {
return this.environment;
}
ConfigurableEnvironment environment = this.applicationContextFactory.createEnvironment(this.webApplicationType);
if (environment == null && this.applicationContextFactory != ApplicationContextFactory.DEFAULT) {
environment = ApplicationContextFactory.DEFAULT.createEnvironment(this.webApplicationType);
}
return (environment != null) ? environment : new ApplicationEnvironment();
}
上面代码是SpringApplication创建环境对象的代码面代码,如果没有对SpringApplication对象设置环境对象,则使用ApplicationContextFactory
对象来创建环境对象,详细说明如下。
1. 确定环境对象类型
在该步骤中,spring boot根据应用类型WebApplicationType
选择可以支持的ApplicationContextFactory
抽象工厂的实现类,
通过该抽象工厂实现类来创建ConfigurableEnvironment
对象。下表是WebApplicationType
对应的环境对象
WebApplicationType枚举 | ApplicationContextFactory实现类名 | 提供的Environment实现类 |
---|---|---|
SERVLET | ReactiveWebServerApplicationContextFactory |
org.springframework.boot.web.servlet.context.ApplicationServletEnvironment |
REACTIVE | ServletWebServerApplicationContextFactory |
org.springframework.boot.web.reactive.context.ApplicationReactiveWebEnvironment |
NONE | DefaultApplicationContextFactory |
org.springframework.boot.ApplicationEnvironment |
2. 创建环境对象,并加载初始配置
这个时候会把各种类型的配置装载为PropertySource
对象,对于Servlet应用,加载的环境配置类型有:(下面标星*
的是所有应用类型都有)
配置类型名称 | 说明 |
---|---|
servletConfigInitParams | servlet应用配置 |
servletContextInitParams | servlet容器上下文配置 |
jndiProperties | JNDI环境配置 |
*systemProperties | 系统属性配置 |
*systemEnvironment | 系统环境变量配置 |
1.5.2 配置环境对象
给环境对象装载配置主要做3件事情的源码如下
// spring boot SpringApplication源码
protected void configureEnvironment(ConfigurableEnvironment environment, String[] args) {
if (this.addConversionService) { // 默认位处
// 1. 添加类型解析器,spring内置了很多类型之间的转换和类型的格式化器
environment.setConversionService(new ApplicationConversionService());
}
// 2. 装载默认配置和命令行指定配置
configurePropertySources(environment, args);
// 配置profile,(实现为空,留给SpringApplication子类扩展的)
configureProfiles(environment, args);
}
- 默认添加
ApplicationConversionService
对象作为类型解析器,这个解析器内置了很多类型转换器和格式化器,具体可见该类的源码,比较简单。在开发中也可以用它来做对象转换。 - 装载默认配置和命令行指定配置,见下面代码
- 如果设置了默认配置,则添加名为
defaultProperties
的PropertySource
,并添加source列表尾部。 - 对于命令行配置,如
--server.port=8080
, 则添加一个名为commandLineArgs
的PropertySource
,并添加source列表头部。
// spring boot SpringApplication源码 protected void configurePropertySources(ConfigurableEnvironment environment, String[] args) { MutablePropertySources sources = environment.getPropertySources(); // i 添加defaultProperties source if (!CollectionUtils.isEmpty(this.defaultProperties)) { DefaultPropertiesPropertySource.addOrMerge(this.defaultProperties, sources); } // ii 添加commandLineArgs source if (this.addCommandLineProperties && args.length > 0) { String name = "commandLineArgs"; if (sources.contains(name)) { PropertySource?> source = sources.get(name); // 组合模式,组合多个命令行配置 CompositePropertySource composite = new CompositePropertySource(name); composite .addPropertySource(new SimpleCommandLinePropertySource("springApplicationCommandLineArgs", args)); composite.addPropertySource(source); sources.replace(name, composite); } else { sources.addFirst(new SimpleCommandLinePropertySource(args)); } } }
- 如果设置了默认配置,则添加名为
SimpleCommandLinePropertySource {name=‘commandLineArgs’}
1.5.3 通知监听器环境对象以准备好
到这一步了,环境对象该准备的基本配置都准备好了,对于Servlet应用,加载的环境配置类型有:(下面标星*的是所有应用类型都有)
配置类型名称 | 说明 |
---|---|
*configurationProperties | attach下面所有配置,通过它可以对所有配置进行很方便的迭代 |
*commandLineArgs | 命令行配置,如java -jar app.jar --server.port=8080
|
servletConfigInitParams | servlet应用配置 |
servletContextInitParams | servlet容器上下文配置 |
jndiProperties | JNDI环境配置 |
*systemProperties | 系统属性配置 |
*systemEnvironment | 系统环境变量配置 |
*defaultProperties | 默认配置,如果通过setDefaultProperties() 方法指定了则有 |
这个时候通过下面3种方式执行扩展的业务逻辑
- 实现
SpringApplicationRunListener
监听器 - 实现
ApplicationListener
监听ApplicationEnvironmentPreparedEvent
事件,如LoggingApplicationListener
会监听到该事件时加载日志配置。 - 实现
EnvironmentPostProcessor
接口,
该接口的内置实现如下表,所有实现类都由EnvironmentPostProcessorApplicationListener
监听器加载并执行。
实现类 | 说明 |
---|---|
RandomValuePropertySourceEnvironmentPostProcessor | 提供随机值配置,以random. 为前缀的配置值获取,比如随机int: random.int ,随机long: random.long ,随机uuid: random.uuid
|
SpringApplicationJsonEnvironmentPostProcessor | 解析以上表格中资源存在的SPRING_APPLICATION_JSON 或spring.application.json 配置键配置的json字符串配置 |
ConfigDataEnvironmentPostProcessor | 用于加载配置文件,如application.yml
|
ReactorEnvironmentPostProcessor |
spring.reactor.debug-agent.enabled=true 时执行ReactorDebugAgent#init 方法 |
IntegrationPropertiesEnvironmentPostProcessor | 加载META-INF/spring.integration.properties 中配置 |
SpringApplicationJsonEnvironmentPostProcessor
加入的json类型的配置先于配置文件的加载,因此SPRING_APPLICATION_JSON
或
spring.application.json
放在配置文件(如application.yml文件)中是不能生效的。
所有EnvironmentPostProcessor
实现执行完后,新增的配置如下表,这些配置中除spring.application.json
外,其它的优先级都在上面表格所述资源的后面。
配置类型名称 | 说明 |
---|---|
*spring.application.json | 以SPRING_APPLICATION_JSON 或spring.application.json 为键配置的json格式数据。对于servlet应用其优先级会排在 servletConfigInitParams 前面,其它情况会排在systemProperties 前面 |
*random | 提供以random. 为前缀的随机值配置值获取 |
*[名称不固定,根据资源类型生成] | 配置文件中提供的配置 |
*[Config resource ‘class path resource [application.yml]’ via location ‘optional:classpath:/’] | application.yml配置文件中提供的配置 |
*[META-INF/spring.integration.properties] |
META-INF/spring.integration.properties 中配置 |
对于application.yml
及格式为application-${profile}.yml
文件,一个文件一个配置类型名称,比如classpath下有如下配置文件
- application.yml
- application-app.yml
- application-mid.yml
- application-dao.yml
如果spring.profiles.active=dao,app,mid
, 则生成的PropertySource
名称和优先级顺序如下。
spring.profiles.active
中配置越后面的profile优先级越高。不过默认配置文件application.yml
最先加载。
配置类型名称 |
---|
Config resource ‘class path resource [application-mid.yml]’ via location ‘optional:classpath:/’ |
Config resource ‘class path resource [application-app.yml]’ via location ‘optional:classpath:/’ |
Config resource ‘class path resource [application-dao.yml]’ via location ‘optional:classpath:/’ |
Config resource ‘class path resource [application.yml]’ via location ‘optional:classpath:/’ |
注意:如果要提供动态配置且动态配置的优先级最高,则需要在本步骤中所有配置都加载完后再加载动态配置。
1.5.4 通过设置以spring.main
为前缀的配置来设置SpringApplication
对象的属性
bindToSpringApplication
方法的源码如下:
// spring boot SpringApplication源码
protected void bindToSpringApplication(ConfigurableEnvironment environment) {
try {
Binder.get(environment).bind("spring.main", Bindable.ofInstance(this));
} catch (Exception ex) {
throw new IllegalStateException("Cannot bind to SpringApplication", ex);
}
}
如果要通过配置来影响SpringApplication
的属性,提供spring.main
为前缀的配置即可. 如在application.yml
配置如下内容,则可以允许bean对象循环依赖
spring:
main:
allowCircularReferences: true
allowBeanDefinitionOverriding: true
上面示例中allowCircularReferences
和allowBeanDefinitionOverriding
都是SpringApplication
对象的属性,可在源码中找到
1.5.5 如果是自定义环境类型,则转为应用类型相符的环境类型
SpringApplication
启动时允许设置自定义类型的环境对象,不过在本步骤时会把这种环境对象转换为应用相对应的环境类型。
自定义的环境类型有两种情况进行设置
- 创建
SpringApplication
时通过setEnvironment()
方法设置 - 创建
SpringApplication
时通过setApplicationContextFactory()
方法设置一个创建自定义环境对象的ApplicationContextFactory
实现类对象
1.6发布ApplicationEnvironmentPreparedEvent事件标识环境配置已准备好
见上一节1.5.3 通知监听器环境对象以准备好
2. Spring容器的创建、准备和刷新
从SpringApplication
的象模板方法run
代码可以看出,它对spring容器的操作分为3步
- 创建Spring容器对象
ConfigurableApplicationContext
,见后面2.1部分 - 对spring容器做刷新操作前的准备工作。这部分工作比较多,见后面2.2-2.6部分
- 刷新spring容器,见后面2.7部分
2.1创建Spring容器ConfigurableApplicationContext
SpringApplication
对象使用ApplicationContextFactory
抽象工厂创建spring容器,见下面源码。
另外,这个抽象工厂除了创建容器外,它还提供创建环境对象的工厂方法,见前面部分1.5.1环境对象创建
// spring boot SpringApplication源码
protected ConfigurableApplicationContext createApplicationContext() {
return this.applicationContextFactory.create(this.webApplicationType);
}
说明:applicationContextFactory
可通过SpringApplication#setApplicationContextFactory
方法指定。
其默认为DefaultApplicationContextFactory
,
2.1.1 使用DefaultApplicationContextFactory
创建不同应用的spring容器
为何默认工厂类可以创建不同应用类型的spring容器?下面就看这个默认工厂类DefaultApplicationContextFactory
的create
方法。
// spring boot DefaultApplicationContextFactory源码
@Override
public ConfigurableApplicationContext create(WebApplicationType webApplicationType) {
try {
// 第1个lambda表示使用META-INF/spring.factories文件加载的工厂创建容器,见下面代码[1]
// 第2个lambda表示兜底方案,如果其它工厂没有为指定webApplicationType创建容器,则使用默认方法。见下面代码[2]
return getFromSpringFactories(webApplicationType, ApplicationContextFactory::create,
this::createDefaultApplicationContext);
} catch (Exception ex) {
throw new IllegalStateException("Unable create a default ApplicationContext instance, "
+ "you may need a custom ApplicationContextFactory", ex);
}
}
private T> T getFromSpringFactories(WebApplicationType webApplicationType,
BiFunctionApplicationContextFactory, WebApplicationType, T> action, SupplierT> defaultResult) {
// [1] 使用从META-INF/spring.factories文件加载的工厂创建容器,如果为空则继续
for (ApplicationContextFactory candidate : SpringFactoriesLoader.loadFactories(ApplicationContextFactory.class,
getClass().getClassLoader())) {
T result = action.apply(candidate, webApplicationType);
if (result != null) {
return result;
}
}
return (defaultResult != null) ? defaultResult.get() : null;
}
// [2] 创建默认容器
private ConfigurableApplicationContext createDefaultApplicationContext() {
if (!AotDetector.useGeneratedArtifacts()) {
// 非AOT时使用
return new AnnotationConfigApplicationContext();
}
return new GenericApplicationContext();
}
从上面代码可以知道
-
DefaultApplicationContextFactory
会先把创建容器的任务委托给从META-INF/spring.factories
加载的ApplicationContextFactory
实现类,这就回答了上面的问题。 - 即使同一应用类型在AOT时和非AOT时使用的容器不同。
2.1.2 spring boot中可以创建哪些类型的容器?
见下面表格,每个应用类型下ApplicationContextFactory
可创建的容器:
WebApplicationType枚举 | ApplicationContextFactory实现类 | 非AOT时创建容器 | AOT时创建容器 |
---|---|---|---|
SERVLET | ReactiveWebServerApplicationContextFactory | AnnotationConfigReactiveWebServerApplicationContext | ReactiveWebServerApplicationContext |
REACTIVE | ServletWebServerApplicationContextFactory | AnnotationConfigServletWebServerApplicationContext | ServletWebServerApplicationContext |
NONE | DefaultApplicationContextFactory | AnnotationConfigApplicationContext | GenericApplicationContext |
每个工厂类的源码比较简单,就不展示了。它们创建容器时都会执行的容器类的默认构造函数。
2.1.3 spring容器创建是会初始化哪些资源?
上面ApplicationContextFactory
的3个实现都会执行容器的默认构造函数,这些默认构造函数及其父类构造函数都会做些初始操作,比如
- AbstractApplicationContext需要指定
ResourcePatternResolver
对象,默认为PathMatchingResourcePatternResolver
,
servlet应用为ServletContextResourcePatternResolver
- GenericApplicationContext需要创建
DefaultListableBeanFactory
bean工厂对象 - 上面3个AnnotationConfig容器会创建
AnnotatedBeanDefinitionReader
和ClassPathBeanDefinitionScanner
。
创建这两个对象时,会间接触发spring容器创建Environment
对象,环境对象默认为StandardEnvironment
,servlet应用为StandardServletEnvironment
,reactive应用为StandardReactiveWebEnvironment
.
注意目前容器创建的环境对象,与前面spring boot的SpringApplication
创建的不一样。后面步骤spring boot会把自身的环境对象覆盖此处创建的环境对象,
同时也会更新scanner和reader持有的环境对象。为什么spring容器会重复创建环境对象呢?可以从spring
boot和spring容器属于两个不同的问题域进行分析,这里不做深入探讨。
2.1.3.1 AnnotatedBeanDefinitionReader
和ClassPathBeanDefinitionScanner
使用环境对象干什么?
scanner和reader会用环境对象来创建ConditionEvaluator
对象,它的作用是:scanner和reader在为bean创建BeanDefinition
时用它来判断bean是否满足创建条件。
ConditionEvaluator
创建时会把环境对象、BeanFactory对象传递给ConditionContext
对象。有些Condition
实现类使用环境对象来获取配置做条件判断。
这就是环境对象在scanner和reader中的作用。
ConditionEvaluator
本质是执行Condition
对象的match
方法判断是否需要创建bean。 Condition
接口定义如下,其中context参数可以用来获取环境对象。
// spring boot Condition源码
public interface Condition {
/**
* 检车条件是否满足
* @param context 可以用来获取环境对象、BeanFactory对象
* @param metadata 类的AnnotationMetadata或或者方法MethodMetadata
* @return true条件满足
*/
boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata);
}
在执行match
方法前,ConditionEvaluator
从类或方法的Conditional
注解获得Condition
对象。在实际的spring
boot项目中,很少直接使用该注解,除非要定义自己的条件判断器,通常都是通过其它条件注解来获得。
spring boot中所有的ConditionalXxx条件注解都使用Conditional
注解。 比如下面的ConditionalOnClass
注解。
下面代码中OnClassCondition
类实现了Condition
接口,它是ConditionalOnClass
注解的处理器,实现了类是否存在的判断逻辑。
// spring boot ConditionalOnClass源码
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Conditional(OnClassCondition.class)
public @interface ConditionalOnClass {
Class?>[] value() default {};
String[] name() default {};
}
2.1.3.2 创建AnnotatedBeanDefinitionReader
对象会初始化哪些配置?
AnnotatedBeanDefinitionReader
用于为使用@Configuration
注解的类创建BeanDefinition,执行它的构造函数时会完成如下配置。
- 设置其
beanNameGenerator
值为AnnotationBeanNameGenerator
, 它从注解的value属性获得bean名称,如果注解没有指定bean名称,则默认使用类名首字母小写作为bean名称。 - 设置其
scopeMetadataResolver
值为AnnotationScopeMetadataResolver
,它用于处理@Scope
注解。 - 执行
AnnotationConfigUtils#registerAnnotationConfigProcessors
静态方法往容器添加如下配置:- 设置beanFactory的
dependencyComparator
值为AnnotationAwareOrderComparator
- 设置beanFactory的
autowireCandidateResolver
值为ContextAnnotationAutowireCandidateResolver
- 添加用于处理
@Configuration
注解的ConfigurationClassPostProcessor
bean工厂后处理器 - 添加用于处理
@Resource
、@PostConstruct
、@PreDestroy
注解的AutowiredAnnotationBeanPostProcessor
bean实例后处理器 - 添加用于处理
@Autowired
、@Value
、@Inject
注解的CommonAnnotationBeanPostProcessor
bean实例后处理器 - 添加用于处理
@PersistenceContext
、@PersistenceUnit
注解的PersistenceAnnotationBeanPostProcessor
bean实例后处理器 - 添加用于处理
@EventListener
注解的EventListenerMethodProcessor
bean工厂后处理器,该后处理器还实现了SmartInitializingSingleton
接口,在容器刷新完成后创建监听器。
- 设置beanFactory的
2.1.3.3 创建ClassPathBeanDefinitionScanner
对象会初始化哪些配置?
ClassPathBeanDefinitionScanner
用于扫描类上有spring@Component
注解或Jakarta/java EE的@ManagedBean
或@Named
注解。
它创建时会
- 添加
AnnotationTypeFilter
过滤器用于匹配有@Component
、@ManagedBean
和@Named
注解的类 - 创建
PathMatchingResourcePatternResolver
类型的ResourcePatternResolver
- 创建
CachingMetadataReaderFactory
类型MetadataReaderFactory
- 加载
META-INF/spring.components
文件创建CandidateComponentsIndex对象,如果该文件不存在,则不创建。
META-INF/spring.components
也用于注册bean,是spring5.0增加的功能,spring6.1又废弃了,不建议使用。
其文件格式为k=v的properties格式,格式为:类的全限定名=注解的全限定名
或者类的全限定名=带有Indexed注解的类的全限定名
,比如下面配置示例# 示例 org.example.MyBeanController=org.springframework.stereotype.Component
2.1.3.4 spring容器实例化后还要做哪些准备?
spring为了扩展性,提供2套扩展方式,一套是基于事件的监听器、另一套是扩展接口。为了加载这些扩展类,又提供了各种灵活多样的加载方式:
注解、配置文件、系统配置、环境配置、命令行命令、META-INF/spring.factories
、代码配置。
至此一个spring容器实例已经创建完成,如果把注解了@SpringBootApplication
类的BeanDefinition和前面创建的环境对象交给spring容器,就可以执行容器的刷新操作了。
但这样,在容器刷新之前,就没法对容器做个性化的控制,所以,还需要执行各种扩展操作。下面是SpringApplication
对容器刷新前做的进一步准备工作。
// spring boot SpringApplication 源码
private void prepareContext(DefaultBootstrapContext bootstrapContext, ConfigurableApplicationContext context,
ConfigurableEnvironment environment, SpringApplicationRunListeners listeners,
ApplicationArguments applicationArguments, Banner printedBanner) {
// 把spring boot创建的环境对象交给spring容器
context.setEnvironment(environment);
// 设置resourceLoader、bean名称生成器beanNameGenerator、类型转换服务ConversionService
postProcessApplicationContext(context);
//
addAotGeneratedInitializerIfNecessary(this.initializers);
// 2.2执行ApplicationContextInitializer初始化Spring容器
applyInitializers(context);
// 2.3容器已准备好,事件通知,可发布ApplicationContextInitializedEvent事件标识容器已初始化
listeners.contextPrepared(context);
// 2.4关闭DefaultBootstrapContext并发布BootstrapContextClosedEvent事件
bootstrapContext.close(context);
if (this.logStartupInfo) {
logStartupInfo(context.getParent() == null);
logStartupProfileInfo(context);
}
// Add boot specific singleton beans
ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
// 把封装了启动参数的对象注册到bean工厂,供其它bean依赖注入使用
beanFactory.registerSingleton("springApplicationArguments", applicationArguments);
if (printedBanner != null) {
beanFactory.registerSingleton("springBootBanner", printedBanner);
}
if (beanFactory instanceof AbstractAutowireCapableBeanFactory autowireCapableBeanFactory) {
// 设置是否允许循环依赖
autowireCapableBeanFactory.setAllowCircularReferences(this.allowCircularReferences);
if (beanFactory instanceof DefaultListableBeanFactory listableBeanFactory) {
// 设置是否允许覆盖bean定义
listableBeanFactory.setAllowBeanDefinitionOverriding(this.allowBeanDefinitionOverriding);
}
}
if (this.lazyInitialization) {
// 延迟初始化
context.addBeanFactoryPostProcessor(new LazyInitializationBeanFactoryPostProcessor());
}
if (this.keepAlive) {
// 会创建一个非守护线程,该线程一直等待到spring容器发出ContextClosedEvent事件时才结束
context.addApplicationListener(new KeepAlive());
}
// 添加bean工厂后处理器,保证默认配置优先级最低, 即名称defaultProperties的PropertySource
context.addBeanFactoryPostProcessor(new PropertySourceOrderingBeanFactoryPostProcessor(context));
if (!AotDetector.useGeneratedArtifacts()) {
// 2.5加载主要资源primarySources和指定资源sources的BeanDefinition,
SetObject> sources = getAllSources();
Assert.notEmpty(sources, "Sources must not be empty");
load(context, sources.toArray(new Object[0]));
}
// 2.6发布ApplicationPreparedEvent事件
listeners.contextLoaded(context);
}
上面代码中,简单的描述下,就不展示其详细源码,其它复杂的见后面章节2.2到2.6.
- context.setEnvironment(environment): 把spring boot创建的环境对象交给spring容器,这样保证了前面准备的环境对象才有用武之地。
- postProcessApplicationContext
- 如果指定了
ResourceLoader
则添加到spring容器,此处默认为null,非必要不用指定,使用容器自带。 - 如果制定了
BeanNameGenerator
bean名称生成器beanNameGenerator,则添加到bean工厂。此处默认为null,非必要不用指定,使用容器自带。 - 设置bean工厂的类型转换服务ConversionService,默认从环境对象获取,
即前面提到的ApplicationConversionService
- 如果指定了
- addAotGeneratedInitializerIfNecessary,如果是运行的是native代码或者AOT编译运行,就会做下面2个事情
- 把所有注册的
AotApplicationContextInitializer
容器初始化器添加到初始化器列表前面。AotApplicationContextInitializer
实现了ApplicationContextInitializer
接口。 - AOT编译期间会生成一个以main方法所在类的名称为前缀
ApplicationContextInitializer
实现,这一步就会创建该实现类对象,并添加到其他AotApplicationContextInitializer
初始化器的前面。
- 把所有注册的
- 会把封装了启动参数的对象注册到bean工厂,供其它bean依赖注入使用。bean名称为
springApplicationArguments
- 通过
allowCircularReferences
属性设置是否允许循环依赖,通过allowBeanDefinitionOverriding
属性设置是否允许覆盖重复的bean定义。 - 如果允许延迟加载,添加用于设置延迟初始化的bean工厂后处理器
LazyInitializationBeanFactoryPostProcessor
。
它会把在执行其后处理器方法前注册的BeanDefinition的lazyInit
属性设置为true, 默认哪些BeanDefinition不会延迟加载:- 实现了
SmartInitializingSingleton
接口的类 - BeanDefinition角色role==BeanDefinition.ROLE_INFRASTRUCTURE
- 实现了
- 如果允许保持存活,会创建一个非守护线程,该线程一直等待到spring容器发出ContextClosedEvent事件时才结束。这个设置对servlet和reactive应用不需要。
- 为保证bean创建前对环境对象添加了新的配置后,默认配置的优先级永远最低,
添加bean工厂后处理器PropertySourceOrderingBeanFactoryPostProcessor
2.2执行ApplicationContextInitializer初始化Spring容器
ApplicationContextInitializer
扩展接口用于进一步设置spring容器所有可设置的属性,包括BeanFactory、ResourceLoader等。它在SpringApplication
构造方法里加载,见前面1.1.2 创建SpringApplication
2.2.1 内置实现有哪些?
spring boot提供的内置实现和作用如下,
实现类 | 说明 |
---|---|
ConfigurationWarningsApplicationContextInitializer | 添加ConfigurationWarningsPostProcessor 工厂后处理器用于检查@ComponentScan 的包是否正确 |
DelegatingApplicationContextInitializer | 代理context.initializer.classes 配置的初始化器对象 |
2.2.2 实现ApplicationContextInitializer
接口可以做些什么扩展?
这个时候配置相关的环境对象、spring容器都已有基本配置,就可以往容器中添加一些个性化功能,比如:
- 添加自定义的工厂后处理器
BeanFactoryPostProcessor
或者器扩展接口——BeanDefinitionRegistryPostProcessor
——可注册新的BeanDefinition的 - 加载自定义的配置文件、或者添加动态配置支持。
- 更改spring容器中各组件的默认实现,实现的个性化需求。
如果需要注册自定义的初始化器,则需要在META-INF/spring.factories
文件中以properties格式添加即可,示例如下:
# 示例Application Context Initializers
org.springframework.context.ApplicationContextInitializer=com.example.context.MyApplicationContextInitializer
2.3 spring容器已准备好,事件通知
所有ApplicationContextInitializer
容器初始化器都执行完,这个时候spring boot会做如下2件事情
- 通知所有
SpringApplicationRunListener
监听器的contextPrepared
方法 -
SpringApplicationRunListener
的其中一个实现EventPublishingRunListener
会发布ApplicationContextInitializedEvent
事件通其他ApplicationListener
表示容器已初始化
2.4关闭DefaultBootstrapContext并发布BootstrapContextClosedEvent事件
spring boot管理spring框架本身定义的context外,还管理自身的上下文,即BootstrapContext
,默认实现为DefaultBootstrapContext
。
这个BootstrapContext
能够作用的范围到这一步也到此为止,因此会发布BootstrapContextClosedEvent
事件通知其监听器ApplicationListener
用于处理收尾工作。
这个监听器的通常通过BootstrapRegistryInitializer
扩展添加,
该扩展在DefaultBootstrapContext
创建后执行。示例如下:
// 示例代码
public class MyBootstrapRegistryInitializer implements BootstrapRegistryInitializer {
@Override
public void initialize(BootstrapRegistry registry) {
// spring context初始化时注册监听器:监听关闭事件
registry.addCloseListener(event -> {
// spring boot context已关闭
BootstrapContext context = event.getBootstrapContext();
// ....
});
}
}
2.5加载主要资源的BeanDefinition
2.5.1 哪些可以作为主要资源
spring boot中加载的初始资源如下代码:
// spring boot SpringApplication 源码
public SetObject> getAllSources() {
SetObject> allSources = new LinkedHashSet>();
// 2.5.1.1 primarySources类型为Class, 代表主要类
if (!CollectionUtils.isEmpty(this.primarySources)) {
allSources.addAll(this.primarySources);
}
// 2.5.1.2 sources类型为String, 可代表类、路径、包名
if (!CollectionUtils.isEmpty(this.sources)) {
allSources.addAll(this.sources);
}
return Collections.unmodifiableSet(allSources);
}
下面对上面的注释标注点做个说明
2.5.1.1 primarySources
介绍
primarySources
定义为private final Set> primarySources
。通常为我们在类上注解了@SpringBootApplication
的类对象,如下面的例子,
// 示例代码
@SpringBootApplication
public class MyApplication {
public static void main(String[] args) {
// MyApplication.class作为primarySources
SpringApplication.run(MyApplication.class, args);
}
}
另外,primarySources
也可以是注解了其他spring注解的类的类对象。
2.5.1.2sources
介绍
定义为private Set sources
,通常为
- 包全限定符名称
- XML文件在classpath下的相对路径
- 类名,指定的类应该是标有spring注解的类
指定source的示例代码如下
// 示例代码
@SpringBootApplication
public class MyApplication {
public static void main(String[] args) {
SpringApplication application = new SpringApplication();
application.setSources(Set.of("my_spring.xml", "com.example.beans", "com.example.MyApplication"));
application.run(args);
}
}
2.5.2 加载资源的目的和作用
使用sources主要还是用于加载这些source代表的Bean定义,如下代码。
// spring boot SpringApplication 源码
protected void load(ApplicationContext context, Object[] sources) {
// ....
BeanDefinitionLoader loader = createBeanDefinitionLoader(getBeanDefinitionRegistry(context), sources);
// .....
loader.load();
}
protected BeanDefinitionLoader createBeanDefinitionLoader(BeanDefinitionRegistry registry, Object[] sources) {
return new BeanDefinitionLoader(registry, sources);
}
BeanDefinitionLoader
用于spring boot加载指定source表示的bean定义,如下源码。
// spring boot BeanDefinitionLoader 源码
private void load(Object source) {
Assert.notNull(source, "Source must not be null");
if (source instanceof Class?> clazz) {
load(clazz);
return;
}
if (source instanceof Resource resource) {
load(resource);
return;
}
if (source instanceof Package pack) {
load(pack);
return;
}
if (source instanceof CharSequence sequence) {
load(sequence);
return;
}
throw new IllegalArgumentException("Invalid source type " + source.getClass());
}
当source为字符串时,它会转为相应的资源类型后在掉想要的load方法,如下源码
// spring boot BeanDefinitionLoader 源码
private void load(CharSequence source) {
// source可以使用${}表达式计算真正的值
String resolvedSource = this.scanner.getEnvironment().resolvePlaceholders(source.toString());
try {
// 尝试当作类名来加载
load(ClassUtils.forName(resolvedSource, null));
return;
} catch (IllegalArgumentException | ClassNotFoundException ex) {
// swallow exception and continue
}
// 尝试当作资源文件来加载
if (loadAsResources(resolvedSource)) {
return;
}
// 尝试当作包名来加载
Package packageResource = findPackage(resolvedSource);
if (packageResource != null) {
load(packageResource);
return;
}
throw new IllegalArgumentException("Invalid source '" + resolvedSource + "'");
}
下面以加载Class资源代表的Bean定义为例
// spring boot BeanDefinitionLoader源码
private void load(Class?> source) {
....
// 使用AnnotatedBeanDefinitionReader来加载
this.annotatedReader.register(source);
}
下面是AnnotatedBeanDefinitionReader
为bean创建BeanDefinition的源码,省略了其他无关代码。
// spring boot AnnotatedBeanDefinitionReader 源码
private T> void doRegisterBean(ClassT> beanClass, String name, ....) {
AnnotatedGenericBeanDefinition abd = new AnnotatedGenericBeanDefinition(beanClass);
// 如果类不满足加载条件,则跳过。
// 比如注解了@ConditionalOnBean("xxxBeanName"),xxxBeanName不存就会跳过,不会创建当前beanClass的BeanDefinition
if (this.conditionEvaluator.shouldSkip(abd.getMetadata())) {
return;
}
abd.setAttribute(ConfigurationClassUtils.CANDIDATE_ATTRIBUTE, Boolean.TRUE);
// ....
String beanName = (name != null ? name : this.beanNameGenerator.generateBeanName(abd, this.registry));
// 解析 @Lazy @Primary @DependsOn @Role @Description
AnnotationConfigUtils.processCommonDefinitionAnnotations(abd);
// ......
BeanDefinitionHolder definitionHolder = new BeanDefinitionHolder(abd, beanName);
// 为bean添加代理配置
definitionHolder = AnnotationConfigUtils.applyScopedProxyMode(scopeMetadata, definitionHolder, this.registry);
// 把bean定义添加到BeanDefinitionRegistry,其实际就是BeanFactory的实现类
BeanDefinitionReaderUtils.registerBeanDefinition(definitionHolder, this.registry);
}
2.6 spring容器已加载完成
到这一步,容器就算可以执行刷新操作了,但为了扩展,在这种情况下仍然有可能还要对spring容器做进一步操作。spring
boot在这一步只提供了事件通知,如下所述2种监听器:
- 通知所有
SpringApplicationRunListener
监听器的contextLoaded
方法 -
SpringApplicationRunListener
的其中一个实现EventPublishingRunListener
会发布ApplicationPreparedEvent
事件通其他ApplicationListener
表示容器加载完成即将执行刷新操作。
2.7 刷新容器
现在终于可以刷新spring容器来创建bean来启动应用了。spring容器ApplicationContext
的refresh方法实现在抽象类AbstractApplicationContext
,该实现采用模板方法模式,
定义了一套容器刷新流程。简单描述流程图如下:
#mermaid-svg-01cfNoJcdCkKABjY {font-family:”trebuchet ms”,verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg-01cfNoJcdCkKABjY .error-icon{fill:#552222;}#mermaid-svg-01cfNoJcdCkKABjY .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-01cfNoJcdCkKABjY .edge-thickness-normal{stroke-width:2px;}#mermaid-svg-01cfNoJcdCkKABjY .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-01cfNoJcdCkKABjY .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-01cfNoJcdCkKABjY .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-01cfNoJcdCkKABjY .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-01cfNoJcdCkKABjY .marker{fill:#333333;stroke:#333333;}#mermaid-svg-01cfNoJcdCkKABjY .marker.cross{stroke:#333333;}#mermaid-svg-01cfNoJcdCkKABjY svg{font-family:”trebuchet ms”,verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-01cfNoJcdCkKABjY .label{font-family:”trebuchet ms”,verdana,arial,sans-serif;color:#333;}#mermaid-svg-01cfNoJcdCkKABjY .cluster-label text{fill:#333;}#mermaid-svg-01cfNoJcdCkKABjY .cluster-label span{color:#333;}#mermaid-svg-01cfNoJcdCkKABjY .label text,#mermaid-svg-01cfNoJcdCkKABjY span{fill:#333;color:#333;}#mermaid-svg-01cfNoJcdCkKABjY .node rect,#mermaid-svg-01cfNoJcdCkKABjY .node circle,#mermaid-svg-01cfNoJcdCkKABjY .node ellipse,#mermaid-svg-01cfNoJcdCkKABjY .node polygon,#mermaid-svg-01cfNoJcdCkKABjY .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-01cfNoJcdCkKABjY .node .label{text-align:center;}#mermaid-svg-01cfNoJcdCkKABjY .node.clickable{cursor:pointer;}#mermaid-svg-01cfNoJcdCkKABjY .arrowheadPath{fill:#333333;}#mermaid-svg-01cfNoJcdCkKABjY .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-01cfNoJcdCkKABjY .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-01cfNoJcdCkKABjY .edgeLabel{background-color:#e8e8e8;text-align:center;}#mermaid-svg-01cfNoJcdCkKABjY .edgeLabel rect{opacity:0.5;background-color:#e8e8e8;fill:#e8e8e8;}#mermaid-svg-01cfNoJcdCkKABjY .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-01cfNoJcdCkKABjY .cluster text{fill:#333;}#mermaid-svg-01cfNoJcdCkKABjY .cluster span{color:#333;}#mermaid-svg-01cfNoJcdCkKABjY div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:”trebuchet ms”,verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-01cfNoJcdCkKABjY :root{–mermaid-font-family:”trebuchet ms”,verdana,arial,sans-serif;}
对上面流程简要说明下:
- 刷新前准备工作: 比如验证环境对象中必要的配置是否存在
- 获取Bean工厂和配置bean工厂的基本特征
- bean工厂通常由子类提供
- 往bean工厂添加工厂后处理器、bean后处理器、BeanDefinition,这部分由默认实现也有让子类实现
- 执行BeanFactoryPostProcessor
- 执行BeanDefinitionRegistry的postProcessBeanDefinitionRegistry方法往bean工厂添加BeanDefinition
- 执行bean后处理器的postProcessBeanFactory方法
- 注册BeanPostProcessor: 主要是使用Bean工厂实例化BeanPostProcessor,并添加到bean工厂的
beanPostProcessors
列表中 - 完成bean工厂初始化如初始化bean:
- 实例化所有单例bean,即scope为
singleton
的bean - 如果单例bean实现了
SmartInitializingSingleton
接口,则执行该bean的afterSingletonsInstantiated
方法
- 实例化所有单例bean,即scope为
- 刷新收尾,比如清理缓存对象,以及发布事件。与事件相关的菜哦在如下:
- 先初始化
LifecycleProcessor
对象,默认为DefaultLifecycleProcessor,然后执行LifeCycle的start
方法 - 发布
ContextRefreshedEvent
事件,监听了该事件的ApplicationListener会被触发执行
- 先初始化
3.容器刷新后处理
3.1发布ApplicationStartedEvent事件
到这一步,容器刷新完成,所有bean都已经创建,为了让应用感知到spring boot已经执行完spring容器的刷新操作,它提供了事件通知,如下所述:
- 通知所有
SpringApplicationRunListener
监听器的started
方法,表示spring boot应用已启动完成。 -
SpringApplicationRunListener
的其中一个实现EventPublishingRunListener
会发布ApplicationStartedEvent
事件通其他ApplicationListener
表示应用已启动完成
3.2执行ApplicationRunner和CommandLineRunner
实现ApplicationRunner和CommandLineRunner任意一个接口就可以获得命令行传参,并使用@Component
注解等方式让spring管理。
下面是spring boot执行ApplicationRunner和CommandLineRunner接口的代码,这个没什么好解释的。
// spring boot SpringApplication 源码
private void callRunners(ConfigurableApplicationContext context, ApplicationArguments args) {
ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
String[] beanNames = beanFactory.getBeanNamesForType(Runner.class);
MapRunner, String> instancesToBeanNames = new IdentityHashMap>();
for (String beanName : beanNames) {
instancesToBeanNames.put(beanFactory.getBean(beanName, Runner.class), beanName);
}
// 对bean进行排序:通过@Priority、@Order、PriorityOrdered接口、Order提供排序值
ComparatorObject> comparator = getOrderComparator(beanFactory)
.withSourceProvider(new FactoryAwareOrderSourceProvider(beanFactory, instancesToBeanNames));
instancesToBeanNames.keySet().stream().sorted(comparator).forEach((runner) -> callRunner(runner, args));
}
private void callRunner(Runner runner, ApplicationArguments args) {
if (runner instanceof ApplicationRunner) {
callRunner(ApplicationRunner.class, runner, (applicationRunner) -> applicationRunner.run(args));
}
if (runner instanceof CommandLineRunner) {
callRunner(CommandLineRunner.class, runner,
(commandLineRunner) -> commandLineRunner.run(args.getSourceArgs()));
}
}
3.3发布ApplicationReadyEvent事件
到这一步,spring boot的启动工作就算完成了,一切都已为应用准备好,为了让应用感知到spring boot已经启动完成,它提供了事件通知,如下所述:
- 通知所有
SpringApplicationRunListener
监听器的ready
方法,表示spring boot应用启动就绪。 -
SpringApplicationRunListener
的其中一个实现EventPublishingRunListener
会发布ApplicationReadyEvent
事件通其他ApplicationListener
表示应用已启动完成
3.4 异常处理
从创建环境对象开始到执行Runner
接口,任何一个地方抛出异常,spring boot会通过事件通知给监听器,如下所述:
- 通知所有
SpringApplicationRunListener
监听器的failed
方法,表示spring boot应用已启动失败. -
SpringApplicationRunListener
的其中一个实现EventPublishingRunListener
会发布ApplicationFailedEvent
事件通其他ApplicationListener
表示应用已启动失败
4 推荐阅读
spring启动过程发送事件总结可见之前博文《Spring及Springboot事件机制详解》
spring boot启动过程的扩展点和注册方式见《Spring boot接口扩展之SPI方式详解》
5 总结
-
SpringApplication
有几种应用方式?回看1.1.1 创建SpringApplication -
WebApplicationType
对应的3种枚举类型对应的不同的ApplicationContext
和Environment
实现类。回看1.1.2.1 判断应用类型 -
SpringApplicationRunListener
监听的各个事件触发时机 - 1.5创建ConfigurableEnvironment加载环境配置如环境变量和系统配置以及命令行参数是加载配置的重头戏,其中也反映了各个配置方式的优先级。
- 配置文件种以spring.main为前缀属性名为结尾的配置来设置SpringApplication对象的属性,回看1.5.4 通过设置以spring.main为前缀的配置
- 创建Spring ApplicationContext容器都会在构造器里面初始化一些资源,可回见2.1.3 spring容器创建是会初始化哪些资源
- 简单总结了下spring容器的刷新过程2.7 刷新容器