上两篇在实践中讲述了如何在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个点,并且一定要在上下文启动之前日志系统要启动成功,不然日志就存在丢失的可能,这在业务中是不可以接受的.

所以必须在environmentPrepared之后并且在contextPrepared之前初始化日志系统。

  1. @Override
  2. public void onApplicationEvent(ApplicationEvent event) {
  3. if (event instanceof ApplicationStartingEvent) {
  4. onApplicationStartingEvent((ApplicationStartingEvent) event);
  5. }
  6. else if (event instanceof ApplicationEnvironmentPreparedEvent) {
  7. onApplicationEnvironmentPreparedEvent((ApplicationEnvironmentPreparedEvent) event);
  8. }
  9. else if (event instanceof ApplicationPreparedEvent) {
  10. onApplicationPreparedEvent((ApplicationPreparedEvent) event);
  11. }
  12. else if (event instanceof ContextClosedEvent
  13. && ((ContextClosedEvent) event).getApplicationContext().getParent() == null) {
  14. onContextClosedEvent();
  15. }
  16. else if (event instanceof ApplicationFailedEvent) {
  17. onApplicationFailedEvent();
  18. }
  19. }

log4j的初始化

  1. LoggingApplicationListener#onApplicationEnvironmentPreparedEvent
  2. LoggingApplicationListener#initialize
  3. LoggingApplicationListener#initializeSystem
  4. 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

  1. @Nullable
  2. protected <T> T getProperty(String key, Class<T> targetValueType, boolean resolveNestedPlaceholders) {
  3. if (this.propertySources != null) {
  4. for (PropertySource<?> propertySource : this.propertySources) {
  5. if (logger.isTraceEnabled()) {
  6. logger.trace("Searching for key '" + key + "' in PropertySource '" +
  7. propertySource.getName() + "'");
  8. }
  9. Object value = propertySource.getProperty(key);
  10. if (value != null) {
  11. if (resolveNestedPlaceholders && value instanceof String) {
  12. value = resolveNestedPlaceholders((String) value);
  13. }
  14. logKeyFound(key, propertySource, value);
  15. return convertValueIfNecessary(value, targetValueType);
  16. }
  17. }
  18. }
  19. if (logger.isTraceEnabled()) {
  20. logger.trace("Could not find key '" + key + "' in any property source");
  21. }
  22. return null;
  23. }

总结

平时多留意一点,用的时候就会轻松一点.