Mybatis-Spring源碼分析圖解
Mybatis-Spring
當(dāng)我們使用mybatis和spring整合后為什么下面的代碼可以運(yùn)行?
一個(gè)問(wèn)題:
我就寫(xiě)了個(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ī)上篇博客寫(xiě)過(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è)類重寫(xiě)了doScan方法,所以會(huì)調(diào)用子類重寫(xiě)的方法
@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)寫(xiě))
這個(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)用重寫(xiě)的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)題
我就寫(xiě)了個(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服務(wù)端表單數(shù)據(jù)校驗(yàn)的實(shí)現(xiàn)方式
這篇文章主要介紹了基于SpringBoot服務(wù)端表單數(shù)據(jù)校驗(yàn)的實(shí)現(xiàn)方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2020-10-10java括號(hào)匹配算法求解(用棧實(shí)現(xiàn))
這篇文章主要介紹了java括號(hào)匹配算法求解(用棧實(shí)現(xiàn)),需要的朋友可以參考下2020-12-12Gateway網(wǎng)關(guān)自定義攔截器的不可重復(fù)讀取數(shù)據(jù)問(wèn)題
這篇文章主要介紹了Gateway網(wǎng)關(guān)自定義攔截器的不可重復(fù)讀取數(shù)據(jù)問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-08-08Spring Security內(nèi)存中認(rèn)證的實(shí)現(xiàn)
本文主要介紹了Spring Security內(nèi)存中認(rèn)證的實(shí)現(xiàn),文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2023-11-11Java在Excel中添加水印的實(shí)現(xiàn)(單一水印、平鋪水印)
這篇文章主要介紹了Java在Excel中添加水印的實(shí)現(xiàn)(單一水印、平鋪水印),文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2021-04-04springboot整合mybatis-plus 實(shí)現(xiàn)分頁(yè)查詢功能
這篇文章主要介紹了springboot整合mybatis-plus 實(shí)現(xiàn)分頁(yè)查詢功能,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2020-09-09解決IDEA項(xiàng)目project包目錄消失的問(wèn)題
這篇文章主要介紹了解決IDEA項(xiàng)目project包目錄消失的問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2021-02-02