您好,登錄后才能下訂單哦!
今天就跟大家聊聊有關(guān)如何從原理上理解MyBatis對(duì)Spring源碼的擴(kuò)展實(shí)現(xiàn),可能很多人都不太了解,為了讓大家更加了解,小編給大家總結(jié)了以下內(nèi)容,希望大家根據(jù)這篇文章可以有所收獲。
今天我們大概從以下幾點(diǎn)去講解MyBatis對(duì)于Spring的一個(gè)擴(kuò)展思路!
首先我們至少要知道一個(gè)事情,就是FactoryBean的一個(gè)大致結(jié)構(gòu):
可以看到,整個(gè) FactoryBean有三個(gè)方法:
此時(shí),至少我們已經(jīng)知道了,我們可以通過一個(gè)FactoryBean來生產(chǎn)一個(gè)對(duì)象,可以獲取這個(gè)對(duì)象的類型以及這個(gè)對(duì)象是不是單例!但是離開了Spring它就什么也不是,那么Spring封裝這個(gè)東西是干嘛的呢?
正是因?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用法的同學(xué)看到這個(gè)代碼是不是就十分的熟悉了?這一段正是MyBatis通過接口生成動(dòng)態(tài)代理的一段邏輯!那么此時(shí)我們至少知道了Spring能夠FactoryBean調(diào)用 getObject()方法能夠創(chuàng)建一個(gè)對(duì)象,并把對(duì)象管理起來!
這個(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)然這都是后話!
相信上述文章看完之后你對(duì)Factory會(huì)有一個(gè)基本的認(rèn)識(shí),我們總結(jié)以下Spring調(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è)掃描邏輯!
舉個(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è)必須元素接口
就有了!
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之前
!
上面我們也說到了,他會(huì)回調(diào)registerBeanDefinitions()
方法,那么意義何在呢?如果只是能夠進(jìn)行回調(diào)的話,BeanDefinitionRegistryPostProcessor
也能完成類似的功能,它的特殊之處在于什么呢?我們看一下它的方法簽名!
我們重點(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
方法傳遞掃描的路徑,以此完成掃描!
雖然這個(gè)BeanDefinitionRegistryPostProcessor
上周復(fù)習(xí)的時(shí)候,我做過大量的源碼層面的講解!但是今天依舊要簡(jiǎn)單說一下!
上周的學(xué)習(xí)我們知道BeanDefinitionRegistryPostProcessor
是BeanFactoryPostProcessor
的子類,他們兩個(gè)有什么區(qū)別嗎?
我們要知道,BeanFactoryPostProcessor
只能夠?qū)σ呀?jīng)存在的 BeanDefinition
進(jìn)行修改,但是沒有辦法進(jìn)行添加和刪除,但是BeanDefinitionRegistryPostProcessor
不一樣,他對(duì)父類進(jìn)行了擴(kuò)展,提供了添加和刪除的API,我們可以通過該類進(jìn)行增加和刪除bean工廠的BeanDefinition
!
我們依舊是以MyBatis為例!
我們此時(shí)通過自定義掃描器把接口轉(zhuǎn)換成了一個(gè)bd,但是我們要如何向Spring工廠添加我們掃描到的Bd呢?就是使用這個(gè)BeanDefinitionRegistryPostProcessor
來進(jìn)行注冊(cè)bean定義!
我相信,通過上面的關(guān)鍵點(diǎn)的講解,你現(xiàn)在心里應(yīng)該有了一個(gè)差不多的概念!MyBatis擴(kuò)展Spring的方式大概如下:
首先我們需要在配置類標(biāo)注一個(gè)注解MapperScan
,并且傳入Mapper接口所在包路徑!
MapperScan
會(huì)通過@Import
注解向Spring注入一個(gè)MapperScannerRegistrar
類,他是ImportBeanDefinitionRegistrar
類型的,會(huì)被Spring自動(dòng)回調(diào)registerBeanDefinitions
方法!
MapperScannerRegistrar
的registerBeanDefinitions
方法會(huì)構(gòu)建一個(gè)類型為MapperScannerConfigurer
的BeanDefinition
,他是BeanDefinitionRegistryPostProcessor
類型的!然后注冊(cè)進(jìn)Spring容器里面!
Spring生命周期會(huì)自動(dòng)回調(diào)MapperScannerConfigurer
的postProcessBeanDefinitionRegistry
方法!
postProcessBeanDefinitionRegistry
方法內(nèi)部創(chuàng)建了一個(gè)自定義的掃描器ClassPathMapperScanner
,掃描你傳入的包路徑下的所有的接口,并轉(zhuǎn)換為BeanDefinition
!
獲取到所有指定接口的BeanDefinition
之后,遍歷所有的BeanDefinition
,然后修改他的BeanClass
為MapperFactoryBean
類,他是FactoryBean
類型的!
設(shè)置完BeanClass之后,通過definition.getPropertyValues().add()
方法,傳入該BeanDefinition
代表的接口!
將所有的BeanDefinition
通過 6、7步驟設(shè)置之后,全部注冊(cè)到bean工廠中!由BeanFactory對(duì)這些FactoryBean進(jìn)行管理,和生命周期的管理!
注意,此時(shí)這些類并沒有被實(shí)例化,被實(shí)例化的是你傳入的FactoryBean
類,真實(shí)的類還沒有被實(shí)例化!
FactoryBean
的時(shí)候,會(huì)調(diào)用
getObjectType
方法,將返回值與你要使用的接口類型作比對(duì)!FactoryBean
的
getObject
方法將對(duì)象創(chuàng)建出來!jdk動(dòng)態(tài)代理
,完成MyBatis的代理邏輯!isSingleton
方法的返回值判斷,如果是單例對(duì)象,就將該對(duì)象緩存起來!并返回!至此,我們完成了整個(gè)MyBatis整合Spring的全部過程!
在MyBatis內(nèi)部是如何自定義掃描器的呢?而且還能打破Spring原有的掃描流程,將接口掃描進(jìn)項(xiàng)目!
整段代碼大致分為兩部分:
毋庸置疑,他是創(chuàng)建了一個(gè)Mybatis自己的掃描器,這個(gè)掃描器是ClassPathBeanDefinitionScanner
子類,這也是Spring為我們提供的擴(kuò)展點(diǎn)之一,我們可以基于該掃描器,擴(kuò)展任意的類變成bd,當(dāng)然,他需要符合我們的預(yù)設(shè)規(guī)則!什么是預(yù)設(shè)規(guī)則呢?我們可以看到在我圈的第一個(gè)紅框里面似乎做了一個(gè)注冊(cè)的操作,注冊(cè)的什么呢?
通常情況下該判斷就都是為true的,所以這里會(huì)執(zhí)行一個(gè)添加的邏輯,添加到哪里了呢?
它添加到了一個(gè)集合里面!至此,我們至少知道了,這里會(huì)向集合里面添加一個(gè)過濾器,至于有什么用,我們后面會(huì)說到,你這里先記住!
我們?cè)倏吹诙€(gè)紅框,開始執(zhí)行掃描操作了!具體里面的代碼我就不粘貼了,他會(huì)調(diào)用父類的掃描邏輯,我們直接看父類是如何做的!
這里將包路徑轉(zhuǎn)換為對(duì)應(yīng)的bd,如何做的呢?
這么長的邏輯,我們重點(diǎn)關(guān)注兩個(gè)判斷:
BeanDefinition
,還記得我們剛剛注冊(cè)的那個(gè)過濾器嗎?一個(gè)過濾器被添加進(jìn)集合里面了,他就是在這里被使用的!因?yàn)槟莻€(gè)過濾器的定義所以這里一定會(huì)返回為true!m所以我們第一個(gè)判斷過了!一個(gè)類別轉(zhuǎn)換成了BeanDefinition
isCandidateComponent
方法,這里是判斷一個(gè)類到底需不需要被添加進(jìn)集合里面返回,我們常識(shí)得知,Spring是不會(huì)替我們管理一個(gè)接口類的,但是Mapper類又偏偏是一個(gè)接口,所以這時(shí)MyBatis不得不改寫原有的邏輯使得它支持掃描接口并轉(zhuǎn)換為bd,我們看下里面的邏輯!因?yàn)镸yBatis的Mapper類是一個(gè)接口,所以這里會(huì)返回為true! 所以我們第二個(gè)判斷進(jìn)去了,一個(gè)接口的BeanDefinition
被添加進(jìn)集合!并返回!
至此,我們大概知道了掃描器的工作原理!我們看一下將接口掃描到之后做了那些操作呢?
他會(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 中正是使用構(gòu)造函數(shù) 的方式注入了一個(gè)接口的值!
強(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è)資訊頻道,感謝大家的支持。
免責(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)容。