先聲明本人并不是標(biāo)題黨,如果看了本篇文章并且認(rèn)為沒有得到任何收獲,請您隨便留言罵我,本人絕不還口,已經(jīng)對springboot了如指掌大大神,求放過!不BB了,直接上代碼,請各位在自己的springboot項目隨...
先聲明本人并不是標(biāo)題黨,如果看了本篇文章并且認(rèn)為沒有得到任何收獲,請您隨便留言罵我,本人絕不還口,已經(jīng)對springboot了如指掌大大神,求放過!
不BB了,直接上代碼,請各位在自己的springboot項目隨便一個包下復(fù)制進(jìn)去如下類(不要修改什么東西),如果你的springboot還能站起來算我輸!
@Component
public class Environment {
}
運行springboot的啟動類會報如下錯誤,然后你刪除這個類,你的springboot又能健步如飛了,你可能就會懷疑人生了,這代碼有毒。先說明我的springboot是2.1.7.RELEASE,我也試了最新的2.2,報錯基本一致!
2019-11-02 00:42:46.181 INFO 13568 --- [ main] com.rdpaas.platform.demo.RunApplication : Starting RunApplication on DESKTOP-9KL4U5L with PID 13568 (E:\project2018\platform\demo\target\classes started by 49519 in E:\project2018\platform)
2019-11-02 00:42:46.183 INFO 13568 --- [ main] com.rdpaas.platform.demo.RunApplication : No active profile set, falling back to default profiles: default
2019-11-02 00:42:48.490 WARN 13568 --- [ main] ConfigServletWebServerApplicationContext : Exception encountered during context initialization - cancelling refresh attempt: org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'methodValidationPostProcessor' defined in class path resource [org/springframework/boot/autoconfigure/validation/ValidationAutoConfiguration.class]: Unsatisfied dependency expressed through method 'methodValidationPostProcessor' parameter 0; nested exception is org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type 'org.springframework.core.env.Environment' available: expected at least 1 bean which qualifies as autowire candidate. Dependency annotations: {}
2019-11-02 00:42:48.499 INFO 13568 --- [ main] ConditionEvaluationReportLoggingListener :
Error starting ApplicationContext. To display the conditions report re-run your application with 'debug' enabled.
2019-11-02 00:42:48.615 ERROR 13568 --- [ main] o.s.b.d.LoggingFailureAnalysisReporter :
***************************
APPLICATION FAILED TO START
***************************
Description:
Parameter 0 of method methodValidationPostProcessor in org.springframework.boot.autoconfigure.validation.ValidationAutoConfiguration required a bean of type 'org.springframework.core.env.Environment' that could not be found.
Action:
Consider defining a bean of type 'org.springframework.core.env.Environment' in your configuration.
如上很普通,誰都可能加上的類,就這樣一個簡單的類,居然可以直接導(dǎo)致springboot站不起來了,如果你認(rèn)命了,其實也很好解決,你可能會換個名字試試,或者你壓根就不會用到這個類,或者是你給@Component("env"),加個別名,可能就碰巧解決了這個問題,那么這時候你可能會當(dāng)成springboot已經(jīng)規(guī)定了你不能使用關(guān)鍵字environment作為bean的名稱,那么這個問題就變得一文不值了,因為你已經(jīng)認(rèn)命了,不讓我用我不用就行了,以后一輩子都不用這個類名就好了。
眼不見心不煩,我用簡稱Env還來的省事點。不過我個人認(rèn)為我們遇到難題應(yīng)該迎難而上,不能隨便認(rèn)命,我們都是驕傲的程序員。
應(yīng)該抱著希望遇到難題的心態(tài),積極去面對難題,多解決一些疑難雜癥,用知識和經(jīng)驗武裝自己,努力成長,走上人生巔峰!如果看到這里覺得不認(rèn)命的請跟著我一起看看這個問題到底為啥會出現(xiàn)吧!
接下來我們一步步來找到問題的根源,為啥用了這個類,springboot就不舉了?首先我們從啟動的錯誤提示中找到唯一的關(guān)鍵信息:method methodValidationPostProcessor in org.springframework.boot.autoconfigure.validation.ValidationAutoConfiguration required a bean of type 'org.springframework.core.env.Environment' that could not be found。
從這句話可以看出來在:org.springframework.boot.autoconfigure.validation.ValidationAutoConfiguration這個類中的這個方法:methodValidationPostProcessor 中需要一個:org.springframework.core.env.Environment類的對象作為參數(shù),但是他找不到,看這個名字和我們自己定義的一樣,先在idea中找到如上類的methodValidationPostProcessor方法的源碼所在:
為了驗證圖中的猜想,由于我這不是源碼編譯的,所以只能自己模仿這個類同樣使用@Bean修飾一個方法看看是不是里面的參數(shù)都是完全按照參數(shù)名稱注入的(可以先注釋掉之前的Environment類排除那個類的影響),如下
package com.rdpaas.platform.demo.env;
import org.springframework.stereotype.Component;
/**
* 用作測試的bean
* @author: rongdi
* @date: 2019-11-2 0:12
*/
@Component
public class TestBean {
}
package com.rdpaas.platform.demo.env;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* 模仿org.springframework.boot.autoconfigure.validation.ValidationAutoConfiguration
* 使用@Configuration注解,并且提供一個static的@Bean修飾的方法
* @author: rongdi
* @date: 2019-11-2 0:11
*/
@Configuration
public class Config {
@Bean
public static TestBean create(TestBean tb) {
System.out.println(tb);
return tb;
}
}
如上我們使用默認(rèn)的beanName為testBean的bean,然后Config類中注入的名稱是tb運行springboot發(fā)現(xiàn)可以正常打印出tb對象,說明名稱不一致同樣可以注入成功,所i有我們大概可以排除之前的猜想,想想頂頂大名的springboot也不可能這么low逼吧!
擴展:11張流程圖幫你搞定 Spring Bean 生命周期
之前的猜測被推翻,我們只能老老實實的使用debug一步步從springboot的入口一步步跟蹤進(jìn)去看看到底啥時候開始報錯的,這一步如果不熟悉spring代碼的一定要耐心一步步找找,如下
之前對spring底層源碼有一定了解的應(yīng)該知道spring是先把那些注解和xml聲明的類加載到一個map里然后再進(jìn)行初始化的,這個map就是beanDefinitionMap,一步步斷點到spring最核心的方法refresh中
當(dāng)執(zhí)行到上圖藍(lán)色位置時,也就是執(zhí)行完invokeBeanFactoryPostProcessors(beanFactory)方法后,當(dāng)前beanFactory里的beanDefinitionMap對象中找到了我們聲明的environment對象的身影,如下
斷點的過程中發(fā)現(xiàn)代碼太多要是一步步找過去,很容易就放棄,所以我們再從上面的錯誤日志找找有用信息,然后通過全局搜索看看到底時哪里報出的錯誤,如通過報錯里的警告信息:expected at least 1 bean which qualifies as autowire candidate.Dependency annotations直接使用全局搜索(前提時先斷點跑一邊,然后根據(jù)idea提示下載好spring的源代碼)
點擊上述方法后如下一如既往的打個斷點重新跑一邊看看
這時候可以把斷點打在if那行再進(jìn)去看看是啥情況導(dǎo)致進(jìn)入了這里,然后我們可以確定只要是if返回了true,就必然會導(dǎo)致報錯,然后我們注釋掉自己的Environment類看看還能否進(jìn)入到這里,通過注釋和不注釋的對比我們發(fā)現(xiàn)兩種情況斷點之后有如下差別
所以問題的關(guān)鍵就是加入了自己的Environment類導(dǎo)致matchingBeans的map為空而產(chǎn)生了本例中的報錯信息。
接下來我們?yōu)榱苏{(diào)試的效率,在每個出現(xiàn)beanName參數(shù)的方法打斷點都使用這個條件斷點,現(xiàn)在問題就回歸到為啥加上了自己的Environment類后給matchingBeans提供數(shù)據(jù)的方法findAutowireCandidates為空了。一如既往的條件斷點打到里面
使用同樣的方式在如下方法也加上條件斷點,再次重復(fù)執(zhí)行斷點直到進(jìn)去如下
耐心的再用如上同樣方式進(jìn)入這個方法,這里由于有多個類請使用F5(斷點進(jìn)入方法,可能快捷鍵不一樣)
從上看出,剛進(jìn)去循環(huán)的數(shù)組中明顯有environment,但是結(jié)果為啥就成了空數(shù)組,進(jìn)一步斷點發(fā)現(xiàn)
對比以上兩個結(jié)果,很明顯當(dāng)我們自己添加了Environment類后,singletonObjects肯定有一個移除操作,然后我們找到所有singletonObjects.remove()的地方打一個條件斷點:beanName.equals("environment"),很明顯從邏輯上看,只要springboot不是全部清空,必然會有一個 remove("environment")才能解釋以上兩者的差別。擴展:SpringBoot內(nèi)容聚合
然后我們再在singletonObjects.put()相關(guān)的方法都打上同樣的條件斷點,放心大膽的繼續(xù)重新斷點執(zhí)行一遍,第一次進(jìn)入斷點如下
上圖如果執(zhí)行過addSingleton方法后this.singletonObjects中確實會放入以environment為key,以spring的StandardServetEnvironment為value的鍵值對進(jìn)去,這里就不截圖了,免得又要重新跑一次斷點,直接點擊左邊調(diào)用棧那個679行后如下:
這里可以先記錄下左邊環(huán)境對象到底是在spring最重要的refresh方法的那一步
根據(jù)上面得出的結(jié)論,之所以報錯最根本的原因就是這個singletonObjects找不到這個environment了,而這里有,所以肯定有地方刪除了這個key,因為這個map看起來如此重要,spring不會無緣無故直接clear吧,所以只要找到唯一的刪除key的方式singletonObjects.remove(),并打上上面說的條件斷點,這一點上面其實說過了,那我們繼續(xù)跑斷點,直到找到在哪刪除了這個key
其實復(fù)盤一下整個調(diào)試過程,發(fā)現(xiàn)其實源頭如下
其實我也不知道這算不算是springboot的bug,還是其實只是一個關(guān)鍵字的限定,因為最終解釋權(quán)不在于我,就像mybatis中的xml里大于符號要用>不然別人根本解析不了,從這一點來說mybatis使用xml存放sql實際上限制了我們使用大于小于等等這些符號的權(quán)力,只能用轉(zhuǎn)義字符類似別名的東西替代。
其實這里也是類似,也可以理解成人家系統(tǒng)需要,你要用這個請改個名字或者取個別名,比如@Component("env)。不過我還是希望springboot能還我們使用單詞的自由,希望英文好的朋友可以發(fā)發(fā)郵件讓springboot團隊考慮下,哈哈!
最后來個篇中總結(jié):
1) 從文筆上來說,一如既往的沒有文筆,請各位大大海涵,真的盡力了,奈何胸?zé)o點墨!
2) 從排版來說,一如既往的沒有排版,我是個純技術(shù)人,這些花里胡哨的東西,真的一點不會,同樣請各位包涵
3) 從知識點來說,其實這篇博客主要是給小白們分享一下看源碼的技巧和基本的調(diào)試能力,還有遇到問題的處理態(tài)度。首先從這篇文章中應(yīng)該能清晰的get到逆向思考一步步找的問題的方法,其次應(yīng)該能獲取到一些斷點調(diào)試源碼的技巧,最后也應(yīng)該能學(xué)會方法調(diào)用棧的作用。其實懂這三點基本就夠了,spring這些源碼是否看過也不會影響你最終能找到這個問題的根源這一結(jié)果,最多會影響你找到根源的時間。
4) 從用心程度來說,這篇博客自認(rèn)為是足夠用心,周五晚上從下班回家一邊一步步斷點一遍寫這篇博客,直到凌晨三點多才沖忙洗洗睡。文章里基本上把我知道的關(guān)于這個知識點的所有東西通過清晰的圖文方式一步步展現(xiàn),本人熱愛技術(shù),也喜歡分享技術(shù),希望與廣大程序猿們相濡以沫,共同進(jìn)步!
5) 從文章質(zhì)量來說,對大牛一文不值,對小白有一定幫助,希望大牛們多多包涵,不要噴我,有錯誤之處請多多指正。
來源:本文內(nèi)容搜集或轉(zhuǎn)自各大網(wǎng)絡(luò)平臺,并已注明來源、出處,如果轉(zhuǎn)載侵犯您的版權(quán)或非授權(quán)發(fā)布,請聯(lián)系小編,我們會及時審核處理。
聲明:江蘇教育黃頁對文中觀點保持中立,對所包含內(nèi)容的準(zhǔn)確性、可靠性或者完整性不提供任何明示或暗示的保證,不對文章觀點負(fù)責(zé),僅作分享之用,文章版權(quán)及插圖屬于原作者。
Copyright©2013-2025 ?JSedu114 All Rights Reserved. 江蘇教育信息綜合發(fā)布查詢平臺保留所有權(quán)利
蘇公網(wǎng)安備32010402000125
蘇ICP備14051488號-3技術(shù)支持:南京博盛藍(lán)睿網(wǎng)絡(luò)科技有限公司
南京思必達(dá)教育科技有限公司版權(quán)所有 百度統(tǒng)計