有道無術,術尚可求也!有術無道,止於術!
今天咱們大概從如下幾點去講解MyBatis對於Spring的一個擴展思路!java
1、FactoryBean是幹什麼?
首先咱們至少要知道一個事情,就是FactoryBean的一個大體結構:web
能夠看到,整個 FactoryBean有三個方法:緩存
-
getObject(): 返回具體建立的真實對象! -
getObjectType(): 返回建立對象的類型! -
isSingleton(): 建立的該對象是否是單例對象!
此時,至少咱們已經知道了,咱們能夠經過一個FactoryBean來生產一個對象,能夠獲取這個對象的類型以及這個對象是否是單例!可是離開了Spring它就什麼也不是,那麼Spring封裝這個東西是幹嗎的呢?微信
1. 自定義Spring實例化的bean
正是由於FactoryBean的存在咱們纔可以插手或者改變一個Bean的建立過程!,爲何這麼說呢?我舉個例子:app
就拿你們經常使用的MyBatis爲例,咱們都知道MyBatis的使用通常都是使用一個接口,映射一個XML文件,MyBatis內部通過動態代理,動態的爲接口生成一個實現類,從而讓咱們可以經過接口直接調用裏面的邏輯!框架
可是MyBatis經過Spring管理以後,同窗們是否疑惑過,咱們明明沒有使用MyBatis那一套邏輯,僅僅經過一個@Autowired
註解,就可以直接注入到Service使用,那麼MyBatis的動態代理邏輯大概是在哪裏作的?編輯器
沒錯就是在FactoryBean裏面作的!函數
熟悉MyBatis用法的同窗看到這個代碼是否是就十分的熟悉了?這一段正是MyBatis經過接口生成動態代理的一段邏輯!那麼此時咱們至少知道了Spring可以FactoryBean調用 getObject()方法可以建立一個對象,並把對象管理起來!post
2. 不遵循Spring的生命週期
這個爲何呢?做者的想法是,正是由於Spring的做者想要放權給使用者,讓使用者本身實現建立一個bean的邏輯,因此Spring並不會過多的插手該Bean的實例化過程,使得一個Bean的實例化徹底又使用者本人去實現!學習
這個類並不會像其它普通的bean那樣在Spring容器初始化的時候就進行實例化,而是會相似於懶加載的一種機制,再獲取的時候纔會進行建立和返回!至因而不是單例,要取決於isSingleton()方法的返回值!
固然,這個建立出來的bean也會被緩存,AOP等邏輯也會對該類生效,固然這都是後話!
3. FactoryBean的總結
相信上述文章看完以後你對Factory會有一個基本的認識,咱們總結如下Spring調用它的基本流程!
2、自定義掃描器
Spring只是一個項目管理的框架,他也是由JAVA語言編寫的,因此它必須遵循JAVA語法的規範!咱們可以使用Spring幫助咱們管理咱們開發過程當中的一些類,可以自動注入或者AOP代理等邏輯!
可是咱們是否發現,Spring它只可以管理咱們指定的包下的類,或者咱們手動添加的一些類!並且Spring也沒有辦法去幫咱們掃描一些抽象類或者接口,可是咱們有時候由於一些特殊的開發,咱們必需要打破Spring原有的掃描過程,好比咱們就要Spring幫咱們管理一個接口、幫咱們掃描一些加了特定註解的類等特殊需求,這個時候,咱們就不可以使用Spring爲咱們提供的掃描邏輯了,須要咱們自定義一個掃描邏輯!
1. 栗子
舉個例子(咱們仍是以MyBatis爲例):
咱們經過上面FactoryBean的學習咱們理解了一件事,Spring中MyBatis可以經過FactoryBean
進行動態代理的建立並返回,可是咱們都知道使用jdk動態代理所必須的一個元素:接口
,由於jdk動態代理就是基於接口來作的!
這些接口從哪裏來呢?要知道Spring是不會把接口也掃描的,因此此時就須要咱們的自定義掃描器了,咱們使用自定義掃描器將接口掃描到,而後經過修改BeanDefinition
強行指定爲FactoryBean類型的bean, 把咱們的接口傳入進去,而後再將BeanDefinition
加入bean工廠,此時咱們須要的一個必須元素接口
就有了!
3、ImportBeanDefinitionRegistrar
1. 調用時機
ImportBeanDefinitionRegistrar
也是Spring生命週期中重要的一環,上週咱們學到,Spring再執行BeanFactoryPostProcessor
時,會實現執行系統內置的一個後置處理器---ConfigurationClassPostProcessor
,它的做用就是掃描項目指定路徑下的類,轉換成對應的BeanDefinition
!可是它的做用可不止這一個哦!
它除了有掃描指定包下的類的功能,還有解析@Import
註解的功能,ImportBeanDefinitionRegistrar
就是@Import
中一個比較特殊的類,它會被Spring自動的回調內部的registerBeanDefinitions()
方法!
那麼由此可知它的調用時機再ConfigurationClassPostProcessor以後
,剩餘其餘的全部BeanFactoryPostProcessor以前
!
2. 回調方法以及意義
上面咱們也說到了,他會回調registerBeanDefinitions()
方法,那麼意義何在呢?若是隻是可以進行回調的話,BeanDefinitionRegistryPostProcessor
也能完成相似的功能,它的特殊之處在於什麼呢?咱們看一下它的方法簽名!
咱們重點關注第一個參數,他在回調的時候,會將標註@Import
註解的類的全部的元信息封裝成AnnotationMetadata
類,攜帶回去!
那麼攜帶回去有什麼意義呢?舉個例子,依舊以MyBatis爲例!
咱們試想如下,上面咱們說呢,咱們能夠經過自定義掃描器將一個個接口轉換成FactoryBean而後交給Spring管理,可是咱們要掃描那個包下的類呢?
使用過Spring整合MyBatis的人都應該知道,咱們通常都會在啓動類上標註一個註解
@MapperScan
指定Mapper接口的包路徑,它的目的就是爲了向registerBeanDefinitions
方法傳遞掃描的路徑,以此完成掃描!
4、BeanDefinitionRegistryPostProcessor
1. 概念
雖然這個BeanDefinitionRegistryPostProcessor
上週複習的時候,我作過大量的源碼層面的講解!可是今天依舊要簡單說一下!
上週的學習咱們知道BeanDefinitionRegistryPostProcessor
是BeanFactoryPostProcessor
的子類,他們兩個有什麼區別嗎?
咱們要知道,BeanFactoryPostProcessor
只可以對已經存在的 BeanDefinition
進行修改,可是沒有辦法進行添加和刪除,可是BeanDefinitionRegistryPostProcessor
不同,他對父類進行了擴展,提供了添加和刪除的API,咱們能夠經過該類進行增長和刪除bean工廠的BeanDefinition
!
2.舉個例子
咱們依舊是以MyBatis爲例!
咱們此時經過自定義掃描器把接口轉換成了一個bd,可是咱們要如何向Spring工廠添加咱們掃描到的Bd呢?就是使用這個BeanDefinitionRegistryPostProcessor
來進行註冊bean定義!
5、MyBatis如何擴展的Spring呢?
1. 擴展步驟(初始化步驟)
我相信,經過上面的關鍵點的講解,你如今內心應該有了一個差很少的概念!MyBatis擴展Spring的方式大概以下:
-
首先咱們須要在配置類標註一個註解
MapperScan
,而且傳入Mapper接口所在包路徑! -
MapperScan
會經過@Import
註解向Spring注入一個MapperScannerRegistrar
類,他是ImportBeanDefinitionRegistrar
類型的,會被Spring自動回調registerBeanDefinitions
方法! -
MapperScannerRegistrar
的registerBeanDefinitions
方法會構建一個類型爲MapperScannerConfigurer
的BeanDefinition
,他是BeanDefinitionRegistryPostProcessor
類型的!而後註冊進Spring容器裏面! -
Spring生命週期會自動回調
MapperScannerConfigurer
的postProcessBeanDefinitionRegistry
方法! -
postProcessBeanDefinitionRegistry
方法內部建立了一個自定義的掃描器ClassPathMapperScanner
,掃描你傳入的包路徑下的全部的接口,並轉換爲BeanDefinition
! -
獲取到全部指定接口的
BeanDefinition
以後,遍歷全部的BeanDefinition
,而後修改他的BeanClass
爲MapperFactoryBean
類,他是FactoryBean
類型的! -
設置完BeanClass以後,經過
definition.getPropertyValues().add()
方法,傳入該BeanDefinition
表明的接口! -
將全部的
BeanDefinition
經過 六、7步驟設置以後,所有註冊到bean工廠中!由BeanFactory對這些FactoryBean進行管理,和生命週期的管理!注意,此時這些類並無被實例化,被實例化的是你傳入的
FactoryBean
類,真實的類尚未被實例化!
2. 擴展步驟(實例化步驟)
-
在使用或者獲取這些bean的時候,Spring會首先獲取你要使用的接口類型! -
遍歷當前容器內全部的bean逐個對比,當有匹配的直接返回!可是,由於Mapper接口還並無被實例化!因此並無找到,因此在遍歷到 FactoryBean
的時候,會調用getObjectType
方法,將返回值與你要使用的接口類型做比對! -
當 FactoryBean的返回類型匹配的時候,Spring會調用 FactoryBean
的getObject
方法將對象建立出來! -
建立過程當中,經過以前傳入的接口,作 jdk動態代理
,完成MyBatis的代理邏輯! -
對象建立完成後,經過 isSingleton
方法的返回值判斷,若是是單例對象,就將該對象緩存起來!並返回!
至此,咱們完成了整個MyBatis整合Spring的所有過程!
3.源碼重點講解
1)自定義掃描器
在MyBatis內部是如何自定義掃描器的呢?並且還能打破Spring原有的掃描流程,將接口掃描進項目!
整段代碼大體分爲兩部分:
-
毋庸置疑,他是建立了一個Mybatis本身的掃描器,這個掃描器是
ClassPathBeanDefinitionScanner
子類,這也是Spring爲咱們提供的擴展點之一,咱們能夠基於該掃描器,擴展任意的類變成bd,固然,他須要符合咱們的預設規則!什麼是預設規則呢?咱們能夠看到在我圈的第一個紅框裏面彷佛作了一個註冊的操做,註冊的什麼呢?
一般狀況下該判斷就都是爲true的,因此這裏會執行一個添加的邏輯,添加到哪裏了呢?
它添加到了一個集合裏面!至此,咱們至少知道了,這裏會向集合裏面添加一個過濾器,至於有什麼用,咱們後面會說到,你這裏先記住!
-
咱們再看第二個紅框,開始執行掃描操做了!具體裏面的代碼我就不粘貼了,他會調用父類的掃描邏輯,咱們直接看父類是如何作的!
這裏將包路徑轉換爲對應的bd,如何作的呢?
這麼長的邏輯,咱們重點關注兩個判斷:
-
第一個判斷,會判斷該類是否被過濾,到底該不應轉換爲 BeanDefinition
,還記得咱們剛剛註冊的那個過濾器嗎?一個過濾器被添加進集合裏面了,他就是在這裏被使用的!
由於那個過濾器的定義因此這裏必定會返回爲true!m因此咱們第一個判斷過了!一個類別轉換成了BeanDefinition
-
第二個判斷,會調用子類的 isCandidateComponent
方法,這裏是判斷一個類到底需不須要被添加進集合裏面返回,咱們常識得知,Spring是不會替咱們管理一個接口類的,可是Mapper類又恰恰是一個接口,因此這時MyBatis不得不改寫原有的邏輯使得它支持掃描接口並轉換爲bd,咱們看下里面的邏輯!
由於MyBatis的Mapper類是一個接口,因此這裏會返回爲true! 因此咱們第二個判斷進去了,一個接口的BeanDefinition
被添加進集合!並返回!
至此,咱們大概知道了掃描器的工做原理!咱們看一下將接口掃描到以後作了那些操做呢?
2)經過BeanDefinition操做建立流程
-
他會循環遍歷全部掃描到的接口bd,向每個bd的構造方法傳遞一個值,他是當前bd所表明的接口的全限定名!
上面介紹MyBatis擴展FactoryBean的時候說到!它經過jdk建立動態代理,可是接口時哪裏來的?就是經過
definition.getConstructorArgumentValues().addGenericArgumentValue(beanClassName);
注入進去的!咱們都知道Spring建立對象是基於
definition
建立的,因此,咱們能夠經過definition
來注入咱們想要注入的值,他經常使用的用法還有相似下面的:MyBatis 中正是使用構造函數 的方式注入了一個接口的值!
-
強行將接口的類型轉換爲FactoryBean類型的!
至於爲何轉換爲FactoryBean文章開篇說的很清楚了,這裏就不詳細贅述了,他是爲了延遲初始化,使用jdk動態代理返回一個對象!從而完成MyBatis的功能!
3. 總結
才疏學淺,若是文章中理解有誤,歡迎大佬們私聊指正!歡迎關注做者的公衆號,一塊兒進步,一塊兒學習!
❤️「轉發」 和 「在看」 ,是對我最大的支持❤️
本文分享自微信公衆號 - JAVA程序狗(javacxg)。
若有侵權,請聯繫 support@oschina.cn 刪除。
本文參與「OSC源創計劃」,歡迎正在閱讀的你也加入,一塊兒分享。