生命過短暫,不要去作一些根本沒有人想要的東西。本文已被 https://www.yourbatman.cn 收錄,裏面一併有Spring技術棧、MyBatis、JVM、中間件等小而美的 專欄供以避免費學習。關注公衆號【 BAT的烏托邦】逐個擊破,深刻掌握,拒絕淺嘗輒止。
各位小夥伴你們好,我是A哥。關於Spring初始化Bean的順序問題,是個老生常談的話題了,結論可總結爲一句話:全局無序,局部有序。Spring Bean
總體上是無序的,而現實是大多數狀況下咱們真的無需關心,無序就無序唄,無所謂嘍。可是(此處應該有可是哈),我有理由相信,對於有必定從業經驗的Javaer來講,或多或少都經歷過Bean初始化順序帶來的「困擾」,也許是由於沒有對你的功能形成影響,也許多是你全然「不知情」,因此最終就不了了之~java
隱患終歸隱患,依照墨菲定律來說,擔憂的事它總歸是會發生的。A哥常常「教唆」程序員要面向工資編程,雖然這價值觀有點扭曲,但不能否認不少小夥伴真是這麼想的(命中你了沒有😄),稍加粉飾了而已。話粗理不粗哦,almost全部的Javaer都在用Spring,你憑什麼工資比你身邊同事的高呢?
Spring對Bean的(生命週期)管理是它最爲核心的能力,同時也是很複雜、很難掌握的一個知識點。如今就能夠啓動你的工程,有木有這句日誌:git
"Bean 'xxx' of type [xxxx] is not eligible for getting processed by all BeanPostProcessors" + "(for example: not eligible for auto-proxying)"
這是一個典型的Spring Bean過早初始化問題,搜搜看你日誌裏是否有此句嘍。這句日誌是由Spring的BeanPostProcessorChecker
這個類負責輸出,含義爲:你的Bean xxx不能被全部的BeanPostProcessors
處理到(有的生命週期觸達不到),提醒你注意。此句日誌在低些的版本里是warn警告級別,在本文約定的版本里官方把它改成了info級別。程序員
絕大多數狀況下,此句日誌的輸出不會對你的功能形成影響,所以無需搭理。這也是Spring官方爲什麼把它從warn調低爲info級別的緣由
我在CSDN上寫過一篇「Spring Bean過早初始化致使的誤傷」的文章,訪問量達近4w:
從這個數據(訪問量)上來看,這件事「並不簡單」,遇到此麻煩的小夥伴不在少數且確實難倒了一衆人。關於Spring Bean的順序,全局是不可控的,可是局部上它提供了多種方式來方便使用者提升/下降優先級(好比前面的使用@AutoConfigureBefore調整配置順序竟沒生效?這篇文章),本文就聊聊static關鍵字對於提供Bean的優先級的功效。github
本文內容若沒作特殊說明,均基於如下版本:編程
1.8
5.2.2.RELEASE
本文采用從 問題提出-結果分析-解決方案-原理剖析 這4個步驟,層層遞進的去感覺static關鍵字在Spring Bean上的魅力~segmentfault
這是最爲常見的一種警告,特別當你的工程使用了shiro
作鑑權框架的時候。在我記憶中這一年來有N多位小夥伴問過我此問題,可見一斑。數組
@Configuration class AppConfig { AppConfig() { System.out.println("AppConfig init..."); } @Bean BeanPostProcessor postProcessor() { return new MyBeanPostProcessor(); } } class MyBeanPostProcessor implements BeanPostProcessor { MyBeanPostProcessor() { System.out.println("MyBeanPostProcessor init..."); } }
運行程序,輸出結果:安全
AppConfig init... 2020-05-31 07:40:50.979 INFO 15740 --- [ main] trationDelegate$BeanPostProcessorChecker : Bean 'appConfig' of type [com.yourbatman.config.AppConfig$$EnhancerBySpringCGLIB$$29b523c8] is not eligible for getting processed by all BeanPostProcessors (for example: not eligible for auto-proxying) MyBeanPostProcessor init... ...
結果分析(問題點/衝突點):app
AppConfig
優先於MyBeanPostProcessor
進行實例化框架
MyBeanPostProcessor
做爲一個後置處理器理應是先被初始化的,而AppConfig
僅僅是個普通Bean而已,初始化理應靠後 出現了BeanPostProcessorChecker
日誌:表示AppConfig
這個Bena不能被全部的BeanPostProcessors處理,因此有可能會讓它「錯過」容器對Bean的某些生命週期管理,所以可能損失某些能力(好比不能被自動代理),存在隱患
BeanPostProcessorChecker
輸出日誌,理應都得引發你的注意,由於這屬於Spring的警告日誌(雖然新版本已下調爲了info級別)說明:這是一個Info日誌,並不是warn/error級別。絕大多數狀況下你確實無需關注,可是若是你是一個容器開發者,建議請務必解決此問題(畢竟貌似大多數中間件開發者都有必定代碼潔癖😄)
基於上例,咱們僅需作以下小改動:
AppConfig: //@Bean //BeanPostProcessor postProcessor() { // return new MyBeanPostProcessor(); //} // 方法前面加上static關鍵字 @Bean static BeanPostProcessor postProcessor() { return new MyBeanPostProcessor(); }
運行程序,結果輸出:
MyBeanPostProcessor init... ... AppConfig init... ...
那個煩人的BeanPostProcessorChecker
日誌就不見了,清爽了不少。同時亦可發現AppConfig
是在MyBeanPostProcessor
以後實例化的,這才符合咱們所想的「正常」邏輯嘛。
這個「警告」就比上一個嚴重得多了,它有極大的可能致使你程序錯誤,而且你還很難定位問題所在。
@Configuration class AppConfig { AppConfig() { System.out.println("AppConfig init..."); } @Bean BeanDefinitionRegistryPostProcessor postProcessor() { return new MyBeanDefinitionRegistryPostProcessor(); } /////////////////////////////// @Bean Son son(){ return new Son(); } @Bean Parent parent(){ return new Parent(son()); } } class MyBeanDefinitionRegistryPostProcessor implements BeanDefinitionRegistryPostProcessor { MyBeanDefinitionRegistryPostProcessor() { System.out.println("MyBeanDefinitionRegistryPostProcessor init..."); } @Override public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException { } @Override public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException { } }
運行程序,結果輸出:
AppConfig init... MyBeanDefinitionRegistryPostProcessor init... 2020-05-31 07:59:06.363 INFO 37512 --- [ main] o.s.c.a.ConfigurationClassPostProcessor : Cannot enhance @Configuration bean definition 'appConfig' since its singleton instance has been created too early. The typical cause is a non-static @Bean method with a BeanDefinitionRegistryPostProcessor return type: Consider declaring such methods as 'static'. ... son init...hashCode() = 1300528434 son init...hashCode() = 1598434875 Parent init...
結果分析(問題點/衝突點):
ConfigurationClassPostProcessor
的日誌中可看到:AppConfig配置類enhance加強失敗 這三步結果環環相扣,由於1致使了2的加強失敗,由於2的加強失敗致使了3的建立多個實例,真可謂一步錯,步步錯。須要注意的是:這裏ConfigurationClassPostProcessor輸出的依舊是info日誌(我我的認爲,Spring把這個輸出調整爲warn級別是更爲合理的,由於它影響較大)。
說明:對這個結果的理解基於對Spring配置類的理解,所以強烈建議你進我公衆號參閱那個多是寫的最全、最好的Spring配置類專欄學習(文章很少,6篇足矣)
源碼處解釋:
ConfigurationClassPostProcessor: // 對Full模式的配置類嘗試使用CGLIB字節碼提高 public void enhanceConfigurationClasses(ConfigurableListableBeanFactory beanFactory) { ... // 對Full模式的配置類有個判斷/校驗 if (ConfigurationClassUtils.CONFIGURATION_CLASS_FULL.equals(configClassAttr)) { if (!(beanDef instanceof AbstractBeanDefinition)) { throw new BeanDefinitionStoreException("Cannot enhance @Configuration bean definition '" + beanName + "' since it is not stored in an AbstractBeanDefinition subclass"); } // 若判斷髮現此時該配置類已是個單例Bean了(說明已初始化完成) // 那就再也不作處理,而且輸出警告日誌告知使用者(雖然是info日誌) else if (logger.isInfoEnabled() && beanFactory.containsSingleton(beanName)) { logger.info("Cannot enhance @Configuration bean definition '" + beanName + "' since its singleton instance has been created too early. The typical cause " + "is a non-static @Bean method with a BeanDefinitionRegistryPostProcessor " + "return type: Consider declaring such methods as 'static'."); } configBeanDefs.put(beanName, (AbstractBeanDefinition) beanDef); } ... }
因爲配置類加強是在BeanFactoryPostProcessor#postProcessBeanFactory()
聲明週期階段去作的,而BeanDefinitionRegistryPostProcessor
它會優先於該步驟完成實例化(其實主要是優先級比BeanFactoryPostProcessor
高),從而間接帶動 AppConfig提早初始化致使了問題,這即是根本緣由所在。
提問點:本處使用了個自定義的BeanDefinitionRegistryPostProcessor
模擬了效果,那若是你是使用的BeanFactoryPostProcessor
能出來這個效果嗎???答案是不能的,具體緣由留給讀者思考,可參考:PostProcessorRegistrationDelegate#invokeBeanFactoryPostProcessors
這段流程輔助理解。
來吧,繼續使用static關鍵字改造一下:
AppConfig: //@Bean //BeanDefinitionRegistryPostProcessor postProcessor() { // return new MyBeanDefinitionRegistryPostProcessor(); //} @Bean static BeanDefinitionRegistryPostProcessor postProcessor() { return new MyBeanDefinitionRegistryPostProcessor(); }
運行程序,結果輸出:
MyBeanDefinitionRegistryPostProcessor init... ... AppConfig init... son init...hashCode() = 2090289474 Parent init... ...
完美。
@Configuration class AppConfig { @Autowired private Parent parent; @PostConstruct void init() { System.out.println("AppConfig.parent = " + parent); } AppConfig() { System.out.println("AppConfig init..."); } @Bean BeanFactoryPostProcessor postProcessor() { return new MyBeanFactoryPostProcessor(); } @Bean Son son() { return new Son(); } @Bean Parent parent() { return new Parent(son()); } } class MyBeanFactoryPostProcessor implements BeanFactoryPostProcessor { MyBeanFactoryPostProcessor() { System.out.println("MyBeanFactoryPostProcessor init..."); } @Override public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException { } }
運行程序,結果輸出:
AppConfig init... 2020-05-31 08:28:06.550 INFO 1464 --- [ main] o.s.c.a.ConfigurationClassEnhancer : @Bean method AppConfig.postProcessor is non-static and returns an object assignable to Spring's BeanFactoryPostProcessor interface. This will result in a failure to process annotations such as @Autowired, @Resource and @PostConstruct within the method's declaring @Configuration class. Add the 'static' modifier to this method to avoid these container lifecycle issues; see @Bean javadoc for complete details. MyBeanFactoryPostProcessor init... ... son init...hashCode() = 882706486 Parent init...
結果分析(問題點/衝突點):
MyBeanFactoryPostProcessor
初始化@Autowired/@PostConstruct
等註解沒有生效,這個問題很大
須要強調的是:此時的AppConfig是被enhance加強成功了的,這樣纔有可能進入到
BeanMethodInterceptor
攔截裏面,纔有可能輸出這句日誌(該攔截器會攔截Full模式配置列的全部的@Bean方法的執行)
這句日誌由ConfigurationClassEnhancer.BeanMethodInterceptor
輸出,含義爲:你的@Bean標註的方法是非static的而且返回了一個BeanFactoryPostProcessor
類型的實例,這就致使了配置類裏面的@Autowired, @Resource,@PostConstruct
等註解都將得不到解析,這是比較危險的(因此其實這個日誌調整爲warn級別也是闊儀的)。
小細節:爲毛日誌看起來是ConfigurationClassEnhancer這個類輸出的呢?這是由於
BeanMethodInterceptor
是它的靜態內部類,和它
共用的一個logger
源碼處解釋:
ConfigurationClassEnhancer.BeanMethodInterceptor: if (isCurrentlyInvokedFactoryMethod(beanMethod)) { if (logger.isInfoEnabled() && BeanFactoryPostProcessor.class.isAssignableFrom(beanMethod.getReturnType())) { logger.info(String.format("@Bean method %s.%s is non-static and returns an object " + "assignable to Spring's BeanFactoryPostProcessor interface. This will " + "result in a failure to process annotations such as @Autowired, " + "@Resource and @PostConstruct within the method's declaring " + "@Configuration class. Add the 'static' modifier to this method to avoid " + "these container lifecycle issues; see @Bean javadoc for complete details.", beanMethod.getDeclaringClass().getSimpleName(), beanMethod.getName())); } return cglibMethodProxy.invokeSuper(enhancedConfigInstance, beanMethodArgs); }
解釋爲:若是當前正在執行的@Bean方法(鐵定不是static,由於靜態方法它也攔截不到嘛)返回類型是BeanFactoryPostProcessor
類型,那就輸出此警告日誌來提醒使用者要小心。
AppConfig: //@Bean //BeanFactoryPostProcessor postProcessor() { // return new MyBeanFactoryPostProcessor(); //} @Bean static BeanFactoryPostProcessor postProcessor() { return new MyBeanFactoryPostProcessor(); }
運行程序,結果輸出:
MyBeanFactoryPostProcessor init... AppConfig init... son init...hashCode() = 1906549136 Parent init... // @PostConstruct註解生效嘍 AppConfig.parent = com.yourbatman.bean.Parent@baf1bb3 ...
世界一會兒又清爽了有木有。
以上三個case是有共同點的,粗略的講致使它們的緣由甚至是同一個:AppConfig這個Bean被過早初始化。然而咱們的解決方案彷佛也是同一個:使用static提高Bean的優先級。
那麼爲什麼AppConfig會被提早初始化呢?爲什麼使用static關鍵字就沒有問題了呢?根本緣由可提早劇透:static靜態方法屬於類,執行靜態方法時並不須要初始化所在類的實例;而實例方法屬於實例,執行它時必須先初始化所在類的實例。聽起來是否是很是的簡單,JavaSE的東西嘛,固然只知曉到這個層次確定是遠遠不夠的,限於篇幅緣由,關於Spring是如何處理的源碼級別的分析我放在了下篇文章,請別走開喲~
看完本文,有些小夥伴就忍不住躍躍欲試了,甚至很武斷的得出結論:static標註的@Bean方法優先級更高,其實這是錯誤的,好比你看以下示例:
@Configuration class AppConfig2 { AppConfig2(){ System.out.println("AppConfig2 init..."); } @Bean Son son() { return new Son(); } @Bean Daughter daughter() { return new Daughter(); } @Bean Parent Parent() { return new Parent(); } }
運行程序,結果輸出:
AppConfig2 init... son init... Daughter init... Parent init...
這時候你想讓Parent在Son以前初始化,所以你想着在用static關鍵字來提高優先級,這麼作:
AppConfig2: //@Bean //Parent Parent() { // return new Parent(); //} @Bean static Parent Parent() { return new Parent(); }
結果:你徒勞了,static貌似並無生效,怎麼回事?
爲了知足你的好奇心,這裏給個淺析,道出關鍵因素。咱們知道@Bean方法(無論是靜態方法仍是實例方法)最終都會被封裝進ConfigurationClass
實例裏面,使用Set<BeanMethod> beanMethods
存儲着,關鍵點在於它是個LinkedHashSet
因此是有序的(存放順序),而存入的順序底層是由clazz.getDeclaredMethods()
來決定的,由此可知@Bean方法執行順序和有無static沒有半毛錢關係。
說明:
clazz.getDeclaredMethods()
獲得的是Method[]數組,是有序的。這個順序由字節碼(定義順序)來保證:先定義,先服務。
因而可知,static並非真正意義上的提升Bean優先級,對於如上你的需求case,你可使用@DependsOn
註解來保證,它也是和Bean順序息息相關的一個註解,在本專欄後續文章中將會詳細講到。
因此關於@Bean方法的執行順序的正確結論應該是:在同一配置類內,在無其它「干擾」狀況下(無@DependsOn、@Lazy等註解
),@Bean方法的執行順序聽從的是定義順序(後置處理器類型除外)。
小提問:若是是垮@Configuration配置類的狀況,順序如何界定呢?那麼這就不是同一層級的問題了,首先考慮的應該是@Configuration配置類的順序問題,前面有文章提到過配置類是支持有限的的@Order註解排序的,具體分析請依舊保持關注A哥後續文章詳解哈...
在同一個@Configuration
配置類內,對static關鍵字的使用作出以下說明,供以參考:
BeanPostProcessor
、BeanFactoryPostProcessor
等類型的方法,而且建議此種方法請務必使用static修飾,不然容易致使隱患,埋雷static關鍵字不要濫用(其實任何關鍵字皆勿亂用),在同一配置類內,與其說它是提高了Bean的優先級,倒不如說它讓@Bean方法靜態化從而再也不須要依賴所在類的實例便可獨立運行。另外咱們知道,static關鍵還能夠修飾(內部)類,那麼若是放在類上它又是什麼表現呢?一樣的,你先思考,下篇文章咱們接着聊~
說明:使用static修飾Class類在 Spring Boot自動配置類裏特別特別常見,因此掌握起來很具價值
今天的思考題比較簡單:爲什麼文首三種case的警告信息都是info級別呢?是否有級別太低之嫌?
本文仍是蠻幹的哈,不出意外它可以幫你解決你工程中的某些問題,排除掉一些隱患,畢竟墨菲定律被驗證了你擔憂的事它總會發生,防患於未然才能把本身置於安全高地嘛。
你可能詫異,A哥竟能把static關鍵字在Spring中的應用都能寫出個專欄出來,是的,這不是就是本公衆號的定位麼 ,小而美和拒絕淺嘗輒止嘛。對於一些知識(好比本文的static關鍵字的使用)我並不推崇強行記憶,由於那真的很容易忘,快速使用能夠簡單記記,但真想記得牢(甚至成爲永久記憶),那必須得去深水區看看。來吧,下文將授之以漁~
不少小夥伴去強行記憶Spring Boot支持的那17種外部化配置,此時你應該問本身:如今你可能記得,一週之後呢?一個月之後呢?因此你須要另闢蹊徑,那就持續關注我吧😄
Author | A哥(YourBatman) |
---|---|
我的站點 | www.yourbatman.cn |
yourbatman@qq.com | |
微 信 | fsx641385712 |
活躍平臺 |
![]() ![]() ![]() ![]() ![]() ![]() ![]() ![]() |
公衆號 | BAT的烏托邦(ID:BAT-utopia) |
知識星球 | BAT的烏托邦 |
每日文章推薦 | 每日文章推薦 |