Spring Boot 项目启动时在 environmentPrepared 阶段到底干了啥?
前言在《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 { MapString, 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