你知道Spring是怎麼解析配置類的嗎?

2020-12-14 計算機java編程

Spring執行流程圖如下:

如果圖片顯示不清楚可以訪問如下連結查看高清大圖:

Spring執行流程圖

這個流程圖會隨著我們的學習不斷的變得越來越詳細,也會越來越複雜,希望在這個過程中我們都能朝著精通Spring的目標不斷前進!

我們已經知道了Spring中的第一行代碼其實就是創建了一個AnnotatedBeanDefinitionReader對象,這個對象的主要作用就是註冊bd(BeanDefinition)到容器中。並且在創建這個對象的過程中,Spring還為容器註冊了開天闢地的幾個bd,包括ConfigurationClassPostProcessor,AutowiredAnnotationBeanPostProcessor等等。

this.scanner = new ClassPathBeanDefinitionScanner(this);

上面流程圖上已經標註。

只是簡單地創建了一個ClassPathBeanDefinitionScanner對象。那麼這個ClassPathBeanDefinitionScanner有什麼作用呢?從名字上來看好像就是這個對象來完成Spring中的掃描的,真的是這樣嗎?希望同學們能帶著這兩個問題往下看

ClassPathBeanDefinitionScanner源碼分析

這個類名直譯過來就是:類路徑下的BeanDefinition的掃描器,所以我們就直接關注其掃描相關的方法,就是其中的doScan方法。其代碼如下:

上面這段代碼主要做了四件事

通過findCandidateComponents方法完成掃描判斷掃描出來的bd是否是一個AbstractBeanDefinition,如果是的話執行postProcessBeanDefinition方法判斷掃描出來的bd是否是一個AnnotatedBeanDefinition,如果是的話執行processCommonDefinitionAnnotations方法檢查容器中是否已經有這個bd了,如果有就不進行註冊了接下來我們就一步步分析這個方法,搞明白ClassPathBeanDefinitionScanner到底能起到什麼作用

1、通過findCandidateComponents方法完成掃描

findCandidateComponents方法源碼如下:

addCandidateComponentsFromIndex不用過多關注這個方法。正常情況下Spring都是採用掃描classpath下的class文件來完成掃描,但是雖然基於classpath掃描速度非常快,但通過在編譯時創建候選靜態列表,可以提高大型應用程式的啟動性能。在這種模式下,應用程式的所有模塊都必須使用這種機制,因為當 ApplicationContext檢測到這樣的索引時,它將自動使用它而不是掃描類路徑。要生成索引,只需向包含組件掃描指令目標組件的每個模塊添加附加依賴項即可

scanCandidateComponents(basePackage)正常情況下我們的應用都是通過這個方法完成掃描的,其代碼如下:

從上圖中可以看出,java class + configuration metadata 最終會轉換為一個BenaDefinition,結合我們上面的代碼分析可以知道,java class + configuration metadata實際上就是一個MetadataReader對象,而轉換成一個BenaDefinition則是指通過這個MetadataReader對象創建一個ScannedGenericBeanDefinition。

2、執行postProcessBeanDefinition方法

可以看出,postProcessBeanDefinition方法最主要的功能就是給掃描出來的bd設置默認值,進一步填充bd中的屬性

3、執行processCommonDefinitionAnnotations方法

這句代碼將進一步解析class上的註解信息,Spring在創建這個abd的信息時候就已經將當前的class放入其中了,所有這行代碼主要做的就是通過class對象獲取到上面的註解(包括@Lazy,@Primary,@DependsOn註解等等),然後將得到註解中對應的配置信息並放入到bd中的屬性中

4、註冊BeanDefinition

通過上面的分析,我們已經知道了ClassPathBeanDefinitionScanner的作用,毋庸置疑,Spring肯定是通過這個類來完成掃描的,但是問題是,Spring是通過第二步創建的這個對象來完成掃描的嗎?我們再來看看這個ClassPathBeanDefinitionScanner的創建過程:

在這個ClassPathBeanDefinitionScanner的創建過程中我們全程無法幹涉,不能對這個ClassPathBeanDefinitionScanner進行任何配置。而我們在配置類上明明是可以對掃描的規則進行配置的,例如:

@ComponentScan(value = "com.spring.study.springfx.aop.service", useDefaultFilters = true, excludeFilters = @ComponentScan.Filter(type = FilterType.ASSIGNABLE_TYPE, classes = {IndexService.class}))

所以Spring中肯定不是使用在這裡創建的這個ClassPathBeanDefinitionScanner對象。

實際上真正完成掃描的時機是在我們流程圖中的3-5-1步。完成掃描這個功能的類就是我們在上篇文章中所提到的ConfigurationClassPostProcessor。接下來我們就通過這個類,看看Spring到底是如何完成的掃描,這也是本文重點想要說明的問題

Spring是怎麼解析配置類的?

1、解析時機分析

解析前Spring做了什麼?

註冊配置類

在分析掃描時機之前我們先回顧下之前的代碼,整個程序的入口如下:

其中在this()空參構造中Spring實例化了兩個對象,一個是AnnotatedBeanDefinitionReader,在上篇文章中已經介紹過了,另外一個是ClassPathBeanDefinitionScanner,在前文中也進行了詳細的分析。

在完成這兩個對象的創建後,Spring緊接著就利用第一步中創建的AnnotatedBeanDefinitionReader去將配置類註冊到了容器中。看到這裡不知道大家有沒有一個疑問,既然Spring是直接通過這種方式來註冊配置類,為什麼我們還非要在配置類上添加@Configuration註解呢?按照這個代碼的話,我不在配置類上添加任何註解,也能將配置類註冊到容器中,例如下面這樣:

大家仔細想想我這個問題,不妨帶著這些疑問繼續往下看。

調用refresh方法

在將配置類註冊到容器中後,Spring緊接著又調用了refresh方法,其源碼如下:

大部分的代碼都寫了很詳細的注釋,對於其中兩個比較複雜的方法我們單獨分析prepareBeanFactoryinvokeBeanFactoryPostProcessors

prepareBeanFactory做了什麼?

上面這段代碼整體來說還是非常簡單的,邏輯也很清晰,就是在為beanFactory做一些配置,我們需要注意的是跟後置處理器相關的內容,可以看到在這一步一共註冊了兩個後置處理器

ApplicationContextAwareProcessor,用於執行xxxAware接口中的方法ApplicationListenerDetector,保證監聽器被添加到容器中invokeBeanFactoryPostProcessors做了什麼?

整的來說,它就是將容器中已經註冊的bean工廠的後置處理器按照一定的順序進行執行。

那麼到這一步為止,容器中已經有哪些bean工廠的後置處理器呢?

還記得我們在上篇文章中提到的ConfigurationClassPostProcessor嗎?在創建AnnotatedBeanDefinitionReader的過程中它對應的BeanDefinition就被註冊到容器中了。接下來我們就來分析ConfigurationClassPostProcessor這個類的源碼

ConfigurationClassPostProcessor源碼分析

它實現了BeanDefinitionRegistryPostProcessor,所以首先執行它的postProcessBeanDefinitionRegistry方法,其源碼如下

processConfigBeanDefinitions方法的代碼很長,我們拆分一段段分析,先看第一段

第一段

上面這段代碼有這麼幾個問題:

當前容器中有哪些BeanDefinition如果你看過上篇文章的話應該知道,在創建AnnotatedBeanDefinitionReader對象的時候Spring已經往容器中註冊了5個BeanDefinition,再加上註冊的配置類,那麼此時容器中應該存在6個BeanDefinition,我們可以打個斷點驗證

不出所料,確實是6個

checkConfigurationClassCandidate代碼如下:

第二段

這段代碼核心目的就是為了創建一個ConfigurationClassParser,這個類主要用於後續的配置類的解析。

第三段

2、解析源碼分析

在上面的源碼分析中,我們已經能夠確定了Spring是通過ConfigurationClassParser的parse方法來完成對配置類的解析的。Spring對類的取名可以說是很講究了,ConfigurationClassParser直譯過來就是配置類解析器。接著我們就來看看它的源碼

2.1、parse方法

2.2、processConfigurationClass方法

2.3、doProcessConfigurationClass方法

可以看到,在doProcessConfigurationClass真正完成了對配置類的解析,一共做了下面幾件事

解析配置類中的內部類,看內部類中是否有配置類,如果有進行遞歸處理

處理配置類上的@PropertySources跟@PropertySource註解

處理@ComponentScan,@ComponentScans註解

處理@Import註解

處理@ImportResource註解

處理@Bean註解

處理接口中的default方法

返回父類,讓外部的循環繼續處理當前配置類的父類

我們逐一進行分析

2.4、處理配置類中的內部類

這段代碼非常簡單,限於篇幅原因我這裡就不再專門分析了,就是獲取到當前配置類中的所有內部類,然後遍歷所有的內部類,判斷是否是一個配置類,如果是配置類的話就遞歸進行解析

2.5、處理@PropertySource註解

代碼也非常簡單,根據註解中的信息加載對應的屬性文件然後添加到容器中

2.6、處理@ComponentScan註解

這個段我們就需要看一看了,Spring在這裡完成的掃描,我們直接查看其核心方法,org.springframework.context.annotation.ComponentScanAnnotationParser#parse

看到了吧,第一步就創建了一個ClassPathBeanDefinitionScanner,緊接著通過解析註解,對這個掃描器進行了各種配置,然後調用doScan方法完成了掃描。

2.7、處理@Import註解

2.8、處理@ImportResource註解

代碼也很簡單,在指定的位置加載資源,然後添加到configClass中。一般情況下,我們通過@ImportResource註解導入的就是一個XML配置文件。將這個Resource添加到configClass後,Spring會在後文中解析這個XML配置文件然後將其中的bd註冊到容器中,可以參考org.springframework.context.annotation.ConfigurationClassBeanDefinitionReader#loadBeanDefinitions方法

2.9、處理@Bean註解

將配置類中所有的被@Bean標註的方法添加到configClass的BeanMethod集合中

2.10、處理接口中的default方法

代碼也很簡單,Java8中接口能定義default方法,這裡就是處理接口中的default方法,看其是否有@Bean標註的方法

到此為止,我們分析完了整個解析的過程。可以發現Spring將所有解析到的配置信息都存儲在了ConfigurationClass類中,但是到目前為止這些存儲的信息都沒有進行使用。那麼Spring是在哪裡使用的這些信息呢?回到我們的第三段代碼,其中有一行代碼如圖所示:

也就是在這裡Spring完成了對解析好的配置類的信息處理。

2.11、加載解析完成的配置信息

這段代碼閱讀起來還是非常簡單的,這裡我就跟大家一起看下BeanMethod的相關代碼,主要是為了讓大家對BeanDefinition的理解能夠更加深入,其源碼如下:

上面這個方法的主要目的就是將@Bean標註的方法解析成BeandDefinition然後註冊到容器中。關於這個方法我們可以對比下之前分析過的org.springframework.context.annotation.AnnotatedBeanDefinitionReader#doRegisterBean方法。對比我們可以發現,這兩個方法最大的不同在於一個是基於Class對象的,而另一個則是基於Method對象的。

正因為如此,所有它們有一個很大的不同點在於BeanDefinition中BeanClasss屬性的設置。可以看到,對於@Bean形式創建的Bean其BeanDefinition中是沒有設置BeanClasss屬性的,但是額外設置了其它的屬性

靜態方法下,設置了BeanClassName以及FactoryMethodName屬性,其中的BeanClassName是靜態方法所在類的類名,FactoryMethodName是靜態方法的方法名實例方法下,設置了FactoryBeanName以及FactoryMethodName屬性,其中FactoryBeanName是實例對應的Bean的名稱,而FactoryMethodName是實例中對應的方法名之所以不用設置BeanClasss屬性是因為,通過指定的靜態方法或者指定的實例中的方法也能唯一確定一個Bean。

除此之外,註冊@Bean形式得到的BeanDefinition時,還進行了一個isOverriddenByExistingDefinition(beanMethod, beanName)方法的判斷,這個方法的主要作用是判斷當前要註冊的bean是否被之前已經存在的Bean覆蓋了。

但是在直接通過AnnotatedBeanDefinitionReader#doRegisterBean方法註冊Bean時是沒有進行這個判斷的,如果存在就直接覆蓋了,而不會用之前的bd來覆蓋現在要註冊的bd。這是為什麼呢?據筆者自己的理解,是因為Spring將Bean也是分成了三六九等的,通過@Bean方式得到的bd可以覆蓋掃描出來的普通bd(ScannedGenericBeanDefinition),但是不能覆蓋配置類,所以當已經存在的bd是一個ScannedGenericBeanDefinition時,那麼直接進行覆蓋,但是當已經存在的bd是一個配置類時,就不能進行覆蓋了,要使用已經存在的bd來覆蓋本次要註冊的bd。

到此為止,我們就完成了Spring中的整個配置類解析、註冊的相關源碼分析,不過還沒完,我們還得解決一個問題,就是為什麼要在配置類上添加@Configuration註解,在之前的源碼分析中我們知道,添加@Configuration註解的作用就是講配置類標誌成了一個full configurationClass,這個的目的是什麼呢?本來是打算一篇文章寫完的,不過實在是太長了,接近6w字,所以還是拆成了兩篇,預知後事如何,請看下文:配置類為什麼要添加@Configuration註解呢?

總結

清晰的知道了執行的流程,我們再來回想下postProcessBeanDefinitionRegistry做了什麼。

碼字不易,對你有幫助的話記得點個讚,關注一波哦,萬分感謝!

相關焦點

  • Spring-Task源碼解析
    >從功能上來說,spring-task這個組件主要包括了兩個/兩種功能:任務的定時調度/執行,對應xml配置的task:scheduler和task:scheduled-tasks標籤。其beanClass為org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler。
  • 面試官:你了解spring嗎?spring的兩大核心是什麼?
    創建bean類,並在spring中進行配置交由spring來管理1 <?xml version="1.0" encoding="UTF-8"?IOC的實現原理在初始化一個Spring容器時,Spring會去解析指定的xml文件,當解析到其中的<bean>標籤時,會根據該標籤中的class屬性指定的類的全路徑名,通過反射創建該類的對象,並將該對象存入內置的Map中管理。其中鍵就是該標籤的id值,值就是該對象。
  • Spring Boot 配置文件的多環境實現
    使用如下:Loader是ConfigFileApplicationListener的一個內部類,其作用就是用來加載各種配置文件。我們看到,Loader類有一個關鍵的類屬性,propertySourceLoaders,它指向了spring.factories中配置的一系列屬性加載器。我們來看Loader中的一些關鍵方法。
  • Spring Boot 配置 log4j2
    log4j,相信大家都熟悉,至今對java影響最大的logging系統,至今仍有很多系統在使用log4j,但畢竟這個版本出的太早,Java都從1.2到7.0了,log4j怎麼總是在1.2的版本呢?不得承認,寫log4j的那個人確實很牛,之後又寫了slf4j和logback作為log4j 1.x的替代品。
  • Spring事務管理
    跨不同事務API的統一的編程模型,無論你使用的是jdbc、jta、jpa、hibernate。2. 支持聲明式事務3. 簡單的事務管理API4.Spring事務管理框架將為我們管理事務,但不清楚該如何替我們管理,我們就通過事務定義來定義我們需要的事務管理信息,把這些信給事務管理器,它就知道我們的意圖了。1.1  來看看它都定義了哪些信息項:
  • 你知道Spring是怎麼將AOP應用到Bean的生命周期中的嗎?
    ,原來AOP是這樣子的一文中已經提到過了,@EnableAspectJAutoProxy註解實際上就是向容器中註冊了一個AnnotationAwareAspectJAutoProxyCreator,這個類本身就是一個後置處理器,AOP代理就是由它在這一步完成的。
  • 安全的Spring Cloud配置
    我的GitHub存儲庫sample-spring-cloud-security在分支secure_config中提供了示例應用程式原始碼:https : //github.com/piomin/sample-spring-cloud-security/tree/secure_config。
  • Spring boot 基於註解方式配置datasource
    >先在spring的配置文件中,加載資料庫配置文件<!>有了大致的思路後,我們再來看看spring boot基於註解方式怎麼配置數據源。註解配置先要知道幾個註解:@Configuration:此註解看用理解為spring的一個xml文件@PropertySource:對應原xml中設置配置文件的@MapperScan
  • spring和spring boot常用註解及使用
    @Component把普通pojo實例化到spring容器中,相當於配置文件中的 <bean id=""/>。6.這樣就可以把User對象的name配置到LinkInfoController的name屬性中。13. @Transactional用於配置spring事務管理。
  • 上盤硬菜,@Transaction源碼深度解析 | Spring系列第48篇
    配置類上添加 @EnableTransactionManagement 註解,這步特別重要,別給忘了,有了這個註解之後,@Trasaction標註的方法才會生效。,重點是他的selectImports方法,這個方法會返回一個類名數組,spring容器啟動過程中會自動調用這個方法,將這個方法指定的類註冊到spring容器中;方法的參數是AdviceMode,這個就是@EnableTransactionManagement註解中mode屬性的值,默認是PROXY,所以會走到@1代碼處public class
  • 那些讓你愛不釋手的 Spring 代碼技巧
    某個功能是否開啟,在配置文件中有個參數可以對它進行控制。如果你有遇到過上述這些問題,那麼恭喜你,本節內容非常適合你。如果不存在B類,則不會實例化A類。有人可能會問:不是判斷有沒有某個jar嗎?怎麼現在判斷某個類了?這個註解有個升級版的應用場景:比如common工程中寫了一個發消息的工具類mqTemplate,業務工程引用了common工程,只需再引入消息中間件,比如rocketmq的jar包,就能開啟mqTemplate的功能。
  • Spring自定義轉換類,讓@Value更方便
    Spring為大家內置了不少開箱即用的轉換類,如字符串轉數字、字符串轉時間等,但有時候需要使用自定義的屬性,則需要自定義轉換類了。但這樣做有一些問題:無法做配置檢驗,不管是否配置錯誤,String類型的屬性都是可以讀取的;3 自定義轉換類使用自定義轉換類是更方便和安全的做法。我們來看看怎麼實現。
  • 自定義標籤與解析 - Spring解密
    在 上一節 Spring解密 - 默認標籤的解析 中,重點分析了 Spring 對 默認標籤是如何解析的,那麼本章繼續講解標籤解析,著重講述如何對 自定義標籤進行解析。1.創建一個類實現 BeanDefinitionParser 接口(也可繼承 Spring 提供的類),用來解析 XSD 文件中的定義和組件定義public class ApplicationBeanDefinitionParser extends AbstractSingleBeanDefinitionParser { @Override
  • 2021-Java後端工程師面試指南-(SpringBoot+SpringCloud)
    知道SpringBoot的鉤子函數嗎,如何對你項目的啟動和死亡做監控。啟動的時候,比如CommandLineRunner 重寫它的run方法,就能在啟動的時候做一個鉤子函數,比如連結釘釘等意外宕機也是可以的,   @PreDestroy 這個註解也能實現,在宕機之前回調這個方法,實現釘釘機器人等。了解spring boot 中的spring factories 機制嗎?
  • 快速創建 Spring Cloud 應用的 Spring Initializr 使用及原理
    使用篇由於 spring-initializr 提供了靈活的擴展能力,以及豐富的默認實現;其使用方式也是非常的靈活多變;為了便於說明,我們直接通過 start.spring.io ,看看 Spring 自己是怎麼使用這套框架的。
  • Spring Boot 和 Spring 到底有啥區別?
    相信對於用了SpringBoot很久的同學來說,還不是很理解SpringBoot到底和Spring有什麼區別,看完文章中的比較,或許你有了不同的答案和看法!作為Java開發人員,大家都Spring都不陌生,簡而言之,Spring框架為開發Java應用程式提供了全面的基礎架構支持。
  • Spring Boot 配置文件 bootstrap / application 到底有什麼區別?
    用過 Spring Boot 的都知道在 Spring Boot 中有以下兩種配置文件
  • Spring 和 Spring Boot 之間到底有啥區別?
    3、提供的 starters 簡化構建配置4、儘可能自動配置 spring應用。5、提供生產指標,例如指標、健壯檢查和外部化配置6、完全沒有代碼生成和 XML配置要求從配置分析Maven依賴首先,讓我們看一下使用Spring創建Web應用程式所需的最小依賴項<dependency><groupId>org.springframework</groupId><artifactId
  • 學渣,你真的知道spring的意思嗎?
    今天以春天spring這個單詞為例,讓你了解一點英語的演變規則。Spring作為春天的意思,我們小學的時候就有學過,這不足為奇。下面我們由作為春天的意思去推理出spring其他的意思。1.Spring泉水、小溪我們知道,春天到了,雨水會增多,那麼山上的樹木、土壤就會變的溼潤,越積越多,最後從山腳下滲出來,滲出來的水就叫泉水,所以,spring衍生出泉水的意思。泉水匯聚到溝渠裡,慢慢的形成了小溪,所以,spring衍生出小溪的意思。
  • Spring常用註解類
    Java5.0過後開始支持註解,Spring作為java中的領軍框架,在Spring2.5過後也開始放棄xml配置文件了,更多的推薦註解來使用Spring框架。Spring中那麼多的註解,其中很多可能在工作中很少能遇見,也就用不上。下面就給大家總結一些常用的註解。