先聲明本人并不是標題黨,如果看了本篇文章并且認為沒有得到任何收獲,請您隨便留言罵我,本人絕不還口,已經對springboot了如指掌大大神,求放過!不BB了,直接上代碼,請各位在自己的springboot項目隨...
先聲明本人并不是標題黨,如果看了本篇文章并且認為沒有得到任何收獲,請您隨便留言罵我,本人絕不還口,已經對springboot了如指掌大大神,求放過!
不BB了,直接上代碼,請各位在自己的springboot項目隨便一個包下復制進去如下類(不要修改什么東西),如果你的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.
如上很普通,誰都可能加上的類,就這樣一個簡單的類,居然可以直接導致springboot站不起來了,如果你認命了,其實也很好解決,你可能會換個名字試試,或者你壓根就不會用到這個類,或者是你給@Component("env"),加個別名,可能就碰巧解決了這個問題,那么這時候你可能會當成springboot已經規定了你不能使用關鍵字environment作為bean的名稱,那么這個問題就變得一文不值了,因為你已經認命了,不讓我用我不用就行了,以后一輩子都不用這個類名就好了。
眼不見心不煩,我用簡稱Env還來的省事點。不過我個人認為我們遇到難題應該迎難而上,不能隨便認命,我們都是驕傲的程序員。
應該抱著希望遇到難題的心態,積極去面對難題,多解決一些疑難雜癥,用知識和經驗武裝自己,努力成長,走上人生巔峰!如果看到這里覺得不認命的請跟著我一起看看這個問題到底為啥會出現吧!
接下來我們一步步來找到問題的根源,為啥用了這個類,springboot就不舉了?首先我們從啟動的錯誤提示中找到唯一的關鍵信息: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類的對象作為參數,但是他找不到,看這個名字和我們自己定義的一樣,先在idea中找到如上類的methodValidationPostProcessor方法的源碼所在:
為了驗證圖中的猜想,由于我這不是源碼編譯的,所以只能自己模仿這個類同樣使用@Bean修飾一個方法看看是不是里面的參數都是完全按照參數名稱注入的(可以先注釋掉之前的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;
}
}
如上我們使用默認的beanName為testBean的bean,然后Config類中注入的名稱是tb運行springboot發現可以正常打印出tb對象,說明名稱不一致同樣可以注入成功,所i有我們大概可以排除之前的猜想,想想頂頂大名的springboot也不可能這么low逼吧!
擴展:11張流程圖幫你搞定 Spring Bean 生命周期
之前的猜測被推翻,我們只能老老實實的使用debug一步步從springboot的入口一步步跟蹤進去看看到底啥時候開始報錯的,這一步如果不熟悉spring代碼的一定要耐心一步步找找,如下
之前對spring底層源碼有一定了解的應該知道spring是先把那些注解和xml聲明的類加載到一個map里然后再進行初始化的,這個map就是beanDefinitionMap,一步步斷點到spring最核心的方法refresh中
當執行到上圖藍色位置時,也就是執行完invokeBeanFactoryPostProcessors(beanFactory)方法后,當前beanFactory里的beanDefinitionMap對象中找到了我們聲明的environment對象的身影,如下
斷點的過程中發現代碼太多要是一步步找過去,很容易就放棄,所以我們再從上面的錯誤日志找找有用信息,然后通過全局搜索看看到底時哪里報出的錯誤,如通過報錯里的警告信息:expected at least 1 bean which qualifies as autowire candidate.Dependency annotations直接使用全局搜索(前提時先斷點跑一邊,然后根據idea提示下載好spring的源代碼)
點擊上述方法后如下一如既往的打個斷點重新跑一邊看看
這時候可以把斷點打在if那行再進去看看是啥情況導致進入了這里,然后我們可以確定只要是if返回了true,就必然會導致報錯,然后我們注釋掉自己的Environment類看看還能否進入到這里,通過注釋和不注釋的對比我們發現兩種情況斷點之后有如下差別
所以問題的關鍵就是加入了自己的Environment類導致matchingBeans的map為空而產生了本例中的報錯信息。
接下來我們為了調試的效率,在每個出現beanName參數的方法打斷點都使用這個條件斷點,現在問題就回歸到為啥加上了自己的Environment類后給matchingBeans提供數據的方法findAutowireCandidates為空了。一如既往的條件斷點打到里面
使用同樣的方式在如下方法也加上條件斷點,再次重復執行斷點直到進去如下
耐心的再用如上同樣方式進入這個方法,這里由于有多個類請使用F5(斷點進入方法,可能快捷鍵不一樣)
從上看出,剛進去循環的數組中明顯有environment,但是結果為啥就成了空數組,進一步斷點發現
對比以上兩個結果,很明顯當我們自己添加了Environment類后,singletonObjects肯定有一個移除操作,然后我們找到所有singletonObjects.remove()的地方打一個條件斷點:beanName.equals("environment"),很明顯從邏輯上看,只要springboot不是全部清空,必然會有一個 remove("environment")才能解釋以上兩者的差別。擴展:SpringBoot內容聚合
然后我們再在singletonObjects.put()相關的方法都打上同樣的條件斷點,放心大膽的繼續重新斷點執行一遍,第一次進入斷點如下
上圖如果執行過addSingleton方法后this.singletonObjects中確實會放入以environment為key,以spring的StandardServetEnvironment為value的鍵值對進去,這里就不截圖了,免得又要重新跑一次斷點,直接點擊左邊調用棧那個679行后如下:
這里可以先記錄下左邊環境對象到底是在spring最重要的refresh方法的那一步
根據上面得出的結論,之所以報錯最根本的原因就是這個singletonObjects找不到這個environment了,而這里有,所以肯定有地方刪除了這個key,因為這個map看起來如此重要,spring不會無緣無故直接clear吧,所以只要找到唯一的刪除key的方式singletonObjects.remove(),并打上上面說的條件斷點,這一點上面其實說過了,那我們繼續跑斷點,直到找到在哪刪除了這個key
其實復盤一下整個調試過程,發現其實源頭如下
其實我也不知道這算不算是springboot的bug,還是其實只是一個關鍵字的限定,因為最終解釋權不在于我,就像mybatis中的xml里大于符號要用>不然別人根本解析不了,從這一點來說mybatis使用xml存放sql實際上限制了我們使用大于小于等等這些符號的權力,只能用轉義字符類似別名的東西替代。
其實這里也是類似,也可以理解成人家系統需要,你要用這個請改個名字或者取個別名,比如@Component("env)。不過我還是希望springboot能還我們使用單詞的自由,希望英文好的朋友可以發發郵件讓springboot團隊考慮下,哈哈!
最后來個篇中總結:
1) 從文筆上來說,一如既往的沒有文筆,請各位大大海涵,真的盡力了,奈何胸無點墨!
2) 從排版來說,一如既往的沒有排版,我是個純技術人,這些花里胡哨的東西,真的一點不會,同樣請各位包涵
3) 從知識點來說,其實這篇博客主要是給小白們分享一下看源碼的技巧和基本的調試能力,還有遇到問題的處理態度。首先從這篇文章中應該能清晰的get到逆向思考一步步找的問題的方法,其次應該能獲取到一些斷點調試源碼的技巧,最后也應該能學會方法調用棧的作用。其實懂這三點基本就夠了,spring這些源碼是否看過也不會影響你最終能找到這個問題的根源這一結果,最多會影響你找到根源的時間。
4) 從用心程度來說,這篇博客自認為是足夠用心,周五晚上從下班回家一邊一步步斷點一遍寫這篇博客,直到凌晨三點多才沖忙洗洗睡。文章里基本上把我知道的關于這個知識點的所有東西通過清晰的圖文方式一步步展現,本人熱愛技術,也喜歡分享技術,希望與廣大程序猿們相濡以沫,共同進步!
5) 從文章質量來說,對大牛一文不值,對小白有一定幫助,希望大牛們多多包涵,不要噴我,有錯誤之處請多多指正。
來源:本文內容搜集或轉自各大網絡平臺,并已注明來源、出處,如果轉載侵犯您的版權或非授權發布,請聯系小編,我們會及時審核處理。
聲明:江蘇教育黃頁對文中觀點保持中立,對所包含內容的準確性、可靠性或者完整性不提供任何明示或暗示的保證,不對文章觀點負責,僅作分享之用,文章版權及插圖屬于原作者。
Copyright?2013-2024 JSedu114 All Rights Reserved. 江蘇教育信息綜合發布查詢平臺保留所有權利
蘇公網安備32010402000125
蘇ICP備14051488號-3技術支持:南京博盛藍睿網絡科技有限公司
南京思必達教育科技有限公司版權所有 百度統計