Mybatis-Spring源碼分析圖解
Mybatis-Spring
當(dāng)我們使用mybatis和spring整合后為什么下面的代碼可以運(yùn)行?


一個(gè)問(wèn)題:
我就寫了個(gè)mapper接口為什么能用?
首先來(lái)看,在spring的配置xml中有一段
<bean id="configurer" class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"/>
<property name="basePackage" value="com.jame.dao"/>
</bean>
這段xml的作用是將一個(gè)類添加到spring容器中,點(diǎn)進(jìn)這個(gè)類看看

它實(shí)現(xiàn)了一個(gè)BeanDefinitionRegistryPostProcessor接口,關(guān)于這個(gè)接口的作用和執(zhí)行時(shí)機(jī)上篇博客寫過(guò)了,這里就不再贅述
那么它必然實(shí)現(xiàn)postProcessBeanDefinitionRegistry方法,點(diǎn)擊這個(gè)方法查看
@Override
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) {
if (this.processPropertyPlaceHolders) {
processPropertyPlaceHolders();
}
ClassPathMapperScanner scanner = new ClassPathMapperScanner(registry);
..........
scanner.registerFilters();
scanner.scan(
StringUtils.tokenizeToStringArray(this.basePackage, ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS));
}
其中將接口注冊(cè)到spring容器中在最后一行,先來(lái)看ClassPathMapperScanner這個(gè)類,它繼承了ClassPathBeanDefinitionScanner這個(gè)掃描器

scan的具體代碼
public int scan(String... basePackages) {
int beanCountAtScanStart = this.registry.getBeanDefinitionCount();
doScan(basePackages);
// Register annotation config processors, if necessary.
if (this.includeAnnotationConfig) {
AnnotationConfigUtils.registerAnnotationConfigProcessors(this.registry);
}
return (this.registry.getBeanDefinitionCount() - beanCountAtScanStart);
}
這個(gè)是spring內(nèi)部的掃描方法,當(dāng)它走到doScan的時(shí)候,因?yàn)镃lassPathMapperScanner這個(gè)類重寫了doScan方法,所以會(huì)調(diào)用子類重寫的方法
@Override
public Set<BeanDefinitionHolder> doScan(String... basePackages) {
Set<BeanDefinitionHolder> beanDefinitions = super.doScan(basePackages);
if (beanDefinitions.isEmpty()) {
LOGGER.warn(() -> "No MyBatis mapper was found in '" + Arrays.toString(basePackages)
+ "' package. Please check your configuration.");
} else {
processBeanDefinitions(beanDefinitions);
}
return beanDefinitions;
}
通過(guò)包名獲取BeanDefinitionHolder,現(xiàn)在它獲取到了User接口的BeanDefinitionHolder,然后判斷如果BeanDefinitionHolder的集合為空,也就是沒(méi)有找到mapper的情況則不做任何處理,而現(xiàn)在有一個(gè)UserMapper的,進(jìn)入else
private void processBeanDefinitions(Set<BeanDefinitionHolder> beanDefinitions) {
AbstractBeanDefinition definition;
BeanDefinitionRegistry registry = getRegistry();
for (BeanDefinitionHolder holder : beanDefinitions) {
definition = (AbstractBeanDefinition) holder.getBeanDefinition();
.........
//主要看這行
definition.setBeanClass(this.mapperFactoryBeanClass);
.........
if (!definition.isSingleton()) {
BeanDefinitionHolder proxyHolder = ScopedProxyUtils.createScopedProxy(holder, registry, true);
if (registry.containsBeanDefinition(proxyHolder.getBeanName())) {
registry.removeBeanDefinition(proxyHolder.getBeanName());
}
registry.registerBeanDefinition(proxyHolder.getBeanName(), proxyHolder.getBeanDefinition());
}
}
}
將MapperFactoryBean類設(shè)置為了UserMapperBeanDefinition的class

spring在創(chuàng)建這個(gè)userMapper這個(gè)Bean的時(shí)候會(huì)使用這個(gè)有參構(gòu)造將當(dāng)前這個(gè)UserMapper類型設(shè)置到mapperInterface屬性上(為啥使用有參構(gòu)造而不是無(wú)參來(lái)初始化對(duì)象我也不知道.....這和spring推斷構(gòu)造方法有關(guān),以后學(xué)會(huì)了在來(lái)寫)
這個(gè)MapperFactoryBean實(shí)現(xiàn)了一個(gè)FactoryBean接口,這個(gè)接口可以讓我們自定義獲取bean的操作
回到spring的代碼,例如當(dāng)我們使用context.getBean(xxx.class)的時(shí)候

spring將xxx.class類型解析為bean名稱,通過(guò)名稱去獲取
protected <T> T doGetBean(
String name, @Nullable Class<T> requiredType, @Nullable Object[] args, boolean typeCheckOnly)
throws BeansException {
//獲取對(duì)應(yīng)的beanName
String beanName = transformedBeanName(name);
Object bean;
Object sharedInstance = getSingleton(beanName);
if (sharedInstance != null && args == null) {
bean = getObjectForBeanInstance(sharedInstance, name, beanName, null);
}
.......
// Create bean instance.
if (mbd.isSingleton()) {
sharedInstance = getSingleton(beanName, () -> {
try {
//真正創(chuàng)建對(duì)象的地方
return createBean(beanName, mbd, args);
}
catch (BeansException ex) {
// Explicitly remove instance from singleton cache: It might have been put there
// eagerly by the creation process, to allow for circular reference resolution.
// Also remove any beans that received a temporary reference to the bean.
destroySingleton(beanName);
throw ex;
}
});
bean = getObjectForBeanInstance(sharedInstance, name, beanName, mbd);
}
}
首先是調(diào)用getSingleton方法,嘗試獲取存在緩存中的bean(其實(shí)就是三個(gè)Map,key為bean名稱,value是對(duì)象),那現(xiàn)在是首次獲取map中沒(méi)有
然后執(zhí)行到下面的createBean,當(dāng)創(chuàng)建完這個(gè)bean后spring需要判斷這個(gè)bean是一個(gè)普通bean還是一個(gè)FactoryBean,程序員是想要獲取普通bean還是FactoryBean,還是FactoryBean的getObject方法返回的從工廠生成的對(duì)象
咱們一段一段看
protected Object getObjectForBeanInstance(
Object beanInstance, String name, String beanName, @Nullable RootBeanDefinition mbd) {
if (BeanFactoryUtils.isFactoryDereference(name)) {
if (beanInstance instanceof NullBean) {
return beanInstance;
}
if (!(beanInstance instanceof FactoryBean)) {
throw new BeanIsNotAFactoryException(beanName, beanInstance.getClass());
}
}
.....
}
BeanFactoryUtils.isFactoryDereference(name)的作用是一個(gè)字符串判斷,當(dāng)返回傳入名稱是否為工廠,如果name不為空,并且以&開(kāi)頭返回true
這個(gè)方法在下面的判斷也使用到了,記一下它的作用即可
來(lái)看例子


在我們使用FactoryBean通過(guò)context.getBean("工廠Bean名稱")的時(shí)候獲取的是FactoryBean的getObject生成的對(duì)象,如果我們想獲取FactoryBean的引用則需要在名稱前面加一個(gè)&符號(hào)

回來(lái)看代碼,如果這個(gè)bean的引用是一個(gè)NullBean類型則直接返回引用,下面有做了一個(gè)判斷
if (!(beanInstance instanceof FactoryBean))再次判斷這個(gè)bean是不是一個(gè)FactoryBean,如果為true則拋出異常,這個(gè)好理解,因?yàn)槲覀冊(cè)趃etBean的時(shí)候完全可以將一個(gè)普通的bean名稱前面加上&符號(hào)
主要的判斷在下面的這個(gè)if
if (!(beanInstance instanceof FactoryBean) || BeanFactoryUtils.isFactoryDereference(name)) {
return beanInstance;
}
現(xiàn)在有3中情況
1.當(dāng)前的bean是一個(gè)普通的bean
第一個(gè)條件false 取反 true 第二個(gè)條件false 結(jié)果true,直接返回bean實(shí)例
2.當(dāng)前是一個(gè)FactoryBean,想通過(guò)工廠獲取Bean
第一個(gè)條件 true 取反false 第二個(gè)條件false 結(jié)果false,進(jìn)行下面的操作
3.當(dāng)前是一個(gè)FactoryBean,想獲取工廠的引用
第一個(gè)條件 true 取反 false 第二個(gè)條件 true 結(jié)果 true 直接返回factoryBean實(shí)例
當(dāng)前我們是想通過(guò)FactoryBean獲取對(duì)象,那么不進(jìn)if,繼續(xù)下面的代碼
Object object = null;
// 如果beanDefinition為null,則嘗試從緩存中獲取給定的FactoryBean公開(kāi)的對(duì)象
if (mbd == null) {
//嘗試從緩存中加載bean
object = getCachedObjectForFactoryBean(beanName);
}
// 未能從緩存中獲得FactoryBean公開(kāi)的對(duì)象,則說(shuō)明該bean是一個(gè)新創(chuàng)建的bean
if (object == null) {
FactoryBean<?> factory = (FactoryBean<?>) beanInstance;
if (mbd == null && containsBeanDefinition(beanName)) {
mbd = getMergedLocalBeanDefinition(beanName);
}
boolean synthetic = (mbd != null && mbd.isSynthetic());
// 從給定的FactoryBean中獲取指定的beanName對(duì)象
object = getObjectFromFactoryBean(factory, beanName, !synthetic);
}
return object;
主要來(lái)看getObjectFromFactoryBean
protected Object getObjectFromFactoryBean(FactoryBean<?> factory, String beanName, boolean shouldPostProcess) {
if (factory.isSingleton() && containsSingleton(beanName)) {
synchronized (getSingletonMutex()) {
Object object = this.factoryBeanObjectCache.get(beanName);
if (object == null) {
//調(diào)用factoryBean的getObject方法
object = doGetObjectFromFactoryBean(factory, beanName);
Object alreadyThere = this.factoryBeanObjectCache.get(beanName);
if (alreadyThere != null) {
object = alreadyThere;
}
}
..........
}
}
}
doGetObjectFromFactoryBean方法
private Object doGetObjectFromFactoryBean(FactoryBean<?> factory, String beanName) throws BeanCreationException {
Object object;
try {
if (System.getSecurityManager() != null) {
AccessControlContext acc = getAccessControlContext();
try {
object = AccessController.doPrivileged((PrivilegedExceptionAction<Object>) factory::getObject, acc);
}
catch (PrivilegedActionException pae) {
throw pae.getException();
}
}
else {
//調(diào)用重寫的getObject方法
object = factory.getObject();
}
}
.......
return object;
}
也就是說(shuō)當(dāng)我們getBean("userMapper")的時(shí)候其實(shí)是調(diào)用FactoryBean的getObject方法,代碼回到mybatis-spring項(xiàng)目的MapperFactoryBean類中的getObject方法
@Override
public T getObject() throws Exception {
return getSqlSession().getMapper(this.mapperInterface);
}
@Override
public <T> T getMapper(Class<T> type) {
return configuration.getMapper(type, this);
}
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type);
if (mapperProxyFactory == null) {
throw new BindingException("Type " + type + " is not known to the MapperRegistry.");
}
try {
return mapperProxyFactory.newInstance(sqlSession);
} catch (Exception e) {
throw new BindingException("Error getting mapper instance. Cause: " + e, e);
}
}
public T newInstance(SqlSession sqlSession) {
final MapperProxy<T> mapperProxy = new MapperProxy<>(sqlSession, mapperInterface, methodCache);
return newInstance(mapperProxy);
}
protected T newInstance(MapperProxy<T> mapperProxy) {
return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
}
到最后發(fā)現(xiàn)是通過(guò)jdk的動(dòng)態(tài)代理來(lái)生成的對(duì)象,那么回答開(kāi)始的問(wèn)題
我就寫了個(gè)接口為什么能用?
因?yàn)閙ybatis在spring加載bean之前修改了beanDefinition,通過(guò)MapperScannerConfigurer類實(shí)現(xiàn)的BeanDefinitionRegistryPostProcessor接口中將我們定義的一些mapper接口的BeanDefinition的BeanClass屬性修改為了MapperFactoryBean,而這個(gè)類實(shí)現(xiàn)了FactoryBean,我們獲取接口實(shí)際上是通過(guò)FactoryBean的getObject方法
到此這篇關(guān)于Mybatis-Spring源碼分析的文章就介紹到這了,更多相關(guān)Mybatis-Spring源碼分析內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
SpringBoot配置文件啟動(dòng)加載順序的方法步驟
SpringBoot的啟動(dòng)加載順序涉及多個(gè)步驟和組件,通過(guò)分層和優(yōu)先級(jí)機(jī)制加載配置文件,確保在啟動(dòng)時(shí)正確配置應(yīng)用程序,本文就來(lái)介紹一下SpringBoot配置文件啟動(dòng)加載順序的方法步驟,感興趣的可以了解一下2024-11-11
Java單例模式實(shí)現(xiàn)靜態(tài)內(nèi)部類方法示例
這篇文章主要介紹了Java單例模式實(shí)現(xiàn)靜態(tài)內(nèi)部類方法示例,涉及構(gòu)造函數(shù)私有化等相關(guān)內(nèi)容,需要的朋友可以了解下。2017-09-09
在SpringBoot中如何利用Redis實(shí)現(xiàn)互斥鎖
當(dāng)我們利用Redis存儲(chǔ)熱點(diǎn)數(shù)據(jù)時(shí),突然就過(guò)期失效或者被刪除了,導(dǎo)致大量請(qǐng)求同時(shí)訪問(wèn)數(shù)據(jù)庫(kù),增加了數(shù)據(jù)庫(kù)的負(fù)載,為減輕數(shù)據(jù)庫(kù)的負(fù)載我們利用互斥鎖,本文重點(diǎn)介紹在SpringBoot中如何利用Redis實(shí)現(xiàn)互斥鎖,感興趣的朋友一起看看吧2023-09-09
springboot如何使用@ConfigurationProperties封裝配置文件
springboot如何使用@ConfigurationProperties封裝配置文件的操作,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-08-08
SpringBoot自定義注解解決公共字段填充問(wèn)題解決
修改時(shí)間,修改人等字段時(shí),這些字段屬于公共字段,本文主要介紹了SpringBoot自定義注解解決公共字段填充問(wèn)題解決,使用它的好處就是可以統(tǒng)一對(duì)這些字段進(jìn)行處理,感興趣的可以了解一下2024-07-07

