程序设计的所有原则和方法论都是追求一件事——简单——功能简单、依赖简单、修改简单、理解简单。因为只有简单才好用,简单才好维护。因此,不应该以评论艺术品的眼光来评价程序设计是否优秀,程序设计的艺术不在于有多复杂多深沉,而在于能用多简单的方式实现多复杂的业务需求;不在于只有少数人能理解,而在于能让更多人理解。
Spring boot为spring集成开发带来很大的遍历,降低了spring中bean的配置工作,几乎0配置即可开发一个spring应用。本篇主要介绍spring boot在启动过程中都做了哪些工作,重点介绍在spring容器ApplicationContext刷新前都做了哪些准备,本篇以源码解释为主,示例为辅进行梳理其过程。
Spring boot整个启动过程分为3部分,分别是
每个部分又有如下图所示的步骤(图片比较宽,左右滑动查看):
以上步骤过程主要在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;
}
下面根据上面代码注释上的编号逐一梳理,注释前的编号与下面的章节号对应,本篇代码中的注释前编号都是与之对应的章节号。
SpringApplication
类大家都不陌生,它是spring boot提供的外观类,屏蔽了spring庞杂的内部结构,通过它只需要一行代码即可启动一个spring
boot应用。它在启动时有3中创建方式:
// 示例代码
@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种方式简要说明:
下面是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节
应用类型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;
}
}
BootstrapRegistryInitializer
: 用于向BootstrapRegistry
中注册对象,在创建BootstrapRegistry
后就会执行,见后面1.2章节ApplicationContextInitializer
: 用于向ConfigurableApplicationContext
spring容器添加对象,在执行refresh()
方法前触发,见2.2 执行ApplicationContextInitializer初始化Spring容器。ApplicationListener
: 这里注册的监听器可以监听spring boot应用的整个过程的事件@SpringBootApplication
注解的类,也可以是其它spring注解的类。main
方法所在类
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;
}
DefaultBootstrapContext
实现了BootstrapRegistry
和BootstrapContext
两个接口,前者用于注册启动相关的类对象,后者用于获取注册的类对象。
BootstrapRegistryInitializer
通过spring.factories
文件注册,一般用于往BootstrapRegistry
即DefaultBootstrapContext
注册自定义框架的对象,用在后面某些步骤中发挥作用。
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);
List<SpringApplicationRunListener> 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);
}
在上面源码中有2个点要注意:
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
)。
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
应用监听器隔离
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, Consumer<SpringApplicationRunListener> listenerAction,
Consumer<StartupStep> stepAction) {
StartupStep step = this.applicationStartup.start(stepName);
// 执行所有监听执行器
this.listeners.forEach(listenerAction);
if (stepAction != null) {
stepAction.accept(step);
}
step.end();
}
接上面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);
}
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);
});
}
}
}
这里使用的多播器和Spring容器默认使用的多播器属于同一个类即SimpleApplicationEventMulticaster
,该类在实现上不单是维护一个监听器列表。
在Spring事件机制的实现中,ApplicationEventMulticaster
多播器是事件机制的外观类,可以根据ApplicationEvent事件对象匹配适合的监听器,从而实现监听器之间的独立性。
准备环境配置的流程如下,注意阅读注释,后面根据注释的编号做进一步分析。
// 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;
}
// 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 | 系统环境变量配置 |
给环境对象装载配置主要做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’}
到这一步了,环境对象该准备的基本配置都准备好了,对于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下有如下配置文件
如果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:/’ |
注意:如果要提供动态配置且动态配置的优先级最高,则需要在本步骤中所有配置都加载完后再加载动态配置。
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
对象的属性,可在源码中找到
SpringApplication
启动时允许设置自定义类型的环境对象,不过在本步骤时会把这种环境对象转换为应用相对应的环境类型。
自定义的环境类型有两种情况进行设置
SpringApplication
时通过setEnvironment()
方法设置SpringApplication
时通过setApplicationContextFactory()
ApplicationContextFactory
实现类对象见上一节1.5.3 通知监听器环境对象以准备好
从SpringApplication
的象模板方法run
代码可以看出,它对spring容器的操作分为3步
ConfigurableApplicationContext
,见后面2.1部分SpringApplication
对象使用ApplicationContextFactory
抽象工厂创建spring容器,见下面源码。
另外,这个抽象工厂除了创建容器外,它还提供创建环境对象的工厂方法,见前面部分1.5.1环境对象创建
// spring boot SpringApplication源码
protected ConfigurableApplicationContext createApplicationContext() {
return this.applicationContextFactory.create(this.webApplicationType);
}
说明:applicationContextFactory
可通过SpringApplication#setApplicationContextFactory
方法指定。
其默认为DefaultApplicationContextFactory
,
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,
BiFunction<ApplicationContextFactory, WebApplicationType, T> action, Supplier<T> 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
实现类,这就回答了上面的问题。见下面表格,每个应用类型下ApplicationContextFactory
可创建的容器:
WebApplicationType枚举 | ApplicationContextFactory实现类 | 非AOT时创建容器 | AOT时创建容器 |
---|---|---|---|
SERVLET | ReactiveWebServerApplicationContextFactory | AnnotationConfigReactiveWebServerApplicationContext | ReactiveWebServerApplicationContext |
REACTIVE | ServletWebServerApplicationContextFactory | AnnotationConfigServletWebServerApplicationContext | ServletWebServerApplicationContext |
NONE | DefaultApplicationContextFactory | AnnotationConfigApplicationContext | GenericApplicationContext |
每个工厂类的源码比较简单,就不展示了。它们创建容器时都会执行的容器类的默认构造函数。
上面ApplicationContextFactory
的3个实现都会执行容器的默认构造函数,这些默认构造函数及其父类构造函数都会做些初始操作,比如
ResourcePatternResolver
对象,默认为PathMatchingResourcePatternResolver
,ServletContextResourcePatternResolver
DefaultListableBeanFactory
bean工厂对象AnnotatedBeanDefinitionReader
和ClassPathBeanDefinitionScanner
。Environment
对象,环境对象默认为StandardEnvironment
StandardServletEnvironment
,reactive应用为StandardReactiveWebEnvironment
.注意目前容器创建的环境对象,与前面spring boot的SpringApplication
创建的不一样。后面步骤spring boot会把自身的环境对象覆盖此处创建的环境对象,
同时也会更新scanner和reader持有的环境对象。为什么spring容器会重复创建环境对象呢?可以从spring
boot和spring容器属于两个不同的问题域进行分析,这里不做深入探讨。
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 {};
}
AnnotatedBeanDefinitionReader
对象会初始化哪些配置?AnnotatedBeanDefinitionReader
用于为使用@Configuration
注解的类创建BeanDefinition,执行它的构造函数时会完成如下配置。
beanNameGenerator
值为AnnotationBeanNameGenerator
, 它从注解的value属性获得bean名称,如果注解没有指定bean名称,则默认使用类名首字母小写作为bean名称。scopeMetadataResolver
值为AnnotationScopeMetadataResolver
,它用于处理@Scope
注解。AnnotationConfigUtils#registerAnnotationConfigProcessors
静态方法往容器添加如下配置:
dependencyComparator
值为AnnotationAwareOrderComparator
autowireCandidateResolver
值为ContextAnnotationAutowireCandidateResolver
@Configuration
注解的ConfigurationClassPostProcessor
bean工厂后处理器@Resource
、@PostConstruct
、@PreDestroy
注解的AutowiredAnnotationBeanPostProcessor
bean实例后处理器@Autowired
、@Value
、@Inject
注解的CommonAnnotationBeanPostProcessor
bean实例后处理器@PersistenceContext
、@PersistenceUnit
注解的PersistenceAnnotationBeanPostProcessor
bean实例后处理器@EventListener
注解的EventListenerMethodProcessor
SmartInitializingSingleton
接口,在容器刷新完成后创建监听器。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又废弃了,不建议使用。类的全限定名=注解的全限定名
或者类的全限定名=带有Indexed注解的类的全限定名
# 示例
org.example.MyBeanController=org.springframework.stereotype.Component
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,
Set<Object> 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.
ResourceLoader
则添加到spring容器,此处默认为null,非必要不用指定,使用容器自带。BeanNameGenerator
bean名称生成器beanNameGenerator,则添加到bean工厂。此处默认为null,非必要不用指定,使用容器自带。AotApplicationContextInitializer
AotApplicationContextInitializer
实现了ApplicationContextInitializer
接口。ApplicationContextInitializer
AotApplicationContextInitializer
初始化器的前面。springApplicationArguments
allowCircularReferences
属性设置是否允许循环依赖,通过allowBeanDefinitionOverriding
属性设置是否允许覆盖重复的bean定义。LazyInitializationBeanFactoryPostProcessor
。lazyInit
属性设置为true, 默认哪些BeanDefinition不会延迟加载:
SmartInitializingSingleton
接口的类PropertySourceOrderingBeanFactoryPostProcessor
ApplicationContextInitializer
扩展接口用于进一步设置spring容器所有可设置的属性,包括BeanFactory、ResourceLoader等。它在SpringApplication
构造方法里加载,见前面1.1.2 创建SpringApplication
spring boot提供的内置实现和作用如下,
实现类 | 说明 |
---|---|
ConfigurationWarningsApplicationContextInitializer | 添加ConfigurationWarningsPostProcessor 工厂后处理器用于检查@ComponentScan 的包是否正确 |
DelegatingApplicationContextInitializer | 代理context.initializer.classes 配置的初始化器对象 |
ApplicationContextInitializer
接口可以做些什么扩展?这个时候配置相关的环境对象、spring容器都已有基本配置,就可以往容器中添加一些个性化功能,比如:
BeanFactoryPostProcessor
或者器扩展接口——BeanDefinitionRegistryPostProcessor
如果需要注册自定义的初始化器,则需要在META-INF/spring.factories
文件中以properties格式添加即可,示例如下:
# 示例Application Context Initializers
org.springframework.context.ApplicationContextInitializer=com.example.context.MyApplicationContextInitializer
所有ApplicationContextInitializer
容器初始化器都执行完,这个时候spring boot会做如下2件事情
SpringApplicationRunListener
监听器的contextPrepared
方法SpringApplicationRunListener
的其中一个实现EventPublishingRunListener
会发布ApplicationContextInitializedEvent
ApplicationListener
表示容器已初始化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();
// ....
});
}
}
spring boot中加载的初始资源如下代码:
// spring boot SpringApplication 源码
public Set<Object> getAllSources() {
Set<Object> 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);
}
下面对上面的注释标注点做个说明
primarySources
介绍primarySources
定义为private final Set
。通常为我们在类上注解了@SpringBootApplication
的类对象,如下面的例子,
// 示例代码
@SpringBootApplication
public class MyApplication {
public static void main(String[] args) {
// MyApplication.class作为primarySources
SpringApplication.run(MyApplication.class, args);
}
}
另外,primarySources
也可以是注解了其他spring注解的类的类对象。
sources
介绍定义为private Set
,通常为
指定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);
}
}
使用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(Class<T> 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);
}
到这一步,容器就算可以执行刷新操作了,但为了扩展,在这种情况下仍然有可能还要对spring容器做进一步操作。spring
boot在这一步只提供了事件通知,如下所述2种监听器:
SpringApplicationRunListener
监听器的contextLoaded
方法SpringApplicationRunListener
的其中一个实现EventPublishingRunListener
会发布ApplicationPreparedEvent
ApplicationListener
表示容器加载完成即将执行刷新操作。
现在终于可以刷新spring容器来创建bean来启动应用了。spring容器ApplicationContext
的refresh方法实现在抽象类AbstractApplicationContext
,该实现采用模板方法模式,
定义了一套容器刷新流程。简单描述流程图如下:
对上面流程简要说明下:
beanPostProcessors
列表中singleton
的beanSmartInitializingSingleton
接口,则执行该bean的afterSingletonsInstantiated
方法LifecycleProcessor
对象,默认为DefaultLifecycleProcessor,然后执行LifeCycle的start
方法ContextRefreshedEvent
事件,监听了该事件的ApplicationListener会被触发执行到这一步,容器刷新完成,所有bean都已经创建,为了让应用感知到spring boot已经执行完spring容器的刷新操作,它提供了事件通知,如下所述:
SpringApplicationRunListener
监听器的started
方法,表示spring boot应用已启动完成。SpringApplicationRunListener
的其中一个实现EventPublishingRunListener
会发布ApplicationStartedEvent
ApplicationListener
表示应用已启动完成实现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);
Map<Runner, String> instancesToBeanNames = new IdentityHashMap<>();
for (String beanName : beanNames) {
instancesToBeanNames.put(beanFactory.getBean(beanName, Runner.class), beanName);
}
// 对bean进行排序:通过@Priority、@Order、PriorityOrdered接口、Order提供排序值
Comparator<Object> 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()));
}
}
到这一步,spring boot的启动工作就算完成了,一切都已为应用准备好,为了让应用感知到spring boot已经启动完成,它提供了事件通知,如下所述:
SpringApplicationRunListener
监听器的ready
方法,表示spring boot应用启动就绪。SpringApplicationRunListener
的其中一个实现EventPublishingRunListener
会发布ApplicationReadyEvent
ApplicationListener
表示应用已启动完成从创建环境对象开始到执行Runner
接口,任何一个地方抛出异常,spring boot会通过事件通知给监听器,如下所述:
SpringApplicationRunListener
监听器的failed
方法,表示spring boot应用已启动失败.SpringApplicationRunListener
的其中一个实现EventPublishingRunListener
会发布ApplicationFailedEvent
ApplicationListener
表示应用已启动失败spring启动过程发送事件总结可见之前博文《Spring及Springboot事件机制详解》
spring boot启动过程的扩展点和注册方式见《Spring boot接口扩展之SPI方式详解》
SpringApplication
有几种应用方式?回看1.1.1 创建SpringApplicationWebApplicationType
对应的3种枚举类型对应的不同的ApplicationContext
和Environment
实现类。回看1.1.2.1 判断应用类型SpringApplicationRunListener
监听的各个事件触发时机