Spring中日志相关对象的创建流程
时间:2026-06-06 16:44
SpringBoot启动时,LoggingApplicationListener监听容器事件并创建LoggingSystem,根据类路径自动选择日志框架(默认Logback),通过四个事件回调完成初始化、配置和清理。Logback配置文件支持springProperty和springProfile标签,由SpringBootJoranConfiguratio
好的,作为一位深耕Java生态多年的技术博主,我来帮你把这段Spring Boot日志初始化的“干货”重新梳理一遍,去掉生硬的AI腔,换上工程师之间聊天的节奏感。
先说说核心流程:Spring Boot启动时,日志系统的初始化并不是什么黑魔法,它依靠一套精心设计的事件监听机制。其中最关键的角色就是 `LoggingApplicationListener`。这个监听器会盯着容器的生命周期事件,然后在合适的时机拉起 `LoggingSystem` 对象——你可以把它理解为整个日志框架的“指挥官”。
---
那么,这个指挥官具体是怎么工作的呢?
**第一步:抽象类 `LoggingSystem` 定规矩**
`LoggingSystem` 是一个抽象类,它定义了所有日志框架(Logback、Log4J2、Java Util Logging)必须遵守的生命周期规范。你看到的这段核心代码,其实就是它的“选秀”机制:
> 通过系统属性 `logging.system` 可以强制指定日志框架,如果不指定,就按类路径中实际存在的日志库自动选择。默认情况下,因为Spring Boot starter引入了Logback,所以最终会得到 `LogbackLoggingSystem`。
这里有个有趣的逻辑:`SYSTEMS` 是一个静态Map,key是框架特有的类(比如 `ch.qos.logback.classic.Logger`),value是对应的工厂类。运行时按顺序检查哪个类在classpath上存在,就用哪个。也就是说,如果你把Logback从依赖中移除了,它会自动降级到Log4J2或者JDK自带的日志。**这才是真正的“无感适配”**。

**第二步:`LoggingApplicationListener` 变身“项目管家”**
这个监听器通过四个事件回调,把整个日志生命周期安排得明明白白:
- **`onApplicationStartingEvent`**:容器还没准备好环境变量时,它先创建 `LoggingSystem` 并调用 `beforeInitialize()`。这个阶段日志能力最基础,只能打印一些控制台警告。
- **`onApplicationEnvironmentPreparedEvent`**:此时 `Environment` 对象已经就绪,可以读取 `application.properties` 中的日志配置了。它调用 `initialize()`,配置日志级别、日志文件路径、Pattern等。
- **`onApplicationPreparedEvent`**:容器刷新前,把之前创建好的 `LoggingSystem` 单例注册到Spring的 `BeanFactory` 中。这样后续其他Bean就能通过依赖注入获取日志系统实例。
- **`onContextClosedEvent`**:容器关闭时,调用 `cleanUp()` 释放日志系统的资源(比如关闭文件指针)。
**第三步:Logback配置文件里的 `springProperty` 标签是怎么被支持的?**
现在问题来了:我们都知道Logback原生配置文件(logback.xml)里可以用 `
` 定义变量,但Spring Boot允许你用 `` 来直接引用Spring Environment中的属性。这个能力从哪来?
答案藏在 `LogbackLoggingSystem` 的初始化流程里。它在加载配置时,没有用Logback原生的`JoranConfigurator`,而是用了一个增强版——`SpringBootJoranConfigurator`。这个类覆写了 `addInstanceRules` 方法,往规则引擎里追加了两组自定义标签解析器:
- `configuration/springProperty` → 交给 `SpringPropertyAction` 处理。这个Action会从Spring的 `Environment` 中读取属性值,注入到Logback上下文中。
- `*/springProfile` → 交给 `SpringProfileAction` 处理,实现按Spring Profile来选择性激活Logback配置片段(类似Maven Profile的效果)。
```ja va
class SpringBootJoranConfigurator extends JoranConfigurator {
// ...
@Override
public void addInstanceRules(RuleStore rs) {
super.addInstanceRules(rs);
Environment environment = this.initializationContext.getEnvironment();
rs.addRule(new ElementSelector("configuration/springProperty"),
new SpringPropertyAction(environment));
rs.addRule(new ElementSelector("*/springProfile"),
new SpringProfileAction(this.initializationContext.getEnvironment()));
rs.addRule(new ElementSelector("*/springProfile/*"), new NOPAction());
}
}
```
注意看最后一条规则:`*/springProfile/*` 匹配的是 `` 标签内部的子元素,用 `NOPAction` 直接忽略——因为子元素的解析工作已经由 `SpringProfileAction` 在匹配父标签时处理完毕了,不需要重复解析。
整个流程走下来,你会发现Spring Boot在日志这块做得非常干净:**抽象层(LoggingSystem)+ 事件驱动 + 自定义配置解析器**,既保持了Logback的原生能力,又无缝融入了Spring的Environment体系。没有魔法,只有设计。