Spring和Mybatis整合的原理詳解
前言
最近讀完了Spring的IOC部分的源碼,受益匪淺,這篇文章講解一下MyBatis是如何做到與Spring整合的。MyBatis是如何做到干擾Spring的生命周期,把Mapper一個(gè)個(gè)的注冊到Spring容器中的將在這里揭秘。
簡單猜想
因?yàn)殚喿x過Spring源碼后對他有了一定的認(rèn)識,這里可以簡單盲猜一下,使用的是什么方式,在上一篇文章揭秘Autowired注解中有介紹到。我們只是向xml中寫入了一行<context:annotation-config/>
配置。Spring就像BeanFatory中寫入了很多的BeanPostProcessor,這里我覺得采用的功能類似。
通過定義spring.handlers文件。然后mybatis定義各種處理標(biāo)簽的Handler和Paser。隨后通過讀取配置文件中的mappers標(biāo)簽,去像register中注冊BeanDefinition,這是其一。
第二種方法就是類似AutowiredAnnotationBeanPostProcessor的實(shí)現(xiàn)方式,通過Xml注冊一個(gè)Bean,這個(gè)Bean繼承自MergedBeanDefinitionPostProcessor,由于繼承自MegedBeanDefinitionPostProcessor所以他會優(yōu)先Bean運(yùn)行,在此時(shí)可以像Bean工廠中添加BeanDefinition。
其實(shí)我們的目標(biāo)很明確,只要我們能在Spring調(diào)用InitiazionBean方法之前去把mapper的BeanDefinition添加進(jìn)Spring容器,都可以實(shí)現(xiàn)當(dāng)前的目的。
那么接下來我們就看看MyBatis本身究竟是如何實(shí)現(xiàn)的吧。
案例搭建
源碼地址:MyBatis整合Spring有兩種方式,第一種是通過Xml,第二種是通過Mapper接口的掃描,具體的整合方法我這里就不演示了,直接看配置文件吧。其實(shí)就是application.xml有一點(diǎn)改動。
通過掃描接口
這里搭建一個(gè)最簡單的整合方式:
正式開始
通過上方的配置文件,可以看見一個(gè)配置了一個(gè)叫scannerConfigurer,這里先去看一下這個(gè)類。
public class MapperScannerConfigurer implements BeanDefinitionRegistryPostProcessor, InitializingBean, ApplicationContextAware, BeanNameAware {
這個(gè)類繼承了幾個(gè)接口:
- BeanDefinitionRegistryPostProcessor
- 這個(gè)接口追進(jìn)去,發(fā)現(xiàn)該接口繼承自BeanFactoryPostProcessor,也就相當(dāng)于spring在refresh方法中有一個(gè)方法專門去執(zhí)行這類的接口。
- InitializingBean
- 在createBean的生命周期中會調(diào)用該接口的afterProperties方法。
- ApplicationContextAware
- Spring在創(chuàng)建該Bean時(shí)會調(diào)用setapplicationContext方法注入上下文
- BeanNameAware
- 創(chuàng)建是調(diào)用setBeanName方法
按照這樣的一個(gè)接口被執(zhí)行的順序是,setBeanName -> setApplicationContext -> afterproperties -> postProcessBeanDefinitionRegistry
setBeanName
@Override public void setBeanName(String name) { this.beanName = name; }
這個(gè)方法很明顯不是。
setApplicationContext
@Override public void setApplicationContext(ApplicationContext applicationContext) { this.applicationContext = applicationContext; }
這個(gè)方法很明顯,也不是。
afterProperties
@Override public void afterPropertiesSet() throws Exception { notNull(this.basePackage, "Property 'basePackage' is required"); }
這個(gè)方法是用來校驗(yàn)的,判斷了basePackage是否為空,如果為空就throw Exception。
postProcessBeanDefinitionRegistry
@Override public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) { if (this.processPropertyPlaceHolders) { processPropertyPlaceHolders(); } ClassPathMapperScanner scanner = new ClassPathMapperScanner(registry); scanner.setAddToConfig(this.addToConfig); scanner.setAnnotationClass(this.annotationClass); scanner.setMarkerInterface(this.markerInterface); scanner.setSqlSessionFactory(this.sqlSessionFactory); scanner.setSqlSessionTemplate(this.sqlSessionTemplate); scanner.setSqlSessionFactoryBeanName(this.sqlSessionFactoryBeanName); scanner.setSqlSessionTemplateBeanName(this.sqlSessionTemplateBeanName); scanner.setResourceLoader(this.applicationContext); scanner.setBeanNameGenerator(this.nameGenerator); scanner.setMapperFactoryBeanClass(this.mapperFactoryBeanClass); if (StringUtils.hasText(lazyInitialization)) { scanner.setLazyInitialization(Boolean.valueOf(lazyInitialization)); } if (StringUtils.hasText(defaultScope)) { scanner.setDefaultScope(defaultScope); } scanner.registerFilters(); scanner.scan( StringUtils.tokenizeToStringArray(this.basePackage, ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS)); }
那這里就很明顯了,這里創(chuàng)建ClassPathMapperScanner。隨后對scanner的一些配置做了一些設(shè)置。
然后就調(diào)用了registerFilters方法,字面意思也就是注冊過濾器,這里就跳過吧,無非是設(shè)置一些屬性,然后在后面解析的時(shí)候判斷過濾條件,在循環(huán)時(shí)continue。
主要是scan方法這里要詳細(xì)看一下:
public int scan(String... basePackages) { int beanCountAtScanStart = this.registry.getBeanDefinitionCount(); this.doScan(basePackages); if (this.includeAnnotationConfig) { AnnotationConfigUtils.registerAnnotationConfigProcessors(this.registry); } return this.registry.getBeanDefinitionCount() - beanCountAtScanStart; }
這里這個(gè)掃描類其實(shí)是Spring中的類,ClassPathBeanDefinitionScanner,當(dāng)中的scan方法。所以這里可能會有點(diǎn)眼熟,類似于創(chuàng)建Bean時(shí),先獲取一下創(chuàng)建之前的Bean總數(shù),然后再獲取創(chuàng)建之后的Bean總數(shù),返回時(shí)減一下就知道這次創(chuàng)建了多少。
總結(jié)
其實(shí)到這里呢,我們就算是結(jié)束了,因?yàn)楹罄m(xù)的包掃描,在嚴(yán)格意義上來講是Spring來實(shí)現(xiàn)的,我后續(xù)開篇文章來講解這個(gè)東西。
這里總結(jié)一下,正如我猜想的一樣,myBatis只要在finishBeanFactoryInitialization方法之前,把Mapper的BeanDefinition塞進(jìn)Spring容器中,在最后的finishBeanFactoryInitialization方法,Spring自然就會根據(jù)BeanDefinition去創(chuàng)建Bean了。
這里使用的方法是,注冊一個(gè)BeanFactoryPostProcessor,所以這個(gè)方法會在finishBeanFactoryInitialization方法之前運(yùn)行,所以這里是成功的。
到此這篇關(guān)于Spring和Mybatis整合的原理詳解的文章就介紹到這了,更多相關(guān)Spring和Mybatis整合內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Spring Boot中使用Actuator的/info端點(diǎn)輸出Git版本信息
這篇文章主要介紹了Spring Boot中使用Actuator的/info端點(diǎn)輸出Git版本信息,需要的朋友可以參考下2017-06-06將一個(gè)數(shù)組按照固定大小進(jìn)行拆分成數(shù)組的方法
下面小編就為大家?guī)硪黄獙⒁粋€(gè)數(shù)組按照固定大小進(jìn)行拆分成數(shù)組的方法。小編覺得挺不錯的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧2016-11-11使用spring框架實(shí)現(xiàn)數(shù)據(jù)庫事務(wù)處理方式
這篇文章主要介紹了使用spring框架實(shí)現(xiàn)數(shù)據(jù)庫事務(wù)處理方式,具有很好的參考價(jià)值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2021-10-10Java 內(nèi)省(Introspector)深入理解
這篇文章主要介紹了Java 內(nèi)省(Introspector)深入理解的相關(guān)資料,需要的朋友可以參考下2017-03-03SpringBoot過濾器與攔截器深入分析實(shí)現(xiàn)方法
大家應(yīng)該都曉得實(shí)現(xiàn)過濾器需要實(shí)現(xiàn) javax.servlet.Filter 接口,而攔截器會在處理指定請求之前和之后進(jìn)行相關(guān)操作,配置攔截器需要兩步,本文通過實(shí)例代碼給大家介紹SpringBoot 過濾器和攔截器的相關(guān)知識,感興趣的朋友一起看看吧2022-11-11idea創(chuàng)建包含多個(gè)springboot module的maven project的方法
這篇文章主要介紹了idea創(chuàng)建包含多個(gè)springboot module的maven project的方法,本文給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2020-09-09Spring的兩種事務(wù)管理機(jī)制的基本概念和demo示例
Spring事務(wù)包括聲明式事務(wù)管理和注解式事務(wù)管理,我們通過概念和小demo的形式一步一步地來一起學(xué)習(xí)這個(gè)知識點(diǎn),需要的朋友可以參考下2023-07-07Java進(jìn)階學(xué)習(xí):網(wǎng)絡(luò)服務(wù)器編程
Java進(jìn)階學(xué)習(xí):網(wǎng)絡(luò)服務(wù)器編程...2006-12-12idea啟動springmvc項(xiàng)目時(shí)報(bào)找不到類的解決方法
這篇文章主要介紹了idea啟動springmvc項(xiàng)目時(shí)報(bào)找不到類的解決方法,本文給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2020-09-09