前言
在《Spring Boot 启动流程详解》一文中详细给大家介绍了 Spring Boot 的启动流程,但是由于Spring Boot 启动时做了很多事件,有些地方没有详细给大家介绍,只是大概介绍了下做了什么,这让大家对具体的实现仍不甚了解,这其中就包括 environmentPrepared 阶段触发的事件。下面就详细给大家介绍下 environmentPrepared 阶段 Spring Boot 具体做了什么。
执行了哪些 ApplicationListener ?
首先让我看看哪些 ApplicationListener 执行了 ApplicationEnvironmentPreparedEvent 事件,通过打断点我们可以看到会执行如下这些 ApplicationListener:
注意:如果Spring Boot 如何广播事件执行的,可以先看看《Spring Boot 启动流程详解》一文。
下面让我们逐个分析每个ApplicationListener 做了什么事?
ConfigFileApplicationListener
顾名思义,该ApplicationListener可能是用来加载配置文件,由于 ConfigFileApplicationListener 内部处理操作较多,特单独写了一篇文章进行说明,详见:
ConfigFileApplicationListener 触发 ApplicationEnvironmentPreparedEvent 事件执行逻辑详解
AnsiOutputApplicationListener
该监听的主要作用是从属性配置中获取 spring.output.ansi.enabled 和 spring.output.ansi.console-available 配置并设置到 AnsiOutput 对象中,以便后续日志框架在输入日志时是否带颜色和格式。
LoggingApplicationListener
首先让我看看 LoggingApplicationListener 触发 ApplicationEnvironmentPreparedEvent 的代码:
private void onApplicationEnvironmentPreparedEvent(ApplicationEnvironmentPreparedEvent event) {
if (this.loggingSystem == null) {
this.loggingSystem = LoggingSystem.get(event.getSpringApplication().getClassLoader());
}
initialize(event.getEnvironment(), event.getSpringApplication().getClassLoader());
}
首先判断 loggingSystem 对象是否存在,不存在则创建,实际这里 loggingSystem 在触发 ApplicationStartingEvent 事件时就已经进行了创建并执行了其 beforeInitialize() 方法,源码如下:
private void onApplicationStartingEvent(ApplicationStartingEvent event) {
this.loggingSystem = LoggingSystem.get(event.getSpringApplication().getClassLoader());
this.loggingSystem.beforeInitialize();
}
LoggingSystem.get()为静态方法,主要是根据系统是否加载对应的class去加载响应的LoggingSystem。LoggingSystem 可通过系统属性 LoggingSystem 指定,如果没有指定,则按代码指定的顺序进行加载,加载是通过反射的方式实现的,如下所示:
static {
Map<String, String> systems = new LinkedHashMap<>();
systems.put("ch.qos.logback.core.Appender", "org.springframework.boot.logging.logback.LogbackLoggingSystem");
systems.put("org.apache.logging.log4j.core.impl.Log4jContextFactory",
"org.springframework.boot.logging.log4j2.Log4J2LoggingSystem");
systems.put("java.util.logging.LogManager", "org.springframework.boot.logging.java.JavaLoggingSystem");
SYSTEMS = Collections.unmodifiableMap(systems);
}
public static LoggingSystem get(ClassLoader classLoader) {
String loggingSystem = System.getProperty(SYSTEM_PROPERTY);
if (StringUtils.hasLength(loggingSystem)) {
if (NONE.equals(loggingSystem)) {
return new NoOpLoggingSystem();
}
return get(classLoader, loggingSystem);
}
return SYSTEMS.entrySet().stream().filter((entry) -> ClassUtils.isPresent(entry.getKey(), classLoader))
.map((entry) -> get(classLoader, entry.getValue())).findFirst()
.orElseThrow(() -> new IllegalStateException("No suitable logging system located"));
}
private static LoggingSystem get(ClassLoader classLoader, String loggingSystemClass) {
try {
Class<?> systemClass = ClassUtils.forName(loggingSystemClass, classLoader);
Constructor<?> constructor = systemClass.getDeclaredConstructor(ClassLoader.class);
constructor.setAccessible(true);
return (LoggingSystem) constructor.newInstance(classLoader);
}
catch (Exception ex) {
throw new IllegalStateException(ex);
}
}
从上面代码可以看出,日志系统的优先级是:logback >> log4j2 >> java logging。
让我们回到上面触发 ApplicationEnvironmentPreparedEvent 事件的地方,继续看看 LoggingApplicationListener 在触发该事件时执行了什么事情。
protected void initialize(ConfigurableEnvironment environment, ClassLoader classLoader) {
//将属性配置文件中读取到的 logging.xxx 相关配置写到系统属性中
new LoggingSystemProperties(environment).apply();
this.logFile = LogFile.get(environment);
if (this.logFile != null) {
//将logFile属性写到系统属性中,主要就是 logging.file.name 和 logging.file.path
this.logFile.applyToSystemProperties();
}
// 构建一个LoggerGroup
this.loggerGroups = new LoggerGroups(DEFAULT_GROUP_LOGGERS);
// 设置springBootLogger日志等级
initializeEarlyLoggingLevel(environment);
// 初始化日志系统(日志系统的初始化是一个繁琐的过程,不是本篇文章的重点,不做详细说明,感兴趣的可以自己看看源码)
initializeSystem(environment, this.loggingSystem, this.logFile);
// 设置最终的日志等级
initializeFinalLoggingLevels(environment, this.loggingSystem);
// 根据logging.register-shutdown-hook 属性设置决定是否注册 ShutdownHook 钩子
registerShutdownHookIfNecessary(environment, this.loggingSystem);
}
从上面代码可以看出 initialize() 方法主要做了 LoggingSystem 的初始化。
ClasspathLoggingApplicationListener
该监听主要作用是日志级别为debug时,如果触发了 ApplicationEnvironmentPreparedEvent 或者 ApplicationFailedEvent 事件,则以debug模式打印 classpath.
BackgroundPreinitializer
该监听主要用于加速启动,在多核cpu的情况下并行加载一些资源。
DelegatingApplicationListener
从环境变量中读取 context.listener.classes 属性配置,context.listener.classes 属性值都是 ApplicationListener实现类,如果配置了,则进行事件广播,执行相应的 ApplicationEnvironmentPreparedEvent 事件动作。这里可以看做是执行自定义 ApplicationListener 的一种扩展点。
DubboHolderListener
该监听器不是Spring boot 默认的 ApplicationListener,是演示项目引用的 spring-boot-starter-dubbo-1.0.0.jar 中的监听器,且其内部没有对 ApplicationEnvironmentPreparedEvent 事件处理逻辑,这个就不过多介绍。
FileEncodingApplicationListener
该监听器主要作用是,如果发现项目启动时系统文件编码格式与设置的环境属性 spring.mandatory-file-encoding 对应的编码不一致,则抛出异常,中断启动。如果未设置 spring.mandatory-file-encoding,则不影响启动。
回顾总结
以上就是在环境准备阶段,哪些ApplicationListener执行了 ApplicationEnvironmentPreparedEvent 事件。通过上面代码分析,在environmentPrepared阶段主要执行了以下三件事,其他一些 ApplicationListener 都是无关紧要的。
- 通过 ConfigFileApplicationListener 加载属性配置文件并装载到 environment 对象中;
- 通过 LoggingApplicationListener 初始化 LoggingSystem,完成日志框架的初始化;
- 通过 DelegatingApplicationListener 提供额外实现 ApplicationListener 的扩展点,无需通过spring.factories文件来设置自定义的 ApplicationListener。
注:spring boot 版本为2.3.10