上两篇在实践中讲述了如何在SpringBoot中使用Log4j , 这里描述下为什么是这么使用的,知其然,知其所以然。以及SpringBoot中几个常用在其他中间件的监听器Listener. 包括Dubbo的WelcomeLogoApplicationListener, Nacos的StartingSpringApplicationRunListener,SpringBoot自带的日志监听器LoggingApplicationListener
**
监听与事件
SpringBoot的事件主要包括如下, 顺序依次发布:
- starting 开始启动事件 SpringBoot启动时发布该事件,此时Spring上下文尚未建立
- environmentPrepared 环境加载完成事件 加载了Env之后发布该事件, 会在事件中读取properties, 会在profile中描述,此时Spring上下文尚未建立
- contextPrepared context实例化并且设置一些属性后发布该事件,此时尚未refresh上下文
- contextLoaded 加载一些配置后发布,目前我的了解看是不太实用.
- started context refresh 后使用,此时Spring已经初始化完成,可以接受流量进入了.
- running 初始化完成后发布该事件. 如果在started事件中出现移除不会跑到这一步.
- failed SpringBoot启动失败发布该事件.
Log加载
加载的日志系统主要设置2个点,并且一定要在上下文启动之前日志系统要启动成功,不然日志就存在丢失的可能,这在业务中是不可以接受的.
- 使用那个日志系统, log4j、log4j2、logback等等
- 日志系统的初始化
- log4j的初始化, DOMConfigurator.configure(log4j.xml);
- log4j2的初始化, 详见 Log4J2LoggingSystem.java
- logback的初始化 LogbackLoggingSystem.java
所以必须在environmentPrepared之后并且在contextPrepared之前初始化日志系统。
@Override
public void onApplicationEvent(ApplicationEvent event) {
if (event instanceof ApplicationStartingEvent) {
onApplicationStartingEvent((ApplicationStartingEvent) event);
}
else if (event instanceof ApplicationEnvironmentPreparedEvent) {
onApplicationEnvironmentPreparedEvent((ApplicationEnvironmentPreparedEvent) event);
}
else if (event instanceof ApplicationPreparedEvent) {
onApplicationPreparedEvent((ApplicationPreparedEvent) event);
}
else if (event instanceof ContextClosedEvent
&& ((ContextClosedEvent) event).getApplicationContext().getParent() == null) {
onContextClosedEvent();
}
else if (event instanceof ApplicationFailedEvent) {
onApplicationFailedEvent();
}
}
log4j的初始化
LoggingApplicationListener#onApplicationEnvironmentPreparedEvent
LoggingApplicationListener#initialize
LoggingApplicationListener#initializeSystem
LoggingSystem#initialize
后边的就是找到配置,初始化配置即可实现日志的初始化.
额外的收获-profile
有经验的开发者都知道通过 mvn clean compile package -Pprod 是可以指定 spring.profiles.active=prod 的,这样SpringBoot在加载key/value就会优先加载application-prod.properties中的数据. 原因如下
SpringBoot在发布environmentPrepared事件时,会在ConfigFileApplicationListener中读取application.properties 和 application-prod.properties 并且填充到propertySources中去,读取的时候按照顺序依次读取,而application-prod.properties排列在application.properties的前边,所以会优先加载,同时,我们的-D参数是配置application-prod.properties的前边的,所以你在properties中配置的server.port没有-Dserver.port的权重高.
读取properties
@Nullable
protected <T> T getProperty(String key, Class<T> targetValueType, boolean resolveNestedPlaceholders) {
if (this.propertySources != null) {
for (PropertySource<?> propertySource : this.propertySources) {
if (logger.isTraceEnabled()) {
logger.trace("Searching for key '" + key + "' in PropertySource '" +
propertySource.getName() + "'");
}
Object value = propertySource.getProperty(key);
if (value != null) {
if (resolveNestedPlaceholders && value instanceof String) {
value = resolveNestedPlaceholders((String) value);
}
logKeyFound(key, propertySource, value);
return convertValueIfNecessary(value, targetValueType);
}
}
}
if (logger.isTraceEnabled()) {
logger.trace("Could not find key '" + key + "' in any property source");
}
return null;
}
总结
平时多留意一点,用的时候就会轻松一点.