同事说他的应用起不来了,因为我的代码里面多了一个空格!
△Hollis, 一個對Coding有著獨特追求的人△
這是Hollis的第?333?篇原創分享
作者 l Hollis
來源 l Hollis(ID:hollischuang)
先交代一下背景,在很久之前,我曾經封裝過一個分庫分表的掃表工具——Full Table Scanner,主要實現方式是通過使用TDDL Hint + 網格任務 + Mybatis Stream Query 提升性能,降低使用成本。
為了方便使用,我把他封裝成了一個SpringBoot Starter,因為他提供了很好的快速掃表能力,所以被很多應用使用,并且一直都跑的好好的。
但是前兩天,突然有人在釘釘上找我,說是他們應用做了改造,啟動的時候報錯,報錯內容和我的這個工具有關。
因為一個空格
于是我開始幫忙排查這個問題,首先查看現場。主要報錯信息如下:
java.lang.reflect.InvocationTargetExceptionat?sun.reflect.NativeMethodAccessorImpl.invoke0(Native?Method)at?sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)at?sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)at?java.lang.reflect.Method.invoke(Method.java:498)at?com.taobao.pandora.boot.loader.MainMethodRunner.run(MainMethodRunner.java:54)at?com.taobao.pandora.boot.loader.Launcher.launch(Launcher.java:87)at?com.taobao.pandora.boot.loader.Launcher.launch(Launcher.java:50)at?com.taobao.pandora.boot.loader.SarLauncher.main(SarLauncher.java:171)Caused?by:?org.springframework.beans.factory.BeanDefinitionStoreException:?Failed?to?process?import?candidates?for?configuration?class?[com.alibaba.fin.xxx.test.CreditXXXStartUp];?nested?exception?is?java.lang.IllegalStateException:?Unable?to?read?meta-data?for?class??com.alibaba.fin.table.scanner.autoconfiguration.TDDLTableTopologyBuilderAutoConfigurationat?org.springframework.context.annotation.ConfigurationClassParser.processDeferredImportSelectors(ConfigurationClassParser.java:556)at?org.springframework.context.annotation.ConfigurationClassParser.parse(ConfigurationClassParser.java:185)at?org.springframework.context.annotation.ConfigurationClassPostProcessor.processConfigBeanDefinitions(ConfigurationClassPostProcessor.java:308)at?org.springframework.context.annotation.ConfigurationClassPostProcessor.postProcessBeanDefinitionRegistry(ConfigurationClassPostProcessor.java:228)at?org.springframework.context.support.PostProcessorRegistrationDelegate.invokeBeanDefinitionRegistryPostProcessors(PostProcessorRegistrationDelegate.java:272)at?org.springframework.context.support.PostProcessorRegistrationDelegate.invokeBeanFactoryPostProcessors(PostProcessorRegistrationDelegate.java:92)at?org.springframework.context.support.AbstractApplicationContext.invokeBeanFactoryPostProcessors(AbstractApplicationContext.java:687)at?org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:525)at?org.springframework.boot.SpringApplication.refresh(SpringApplication.java:693)at?org.springframework.boot.SpringApplication.refreshContext(SpringApplication.java:360)at?org.springframework.boot.SpringApplication.run(SpringApplication.java:303)at?org.springframework.boot.builder.SpringApplicationBuilder.run(SpringApplicationBuilder.java:134)at?com.alibaba.fin.xxx.start.CreditpayXXXBootStartUp.main(CreditpayMercuryApplicationBootStartUp.java:88)...?8?moreCaused?by:?java.lang.IllegalStateException:?Unable?to?read?meta-data?for?class??com.alibaba.fin.table.scanner.autoconfiguration.TDDLTableTopologyBuilderAutoConfigurationat?org.springframework.boot.autoconfigure.AutoConfigurationSorter$AutoConfigurationClass.getAnnotationMetadata(AutoConfigurationSorter.java:217)at?org.springframework.boot.autoconfigure.AutoConfigurationSorter$AutoConfigurationClass.getAnnotationValue(AutoConfigurationSorter.java:198)at?org.springframework.boot.autoconfigure.AutoConfigurationSorter$AutoConfigurationClass.readBefore(AutoConfigurationSorter.java:186)at?org.springframework.boot.autoconfigure.AutoConfigurationSorter$AutoConfigurationClass.<init>(AutoConfigurationSorter.java:158)at?org.springframework.boot.autoconfigure.AutoConfigurationSorter$AutoConfigurationClasses.<init>(AutoConfigurationSorter.java:115)at?org.springframework.boot.autoconfigure.AutoConfigurationSorter.getInPriorityOrder(AutoConfigurationSorter.java:57)at?org.springframework.boot.autoconfigure.AutoConfigurationImportSelector.sort(AutoConfigurationImportSelector.java:241)at?org.springframework.boot.autoconfigure.AutoConfigurationImportSelector.selectImports(AutoConfigurationImportSelector.java:98)at?org.springframework.context.annotation.ConfigurationClassParser.processDeferredImportSelectors(ConfigurationClassParser.java:547)...?20?moreCaused?by:?java.io.FileNotFoundException:?class?path?resource?[?com/alibaba/fin/table/scanner/autoconfiguration/TDDLTableTopologyBuilderAutoConfiguration.class]?cannot?be?opened?because?it?does?not?existat?org.springframework.core.io.ClassPathResource.getInputStream(ClassPathResource.java:172)at?org.springframework.core.type.classreading.SimpleMetadataReader.<init>(SimpleMetadataReader.java:50)at?org.springframework.core.type.classreading.SimpleMetadataReaderFactory.getMetadataReader(SimpleMetadataReaderFactory.java:98)at?org.springframework.boot.type.classreading.ConcurrentReferenceCachingMetadataReaderFactory.createMetadataReader(ConcurrentReferenceCachingMetadataReaderFactory.java:89)at?org.springframework.boot.type.classreading.ConcurrentReferenceCachingMetadataReaderFactory.getMetadataReader(ConcurrentReferenceCachingMetadataReaderFactory.java:76)at?org.springframework.core.type.classreading.SimpleMetadataReaderFactory.getMetadataReader(SimpleMetadataReaderFactory.java:93)at?org.springframework.boot.autoconfigure.AutoConfigurationSorter$AutoConfigurationClass.getAnnotationMetadata(AutoConfigurationSorter.java:213)...?28?more于是我開始幫忙排查,錯誤信息很長,其中最關鍵的信息就是這句:
Caused?by:?java.io.FileNotFoundException:? class?path?resource?[?com/alibaba/fin/table/scanner/autoconfiguration/TDDLTableTopologyBuilderAutoConfiguration.class]?cannot?be?opened?because?it?does?not?exist看到這個報錯,最開始的反應以為是jar包沖突了。
開始進行maven的依賴分析,發現并沒有什么沖突的。
為了定位問題到底和這個Starter是否有關,我們先把關于這個工具的依賴排除掉了,之后就可以正常啟動了。
基本確定一定是和這個工具的引入有關。于是開始排查這個包到底做了什么會導致應用啟動失敗。
排查的細節就不多說了,最終經過多方排查,我們最終發現這個問題竟然和一個空格有關。
仔細看上面的報錯信息,提示找不到
[?com/alibaba/fin/table/scanner/autoconfiguration/TDDLTableTopologyBuilderAutoConfiguration.class]?有沒有發現,在類前面多了個空格?
后來發現,錯誤信息中下面這句也更為關鍵:
Caused?by:?java.lang.IllegalStateException:? Unable?to?read?meta-data?for?class??com.alibaba.fin.table.scanner.autoconfiguration.TDDLTableTopologyBuilderAutoConfiguration仔細觀察發現,這里的報錯提示中TDDLTableTopologyBuilderAutoConfiguration類前面也多了個空格?
大家都知道,應用在啟動的時候,會掃描classpath下面的所有spring.factories文件,并加載其中的自動配置信息。如果在加載初始化的時候遇到什么特殊的情況,應用就會啟動失敗。
而上面報錯的TDDLTableTopologyBuilderAutoConfiguration正是我的starter的自動配置文件。
于是打開我的spring.factories,發現在TDDLTableTopologyBuilderAutoConfiguration 的配置前面,真的就多了一個空格:
為了快速定位是不是這個問題,我先把這個空格刪除,然后很快幫同事打了一個SNAPSHOT的包。
同事引入了我的新包之后,重啟應用,發現問題解決了。
到這個地方,基本定位到了問題,就是因為我的spring.factories文件中多了一個空格,導致應用啟動失敗了。
但是,為什么這個問題之前沒有發現呢?這次這位同事又是改了什么東西導致問題出現了呢?
問題原因分析
根據報錯內容的堆棧,我們先定位到org.springframework.core.io.ClassPathResource 這個類在讀取文件的時候失敗了。
這個類是Spring中的一個文件讀取的相關類。首先第一個想到是不是和Spring的版本有關。
于是我們快速到啟動失敗的應用的日常環境和線上環境分別看了一下打包之后的Spring的版本。
日常啟動失敗的機器中,打包后引用的Spring版本為:
$ls|grep?spring-core spring-core-4.3.13.RELEASE.jar線上正常運行的機器中,打包后引用的Spring版本為:
$ls|grep?spring-core spring-core-4.3.22.RELEASE.jar果然,這貨是因為Spring的版本不同,和這位同學溝通后,得知他是改動了應用里面的一個包的依賴關系,那肯定是因為這個,導致了他的Spring版本仲裁關系發生了改變,從4.3.22.RELEASE降到了4.3.13.RELEASE。
先讓這位同學去排查他的Spring仲裁的問題了,我繼續分析,因為Spring版本不同,并且高版本沒有這個問題,說明可能是一個Spring的bug,于是我嘗試求助Google。
在這之前,同事的電腦瀏覽器默認是百度,我先用百度查詢過,但是沒查到,于是改用Google。
使用關鍵詞:"spring.factories space?trim"(這幾個關鍵詞很關鍵,spring.factories space是問題的根源,而且我覺得這一定是個被修復的bug,而且修復方式是用了trim)
很快就找到了一個相關內容,早在2018年有網友反饋過這個問題:
在這個ISSUE中,有官方人員給出了回復,這個問題在SPR-17413(https://jira.spring.io/browse/SPR-17413?redirect=false )中被修復:
其中提到,在4.3.21, 5.0.11, 5.1.2等版本中被修復,而我們的現象也確實是,線上正常運營的版本使用的是4.3.22。
順藤摸瓜,查看一下Spring關于本次Bug修復的提交記錄:
-w999
并且在他們的測試使用的spring-factories中也增加了空格,用于測試。
上面只是截圖了一部分,其實這次bug修復,官方共提交了3次,涉及到43行代碼的改動。他們在多處相關操作的地方,做了trim。
總結
本次問題的發生主要是因為在舊版本的Spring中,如果引入的starter中的spring.factories中配置內容中包含空格,會導致解析失敗,進而導致應用啟動失敗。
這是一個已經被報告出來,并且也被解決了的bug。在SPR-17413中已經被修復,在高于4.3.21的版本中被修復。
其實,這篇文章的內容并不復雜,只是介紹了一個Bug,本來只是記錄一下,并沒什么可講的。
但是,這個問題的排查過程還是值得學習的,有這樣幾個關鍵點:
1、從報錯信息本身入手。
2、仔細看報錯內容,不要忽略任何一個細節,哪怕是一個空格。
3、學會問題猜測,猜測后再驗證。
4、請用Google!
參考資料:
https://github.com/spring-projects/spring-framework/issues/21946
https://github.com/spring-projects/spring-boot/issues/14903
https://jira.spring.io/browse/SPR-17413?redirect=false
往期推薦 別瞎學了,這幾門語言要被淘汰了! 中國編程第一人,一人抵一城! 再見 Win10系統!下一代操作系統要來了!!直面Java第343期:為什么TOMCAT要破壞雙親委派深入并發第013期:拓展synchronized——鎖優化如果你喜歡本文,請長按二維碼,關注?Hollis.轉發至朋友圈,是對我最大的支持。點個?在看? 喜歡是一種感覺 在看是一種支持 ↘↘↘總結
以上是生活随笔為你收集整理的同事说他的应用起不来了,因为我的代码里面多了一个空格!的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 程序员到底会不会修电脑?
- 下一篇: redis缓存穿透-解决方案