好得很程序员自学网

<tfoot draggable='sEl'></tfoot>

解决spring-boot使用logback的大坑

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

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

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

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

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

可以看到这次的初始化是由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 > Collection <? extends T > getSpringFactoriesInstances ( Class < T > type , Class <?>[] parameterTypes , Object ... args ) { ClassLoader classLoader = Thread . currentThread (). getContextClassLoader (); // Use names and ensure unique to protect against duplicates Set < String > names = new LinkedHashSet < String >( SpringFactoriesLoader . loadFactoryNames ( type , classLoader )); List < T > instances = createSpringFactoriesInstances ( type , parameterTypes , classLoader , args , names ); AnnotationAwareOrderComparator . sort ( instances ); return instances ; }

 

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

 

public static List < String > loadFactoryNames ( Class <?> factoryClass , ClassLoader classLoader ) { String factoryClassName = factoryClass . getName (); try { Enumeration < URL > urls = ( classLoader != null ? classLoader . getResources ( FACTORIES_RESOURCE_LOCATION ) : ClassLoader . getSystemResources ( FACTORIES_RESOURCE_LOCATION )); List < String > result = new ArrayList < String >(); 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 < URL > 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 < ApplicationListener <?>> listeners = builder . application (). getListeners (); for ( Iterator < ApplicationListener <?>> it = listeners . iterator (); it . hasNext ();) { ApplicationListener <?> listener = it . next (); if ( listener instanceof LoggingApplicationListener ) { it . remove (); } } builder . application (). setListeners ( listeners ); builder . run ( args );

 

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

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

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

查看更多关于解决spring-boot使用logback的大坑的详细内容...

  阅读:16次