解決spring-boot使用logback的大坑
最近在寫(xiě)一個(gè)logback的kafka appender,無(wú)意中發(fā)現(xiàn)spring-boot在使用logback時(shí)的一個(gè)坑
用ConsoleAppender.java來(lái)舉例,假設(shè)在logback.xml中使用了該appender,那么這個(gè)類(lèi)的相關(guān)的初始化方法都會(huì)調(diào)兩次,如start()方法
打斷點(diǎn)進(jìn)行debug,第一次進(jìn)入start()方法如下:
可以看到所有的調(diào)用鏈(除了自己代碼的方法)都是logback或者slf4j相關(guān)的比較正常
當(dāng)跳過(guò)該斷點(diǎn)時(shí)又會(huì)進(jìn)入以此這個(gè)方法,看下調(diào)用鏈:
可以看到這次的初始化是由spring-boot發(fā)起的,所以這樣logback初始化一次,然后spring-boot初始化一次,一共兩次
我們現(xiàn)在可以將spring-boot的初始化去掉
debug代碼可以發(fā)現(xiàn)LoggingApplicationListener.java這個(gè)監(jiān)聽(tīng)器主要是用來(lái)初始化spring-boot的日志系統(tǒng),現(xiàn)在目的將該listener在啟動(dòng)之前去掉
spring-boot的啟動(dòng)代碼為:
new SpringApplicationBuilder(Launcher.class).application().run(args);
在SpringApplicationBuilder.java的構(gòu)造方法打斷點(diǎn)進(jìn)行跟蹤,
進(jìn)入SpringAppication.java會(huì)發(fā)現(xiàn)該類(lèi)中的代碼:
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(); }
第八行應(yīng)該就是注冊(cè)監(jiān)聽(tīng)器的地方了,繼續(xù)往下跟蹤,進(jìn)入以下方法:
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; }
繼續(xù)進(jìn)入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這個(gè)常量的值為META-INF/spring.factories,
打開(kāi)該文件可以發(fā)現(xiàn):
# 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應(yīng)該就是我們需要修改的地方了,去掉org.springframework.boot.logging.LoggingApplicationListener就可以了,我們可以在代碼里面覆蓋一份這塊代碼從而實(shí)現(xiàn)去掉這行,但是實(shí)際得再跑一遍,發(fā)現(xiàn)還是一樣初始化兩次
問(wèn)題出在
Enumeration<URL> urls = (classLoader != null ? classLoader.getResources(FACTORIES_RESOURCE_LOCATION) : ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION));
這塊代碼是將所有的META-INF/spring.factories都讀取過(guò)來(lái)了然后進(jìn)行合并,所以說(shuō)哦這個(gè)META-INF/spring.factories只能增加內(nèi)容,但是不能去掉某些內(nèi)容,沒(méi)辦法了只能在代碼初始化了所有的listener之后再將listener去掉,
具體代碼如下(啟動(dòng)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:其實(shí)log初始化兩次并無(wú)傷大雅,關(guān)鍵是遇到了問(wèn)題總是想解決下或者了解下原理
以上為個(gè)人經(jīng)驗(yàn),希望能給大家一個(gè)參考,也希望大家多多支持腳本之家。
- springboot如何使用logback-spring配置日志格式,并分環(huán)境配置
- Springboot如何使用logback實(shí)現(xiàn)多環(huán)境配置?
- SpringBoot配置logback.xml 多環(huán)境的操作步驟
- 詳解Springboot之Logback的使用學(xué)習(xí)
- 解決springboot使用logback日志出現(xiàn)LOG_PATH_IS_UNDEFINED文件夾的問(wèn)題
- springboot項(xiàng)目配置logback日志系統(tǒng)的實(shí)現(xiàn)
- spring boot使用logback日志級(jí)別打印控制操作
- springboot logback調(diào)整mybatis日志級(jí)別無(wú)效的解決
相關(guān)文章
Java基本知識(shí)點(diǎn)之變量和數(shù)據(jù)類(lèi)型
這篇文章主要給大家介紹了關(guān)于Java基本知識(shí)點(diǎn)之變量和數(shù)據(jù)類(lèi)型的相關(guān)資料,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2021-04-04java中Socket設(shè)置超時(shí)時(shí)間的兩種方式
這篇文章主要介紹了java中Socket設(shè)置超時(shí)時(shí)間的兩種方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-11-11Spring+Quartz配置定時(shí)任務(wù)實(shí)現(xiàn)代碼
這篇文章主要介紹了Spring+Quartz配置定時(shí)任務(wù)實(shí)現(xiàn)代碼,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2020-04-04Java對(duì)象轉(zhuǎn)JSON時(shí)動(dòng)態(tài)的增刪改查屬性詳解
這篇文章主要介紹了Java對(duì)象轉(zhuǎn)JSON時(shí)如何動(dòng)態(tài)的增刪改查屬性的相關(guān)資料,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2020-11-11SSH框架網(wǎng)上商城項(xiàng)目第22戰(zhàn)之銀行圖標(biāo)以及支付頁(yè)面顯示
這篇文章主要為大家詳細(xì)介紹了SSH框架網(wǎng)上商城項(xiàng)目第22戰(zhàn)之銀行圖標(biāo)以及支付頁(yè)面顯示,感興趣的小伙伴們可以參考一下2016-06-06spring boot 即時(shí)重新啟動(dòng)(熱更替)使用說(shuō)明
這篇文章主要介紹了spring boot 即時(shí)重新啟動(dòng)(熱更替)的相關(guān)資料,需要的朋友可以參考下2017-12-12Java中對(duì)象的深復(fù)制(深克隆)和淺復(fù)制(淺克?。┙榻B
這篇文章主要介紹了Java中對(duì)象的深復(fù)制(深克?。┖蜏\復(fù)制(淺克隆) ,需要的朋友可以參考下2015-03-03