溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊(cè)×
其他方式登錄
點(diǎn)擊 登錄注冊(cè) 即表示同意《億速云用戶服務(wù)條款》

如何從原理上理解MyBatis對(duì)Spring源碼的擴(kuò)展實(shí)現(xiàn)

發(fā)布時(shí)間:2021-12-18 14:59:37 來源:億速云 閱讀:180 作者:柒染 欄目:大數(shù)據(jù)

今天就跟大家聊聊有關(guān)如何從原理上理解MyBatis對(duì)Spring源碼的擴(kuò)展實(shí)現(xiàn),可能很多人都不太了解,為了讓大家更加了解,小編給大家總結(jié)了以下內(nèi)容,希望大家根據(jù)這篇文章可以有所收獲。

今天我們大概從以下幾點(diǎn)去講解MyBatis對(duì)于Spring的一個(gè)擴(kuò)展思路!

如何從原理上理解MyBatis對(duì)Spring源碼的擴(kuò)展實(shí)現(xiàn)

一、FactoryBean是干什么?

首先我們至少要知道一個(gè)事情,就是FactoryBean的一個(gè)大致結(jié)構(gòu):

如何從原理上理解MyBatis對(duì)Spring源碼的擴(kuò)展實(shí)現(xiàn)      
FactoryBean的大致結(jié)構(gòu)

可以看到,整個(gè) FactoryBean有三個(gè)方法:

  • getObject(): 返回具體創(chuàng)建的真實(shí)對(duì)象!
  • getObjectType(): 返回創(chuàng)建對(duì)象的類型!
  • isSingleton(): 創(chuàng)建的該對(duì)象是不是單例對(duì)象!

此時(shí),至少我們已經(jīng)知道了,我們可以通過一個(gè)FactoryBean來生產(chǎn)一個(gè)對(duì)象,可以獲取這個(gè)對(duì)象的類型以及這個(gè)對(duì)象是不是單例!但是離開了Spring它就什么也不是,那么Spring封裝這個(gè)東西是干嘛的呢?

     

1. 自定義Spring實(shí)例化的bean

正是因?yàn)镕actoryBean的存在我們才能夠插手或者改變一個(gè)Bean的創(chuàng)建過程!,為什么這么說呢?我舉個(gè)例子:

就拿大家常用的MyBatis為例,我們都知道MyBatis的使用一般都是使用一個(gè)接口,映射一個(gè)XML文件,MyBatis內(nèi)部經(jīng)過動(dòng)態(tài)代理,動(dòng)態(tài)的為接口生成一個(gè)實(shí)現(xiàn)類,從而讓我們能夠通過接口直接調(diào)用里面的邏輯!

但是MyBatis通過Spring管理之后,同學(xué)們是否疑惑過,我們明明沒有使用MyBatis那一套邏輯,僅僅通過一個(gè)@Autowired注解,就能夠直接注入到Service使用,那么MyBatis的動(dòng)態(tài)代理邏輯大概是在哪里做的?

沒錯(cuò)就是在FactoryBean里面做的!

如何從原理上理解MyBatis對(duì)Spring源碼的擴(kuò)展實(shí)現(xiàn)      
MyBatis使用FactoryBean進(jìn)行動(dòng)態(tài)代理

熟悉MyBatis用法的同學(xué)看到這個(gè)代碼是不是就十分的熟悉了?這一段正是MyBatis通過接口生成動(dòng)態(tài)代理的一段邏輯!那么此時(shí)我們至少知道了Spring能夠FactoryBean調(diào)用 getObject()方法能夠創(chuàng)建一個(gè)對(duì)象,并把對(duì)象管理起來!

     

2. 不遵循Spring的生命周期

這個(gè)為什么呢?作者的想法是,正是因?yàn)镾pring的作者想要放權(quán)給使用者,讓使用者自己實(shí)現(xiàn)創(chuàng)建一個(gè)bean的邏輯,所以Spring并不會(huì)過多的插手該Bean的實(shí)例化過程,使得一個(gè)Bean的實(shí)例化完全又使用者本人去實(shí)現(xiàn)!

這個(gè)類并不會(huì)像其它普通的bean那樣在Spring容器初始化的時(shí)候就進(jìn)行實(shí)例化,而是會(huì)類似于懶加載的一種機(jī)制,再獲取的時(shí)候才會(huì)進(jìn)行創(chuàng)建和返回!至于是不是單例,要取決于isSingleton()方法的返回值!

當(dāng)然,這個(gè)創(chuàng)建出來的bean也會(huì)被緩存,AOP等邏輯也會(huì)對(duì)該類生效,當(dāng)然這都是后話!

     

3. FactoryBean的總結(jié)

相信上述文章看完之后你對(duì)Factory會(huì)有一個(gè)基本的認(rèn)識(shí),我們總結(jié)以下Spring調(diào)用它的基本流程!

如何從原理上理解MyBatis對(duì)Spring源碼的擴(kuò)展實(shí)現(xiàn)      
FactoryBean的調(diào)用流程
     

二、自定義掃描器

Spring只是一個(gè)項(xiàng)目管理的框架,他也是由JAVA語言編寫的,所以它必須遵循JAVA語法的規(guī)范!我們能夠使用Spring幫助我們管理我們開發(fā)過程中的一些類,能夠自動(dòng)注入或者AOP代理等邏輯!

但是我們是否發(fā)現(xiàn),Spring它只能夠管理我們指定的包下的類,或者我們手動(dòng)添加的一些類!而且Spring也沒有辦法去幫我們掃描一些抽象類或者接口,但是我們有時(shí)候因?yàn)橐恍┨厥獾拈_發(fā),我們必須要打破Spring原有的掃描過程,比如我們就要Spring幫我們管理一個(gè)接口、幫我們掃描一些加了特定注解的類等特殊需求,這個(gè)時(shí)候,我們就不能夠使用Spring為我們提供的掃描邏輯了,需要我們自定義一個(gè)掃描邏輯!

     

1. 栗子

舉個(gè)例子(我們還是以MyBatis為例):

我們通過上面FactoryBean的學(xué)習(xí)我們理解了一件事,Spring中MyBatis能夠通過FactoryBean進(jìn)行動(dòng)態(tài)代理的創(chuàng)建并返回,但是我們都知道使用jdk動(dòng)態(tài)代理所必須的一個(gè)元素:接口,因?yàn)閖dk動(dòng)態(tài)代理就是基于接口來做的!

這些接口從哪里來呢?要知道Spring是不會(huì)把接口也掃描的,所以此時(shí)就需要我們的自定義掃描器了,我們使用自定義掃描器將接口掃描到,然后通過修改BeanDefinition強(qiáng)行指定為FactoryBean類型的bean, 把我們的接口傳入進(jìn)去,然后再將BeanDefinition加入bean工廠,此時(shí)我們需要的一個(gè)必須元素接口就有了!

如何從原理上理解MyBatis對(duì)Spring源碼的擴(kuò)展實(shí)現(xiàn)      
自定義掃描器結(jié)合FactoryBean
     

三、ImportBeanDefinitionRegistrar

     

1. 調(diào)用時(shí)機(jī)

ImportBeanDefinitionRegistrar也是Spring生命周期中重要的一環(huán),上周我們學(xué)到,Spring再執(zhí)行BeanFactoryPostProcessor時(shí),會(huì)實(shí)現(xiàn)執(zhí)行系統(tǒng)內(nèi)置的一個(gè)后置處理器---ConfigurationClassPostProcessor,它的作用就是掃描項(xiàng)目指定路徑下的類,轉(zhuǎn)換成對(duì)應(yīng)的BeanDefinition!但是它的作用可不止這一個(gè)哦!

它除了有掃描指定包下的類的功能,還有解析@Import注解的功能,ImportBeanDefinitionRegistrar就是@Import中一個(gè)比較特殊的類,它會(huì)被Spring自動(dòng)的回調(diào)內(nèi)部的registerBeanDefinitions()方法!

那么由此可知它的調(diào)用時(shí)機(jī)再ConfigurationClassPostProcessor之后,剩余其他的所有BeanFactoryPostProcessor之前

     

2. 回調(diào)方法以及意義

上面我們也說到了,他會(huì)回調(diào)registerBeanDefinitions()方法,那么意義何在呢?如果只是能夠進(jìn)行回調(diào)的話,BeanDefinitionRegistryPostProcessor也能完成類似的功能,它的特殊之處在于什么呢?我們看一下它的方法簽名!

如何從原理上理解MyBatis對(duì)Spring源碼的擴(kuò)展實(shí)現(xiàn)      
image-20200914224036880

我們重點(diǎn)關(guān)注第一個(gè)參數(shù),他在回調(diào)的時(shí)候,會(huì)將標(biāo)注@Import注解的類的所有的元信息封裝成AnnotationMetadata類,攜帶回去!

那么攜帶回去有什么意義呢?舉個(gè)例子,依舊以MyBatis為例!

我們?cè)囅胍韵拢厦嫖覀冋f呢,我們可以通過自定義掃描器將一個(gè)個(gè)接口轉(zhuǎn)換成FactoryBean然后交給Spring管理,但是我們要掃描那個(gè)包下的類呢?

使用過Spring整合MyBatis的人都應(yīng)該知道,我們一般都會(huì)在啟動(dòng)類上標(biāo)注一個(gè)注解@MapperScan指定Mapper接口的包路徑,它的目的就是為了向registerBeanDefinitions方法傳遞掃描的路徑,以此完成掃描!

如何從原理上理解MyBatis對(duì)Spring源碼的擴(kuò)展實(shí)現(xiàn)      
image-20200914225321751
     

四、BeanDefinitionRegistryPostProcessor

     

1. 概念

雖然這個(gè)BeanDefinitionRegistryPostProcessor上周復(fù)習(xí)的時(shí)候,我做過大量的源碼層面的講解!但是今天依舊要簡(jiǎn)單說一下!

上周的學(xué)習(xí)我們知道BeanDefinitionRegistryPostProcessorBeanFactoryPostProcessor的子類,他們兩個(gè)有什么區(qū)別嗎?

我們要知道,BeanFactoryPostProcessor只能夠?qū)σ呀?jīng)存在的 BeanDefinition進(jìn)行修改,但是沒有辦法進(jìn)行添加和刪除,但是BeanDefinitionRegistryPostProcessor不一樣,他對(duì)父類進(jìn)行了擴(kuò)展,提供了添加和刪除的API,我們可以通過該類進(jìn)行增加和刪除bean工廠的BeanDefinition!

     

2.舉個(gè)例子

我們依舊是以MyBatis為例!

我們此時(shí)通過自定義掃描器把接口轉(zhuǎn)換成了一個(gè)bd,但是我們要如何向Spring工廠添加我們掃描到的Bd呢?就是使用這個(gè)BeanDefinitionRegistryPostProcessor來進(jìn)行注冊(cè)bean定義!

如何從原理上理解MyBatis對(duì)Spring源碼的擴(kuò)展實(shí)現(xiàn)      
BeanDefinitionRegistryPostProcessor
     

五、MyBatis如何擴(kuò)展的Spring呢?

     

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

我相信,通過上面的關(guān)鍵點(diǎn)的講解,你現(xiàn)在心里應(yīng)該有了一個(gè)差不多的概念!MyBatis擴(kuò)展Spring的方式大概如下:

  1. 首先我們需要在配置類標(biāo)注一個(gè)注解MapperScan,并且傳入Mapper接口所在包路徑!

  2. MapperScan會(huì)通過@Import注解向Spring注入一個(gè)MapperScannerRegistrar類,他是ImportBeanDefinitionRegistrar類型的,會(huì)被Spring自動(dòng)回調(diào)registerBeanDefinitions方法!

  3. MapperScannerRegistrarregisterBeanDefinitions方法會(huì)構(gòu)建一個(gè)類型為MapperScannerConfigurerBeanDefinition ,他是BeanDefinitionRegistryPostProcessor類型的!然后注冊(cè)進(jìn)Spring容器里面!

  4. Spring生命周期會(huì)自動(dòng)回調(diào)MapperScannerConfigurerpostProcessBeanDefinitionRegistry方法!

  5. postProcessBeanDefinitionRegistry方法內(nèi)部創(chuàng)建了一個(gè)自定義的掃描器ClassPathMapperScanner,掃描你傳入的包路徑下的所有的接口,并轉(zhuǎn)換為BeanDefinition !

  6. 獲取到所有指定接口的BeanDefinition之后,遍歷所有的BeanDefinition,然后修改他的BeanClassMapperFactoryBean類,他是FactoryBean類型的!

  7. 設(shè)置完BeanClass之后,通過definition.getPropertyValues().add()方法,傳入該BeanDefinition代表的接口!

  8. 將所有的BeanDefinition通過 6、7步驟設(shè)置之后,全部注冊(cè)到bean工廠中!由BeanFactory對(duì)這些FactoryBean進(jìn)行管理,和生命周期的管理!

    注意,此時(shí)這些類并沒有被實(shí)例化,被實(shí)例化的是你傳入的FactoryBean類,真實(shí)的類還沒有被實(shí)例化!

     

2. 擴(kuò)展步驟(實(shí)例化步驟)

  1. 在使用或者獲取這些bean的時(shí)候,Spring會(huì)首先獲取你要使用的接口類型!
  2. 遍歷當(dāng)前容器內(nèi)所有的bean逐個(gè)對(duì)比,當(dāng)有匹配的直接返回!但是,因?yàn)镸apper接口還并沒有被實(shí)例化!所以并沒有找到,所以在遍歷到         FactoryBean的時(shí)候,會(huì)調(diào)用         getObjectType方法,將返回值與你要使用的接口類型作比對(duì)!
  3. 當(dāng) FactoryBean的返回類型匹配的時(shí)候,Spring會(huì)調(diào)用         FactoryBean的         getObject方法將對(duì)象創(chuàng)建出來!
  4. 創(chuàng)建過程中,通過之前傳入的接口,做         jdk動(dòng)態(tài)代理,完成MyBatis的代理邏輯!
  5. 對(duì)象創(chuàng)建完成后,通過         isSingleton方法的返回值判斷,如果是單例對(duì)象,就將該對(duì)象緩存起來!并返回!

至此,我們完成了整個(gè)MyBatis整合Spring的全部過程!

     

3.源碼重點(diǎn)講解

     
1)自定義掃描器

在MyBatis內(nèi)部是如何自定義掃描器的呢?而且還能打破Spring原有的掃描流程,將接口掃描進(jìn)項(xiàng)目!

如何從原理上理解MyBatis對(duì)Spring源碼的擴(kuò)展實(shí)現(xiàn)      
image-20200915215932029

整段代碼大致分為兩部分:

  1. 毋庸置疑,他是創(chuàng)建了一個(gè)Mybatis自己的掃描器,這個(gè)掃描器是ClassPathBeanDefinitionScanner子類,這也是Spring為我們提供的擴(kuò)展點(diǎn)之一,我們可以基于該掃描器,擴(kuò)展任意的類變成bd,當(dāng)然,他需要符合我們的預(yù)設(shè)規(guī)則!什么是預(yù)設(shè)規(guī)則呢?我們可以看到在我圈的第一個(gè)紅框里面似乎做了一個(gè)注冊(cè)的操作,注冊(cè)的什么呢?

    如何從原理上理解MyBatis對(duì)Spring源碼的擴(kuò)展實(shí)現(xiàn)          
    image-20200915220235102

通常情況下該判斷就都是為true的,所以這里會(huì)執(zhí)行一個(gè)添加的邏輯,添加到哪里了呢?

如何從原理上理解MyBatis對(duì)Spring源碼的擴(kuò)展實(shí)現(xiàn)      
image-20200915220432302

它添加到了一個(gè)集合里面!至此,我們至少知道了,這里會(huì)向集合里面添加一個(gè)過濾器,至于有什么用,我們后面會(huì)說到,你這里先記住!

  1. 我們?cè)倏吹诙€(gè)紅框,開始執(zhí)行掃描操作了!具體里面的代碼我就不粘貼了,他會(huì)調(diào)用父類的掃描邏輯,我們直接看父類是如何做的!

    如何從原理上理解MyBatis對(duì)Spring源碼的擴(kuò)展實(shí)現(xiàn)          
    image-20200915220818278


    這里將包路徑轉(zhuǎn)換為對(duì)應(yīng)的bd,如何做的呢?

如何從原理上理解MyBatis對(duì)Spring源碼的擴(kuò)展實(shí)現(xiàn)      
image-20200915221123343

這么長的邏輯,我們重點(diǎn)關(guān)注兩個(gè)判斷:

  • 第一個(gè)判斷,會(huì)判斷該類是否被過濾,到底該不該轉(zhuǎn)換為         BeanDefinition,還記得我們剛剛注冊(cè)的那個(gè)過濾器嗎?一個(gè)過濾器被添加進(jìn)集合里面了,他就是在這里被使用的!
如何從原理上理解MyBatis對(duì)Spring源碼的擴(kuò)展實(shí)現(xiàn)      
image-20200915221801594

因?yàn)槟莻€(gè)過濾器的定義所以這里一定會(huì)返回為true!m所以我們第一個(gè)判斷過了!一個(gè)類別轉(zhuǎn)換成了BeanDefinition

  • 第二個(gè)判斷,會(huì)調(diào)用子類的         isCandidateComponent方法,這里是判斷一個(gè)類到底需不需要被添加進(jìn)集合里面返回,我們常識(shí)得知,Spring是不會(huì)替我們管理一個(gè)接口類的,但是Mapper類又偏偏是一個(gè)接口,所以這時(shí)MyBatis不得不改寫原有的邏輯使得它支持掃描接口并轉(zhuǎn)換為bd,我們看下里面的邏輯!
如何從原理上理解MyBatis對(duì)Spring源碼的擴(kuò)展實(shí)現(xiàn)      
image-20200915221458473

因?yàn)镸yBatis的Mapper類是一個(gè)接口,所以這里會(huì)返回為true!  所以我們第二個(gè)判斷進(jìn)去了,一個(gè)接口的BeanDefinition被添加進(jìn)集合!并返回!

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

     
2)通過BeanDefinition操作創(chuàng)建流程
如何從原理上理解MyBatis對(duì)Spring源碼的擴(kuò)展實(shí)現(xiàn)      
image-20200915222512900
  • 他會(huì)循環(huán)遍歷所有掃描到的接口bd,向每一個(gè)bd的構(gòu)造方法傳遞一個(gè)值,他是當(dāng)前bd所代表的接口的全限定名!

    上面介紹MyBatis擴(kuò)展FactoryBean的時(shí)候說到!它通過jdk創(chuàng)建動(dòng)態(tài)代理,但是接口時(shí)哪里來的?就是通過

     definition.getConstructorArgumentValues().addGenericArgumentValue(beanClassName);
           

    注入進(jìn)去的!我們都知道Spring創(chuàng)建對(duì)象是基于definition創(chuàng)建的,所以,我們可以通過definition來注入我們想要注入的值,他常用的用法還有類似下面的:

    如何從原理上理解MyBatis對(duì)Spring源碼的擴(kuò)展實(shí)現(xiàn)          
    image-20200915223229818

    MyBatis 中正是使用構(gòu)造函數(shù) 的方式注入了一個(gè)接口的值!

    如何從原理上理解MyBatis對(duì)Spring源碼的擴(kuò)展實(shí)現(xiàn)          
    image-20200915223354790
  • 強(qiáng)行將接口的類型轉(zhuǎn)換為FactoryBean類型的!

    他是為了延遲初始化,使用jdk動(dòng)態(tài)代理返回一個(gè)對(duì)象!從而完成MyBatis的功能!

看完上述內(nèi)容,你們對(duì)如何從原理上理解MyBatis對(duì)Spring源碼的擴(kuò)展實(shí)現(xiàn)有進(jìn)一步的了解嗎?如果還想了解更多知識(shí)或者相關(guān)內(nèi)容,請(qǐng)關(guān)注億速云行業(yè)資訊頻道,感謝大家的支持。

向AI問一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI