服务器之家:专注于VPS、云服务器配置技术及软件下载分享
分类导航

PHP教程|ASP.NET教程|Java教程|ASP教程|编程技术|正则表达式|C/C++|IOS|C#|Swift|Android|VB|R语言|JavaScript|易语言|vb.net|

服务器之家 - 编程语言 - Java教程 - 解决spring-boot使用logback的大坑

解决spring-boot使用logback的大坑

2021-10-25 12:36Cumu_ Java教程

这篇文章主要介绍了解决spring-boot使用logback的大坑,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教

最近在写一个logback的kafka appender,无意中发现spring-boot在使用logback时的一个坑

用ConsoleAppender.java来举例,假设在logback.xml中使用了该appender,那么这个类的相关的初始化方法都会调两次,如start()方法

打断点进行debug,第一次进入start()方法如下:

解决spring-boot使用logback的大坑

可以看到所有的调用链(除了自己代码的方法)都是logback或者slf4j相关的比较正常

当跳过该断点时又会进入以此这个方法,看下调用链:

解决spring-boot使用logback的大坑

可以看到这次的初始化是由spring-boot发起的,所以这样logback初始化一次,然后spring-boot初始化一次,一共两次

我们现在可以将spring-boot的初始化去掉

debug代码可以发现LoggingApplicationListener.java这个监听器主要是用来初始化spring-boot的日志系统,现在目的将该listener在启动之前去掉

spring-boot的启动代码为:

 

  1. new SpringApplicationBuilder(Launcher.class).application().run(args);

 

在SpringApplicationBuilder.java的构造方法打断点进行跟踪,

进入SpringAppication.java会发现该类中的代码:

 

  1. private void initialize(Object[] sources) {
  2. if (sources != null && sources.length > 0) {
  3. this.sources.addAll(Arrays.asList(sources));
  4. }
  5. this.webEnvironment = deduceWebEnvironment();
  6. setInitializers((Collection) getSpringFactoriesInstances(
  7. ApplicationContextInitializer.class));
  8. setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
  9. this.mainApplicationClass = deduceMainApplicationClass();
  10. }

 

第八行应该就是注册监听器的地方了,继续往下跟踪,进入以下方法:

 

  1. private <T> Collection<? extends T> getSpringFactoriesInstances(Class<T> type,
  2. Class<?>[] parameterTypes, Object... args) {
  3. ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
  4. // Use names and ensure unique to protect against duplicates
  5. Set<String> names = new LinkedHashSet<String>(
  6. SpringFactoriesLoader.loadFactoryNames(type, classLoader));
  7. List<T> instances = createSpringFactoriesInstances(type, parameterTypes,
  8. classLoader, args, names);
  9. AnnotationAwareOrderComparator.sort(instances);
  10. return instances;
  11. }

 

继续进入loadFactoryNames()方法,核心就在这里了

 

  1. public static List<String> loadFactoryNames(Class<?> factoryClass, ClassLoader classLoader) {
  2. String factoryClassName = factoryClass.getName();
  3. try {
  4. Enumeration<URL> urls = (classLoader != null ? classLoader.getResources(FACTORIES_RESOURCE_LOCATION) :
  5. ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION));
  6. List<String> result = new ArrayList<String>();
  7. while (urls.hasMoreElements()) {
  8. URL url = urls.nextElement();
  9. Properties properties = PropertiesLoaderUtils.loadProperties(new UrlResource(url));
  10. String factoryClassNames = properties.getProperty(factoryClassName);
  11. result.addAll(Arrays.asList(StringUtils.commaDelimitedListToStringArray(factoryClassNames)));
  12. }
  13. return result;
  14. }
  15. catch (IOException ex) {
  16. throw new IllegalArgumentException("Unable to load [" + factoryClass.getName() +
  17. "] factories from location [" + FACTORIES_RESOURCE_LOCATION + "]", ex);
  18. }
  19. }

 

FACTORIES_RESOURCE_LOCATION这个常量的值为META-INF/spring.factories,

打开该文件可以发现:

 

  1. # PropertySource Loaders
  2. org.springframework.boot.env.PropertySourceLoader=\
  3. org.springframework.boot.env.PropertiesPropertySourceLoader,\
  4. org.springframework.boot.env.YamlPropertySourceLoader
  5. # Run Listeners
  6. org.springframework.boot.SpringApplicationRunListener=\
  7. org.springframework.boot.context.event.EventPublishingRunListener
  8. # Application Context Initializers
  9. org.springframework.context.ApplicationContextInitializer=\
  10. org.springframework.boot.context.ConfigurationWarningsApplicationContextInitializer,\
  11. org.springframework.boot.context.ContextIdApplicationContextInitializer,\
  12. org.springframework.boot.context.config.DelegatingApplicationContextInitializer,\
  13. org.springframework.boot.context.web.ServerPortInfoApplicationContextInitializer
  14. # Application Listeners
  15. org.springframework.context.ApplicationListener=\
  16. org.springframework.boot.ClearCachesApplicationListener,\
  17. org.springframework.boot.builder.ParentContextCloserApplicationListener,\
  18. org.springframework.boot.context.FileEncodingApplicationListener,\
  19. org.springframework.boot.context.config.AnsiOutputApplicationListener,\
  20. org.springframework.boot.context.config.ConfigFileApplicationListener,\
  21. org.springframework.boot.context.config.DelegatingApplicationListener,\
  22. org.springframework.boot.liquibase.LiquibaseServiceLocatorApplicationListener,\
  23. org.springframework.boot.logging.ClasspathLoggingApplicationListener,\
  24. org.springframework.boot.logging.LoggingApplicationListener
  25. # Environment Post Processors
  26. org.springframework.boot.env.EnvironmentPostProcessor=\
  27. org.springframework.boot.cloud.CloudFoundryVcapEnvironmentPostProcessor,\
  28. org.springframework.boot.env.SpringApplicationJsonEnvironmentPostProcessor
  29. # Failure Analyzers
  30. org.springframework.boot.diagnostics.FailureAnalyzer=\
  31. org.springframework.boot.diagnostics.analyzer.BeanCurrentlyInCreationFailureAnalyzer,\
  32. org.springframework.boot.diagnostics.analyzer.BeanNotOfRequiredTypeFailureAnalyzer,\
  33. org.springframework.boot.diagnostics.analyzer.BindFailureAnalyzer,\
  34. org.springframework.boot.diagnostics.analyzer.NoUniqueBeanDefinitionFailureAnalyzer,\
  35. org.springframework.boot.diagnostics.analyzer.PortInUseFailureAnalyzer,\
  36. org.springframework.boot.diagnostics.analyzer.ValidationExceptionFailureAnalyzer
  37. # FailureAnalysisReporters
  38. org.springframework.boot.diagnostics.FailureAnalysisReporter=\
  39. org.springframework.boot.diagnostics.LoggingFailureAnalysisReporter

 

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

问题出在

 

  1. Enumeration<URL> urls = (classLoader != null ? classLoader.getResources(FACTORIES_RESOURCE_LOCATION) :
  2. ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION));

 

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

具体代码如下(启动spring-boot的main方法中):

 

  1. SpringApplicationBuilder builder = new SpringApplicationBuilder(Launcher.class);
  2. Set<ApplicationListener<?>> listeners = builder.application().getListeners();
  3. for (Iterator<ApplicationListener<?>> it = listeners.iterator(); it.hasNext();) {
  4. ApplicationListener<?> listener = it.next();
  5. if (listener instanceof LoggingApplicationListener) {
  6. it.remove();
  7. }
  8. }
  9. builder.application().setListeners(listeners);
  10. builder.run(args);

 

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

以上为个人经验,希望能给大家一个参考,也希望大家多多支持服务器之家。

原文链接:https://jthink.blog.csdn.net/article/details/52513963

延伸 · 阅读

精彩推荐