Spring和Mybatis整合的原理詳解
前言
最近讀完了Spring的IOC部分的源碼,受益匪淺,這篇文章講解一下MyBatis是如何做到與Spring整合的。MyBatis是如何做到干擾Spring的生命周期,把Mapper一個個的注冊到Spring容器中的將在這里揭秘。
簡單猜想
因為閱讀過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注冊一個Bean,這個Bean繼承自MergedBeanDefinitionPostProcessor,由于繼承自MegedBeanDefinitionPostProcessor所以他會優(yōu)先Bean運(yùn)行,在此時可以像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)改動。
通過掃描接口
這里搭建一個最簡單的整合方式:

正式開始
通過上方的配置文件,可以看見一個配置了一個叫scannerConfigurer,這里先去看一下這個類。
public class MapperScannerConfigurer
implements BeanDefinitionRegistryPostProcessor, InitializingBean, ApplicationContextAware, BeanNameAware {這個類繼承了幾個接口:
- BeanDefinitionRegistryPostProcessor
- 這個接口追進(jìn)去,發(fā)現(xiàn)該接口繼承自BeanFactoryPostProcessor,也就相當(dāng)于spring在refresh方法中有一個方法專門去執(zhí)行這類的接口。
- InitializingBean
- 在createBean的生命周期中會調(diào)用該接口的afterProperties方法。
- ApplicationContextAware
- Spring在創(chuàng)建該Bean時會調(diào)用setapplicationContext方法注入上下文
- BeanNameAware
- 創(chuàng)建是調(diào)用setBeanName方法
按照這樣的一個接口被執(zhí)行的順序是,setBeanName -> setApplicationContext -> afterproperties -> postProcessBeanDefinitionRegistry
setBeanName
@Override
public void setBeanName(String name) {
this.beanName = name;
}這個方法很明顯不是。
setApplicationContext
@Override
public void setApplicationContext(ApplicationContext applicationContext) {
this.applicationContext = applicationContext;
}這個方法很明顯,也不是。
afterProperties
@Override
public void afterPropertiesSet() throws Exception {
notNull(this.basePackage, "Property 'basePackage' is required");
}
這個方法是用來校驗的,判斷了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è)置一些屬性,然后在后面解析的時候判斷過濾條件,在循環(huán)時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;
}這里這個掃描類其實(shí)是Spring中的類,ClassPathBeanDefinitionScanner,當(dāng)中的scan方法。所以這里可能會有點(diǎn)眼熟,類似于創(chuàng)建Bean時,先獲取一下創(chuàng)建之前的Bean總數(shù),然后再獲取創(chuàng)建之后的Bean總數(shù),返回時減一下就知道這次創(chuàng)建了多少。
總結(jié)
其實(shí)到這里呢,我們就算是結(jié)束了,因為后續(xù)的包掃描,在嚴(yán)格意義上來講是Spring來實(shí)現(xiàn)的,我后續(xù)開篇文章來講解這個東西。
這里總結(jié)一下,正如我猜想的一樣,myBatis只要在finishBeanFactoryInitialization方法之前,把Mapper的BeanDefinition塞進(jìn)Spring容器中,在最后的finishBeanFactoryInitialization方法,Spring自然就會根據(jù)BeanDefinition去創(chuàng)建Bean了。
這里使用的方法是,注冊一個BeanFactoryPostProcessor,所以這個方法會在finishBeanFactoryInitialization方法之前運(yùn)行,所以這里是成功的。
到此這篇關(guān)于Spring和Mybatis整合的原理詳解的文章就介紹到這了,更多相關(guān)Spring和Mybatis整合內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
淺析Java中對象的創(chuàng)建與對象的數(shù)據(jù)類型轉(zhuǎn)換
這篇文章主要介紹了Java中對象的創(chuàng)建與對象的數(shù)據(jù)類型轉(zhuǎn)換,是Java入門學(xué)習(xí)中的基礎(chǔ)知識,需要的朋友可以參考下2016-01-01
Java日常練習(xí)題,每天進(jìn)步一點(diǎn)點(diǎn)(50)
下面小編就為大家?guī)硪黄狫ava基礎(chǔ)的幾道練習(xí)題(分享)。小編覺得挺不錯的,現(xiàn)在就分享給大家,也給大家做個參考。一起跟隨小編過來看看吧,希望可以幫到你2021-08-08
java設(shè)計模式之實(shí)現(xiàn)對象池模式示例分享
對象池模式經(jīng)常用在頻繁創(chuàng)建、銷毀對象(并且對象創(chuàng)建、銷毀開銷很大)的場景,比如數(shù)據(jù)庫連接池、線程池、任務(wù)隊列池等。本代碼簡單,沒有限制對象池大小2014-02-02
SpringBoot集成Prometheus實(shí)現(xiàn)監(jiān)控的過程
這篇文章主要介紹了SpringBoot集成Prometheus實(shí)現(xiàn)監(jiān)控,本文通過實(shí)例代碼給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下2023-09-09
如何通過idea實(shí)現(xiàn)springboot集成mybatis
這篇文章主要介紹了如何通過idea實(shí)現(xiàn)springboot集成mybatis,使用springboot 集成 mybatis后,通過http請求接口,使得通過http請求可以直接操作數(shù)據(jù)庫,本文結(jié)合實(shí)例代碼給大家介紹的非常詳細(xì),需要的朋友可以參考下2023-09-09
解決Java中SimpleDateFormat線程不安全的五種方案
SimpleDateFormat 就是一個典型的線程不安全事例,本文主要介紹了解決Java中SimpleDateFormat線程不安全的五種方案,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2021-05-05

