帶你跳出源碼地獄,從原理上理解MyBatis對Spring源碼的擴展實現

有道無術,術尚可求也!有術無道,止於術!

今天咱們大概從如下幾點去講解MyBatis對於Spring的一個擴展思路!java

大綱

本文章只對原理和部分重要代碼進行分析,源碼的詳細分析請跳轉到:【 牛逼哄哄的Spring是怎麼被MyBatis給征服了?】 具體觀看,本文帶你從原理上全面的理解MyBatis擴展Spring源碼的體系!

1、FactoryBean是幹什麼?

首先咱們至少要知道一個事情,就是FactoryBean的一個大體結構:web

FactoryBean的大體結構

能夠看到,整個 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使用FactoryBean進行動態代理

熟悉MyBatis用法的同窗看到這個代碼是否是就十分的熟悉了?這一段正是MyBatis經過接口生成動態代理的一段邏輯!那麼此時咱們至少知道了Spring可以FactoryBean調用 getObject()方法可以建立一個對象,並把對象管理起來!post

2. 不遵循Spring的生命週期

這個爲何呢?做者的想法是,正是由於Spring的做者想要放權給使用者,讓使用者本身實現建立一個bean的邏輯,因此Spring並不會過多的插手該Bean的實例化過程,使得一個Bean的實例化徹底又使用者本人去實現!學習

這個類並不會像其它普通的bean那樣在Spring容器初始化的時候就進行實例化,而是會相似於懶加載的一種機制,再獲取的時候纔會進行建立和返回!至因而不是單例,要取決於isSingleton()方法的返回值!

固然,這個建立出來的bean也會被緩存,AOP等邏輯也會對該類生效,固然這都是後話!

3. FactoryBean的總結

相信上述文章看完以後你對Factory會有一個基本的認識,咱們總結如下Spring調用它的基本流程!

FactoryBean的調用流程

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工廠,此時咱們須要的一個必須元素接口就有了!

自定義掃描器結合FactoryBean

3、ImportBeanDefinitionRegistrar

1. 調用時機

ImportBeanDefinitionRegistrar也是Spring生命週期中重要的一環,上週咱們學到,Spring再執行BeanFactoryPostProcessor時,會實現執行系統內置的一個後置處理器---ConfigurationClassPostProcessor,它的做用就是掃描項目指定路徑下的類,轉換成對應的BeanDefinition!可是它的做用可不止這一個哦!

它除了有掃描指定包下的類的功能,還有解析@Import註解的功能,ImportBeanDefinitionRegistrar就是@Import中一個比較特殊的類,它會被Spring自動的回調內部的registerBeanDefinitions()方法!

那麼由此可知它的調用時機再ConfigurationClassPostProcessor以後剩餘其餘的全部BeanFactoryPostProcessor以前

2. 回調方法以及意義

上面咱們也說到了,他會回調registerBeanDefinitions()方法,那麼意義何在呢?若是隻是可以進行回調的話,BeanDefinitionRegistryPostProcessor也能完成相似的功能,它的特殊之處在於什麼呢?咱們看一下它的方法簽名!

image-20200914224036880

咱們重點關注第一個參數,他在回調的時候,會將標註@Import註解的類的全部的元信息封裝成AnnotationMetadata類,攜帶回去!

那麼攜帶回去有什麼意義呢?舉個例子,依舊以MyBatis爲例!

咱們試想如下,上面咱們說呢,咱們能夠經過自定義掃描器將一個個接口轉換成FactoryBean而後交給Spring管理,可是咱們要掃描那個包下的類呢?

使用過Spring整合MyBatis的人都應該知道,咱們通常都會在啓動類上標註一個註解@MapperScan指定Mapper接口的包路徑,它的目的就是爲了向registerBeanDefinitions方法傳遞掃描的路徑,以此完成掃描!

image-20200914225321751

4、BeanDefinitionRegistryPostProcessor

1. 概念

雖然這個BeanDefinitionRegistryPostProcessor上週複習的時候,我作過大量的源碼層面的講解!可是今天依舊要簡單說一下!

上週的學習咱們知道BeanDefinitionRegistryPostProcessorBeanFactoryPostProcessor的子類,他們兩個有什麼區別嗎?

咱們要知道,BeanFactoryPostProcessor只可以對已經存在的 BeanDefinition進行修改,可是沒有辦法進行添加和刪除,可是BeanDefinitionRegistryPostProcessor不同,他對父類進行了擴展,提供了添加和刪除的API,咱們能夠經過該類進行增長和刪除bean工廠的BeanDefinition!

2.舉個例子

咱們依舊是以MyBatis爲例!

咱們此時經過自定義掃描器把接口轉換成了一個bd,可是咱們要如何向Spring工廠添加咱們掃描到的Bd呢?就是使用這個BeanDefinitionRegistryPostProcessor來進行註冊bean定義!

BeanDefinitionRegistryPostProcessor

5、MyBatis如何擴展的Spring呢?

1. 擴展步驟(初始化步驟)

我相信,經過上面的關鍵點的講解,你如今內心應該有了一個差很少的概念!MyBatis擴展Spring的方式大概以下:

  1. 首先咱們須要在配置類標註一個註解MapperScan,而且傳入Mapper接口所在包路徑!

  2. MapperScan會經過@Import註解向Spring注入一個MapperScannerRegistrar類,他是ImportBeanDefinitionRegistrar類型的,會被Spring自動回調registerBeanDefinitions方法!

  3. MapperScannerRegistrarregisterBeanDefinitions方法會構建一個類型爲MapperScannerConfigurerBeanDefinition ,他是BeanDefinitionRegistryPostProcessor類型的!而後註冊進Spring容器裏面!

  4. Spring生命週期會自動回調MapperScannerConfigurerpostProcessBeanDefinitionRegistry方法!

  5. postProcessBeanDefinitionRegistry方法內部建立了一個自定義的掃描器ClassPathMapperScanner,掃描你傳入的包路徑下的全部的接口,並轉換爲BeanDefinition !

  6. 獲取到全部指定接口的BeanDefinition以後,遍歷全部的BeanDefinition,而後修改他的BeanClassMapperFactoryBean類,他是FactoryBean類型的!

  7. 設置完BeanClass以後,經過definition.getPropertyValues().add()方法,傳入該BeanDefinition表明的接口!

  8. 將全部的BeanDefinition經過 六、7步驟設置以後,所有註冊到bean工廠中!由BeanFactory對這些FactoryBean進行管理,和生命週期的管理!

    注意,此時這些類並無被實例化,被實例化的是你傳入的FactoryBean類,真實的類尚未被實例化!

2. 擴展步驟(實例化步驟)

  1. 在使用或者獲取這些bean的時候,Spring會首先獲取你要使用的接口類型!
  2. 遍歷當前容器內全部的bean逐個對比,當有匹配的直接返回!可是,由於Mapper接口還並無被實例化!因此並無找到,因此在遍歷到 FactoryBean的時候,會調用 getObjectType方法,將返回值與你要使用的接口類型做比對!
  3. 當 FactoryBean的返回類型匹配的時候,Spring會調用 FactoryBeangetObject方法將對象建立出來!
  4. 建立過程當中,經過以前傳入的接口,作 jdk動態代理,完成MyBatis的代理邏輯!
  5. 對象建立完成後,經過 isSingleton方法的返回值判斷,若是是單例對象,就將該對象緩存起來!並返回!

至此,咱們完成了整個MyBatis整合Spring的所有過程!

3.源碼重點講解

1)自定義掃描器

在MyBatis內部是如何自定義掃描器的呢?並且還能打破Spring原有的掃描流程,將接口掃描進項目!

image-20200915215932029

整段代碼大體分爲兩部分:

  1. 毋庸置疑,他是建立了一個Mybatis本身的掃描器,這個掃描器是ClassPathBeanDefinitionScanner子類,這也是Spring爲咱們提供的擴展點之一,咱們能夠基於該掃描器,擴展任意的類變成bd,固然,他須要符合咱們的預設規則!什麼是預設規則呢?咱們能夠看到在我圈的第一個紅框裏面彷佛作了一個註冊的操做,註冊的什麼呢?

    image-20200915220235102

一般狀況下該判斷就都是爲true的,因此這裏會執行一個添加的邏輯,添加到哪裏了呢?

image-20200915220432302

它添加到了一個集合裏面!至此,咱們至少知道了,這裏會向集合裏面添加一個過濾器,至於有什麼用,咱們後面會說到,你這裏先記住!

  1. 咱們再看第二個紅框,開始執行掃描操做了!具體裏面的代碼我就不粘貼了,他會調用父類的掃描邏輯,咱們直接看父類是如何作的!

    image-20200915220818278


    這裏將包路徑轉換爲對應的bd,如何作的呢?

image-20200915221123343

這麼長的邏輯,咱們重點關注兩個判斷:

  • 第一個判斷,會判斷該類是否被過濾,到底該不應轉換爲 BeanDefinition,還記得咱們剛剛註冊的那個過濾器嗎?一個過濾器被添加進集合裏面了,他就是在這裏被使用的!
image-20200915221801594

由於那個過濾器的定義因此這裏必定會返回爲true!m因此咱們第一個判斷過了!一個類別轉換成了BeanDefinition

  • 第二個判斷,會調用子類的 isCandidateComponent方法,這裏是判斷一個類到底需不須要被添加進集合裏面返回,咱們常識得知,Spring是不會替咱們管理一個接口類的,可是Mapper類又恰恰是一個接口,因此這時MyBatis不得不改寫原有的邏輯使得它支持掃描接口並轉換爲bd,咱們看下里面的邏輯!
image-20200915221458473

由於MyBatis的Mapper類是一個接口,因此這裏會返回爲true!  因此咱們第二個判斷進去了,一個接口的BeanDefinition被添加進集合!並返回!

至此,咱們大概知道了掃描器的工做原理!咱們看一下將接口掃描到以後作了那些操做呢?

2)經過BeanDefinition操做建立流程

image-20200915222512900
  • 他會循環遍歷全部掃描到的接口bd,向每個bd的構造方法傳遞一個值,他是當前bd所表明的接口的全限定名!

    上面介紹MyBatis擴展FactoryBean的時候說到!它經過jdk建立動態代理,可是接口時哪裏來的?就是經過

     definition.getConstructorArgumentValues().addGenericArgumentValue(beanClassName);

    注入進去的!咱們都知道Spring建立對象是基於definition建立的,因此,咱們能夠經過definition來注入咱們想要注入的值,他經常使用的用法還有相似下面的:

    image-20200915223229818

    MyBatis 中正是使用構造函數 的方式注入了一個接口的值!

    image-20200915223354790
  • 強行將接口的類型轉換爲FactoryBean類型的!

    至於爲何轉換爲FactoryBean文章開篇說的很清楚了,這裏就不詳細贅述了,他是爲了延遲初始化,使用jdk動態代理返回一個對象!從而完成MyBatis的功能!

3. 總結

MyBatis整合Spring的實例化過程

才疏學淺,若是文章中理解有誤,歡迎大佬們私聊指正!歡迎關注做者的公衆號,一塊兒進步,一塊兒學習!



       
❤️「轉發」 「在看」 ,是對我最大的支持❤️



本文分享自微信公衆號 - JAVA程序狗(javacxg)。
若有侵權,請聯繫 support@oschina.cn 刪除。
本文參與「OSC源創計劃」,歡迎正在閱讀的你也加入,一塊兒分享。

相關文章
相關標籤/搜索