spring-boot使用logback的坑

最近在写一个logback的kafka appender,无意中发现spring-boot在使用logback时的一个坑
用ConsoleAppender.java来举例,假设在logback.xml中使用了该appender,那么这个类的相关的初始化方法都会调两次,如start()方法
打断点进行debug,第一次进入start()方法如下:
spring-boot使用logback的坑_第1张图片
可以看到所有的调用链(除了自己代码的方法)都是logback或者slf4j相关的比较正常
当跳过该断点时又会进入以此这个方法,看下调用链:
spring-boot使用logback的坑_第2张图片
可以看到这次的初始化是由spring-boot发起的,所以这样logback初始化一次,然后spring-boot初始化一次,一共两次
我们现在可以将spring-boot的初始化去掉
debug代码可以发现LoggingApplicationListener.java这个监听器主要是用来初始化spring-boot的日志系统,现在目的将该listener在启动之前去掉
spring-boot的启动代码为:
new SpringApplicationBuilder(Launcher.class).application().run(args);
SpringApplicationBuilder.java的构造方法打断点进行跟踪,进入SpringAppication.java会发现该类中的代码:
private void initialize(Object[] sources) {
   if (sources != null && sources.length 0) {
      this.sources.addAll(Arrays.asList(sources));
   }
   this.webEnvironment = deduceWebEnvironment();
   setInitializers((Collection) getSpringFactoriesInstances(
         ApplicationContextInitializer.class));
   setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
   this.mainApplicationClass = deduceMainApplicationClass();
}
第八行应该就是注册监听器的地方了,继续往下跟踪,进入以下方法:
private <T> Collectionextends TgetSpringFactoriesInstances(Class<T> type,
      Class[] parameterTypesObject... args) {
   ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
   // Use names and ensure unique to protect against duplicates
   Set names = new LinkedHashSet(
         SpringFactoriesLoader.loadFactoryNames(typeclassLoader));
   List<T> instances = createSpringFactoriesInstances(typeparameterTypes,
         classLoaderargsnames);
   AnnotationAwareOrderComparator.sort(instances);
   return instances;
}
继续进入loadFactoryNames()方法,核心就在这里了
public static List loadFactoryNames(Class factoryClassClassLoader classLoader) {
   String factoryClassName = factoryClass.getName();
   try {
      Enumeration urls = (classLoader != null ? classLoader.getResources(FACTORIES_RESOURCE_LOCATION) :
            ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION));
      List result = new ArrayList();
      while (urls.hasMoreElements()) {
         URL url = urls.nextElement();
         Properties properties = PropertiesLoaderUtils.loadProperties(new UrlResource(url));
         String factoryClassNames = properties.getProperty(factoryClassName);
         result.addAll(Arrays.asList(StringUtils.commaDelimitedListToStringArray(factoryClassNames)));
      }
      return result;
   }
   catch (IOException ex) {
      throw new IllegalArgumentException("Unable to load [" + factoryClass.getName() +
            "] factories from location [" FACTORIES_RESOURCE_LOCATION "]"ex);
   }
}
FACTORIES_RESOURCE_LOCATION这个常量的值为META-INF/spring.factories,打开该文件可以发现:
# PropertySource Loaders
org.springframework.boot.env.PropertySourceLoader=\
org.springframework.boot.env.PropertiesPropertySourceLoader,\
org.springframework.boot.env.YamlPropertySourceLoader

# Run Listeners
org.springframework.boot.SpringApplicationRunListener=\
org.springframework.boot.context.event.EventPublishingRunListener

# Application Context Initializers
org.springframework.context.ApplicationContextInitializer=\
org.springframework.boot.context.ConfigurationWarningsApplicationContextInitializer,\
org.springframework.boot.context.ContextIdApplicationContextInitializer,\
org.springframework.boot.context.config.DelegatingApplicationContextInitializer,\
org.springframework.boot.context.web.ServerPortInfoApplicationContextInitializer

# Application Listeners
org.springframework.context.ApplicationListener=\
org.springframework.boot.ClearCachesApplicationListener,\
org.springframework.boot.builder.ParentContextCloserApplicationListener,\
org.springframework.boot.context.FileEncodingApplicationListener,\
org.springframework.boot.context.config.AnsiOutputApplicationListener,\
org.springframework.boot.context.config.ConfigFileApplicationListener,\
org.springframework.boot.context.config.DelegatingApplicationListener,\
org.springframework.boot.liquibase.LiquibaseServiceLocatorApplicationListener,\
org.springframework.boot.logging.ClasspathLoggingApplicationListener,\
org.springframework.boot.logging.LoggingApplicationListener

# Environment Post Processors
org.springframework.boot.env.EnvironmentPostProcessor=\
org.springframework.boot.cloud.CloudFoundryVcapEnvironmentPostProcessor,\
org.springframework.boot.env.SpringApplicationJsonEnvironmentPostProcessor

# Failure Analyzers
org.springframework.boot.diagnostics.FailureAnalyzer=\
org.springframework.boot.diagnostics.analyzer.BeanCurrentlyInCreationFailureAnalyzer,\
org.springframework.boot.diagnostics.analyzer.BeanNotOfRequiredTypeFailureAnalyzer,\
org.springframework.boot.diagnostics.analyzer.BindFailureAnalyzer,\
org.springframework.boot.diagnostics.analyzer.NoUniqueBeanDefinitionFailureAnalyzer,\
org.springframework.boot.diagnostics.analyzer.PortInUseFailureAnalyzer,\
org.springframework.boot.diagnostics.analyzer.ValidationExceptionFailureAnalyzer

# FailureAnalysisReporters
org.springframework.boot.diagnostics.FailureAnalysisReporter=\
org.springframework.boot.diagnostics.LoggingFailureAnalysisReporter

ApplicationListener应该就是我们需要修改的地方了,去掉org.springframework.boot.logging.LoggingApplicationListener就可以了,我们可以在代码里面覆盖一份这块代码从而实现去掉这行,但是实际得再跑一遍,发现还是一样初始化两次,问题出在

Enumeration urls = (classLoader != null ? classLoader.getResources(FACTORIES_RESOURCE_LOCATION) :
      ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION));

这块代码是将所有的META-INF/spring.factories都读取过来了然后进行合并,所以说哦这个META-INF/spring.factories只能增加内容,但是不能去掉某些内容,没办法了只能在代码初始化了所有的listener之后再将listener去掉,具体代码如下(启动spring-boot的main方法中):

SpringApplicationBuilder builder = new SpringApplicationBuilder(Launcher.class);
Set> listeners = builder.application().getListeners();
for (Iterator> it = listeners.iterator()it.hasNext();) {
    ApplicationListener listener = it.next();
    if (listener instanceof LoggingApplicationListener) {
        it.remove();
    }
}
builder.application().setListeners(listeners);
builder.run(args);

PS:其实log初始化两次并无伤大雅,关键是遇到了问题总是想解决下或者了解下原理

你可能感兴趣的:(spring-boot,spring,cloud,微服务,spring,cloud学习笔记)