專一Java領域分享、成長,拒絕淺嘗輒止。關注公衆號【 BAT的烏托邦】開啓專欄式學習,拒絕淺嘗輒止。本文 https://www.yourbatman.cn 已收錄,裏面一併有Spring技術棧、MyBatis、中間件等小而美的專欄供以學習哦。
各位小夥伴你們好,我是A哥。這是繼上篇文章:真懂Spring的@Configuration配置類?你可能自我感受太良好 的原理/源碼解釋篇。按照本公衆號的定位,原理通常跑不了,雖然很枯燥,但還得作,畢竟作難事必有所得,真的掌握了纔有底氣談漲薪嘛。java
Tips:鑑於常常有些同窗沒法區分某個功能/某項能力屬於Spring Framework
的仍是Spring Boot
,你能夠參考文章裏的【版本約定】目錄,那裏會說明本文的版本依賴,也就是功能所屬嘍。好比本文內容它就屬於Spring Framework
,和Spring Boot
木有關係。架構
本文內容若沒作特殊說明,均基於如下版本:工具
1.8
5.2.2.RELEASE
Spring的IoC就像個「大熔爐」,什麼都看成Bean放在裏面。然而,雖然它們都放在了一塊兒,可是實際在功能上是有區別的,好比咱們熟悉的BeanPostProcessor
就屬於後置處理器功能的Bean,還有本文要討論的@Configuration
配置Bean也屬於一種特殊的組件。post
判斷一個Bean是不是Bean的後置處理器很方便,只需看它是否實現了BeanPostProcessor
接口便可;那麼如何去肯定一個Bean是不是@Configuration配置Bean呢?如果,如何區分是Full模式仍是Lite模式呢?這便就是本文將要討論的內容。學習
首先須要明確:@Configuration
配置前提必須是IoC管理的一個組件(也就是常說的Bean)。Spring使用BeanDefinitionRegistry
註冊中心管理着全部的Bean定義信息,那麼對於這些Bean信息哪些屬於@Configuration
配置呢,這是須要甄選出來的。this
判斷一個Bean是不是@Configuration
配置類這個邏輯統一交由ConfigurationClassUtils
這個工具類去完成。spa
見名之意,它是和配置有關的一個工具類,提供幾個靜態工具方法供以使用。它是Spring 3.1
新增,對於它的做用,官方給的解釋是:用於標識@Configuration
類的實用程序(Utilities)。它主要提供了一個方法:checkConfigurationClassCandidate()
用於檢查給定的Bean定義是不是配置類的候選對象(或者在配置/組件類中聲明的嵌套組件類),並作相應的標記。debug
它是一個public static工具方法,用於判斷某個Bean定義是不是@Configuration
配置。設計
ConfigurationClassUtils: public static boolean checkConfigurationClassCandidate(BeanDefinition beanDef, MetadataReaderFactory metadataReaderFactory) { ... // 根據Bean定義信息,拿到器對應的註解元數據 AnnotationMetadata metadata = xxx; ... // 根據註解元數據判斷該Bean定義是不是配置類。如果:那是Full模式仍是Lite模式 Map<String, Object> config = metadata.getAnnotationAttributes(Configuration.class.getName()); if (config != null && !Boolean.FALSE.equals(config.get("proxyBeanMethods"))) { beanDef.setAttribute(CONFIGURATION_CLASS_ATTRIBUTE, CONFIGURATION_CLASS_FULL); } else if (config != null || isConfigurationCandidate(metadata)) { beanDef.setAttribute(CONFIGURATION_CLASS_ATTRIBUTE, CONFIGURATION_CLASS_LITE); } else { return false; } ... // 到這。它確定是一個完整配置(Full or Lite) 這裏進一步把@Order排序值放上去 Integer order = getOrder(metadata); if (order != null) { beanDef.setAttribute(ORDER_ATTRIBUTE, order); } return true; }
步驟總結:3d
根據Bean定義信息解析成爲一個註解元數據對象AnnotationMetadata metadata
AnnotatedBeanDefinition
,也多是個StandardAnnotationMetadata
根據註解元數據metadata判斷是不是個@Configuration
配置類,有以下三種可能case:
@Configuration
註解而且該註解的proxyBeanMethods = false
,那麼mark一下它是Full模式的配置。不然進入下一步判斷@Configuration
註解或者符合Lite模式的條件(上文有說一共有5種多是Lite模式,源碼處在isConfigurationCandidate(metadata)
這個方法裏表述),那麼mark一下它是Lite模式的配置。不然進入下一步判斷return false
@Order
值(如有的話),而後mark進Bean定義裏面去這個mark動做頗有意義:後面判斷一個配置類是Full模式仍是Lite模式,甚至判斷它是不是個配置類都可經過beanDef.getAttribute(CONFIGURATION_CLASS_ATTRIBUTE)
這樣完成判斷。
知曉了checkConfigurationClassCandidate()
可以判斷一個Bean(定義)是不是一個配置類,那麼它在何時會被使用呢?經過查找能夠發現它被以下兩處使用到:
ConfigurationClassPostProcessor.processConfigBeanDefinitions()
處理配置Bean定義階段。ConfigurationClassPostProcessor: public void processConfigBeanDefinitions(BeanDefinitionRegistry registry) { // 拿出當前全部的Bean定義信息,一個個的檢查是不是配置類 String[] candidateNames = registry.getBeanDefinitionNames(); for (String beanName : candidateNames) { BeanDefinition beanDef = registry.getBeanDefinition(beanName); if (beanDef.getAttribute(ConfigurationClassUtils.CONFIGURATION_CLASS_ATTRIBUTE) != null) { logger.debug("Bean definition has already been processed as a configuration class: " + beanDef); } // 若是該Bean定義不是配置類,那就繼續判斷一次它是不是配置類,如果就加入結果集合裏 else if (ConfigurationClassUtils.checkConfigurationClassCandidate(beanDef, this.metadataReaderFactory)) { configCandidates.add(new BeanDefinitionHolder(beanDef, beanName)); } } ... }
ConfigurationClassPostProcessor
是個BeanDefinitionRegistryPostProcessor
,會在BeanFactory
準備好後執行生命週期方法。所以天然而然的,checkConfigurationClassCandidate()
會在此階段調用,用於區分出哪些是配置Bean。
值得注意的是:ConfigurationClassPostProcessor
的執行時期是很是早期的(BeanFactory
準備好後就執行嘛),這個時候容器內的Bean定義不多。這個時候只有主配置類才被註冊了進來,那些想經過@ComponentScan
掃進來的配置類都還沒到「時間」,這個時間節點很重要,請注意區分。爲了方便你理解,我分別把Spring和Spring Boot在此階段的Bean定義信息截圖展現以下:
以上是Spring環境,對應代碼爲:
new AnnotationConfigApplicationContext(AppConfig.class);
以上是Spring Boot環境,對應代碼爲:
@SpringBootApplication public class Boot2Demo1Application { public static void main(String[] args) { SpringApplication.run(Boot2Demo1Application.class, args); } }
相比之下,Spring Boot裏多了internalCachingMetadataReaderFactory
這個Bean定義。緣由是SB定義了一個CachingMetadataReaderFactoryPostProcessor
把它放進去的,因爲此Processor也是個BeanDefinitionRegistryPostProcessor
而且order值爲Ordered.HIGHEST_PRECEDENCE
,因此它會優先於ConfigurationClassPostProcessor
執行把它註冊進去~
ConfigurationClassParser.doProcessConfigurationClass()
解析 @Configuration
配置類階段。所處的大階段同上使用處,仍舊是ConfigurationClassPostProcessor#postProcessBeanDefinitionRegistry()
階段ConfigurationClassParser: @Nullable protected final SourceClass doProcessConfigurationClass(ConfigurationClass configClass, SourceClass sourceClass) throws IOException { ... // 先解析nested內部類(內部類會存在@Bean方法嘛~) ... // 解析@PropertySource資源,加入到environment環境 ... // 解析@ComponentScan註解,把組件掃描進來 scannedBeanDefinitions = ComponentScanAnnotationParser.parse(componentScan, ...); // 把掃描到的Bean定義信息依舊須要一個個的判斷,是不是配置類 // 如果配置類,就繼續看成一個@Configuration配置類來解析parse() 遞歸嘛 for (BeanDefinitionHolder holder : scannedBeanDefinitions) { ... if (ConfigurationClassUtils.checkConfigurationClassCandidate(bdCand, this.metadataReaderFactory)) { parse(bdCand.getBeanClassName(), holder.getBeanName()); } } ... // 解析@Import註解 ... // 解析@ImportResource註解 ... // 解析當前配置裏配置的@Bean方法 ... // 解析接口默認方法(由於配置類可能實現接口,而後接口默認方法可能標註有@Bean ) ... // 處理父類(遞歸,直到父類爲java.打頭的爲止) }
這個方法是Spring對配置類解析的最核心步驟,經過它順帶也可以解答你的疑惑了吧:爲什麼你僅需在類上標註一個@Configuration
註解便可讓它成爲一個配置類?由於被Scan掃描進去了嘛~
經過以上兩個使用處的分析和對比,對於@Configuration
配置類的理解,你至少應該掌握了以下訊息:
@Configuration
配置類確定是個組件,存在於IoC容器裏@Configuration
配置類是有主次之分的,主配置類是驅動整個程序的入口,能夠是一個,也能夠是多個(若存在多個,支持使用@Order排序)@ComponentScan
能力完成加載進而解析的(固然也多是@Import
、又或是被其它次配置類驅動的)聊完了最爲重要的checkConfigurationClassCandidate()
方法,固然還有必要看看ConfigurationClassUtils
的另外一個工具方法isConfigurationCandidate()
。
它是一個public static工具方法,經過給定的註解元數據信息來判斷它是不是一個Configuration
。
ConfigurationClassUtils: static { candidateIndicators.add(Component.class.getName()); candidateIndicators.add(ComponentScan.class.getName()); candidateIndicators.add(Import.class.getName()); candidateIndicators.add(ImportResource.class.getName()); } public static boolean isConfigurationCandidate(AnnotationMetadata metadata) { // 不考慮接口 or 註解 說明:註解的話也是一種「特殊」的接口哦 if (metadata.isInterface()) { return false; } // 只要該類上標註有以上4個註解任意一個,都算配置類 for (String indicator : candidateIndicators) { if (metadata.isAnnotated(indicator)) { return true; } } // 若一個註解都沒標註,那就看有木有@Bean方法 如有那也算配置類 return metadata.hasAnnotatedMethods(Bean.class.getName()); }
步驟總結:
@Component、@ComponentScan、@Import、@ImportResource
任意一個註解,就判斷成功返回true。不然繼續判斷須要特別特別特別注意的是:此方法它的並不考慮@Configuration
註解,是「輕量級」判斷,這是它和checkConfigurationClassCandidate()
方法的最主要區別。固然,後者依賴於前者,依賴它來根據註解元數據判斷是不是Lite模式的配置。
由於本文的講解和代碼均是基於Spring 5.2.2.RELEASE
的,而並非全部小夥伴都會用到這麼新的版本。關於此部分的實現,以Spring 5.2.0版本爲分界線實現上有些許差別,因此在此處作出說明。
proxyBeanMethods
屬性是Spring 5.2.0版本爲@Configuration
註解新增長的一個屬性:
@Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) @Documented @Component public @interface Configuration { @AliasFor(annotation = Component.class) String value() default ""; // @since 5.2 boolean proxyBeanMethods() default true; }
它的做用是:是否容許代理@Bean方法。說白了:決定此配置使用Full模式仍是Lite模式。爲了保持向下兼容,proxyBeanMethods
的默認值是true,使用Full模式配置。
Spring 5.2提出了這個屬性項,是指望你在已經瞭解了它的做用以後,顯示的把它置爲false的,由於在雲原生將要到來的今天,啓動速度方面Spring一直在作着努力,也但願你能配合嘛。這不Spring Boot
就「配合」得很好,它在2.2.0版本(依賴於Spring 5.2.0)起就把它的全部的自動配置類的此屬性改成了false,即@Configuration(proxyBeanMethods = false)
。
因爲Spring 5.2.0新增了proxyBeanMethods
屬性來控制模式,所以實現上也有些許詫異,請各位注意甄別:
Spring 5.2.0+版本判斷實現:
ConfigurationClassUtils: Map<String, Object> config = metadata.getAnnotationAttributes(Configuration.class.getName()); if (config != null && !Boolean.FALSE.equals(config.get("proxyBeanMethods"))) { beanDef.setAttribute(CONFIGURATION_CLASS_ATTRIBUTE, CONFIGURATION_CLASS_FULL); } else if (config != null || isConfigurationCandidate(metadata)) { beanDef.setAttribute(CONFIGURATION_CLASS_ATTRIBUTE, CONFIGURATION_CLASS_LITE); } else { return false; }
Spring 5.2.0-版本判斷實現:
ConfigurationClassUtils: if (isFullConfigurationCandidate(metadata)) { beanDef.setAttribute(CONFIGURATION_CLASS_ATTRIBUTE, CONFIGURATION_CLASS_FULL); } else if (isLiteConfigurationCandidate(metadata)) { beanDef.setAttribute(CONFIGURATION_CLASS_ATTRIBUTE, CONFIGURATION_CLASS_LITE); } else { return false; }
isConfigurationCandidate()
判斷方法是爲checkConfigurationClassCandidate()
服務,那Spring爲什麼也把它設計爲public static呢?ConfigurationClassUtils
裏還存在對@Order
順序的解析方法,不是說Spring的Bean是無序的嗎?這又如何理解呢?本文做爲上篇文章的續篇,解釋了@Configuration配置的Full模式和Lite模式的判斷原理,同時順帶的也介紹了什麼叫主配置配和次配置類,這個概念(雖然官方並不這麼叫)對你理解Spring Framework是很是有幫助的。若是你使用是基於Spring 5.2.0+的版本,在瞭解了這兩篇文章內容的基礎上,建議你的配置類均採用Lite模式去作,即顯示設置proxyBeanMethods = false
。
另外關於此部份內容,有些更爲感興趣的小夥伴問到:爲何Full模式下經過方法調用指向的仍舊是原來的Bean,保證了只會執行一次呢?開啓的是Full模式這只是表象緣由,想要回答此問題須要涉及到CGLIB加強實現的深水區內容,爲了知足這些好奇(好學)的娃子,計劃會在下篇文章繼續再拿一篇專程講解(預計篇幅不短,萬字以上),你可訂閱個人公衆號持續保持關注。