Springboot | 启动 - [00 准备环境]
§1 new SpringApplication


图 1-1 SpringApplication.run() 的实际逻辑

这里先关注上图 <1> 的部分,对应的代码如下
public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
this.resourceLoader = resourceLoader;
Assert.notNull(primarySources, "PrimarySources must not be null");
//0,设置主启动类
this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
//1,推断网络应用类型
this.webApplicationType = WebApplicationType.deduceFromClasspath();
//2,设置初始化器
setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
//3,设置监听器
setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
this.mainApplicationClass = deduceMainApplicationClass();
}
入参
- resourceLoader:当前是 null
- primarySources:主类,就是当前应用的 Application 类的 class
如果项目叫 demo,这里就是 DemoApplication.class
主要逻辑
//1 推断网络应用类型
springboot 定义了 3 种类型
| 类型 | 含义 | 判断条件 |
|---|---|---|
| NONE | 非 web 应用 不启动内嵌 web 服务器 |
不能加载到 javax.servlet.Servlet 和 org.springframework.web.context.ConfigurableWebApplicationContext |
| SERVLET | 基于 serverlet 的网络应用 | 除 NONE / REACTIVE 的其他情况 |
| REACTIVE | 响应式网络应用 内嵌 reactive web 服务器 |
可以加载 org.springframework.web.reactive.DispatcherHandler 且不能加载到 org.springframework.web.servlet.DispatcherServlet 和 org.glassfish.jersey.servlet.ServletContainer |
//2 设置初始化器
private <T> Collection<T> getSpringFactoriesInstances(Class<T> type, Class<?>[] parameterTypes, Object... args) {
ClassLoader classLoader = getClassLoader();
//2-1,从所有 META-INF/spring.factories 里获取初始化器的实现类名(如下图 2-1),扔到 set 里去重
Set<String> names = new LinkedHashSet<>(SpringFactoriesLoader.loadFactoryNames(type, classLoader));
//2-2,实例化,里面核心逻辑就是 Class.forName 获取 Class,然后进行实例化
List<T> instances = createSpringFactoriesInstances(type, parameterTypes, classLoader, args, names);
//2-3,排序,先按实现的 Ordered 接口,后按 @Order 注解,最后按装饰类的 @Order 注解排序
AnnotationAwareOrderComparator.sort(instances);
return instances;
}
图 2-1 META-INF/spring.factories 里的初始化器实现类名

//3 设置监听器
与 //2 同理,代码也是同一段
只不过是从 META-INF/spring.factories 里加载 org.springframework.context.ApplicationListener,类似下面这些

§2 run
现在关注最上面图 1-1 中 <2> 的部分,整个 run() 如下
//0,启动计时器
StopWatch stopWatch = new StopWatch();
stopWatch.start();
ConfigurableApplicationContext context = null;
//1,设置无头模式
configureHeadlessProperty();
//2,整理 SpringApplicationRunListener
SpringApplicationRunListeners listeners = getRunListeners(args);
listeners.starting();
try {
ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
//3,准备环境
ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments);
configureIgnoreBeanInfo(environment);
Banner printedBanner = printBanner(environment);
context = createApplicationContext();
prepareContext(context, environment, listeners, applicationArguments, printedBanner);
refreshContext(context);
afterRefresh(context, applicationArguments);
stopWatch.stop();
//0-1,停止计时
if (this.logStartupInfo) {
new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), stopWatch);
}
listeners.started(context);
callRunners(context, applicationArguments);
}
catch (Throwable ex) {
handleRunFailure(context, ex, listeners);
throw new IllegalStateException(ex);
}
try {
listeners.running(context);
}
catch (Throwable ex) {
handleRunFailure(context, ex, null);
throw new IllegalStateException(ex);
}
return context;
//0 启动计时器
一个简单的计时器,在 //0-1 处停止并完成计时,最终用于打印如下的日志
下面的两个时间,前者是 当前计时器时长,后者是 jvm上电时长
Started xxApplication in 6.59 seconds (JVM running for 7.59)
同时,从 //0-1 处代码可知:
上述日志的打印,不意味着项目启动完成,只是 spring 容器启动完成 了,在这之后,spring 还需要启动
SpringApplicationRunListenerApplicationRunner、CommandLineRunner
//1 设置无头模式
springboot 项目基本都运行在服务器上,因此通常设置其工作于 <无头模式> 下,即无显示器、键盘、鼠标
其核心其实就是下面这句
System.setProperty("java.awt.headless", "true");
//2 整理 SpringApplicationRunListener
和上文 设置初始化器 处用的同一个方法,这次是为了加载 org.springframework.boot.SpringApplicationRunListener
但实际只加载到 EventPublishingRunListener,如下图
前面(下图)的是
org.springframework.context.ApplicationListener,用于处理 spring 的事件,不同的实现中会过滤当前实现关注的事件处理
现在(下下图)的是org.springframework.boot.SpringApplicationRunListener,用于处理 springboot 的事件


SpringApplicationRunListener 说是个 listener,但其接口与实现实际更像是个 publisher,接口中方法如下
观察其中方法,对比上文中 run() 中逻辑,可见整体脉络大约是能对上的
public interface SpringApplicationRunListener {
default void starting() {}
default void environmentPrepared(ConfigurableEnvironment environment) {}
default void contextPrepared(ConfigurableApplicationContext context) {}
default void contextLoaded(ConfigurableApplicationContext context) {}
default void started(ConfigurableApplicationContext context) {}
default void running(ConfigurableApplicationContext context) {}
default void failed(ConfigurableApplicationContext context, Throwable exception) {}
}
SpringApplicationRunListener 有一个唯一的默认实现 EventPublishingRunListener
其核心属性是 SimpleApplicationEventMulticaster
其对 SpringApplicationRunListener 中各个方法的实现,也是用这个 Multicaster 去广播对应的事件
// 下面的代码是 EventPublishingRunListener 中的一部分
// EventPublishingRunListener 是 SpringApplicationRunListener 的实现
// EventPublishingRunListener 的构造器,可见其核心属性 initialMulticaster 的定义
public EventPublishingRunListener(SpringApplication application, String[] args) {
// ... 前面的不重要
this.initialMulticaster = new SimpleApplicationEventMulticaster();
// ... 后面的也不重要
}
// EventPublishingRunListener 中的 starting() 方法实现
// 其逻辑就是广播了 ApplicationStartingEvent 事件
public void starting() {
this.initialMulticaster.multicastEvent(new ApplicationStartingEvent(this.application, this.args));
}
SpringApplicationRunListener 的接口是 ApplicationEventMulticaster,核心方法 multicastEvent() 的主要逻辑也在下面简要说明
/**************************************
* 实现此接口的对象,可以管理若干 ApplicationListener 对象并向它们发布事件
* ApplicationEventPublisher 可以委派此接口的对象为代表,在实际上代其发布事件
* 这里说的 ApplicationEventPublisher 通常是 org.springframework.context.ApplicationContext
**************************************/
public interface ApplicationEventMulticaster {}
/**************************************
* ApplicationEventMulticaster 接口的简单实现
*
* 此实现会广播所有事件给 listeners,这里的 listener 是指 ApplicationListener
* 如果 listener 对广播的事件不感兴趣,它会自己忽略对应的事件
* 一般情况下,listener 会对传入的事件进行 instanceof 检查,这决定了它是否对此事件感兴趣
*
* 默认情况下,所有 listener 都在调用线程中执行。
* 如果有个无赖 listener(应该指的是处理很慢的监听器),就会有阻塞整个应用的风险
* 但这个方式具有最小的开销(这个实现较 simple 应该就是因为这个原因)
* 也可以指定一个替代的替代的任务执行器,比如一个线程池,这样可以使 listener 执行与不同的线程
**************************************/
public class SimpleApplicationEventMulticaster extends AbstractApplicationEventMulticaster {
// 整个过程只需要关注两个步骤,见下面注释
public void multicastEvent(final ApplicationEvent event, @Nullable ResolvableType eventType) {
ResolvableType type = (eventType != null ? eventType : resolveDefaultEventType(event));
Executor executor = getTaskExecutor();
// 1 按事件类型筛选 listener
for (ApplicationListener<?> listener : getApplicationListeners(event, type)) {
if (executor != null) {
executor.execute(() -> invokeListener(listener, event));
}
else {
// 2 依次调用它们处理事件
// 上面注释中的说的 "调用线程中执行" 说的就是这里:一个 for 循环就直接调用了
// 实际就是走了这句话 listener.onApplicationEvent(event)
invokeListener(listener, event);
}
}
}
}
这个话题暂时看到这里就可以了,继续按 run() 的逻辑向下
//3 准备环境
private ConfigurableEnvironment prepareEnvironment(SpringApplicationRunListeners listeners, ApplicationArguments applicationArguments) {
//3-1,创建 env 实例,会根据前面的网络应用类型创建不同的实例
ConfigurableEnvironment environment = getOrCreateEnvironment();
//3-2,配置环境
configureEnvironment(environment, applicationArguments.getSourceArgs());
//3-3,环境依附
ConfigurationPropertySources.attach(environment);
//3-4,环境预备
listeners.environmentPrepared(environment);
bindToSpringApplication(environment);
if (!this.isCustomEnvironment) {
environment = new EnvironmentConverter(getClassLoader()).convertEnvironmentIfNecessary(environment,
deduceEnvironmentClass());
}
ConfigurationPropertySources.attach(environment);
return environment;
}
//3-1 创建 env 实例
getOrCreateEnvironment() 会根据网络应用类型创建对应的 environment 实例,大部分情况下是 StandardServletEnvironment
StandardServletEnvironment 比较特殊,其构造器在父类 AbstractEnvironment 中
而其逻辑又被委托给了一个待实际实现的方法 customizePropertySources()
public AbstractEnvironment() {
customizePropertySources(this.propertySources);
}
protected void customizePropertySources(MutablePropertySources propertySources) {}
此方法经历了两级扩展,这两级扩展一共为最终的 StandardServletEnvironment 增加了 4 个配置源
// StandardServletEnvironment
@Override
protected void customizePropertySources(MutablePropertySources propertySources) {
// 增加配置源 servletConfigInitParams
propertySources.addLast(new StubPropertySource(SERVLET_CONFIG_PROPERTY_SOURCE_NAME));
// 增加配置源 servletContextInitParams
propertySources.addLast(new StubPropertySource(SERVLET_CONTEXT_PROPERTY_SOURCE_NAME));
if (JndiLocatorDelegate.isDefaultJndiEnvironmentAvailable()) {// JNDI 不需要关注,J2EE 的规范,但用的不宽泛
propertySources.addLast(new JndiPropertySource(JNDI_PROPERTY_SOURCE_NAME));
}
super.customizePropertySources(propertySources);
}
// StandardEnvironment
@Override
protected void customizePropertySources(MutablePropertySources propertySources) {
// 增加配置源 systemProperties
propertySources.addLast(
new PropertiesPropertySource(SYSTEM_PROPERTIES_PROPERTY_SOURCE_NAME, getSystemProperties()));
// 增加配置源 systemEnvironment
propertySources.addLast(
new SystemEnvironmentPropertySource(SYSTEM_ENVIRONMENT_PROPERTY_SOURCE_NAME, getSystemEnvironment()));
}
//3-2 配置环境
configureEnvironment() 可以理解为对上面创建的 environment 实例本身 进行配置,而不是通过向其中增加配置而达到配置项目的目的,代码如下
protected void configureEnvironment(ConfigurableEnvironment environment, String[] args) {
if (this.addConversionService) {
ConversionService conversionService = ApplicationConversionService.getSharedInstance();
environment.setConversionService((ConfigurableConversionService) conversionService);
}
configurePropertySources(environment, args);
configureProfiles(environment, args);
}
configureEnvironment() 整体上是一个模板方法,其中的逻辑主要分两个部分,分别由两个方法实现
-
configurePropertySources(),配置 environment 的属性源(即 propertySource)- 此阶段,spring 容器可以加载到 4 个源(就是上面那 4 个),有 2 个有具体属性,里面是环境参数和系统属性
- 此方法会在满足条件的情况下额外添加 2 种源
- defaultProperties:默认属性,如果创建
SpringApplication的父子容器,则源容器中的系统和环境属性会以此形式作为源 - commandLineProperties:命令行参数,即下图的配置(不是 vm options),如果有实际值则会作为源


- defaultProperties:默认属性,如果创建
-
configureProfiles(),配置激活的 profile,spring.profiles.active属性即在此生效
//3-3 环境依附
对指定的 environment(就是之前创建的那个)附加对 ConfigurationPropertySource 的支持
被 environment 管理的每个配置源(PropertySource)都会适配一个 ConfigurationPropertySource
ConfigurationPropertySource 允许经典的 PropertySourcesPropertyResolver 按配置属性名称调用解析 PropertySource
其原理大约如下
- 从 environment 里获取所有
PropertySource - 清除历史 attach 的结果(可以回去看一眼
prepareEnvironment()的代码,此步骤调用了两次) - 进行当前 attach
将前面获取的 PropertySource 列表封装为 ConfigurationPropertySourcesPropertySource 对象
并把这个新对象本身增加到原 PropertySource 列表的第一个元素public static void attach(Environment environment) { // 前面不重要,sources 就是从 environment 中获取的 PropertySource sources.addFirst( new ConfigurationPropertySourcesPropertySource( ATTACHED_PROPERTY_SOURCE_NAME,new SpringConfigurationPropertySources(sources) ) ); }
ConfigurationPropertySourcesPropertySource提供按属性名获取属性的能力
SpringConfigurationPropertySources提供了实际的adapt()能力,其实际作用是将
PropertySource转变为ConfigurationPropertySource并建立二者的映射,然后
ConfigurationPropertySourcesPropertySource的能力就有用武之地了
//3-4 环境预备
就是为了执行下面这句话,其目的是向 spring 的 listener 广播 ApplicationEnvironmentPreparedEvent
this.initialMulticaster
.multicastEvent(new ApplicationEnvironmentPreparedEvent(this.application, this.args, environment));
上文已经简单的串过 multicastEvent() 的浅层逻辑:
按事件类型过滤所有的 ApplicationListener,并将事件广播过去,然后被各自的 onApplicationEvent() 处理
现在继续深入 onApplicationEvent(),先来看一眼都有哪些 ApplicationListener 响应了这个事件

- ConfigFileApplicationListener
从已知位置加载 properties 来配置上下文环境的
EnvironmentPostProcessor,先不需要关注这个类型是啥
默认的配置就是熟知的application.properties/application.yml,已知位置(按优先级)即
- file:./ config/
- file:./ config/*/
- file:./
- classpath:config/
- classpath:
基于
spring.profiles.active的附加配置文件也会被加载
- AnsiOutputApplicationListener
按
spring.output.ansi.enabled配置 AnsiOutput,此配置开启后,可以通过输出 ANSI 转移序列来给应用的输出着色
- LoggingApplicationListener
用于配置日志系统的
此 listener 优先按 environment 中包含的 logging.config启动日志系统,如果没有就按默认配置启动
- ClasspathLoggingApplicationListener
用于响应环境就绪事件/环境失败事件,并以 DEBUG 级别日志记录线程上下文类加载器(TCCL)的类路径
其实就是在 DEBUG 日志开启时,打印Application started with classpath:日志
- BackgroundPreinitializer
用于触发预初始化,此预初始化是一个比较耗时的过程,所以在后台线程中进行
包括 spring 的默认转换器、格式化器、消息转换器(http相关),javax.validation,json,编码集的预初始化
预初始化,有的是向容器中增加示例,有的仅仅是调用相关类以保证类的加载
- WelcomeLogoApplicationListener(忽略,这是 dubbo 引入的)
- DelegatingApplicationListener
这是一个扩展点,它会把事件的处理委派个其他 lisenter
可以通过context.listener.classes配置,决定把事件委派给哪些 lisenter
- FileEncodingApplicationListener
用于当系统编码值与设置在 environment 里的预期值不符时,终止应用启动
默认情况下不生效,但当设置了spring.mandatory_file_encoding且与系统属性file.encoding不一致时就会抛出异常
上述 ApplicationListener 中,我们先只关注其中的两个,他们分别负责两个重要的过程
ConfigFileApplicationListener,它负责默认配置文件的加载LoggingApplicationListener,它负责日志系统的启动
ConfigFileApplicationListener
入口的事件校验
此 listener 中 onApplicationEvent() 的实现长下图这样
不仅是此 Listener,基本所有 ApplicationListener 都有此逻辑,这就是 SpringApplicationRunListener 注释中所谓的 instanceof 检查
public void onApplicationEvent(ApplicationEvent event) {
if (event instanceof ApplicationEnvironmentPreparedEvent) {
onApplicationEnvironmentPreparedEvent((ApplicationEnvironmentPreparedEvent) event);
}
// 后面还有一个 if,用于处理另一个事件
}
实际处理逻辑的代码如下
private void onApplicationEnvironmentPreparedEvent(ApplicationEnvironmentPreparedEvent event) {
//3-4-1 EnvironmentPostProcessor
//3-4-2 准备 EnvironmentPostProcessor 工作链
List<EnvironmentPostProcessor> postProcessors = loadPostProcessors();
postProcessors.add(this);
AnnotationAwareOrderComparator.sort(postProcessors);
//3-4-3 驱动执行 EnvironmentPostProcessor 链
for (EnvironmentPostProcessor postProcessor : postProcessors) {
postProcessor.postProcessEnvironment(event.getEnvironment(), event.getSpringApplication());
}
}
//3-4-1 关于 EnvironmentPostProcessor
springboot 中有很多 PostProcessor,比如 BeanPostProcessor,直译的话可以称呼他们为 <XX 后置处理器>
所谓后置处理器,可以理解是 实例化的后置,即:<XX 后置处理器> 是在 XX 实例化之后、实际投入使用之前触发执行的,对 XX 实现定制化的处理器
因此,EnvironmentPostProcessor 是在实例化后定制Environment 的处理器,和当前进度吻合,其接口定义与注释如下:
/**************************************
* 允许在应用上下文 refreshed 之前,定制 Environment(的处理)
* 此接口的实现必须注册在 META-INF/spring.factories
* 鼓励在此接口实现执行之前,先检查是否实现 Ordered 接口或带有 @Order 注解,并基于此排序
**************************************/
public interface EnvironmentPostProcessor {
void postProcessEnvironment(ConfigurableEnvironment environment, SpringApplication application);
}
诶,又提到了 spring.factories
不妨按接口上的注释,尝试增加一个 EnvironmentPostProcessor 试试
@Order(Ordered.HIGHEST_PRECEDENCE + 20)
public class BbyijuEnvironmentPostProcessor implements EnvironmentPostProcessor {
@Override
public void postProcessEnvironment(ConfigurableEnvironment environment, SpringApplication application) {
System.out.println("我就 BB 一句");
}
}
resources/META-INF/spring.factories
org.springframework.boot.env.EnvironmentPostProcessor=\
com.a.b.c.BbyijuEnvironmentPostProcessor
启动验证,可以看到他确实生效了(整理到这的进度,还没到打印 banner 呢……)

//3-4-2 准备工作链
回到 ConfigFileApplicationListener 的处理逻辑,如果忘了可以回 这里 看一眼,暂时先关注下面三句
//3-4-2 准备工作链
List<EnvironmentPostProcessor> postProcessors = loadPostProcessors();
postProcessors.add(this);
AnnotationAwareOrderComparator.sort(postProcessors);
结合 EnvironmentPostProcessor 的 spring.factories 机制
不难猜 loadPostProcessors() 又是走的 loadFactories() 方法(或 loadFactoryNames()),果然
List<EnvironmentPostProcessor> loadPostProcessors() {
return SpringFactoriesLoader.loadFactories(EnvironmentPostProcessor.class, getClass().getClassLoader());
}
这样得到了所有 EnvironmentPostProcessor 实例,然后将 ConfigFileApplicationListener 自身加入实例列表
最后对所有实例进行排序,这个排序方法也是老朋友,忘了的同学参考 <//2 设置初始化器>,最后得到的就是最终的工作链
这里的写法可以看做是一个变种的简化责任链,其设计目的就是自己确定核心,同时开发定制化能力给用户
- springboot 将加载配置文件的核心逻辑在自己定义的
ConfigFileApplicationListener中进行了实现 - 同时希望留下足够的可扩展性供用户自定义,这个可扩展性的实现,就交由 SPI 去实现
- SPI 需要接口识别,于是 springboot 定义了
EnvironmentPostProcessor接口,其实现是对ConfigFileApplicationListener的扩展和补充换句话说,
ConfigFileApplicationListener和这些实现其实是同一类东西,因此ConfigFileApplicationListener也需要实现此接口 - 定制化的扩展要求某个扩展在谁前面、在谁后面,那都是合理的需求,因此需要排序来确定工作的先后顺序
其实就算忽略设计意义,仅从编码的角度,
ConfigFileApplicationListener也得实现EnvironmentPostProcessor
否则和其他的扩展组件都不保持队形,那怎么参与这里的排序,更后面怎么被一个 for 循环驱动执行
在上述步骤执行后,最终的工作链如下图,简单串一下它们的作用

- SystemEnvironmentPropertySourceEnvironmentPostProcessor
environment 里目前有 6 个
propertySource
附带一嘴:propertySource一开始有 4 个,然后 <配置环境> 阶段增加了一个命令行来源SimpleCommandLinePropertySource,接着 <环境依附> 阶段又增加了一个用属性名来访问属性的propertySource,即ConfigurationPropertySourcesPropertySource
一开始的 4 个里,有个叫 systemEnvironment、类型为SystemEnvironmentPropertySource系统环境配置源,没有印象的同学看 <配置环境> 里的截图
此EnvironmentPostProcessor,就是为了把它替换成OriginAwareSystemEnvironmentPropertySource
这是SystemEnvironmentPropertySource的子类(原本类型的子类),目的是增加其跟踪每个系统环境属性SystemEnvironmentOrigin的能力
- SpringApplicationJsonEnvironmentPostProcessor
使 environment 增加对 json 格式配置源的支持,其实就是下图这种写法(或一个 .json 文件)
这会使 environment 增加一个叫spring.application.json的propertySource
- CloudFoundryVcapEnvironmentPostProcessor
如果应用运行于 VCAP 环境,此
EnvironmentPostProcessor用于找到并解析其元数据,基本需要关注
VCAP = vmware's cloud application platform(应该是这个),即目前的 Cloud Foundry(开源云应用 PaaS 平台)
- ConfigFileApplicationListener
本尊,后文细说
- BbyijuEnvironmentPostProcessor
咱自己加的那个用来 "我就 BB 一句" 的那个,不需要关注
- DebugAgentEnvironmentPostProcessor
是否允许 java Reactor 的异步调试工具
此参数疑似在生产环境中建议关闭,待确认
- DubboDefaultPropertiesEnvironmentPostProcessor
dubbo 的,当前场景不需要关注
//3-4-3 驱动执行
回到 ConfigFileApplicationListener 的处理流程
从上文,ConfigFileApplicationListener 已经组装好了最终的工作链,这里就开始驱动它们执行了,相关代码如下
说白了就是开个循环,调用每一个 EnvironmentPostProcessor 的 postProcessEnvironment(),ConfigFileApplicationListener 也是其一
//3-4-3 驱动执行
for (EnvironmentPostProcessor postProcessor : postProcessors) {
postProcessor.postProcessEnvironment(event.getEnvironment(), event.getSpringApplication());
}
众多 EnvironmentPostProcessor 中,暂时只关注 ConfigFileApplicationListener 自身的细节,非细节的部分上文已经简单的串过
这个步骤会涉及 springboot 读取配置文件的逻辑,另开新帖单独说明


浙公网安备 33010602011771号