應用遷移CentOS 7 后Spring啟動出錯問題復盤

一個Spring2.5的老應用從CentOS 5 遷移到CentOS 7之后啟動報錯。該問題是由同事定位解決嗜傅,本文是我之后的復盤和源碼走讀金句。
一句話結論:CentOS 7改變了Spring BeanFactory中的BeanDefinition順序讓另一個類排在最前先加載,該類的filed定義的是實現(xiàn)類而不是接口類型吕嘀,該field被@Autowired標注在自動注入時會去匹配類型违寞,由于定義的不是接口類型導致匹配失敗。

發(fā)現(xiàn)問題

啟動報錯日志如下币他,做出少量精簡:

ERROR [org.springframework.web.context.ContextLoader.initWebApplicationContext(ContextLoader.java:214)] - Context initialization failed
BeanCreationException: Error creating bean 'cacheServiceFacade': Autowiring of fields failed (CacheService) CacheServiceFacade.cacheService
BeanCreationException: Error creating bean 'cacheServiceImpl': Autowiring of fields failed (net.sf.ehcache.Cache) CacheServiceImpl.parameterCache
NoSuchBeanDefinitionException: No unique bean of type [net.sf.ehcache.Cache] is defined: Unsatisfied dependency of type [class net.sf.ehcache.Cache]: expected at least 1 matching bean
    at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor.postProcessAfterInstantiation(AutowiredAnnotationBeanPostProcessor.java:241)
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.populateBean(AbstractAutowireCapableBeanFactory.java:927)
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:462)
    ............
Caused by:
BeanCreationException: Could not autowire field (CacheService) CacheServiceFacade.cacheService
BeanCreationException: Error creating bean with name 'cacheServiceImpl': Autowiring of fields failed
BeanCreationException: Could not autowire field (net.sf.ehcache.Cache) CacheServiceImpl.parameterCache
NoSuchBeanDefinitionException: No unique bean of type [net.sf.ehcache.Cache] is defined: Unsatisfied dependency of type [class net.sf.ehcache.Cache]: expected at least 1 matching bean
    at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor$AutowiredFieldElement.inject(AutowiredAnnotationBeanPostProcessor.java:433)
    at org.springframework.beans.factory.annotation.InjectionMetadata.injectFields(InjectionMetadata.java:104)
    at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor.postProcessAfterInstantiation(AutowiredAnnotationBeanPostProcessor.java:238)
    ... 35 more
Caused by:
BeanCreationException: Error creating bean with name 'cacheServiceImpl': Autowiring of fields failed
BeanCreationException: Could not autowire field (net.sf.ehcache.Cache) CacheServiceImpl.parameterCache
NoSuchBeanDefinitionException: No unique bean of type [net.sf.ehcache.Cache] is defined: Unsatisfied dependency of type [class net.sf.ehcache.Cache]: expected at least 1 matching bean
    at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor.postProcessAfterInstantiation(AutowiredAnnotationBeanPostProcessor.java:241)
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.populateBean(AbstractAutowireCapableBeanFactory.java:927)
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:462)
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory$1.run(AbstractAutowireCapableBeanFactory.java:404)
    at java.security.AccessController.doPrivileged(Native Method)
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:375)
    at org.springframework.beans.factory.support.AbstractBeanFactory$1.getObject(AbstractBeanFactory.java:263)
    at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:170)
    at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:260)
    at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:184)
    at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:163)
    at org.springframework.beans.factory.support.DefaultListableBeanFactory.findAutowireCandidates(DefaultListableBeanFactory.java:671)
    at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveDependency(DefaultListableBeanFactory.java:611)
    at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor$AutowiredFieldElement.inject(AutowiredAnnotationBeanPostProcessor.java:410)
    ... 37 more
Caused by:
BeanCreationException: Could not autowire field (net.sf.ehcache.Cache) CacheServiceImpl.parameterCache
NoSuchBeanDefinitionException: No unique bean of type [net.sf.ehcache.Cache] is defined: Unsatisfied dependency of type [class net.sf.ehcache.Cache]: expected at least 1 matching bean
    at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor$AutowiredFieldElement.inject(AutowiredAnnotationBeanPostProcessor.java:433)
    at org.springframework.beans.factory.annotation.InjectionMetadata.injectFields(InjectionMetadata.java:104)
    at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor.postProcessAfterInstantiation(AutowiredAnnotationBeanPostProcessor.java:238)
    ... 50 more
Caused by:
NoSuchBeanDefinitionException: No unique bean of type [net.sf.ehcache.Cache] is defined: Unsatisfied dependency of type [class net.sf.ehcache.Cache]: expected at least 1 matching bean
    at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveDependency(DefaultListableBeanFactory.java:614)
    at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor$AutowiredFieldElement.inject(AutowiredAnnotationBeanPostProcessor.java:410)
    ... 52 more

分析問題

從描述信息可以看到是Spring啟動出錯坞靶,CacheServiceFacade -> (@Autowired) CacheServiceImpl -> (@Autowired) Cache 依賴關系上因為Cache創(chuàng)建失敗無法注入導致最上層的CacheServiceFacade創(chuàng)建失敗。一個常見的Bean缺失無法層級注入的錯誤蝴悉,一般是開發(fā)階段Spring Bean定義遺漏導致Bean缺失無法注入彰阴,但這里只是遷移了部署環(huán)境并沒有涉及開發(fā)改動。

首先我們閱讀錯誤日志里挖掘些有用信息拍冠,第一段堆棧說的是頂層的CacheServiceFacade創(chuàng)建失敗尿这,堆棧調用信息里doCreateBean、populateBean之后就到了AutowiredAnnotationBeanPostProcessor.postProcessAfterInstantiation庆杜,即使對Spring加載機制不熟悉也能獲知CacheServiceFacade的成員注入就是在AutowiredAnnotationBeanPostProcessor這個BeanPostProcessor的postProcessAfterInstantiation方法里完成(高版本Spring將注入邏輯移至 postProcessPropertyValues方法)射众。接著一段段的Caused by說的是層級注入失敗,明確的事情重復說晃财,可以忽略叨橱。最后一段堆棧就是錯誤源頭Cache創(chuàng)建失敗,堆棧調用信息里是從resolveDependency方法拋出的異常断盛。

所以我們從AutowiredAnnotationBeanPostProcessor.postProcessAfterInstantiation方法開始代碼走讀罗洗,查找Cache被上層依賴后,是如何被創(chuàng)建和注入钢猛,以及發(fā)生創(chuàng)建失敗的伙菜。

圖片.png

findAutowiringMetadata方法是將需要綁定的(注解有@Autowired,高版本Spring支持@Autowired/@Value/@Inject)的變量(AutowiredFieldElement)和方法(AutowiredMethodElement)收集至InjectionMetadata中命迈。

圖片.png

這里又回到AutowiredAnnotationBeanPostProcessor的內(nèi)部類AutowiredFieldElement的inject方法中贩绕,里面會使用核心類DefaultListableBeanFactory的resolveDependency(..., beanName, ...)方法來尋找注入Bean也就是Cache Bean。

圖片.png

resolveDependency方法的每一段if-else if都是在處理注入Bean是否是集合類型壶愤,非集合也就在最后的else里我們看到了啟動日志里的異常信息淑倾,位置和日志堆棧里的調用信息也一致。其實若能熟練閱讀錯誤日志征椒,可以直接從日志里找到resolveDependency方法開始走讀娇哆。
說明findAutowireCandidates(beanName, type, descriptor)方法沒有通過名稱找到注入Bean。

圖片.png

圖片.png

BeanFactoryUtils.beanNamesForTypeIncludingAncestors這個方法像是在找bean了,點進去發(fā)現(xiàn)其實又回到beanFactory用核心的getBeanNamesForType方法來找bean迂尝。

圖片.png

通過debug得到入?yún)閠ype="net.sf.ehcache.Cache" includePrototypes=true allowEagerInit=true。這個核心方法需要關注幾個點:
1)beanFactory拿出所有的BeanDefinition來遍歷核對剪芥,Spring加載機制先收集bean信息也就是BeanDefinition垄开,然后才挨個definition的去創(chuàng)建實例bean并遞歸創(chuàng)建注入依賴bean。
2)這個net.sf.ehcache.Cache是個實現(xiàn)類税肪,實現(xiàn)net.sf.ehcache.Ehcache接口溉躲。定義Cache時只需要定義Spring提供的EhCacheFactoryBean,由factoryBean的工廠方法來生成Cache Bean益兄。
<bean id="parameterCache" class="org.springframework.cache.ehcache.EhCacheFactoryBean">
<property name="cacheManager"><ref local="ehCacheManager" /></property>
<property name="cacheName"><value>parameterCache</value></property>
</bean>
所以definition中是沒有Cache的锻梳,只會有Cache的工廠類EhCacheFactoryBean,因為有定義id所以definition具體為"parameterCache"净捅。
3)遍歷definition時疑枯,beanName為definition也就是"parameterCache"(EhCacheFactoryBean),所以isFactoryBean為true蛔六。type依然為Cache所以第一個isTypeMatch里"parameterCache"(EhCacheFactoryBean)不會直接等于Cache所以matchFound為false荆永,進入if后第二個isTypeMatch就很關鍵了。注意此時beanName帶上了個"&"前綴以區(qū)分當做FactoryBean來使用国章。
再次進入isTypeMatch這個方法有點難讀用debug來跟蹤具钥,入?yún)閚ame="&parameterCache" targetType="net.sf.ehcache.Cache"。beanClass為EhCacheFactoryBean.class液兽,getTypeForFactoryBean返回得到的type=net.sf.ehcache.Ehcache.class骂删,導致return語句Cache.class.isAssignableFrom(net.sf.ehcache.Ehcache.class) == false 因為net.sf.ehcache.Cache 是 net.sf.ehcache.Ehcache 的實現(xiàn)類。就是此處return false導致關鍵的第二個isTypeMatch返回false四啰,于是創(chuàng)建Cache Bean失敗宁玫。


圖片.png

很好奇為什么getTypeForFactoryBean返回net.sf.ehcache.Ehcache,打開來看里面要創(chuàng)建EhCacheFactoryBean拟逮,但創(chuàng)建出來的EhCacheFactoryBean沒有完全初始化撬统,debug發(fā)現(xiàn)成員Ehcache cache=null,所以此時調用getObjectType方法直接返回默認值Ehcache.class了敦迄。


圖片.png

解決問題

既然Spring都假設入?yún)ype是接口類型恋追,factoryBean返回的ObjectType是接口或實現(xiàn)類,那就按編碼規(guī)范將@Autowired標注的field定義成接口類型net.sf.ehcache.Ehcache罚屋。

回顧問題

1)為什么本地及 CentOS 5 下啟動正常苦囱,而 CentOS 7 下啟動失敗脾猛?
打印 BeanFactory 中注冊的 BeanDefinition 列表撕彤,發(fā)現(xiàn)本地和 CentOS 7 下的順序不一致,猜測是文件系統(tǒng)導致掃描的class文件順序不一致:
本地:
rcacheManager | com.xxx.RcacheManager
redisProvider | com.xxx.RedisCacheProvider
redisCacheManager | null
ddsRedisManager | null
accountFacade | com.xxx.AccountFacade
adsManagerFacade | com.xxx.AdsManagerFacade
cacheServiceFacade | com.xxx.CacheServiceFacade
......

CentOS 7:
rcacheManager | com.xxx.RcacheManager
redisProvider | com.xxx.RedisCacheProvider
redisCacheManager | null
ddsRedisManager | null
cacheServiceFacade | com.xxx.CacheServiceFacade
adsManagerFacade | com.xxx.AdsManagerFacade
accountFacade | com.xxx.AccountFacade
......

CentOS 7里出錯的cacheServiceFacade出現(xiàn)在第一位,本地里cacheServiceFacade前有accountFacade和adsManagerFacade羹铅,如果在本地的Spring注釋掉accountFacade和adsManagerFacade的定義讓cacheServiceFacade排在最前面蚀狰,這樣的話本地也能復現(xiàn)同樣的錯誤。

2)為什么accountFacade也依賴了net.sf.ehcache.Cache parameterCache卻不會出錯呢职员?
AccountFacade -> (@Autowired) CustomerServiceImpl -> (@Autowired) AdminParameterServiceImpl | (AOP XML) ParameterCacheInterceptor -> (XML) Cache
<aop:config>
<aop:advisor pointcut="execution(* com.xxx.AdminParameterServiceImpl.listParameters(..))" advice-ref="parameterCacheInterceptor" order="3" />
</aop:config>
<bean id="parameterCacheInterceptor" class="com.xxx.ParameterCacheInterceptor">
<property name="cache"><ref local="parameterCache" /></property>
</bean>
parameterCacheInterceptor是通過XML直接定義注入Cache Bean麻蹋,所以不會像@Autowired自動注入會去遍歷BeanDefinition去做類型匹配。

?著作權歸作者所有,轉載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末焊切,一起剝皮案震驚了整個濱河市扮授,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌专肪,老刑警劉巖刹勃,帶你破解...
    沈念sama閱讀 218,546評論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異嚎尤,居然都是意外死亡荔仁,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,224評論 3 395
  • 文/潘曉璐 我一進店門芽死,熙熙樓的掌柜王于貴愁眉苦臉地迎上來咕晋,“玉大人,你說我怎么就攤上這事收奔≌莆兀” “怎么了?”我有些...
    開封第一講書人閱讀 164,911評論 0 354
  • 文/不壞的土叔 我叫張陵坪哄,是天一觀的道長质蕉。 經(jīng)常有香客問我,道長翩肌,這世上最難降的妖魔是什么模暗? 我笑而不...
    開封第一講書人閱讀 58,737評論 1 294
  • 正文 為了忘掉前任,我火速辦了婚禮念祭,結果婚禮上兑宇,老公的妹妹穿的比我還像新娘。我一直安慰自己粱坤,他們只是感情好隶糕,可當我...
    茶點故事閱讀 67,753評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著站玄,像睡著了一般枚驻。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上株旷,一...
    開封第一講書人閱讀 51,598評論 1 305
  • 那天再登,我揣著相機與錄音,去河邊找鬼。 笑死锉矢,一個胖子當著我的面吹牛梯嗽,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播沽损,決...
    沈念sama閱讀 40,338評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼慷荔,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了缠俺?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 39,249評論 0 276
  • 序言:老撾萬榮一對情侶失蹤贷岸,失蹤者是張志新(化名)和其女友劉穎壹士,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體偿警,經(jīng)...
    沈念sama閱讀 45,696評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡躏救,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,888評論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了螟蒸。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片盒使。...
    茶點故事閱讀 40,013評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖七嫌,靈堂內(nèi)的尸體忽然破棺而出少办,到底是詐尸還是另有隱情,我是刑警寧澤诵原,帶...
    沈念sama閱讀 35,731評論 5 346
  • 正文 年R本政府宣布英妓,位于F島的核電站,受9級特大地震影響绍赛,放射性物質發(fā)生泄漏蔓纠。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,348評論 3 330
  • 文/蒙蒙 一吗蚌、第九天 我趴在偏房一處隱蔽的房頂上張望腿倚。 院中可真熱鬧,春花似錦蚯妇、人聲如沸敷燎。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,929評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽懈叹。三九已至,卻和暖如春分扎,著一層夾襖步出監(jiān)牢的瞬間澄成,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,048評論 1 270
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留墨状,地道東北人卫漫。 一個月前我還...
    沈念sama閱讀 48,203評論 3 370
  • 正文 我出身青樓,卻偏偏與公主長得像肾砂,于是被迫代替她去往敵國和親列赎。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 44,960評論 2 355

推薦閱讀更多精彩內(nèi)容