文章目錄
- 前言
- MapperFactoryBean的工作原理
- 底層實現剖析
- MapperFactoryBean的checkDaoConfig()方法
- 總結
- MapperFactoryBean的getObject()方法
- 思考聯想
- 后續
系列相關相關文章 |
---|
究竟FactoryBean是什么?深入理解Spring的工廠神器 |
超硬核解析Mybatis動態代理原理!只有接口沒實現也能跑? |
Mybatis與Spring結合深探——MapperFactoryBean的奧秘 |
后續TODO:MapperScannerConfigurer |
前言
在沒有Spring單獨使用Mybatis的時候,我在之前的文章超硬核解析Mybatis動態代理原理!只有接口沒實現也能跑? 講解到了調用鏈路new SqlSessionFactoryBuilder().build(xml)-->XMLConfigBuilder#parse-->>XMLConfigBuilder#parseConfiguration--->XMLConfigBuilder#mapperElement-->XMLMapperBuilder#mapperParser.parse()-->XMLMapperBuilder#configurationElement-->XMLMapperBuilder#bindMapperForNamespace-->Configuration#MapperRegistry#addMappper()
在SqlSessionFactoryBuilder().build方法 中最終調用Configuration對象的addMappper()方法(實際上是委托給MapperRegistry的addMapper)添加對應的MapperProxyFactory代理工廠類,最終通過這個工廠類生成對應的代理對象MapperProxy 。
也就是MapperRegistry內部維護一個映射關系,每個接口對應一個MapperProxyFactory(生成動態代理工廠類)
private final Map<Class<?>, MapperProxyFactory<?>> knownMappers = new HashMap<>();
這樣便于在后面調用MapperRegistry的getMapper()時,直接從Map中獲取某個接口對應的動態代理工廠類,然后再利用工廠類針對其接口生成真正的動態代理類。
如果想了解什么是FactoryBean是什么,可以查看前文究竟FactoryBean是什么?深入理解Spring的工廠神器
更詳細的內容可以查看我之前的文章:超硬核解析Mybatis動態代理原理!只有接口沒實現也能跑?
而我們現在在Spring框架中整合Mybatis時,我們通常會使用MapperFactoryBean
來生成Mapper的代理實例,也就是不需要再通過new SqlSessionFactoryBuilder().build(xml)的方式去注冊動態代理接口。這是一種更簡單且易于配置的方式,可讓我們以Spring的形式操作Mybatis的持久層。本文將深入探索MapperFactoryBean
的工作原理,并說明如何將Mybatis和Spring框架結合起來,以構建一個響應迅速而又易于維護的數據訪問層。
MapperFactoryBean的工作原理
當應用啟動時,Spring容器會為每個MapperFactoryBean
生成一個相應的Bean
實例。這個過程包含了幾個關鍵步驟:
- Bean的定義:在Spring配置文件中定義
MapperFactoryBean
,這包括指定其sqlSessionFactory
或sqlSessionTemplate
。 - Bean的實例化:Spring容器將調用
MapperFactoryBean
的getObject()
方法,這個方法內部又會調用Mybatis的SqlSession.getMapper()
。 - 生成Mapper代理:正如前面提到的,Mybatis使用動態代理技術生成代理對象。這個過程由Mybatis內部的
MapperProxyFactory
完成。 - Bean的使用:最終創建的Mapper被注入到其他組件中,這樣,業務代碼就可以通過普通的Java方法調用來執行SQL操作了。
下面我們看看實際的配置代碼示例:
<!-- Mybatis的SqlSessionFactory配置 -->
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean"><property name="dataSource" ref="dataSource" /><property name="mapperLocations" value="classpath*:mapper/*.xml" />
</bean><!-- Mapper接口對應的FactoryBean配置 -->
<bean id="userMapper" class="org.mybatis.spring.mapper.MapperFactoryBean"><property name="mapperInterface" value="com.example.mapper.UserMapper" /><property name="sqlSessionFactory" ref="sqlSessionFactory" />
</bean>
當然,在Spring的Java配置中,我們通常用注解來代替上述XML配置,得益于Spring的@MapperScan
,可以大幅簡化這個配置:
@Configuration
@MapperScan("com.example.mapper")
public class AppConfig {@Beanpublic SqlSessionFactory sqlSessionFactory(DataSource dataSource) throws Exception {SqlSessionFactoryBean sessionFactory = new SqlSessionFactoryBean();sessionFactory.setDataSource(dataSource);// ...其他配置...return sessionFactory.getObject();}
}
底層實現剖析
MapperFactoryBean的checkDaoConfig()方法
MapperFactoryBean
本身extend自SqlSessionDaoSupport
,SqlSessionDaoSupport又extend自DaoSupport接口,DaoSupport接口實現了InitializingBean接口,在對象初始化的時候會調用它的afterPropertiesSet方法,該方法中首先調用了checkDaoConfig()方法,MapperFactoryBean重載的checkDaoConfig()如下面所示—>這里最終會將調用configuration.addMapper(this.mapperInterface)
(實際也是委托給MapperRegistry)
public class MapperFactoryBean<T> extends SqlSessionDaoSupport implements FactoryBean<T> {private Class<T> mapperInterface;public void setMapperInterface(Class<T> mapperInterface) {this.mapperInterface = mapperInterface;}protected void checkDaoConfig() {super.checkDaoConfig();Assert.notNull(this.mapperInterface, "Property 'mapperInterface' is required");Configuration configuration = this.getSqlSession().getConfiguration();if (this.addToConfig && !configuration.hasMapper(this.mapperInterface)) {try {configuration.addMapper(this.mapperInterface);} catch (Exception var6) {this.logger.error("Error while adding the mapper '" + this.mapperInterface + "' to configuration.", var6);throw new IllegalArgumentException(var6);} finally {ErrorContext.instance().reset();}}}@Overridepublic T getObject() throws Exception {return getSqlSession().getMapper(this.mapperInterface);}// ...
}
在checkDaoConfig
方法中,會檢查mapperInterface
是否已設置,符合Spring管理Bean生命周期的要求。
接著通過configuration.addMapper(this.mapperInterface)
方法重點關注,最終實現是在MapperRegistry中:
到這里以后,跟我之前文章超硬核解析Mybatis動態代理原理!只有接口沒實現也能跑?解析的步驟又是一樣的了
new SqlSessionFactoryBuilder().build(xml)-->XMLConfigBuilder#parse-->>XMLConfigBuilder#parseConfiguration--->XMLConfigBuilder#mapperElement-->XMLMapperBuilder#mapperParser.parse()-->XMLMapperBuilder#configurationElement-->XMLMapperBuilder#bindMapperForNamespace-->Configuration#MapperRegistry#addMappper()
public <T> void addMapper(Class<T> type) {if (type.isInterface()) {if (hasMapper(type)) {throw new BindingException("Type " + type + " is already known to the MapperRegistry.");}boolean loadCompleted = false;try {knownMappers.put(type, new MapperProxyFactory<T>(type));// It's important that the type is added before the parser is run// otherwise the binding may automatically be attempted by the// mapper parser. If the type is already known, it won't try.MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type);parser.parse();loadCompleted = true;} finally {if (!loadCompleted) {knownMappers.remove(type);}}}}
parser.parse()完成了對mapper對應xml的解析成MappedStatement,并添加到了configuration對象中,這里的configuration也就是我們上面提到的new Configuration()創建的那個對象(非常重要)。
總結
mapper接口的定義在bean加載階段會被替換成MapperFactoryBean類型,在spring容器初始化的時候會給我們生成MapperFactoryBean類型的對象,在該對象生成的過程中調用afterPropertiesSet()方法,為我們生成了一個MapperProxyFactory類型的對象存放于Configuration里的MapperRegistry對象中,同時解析了mapper接口對應的xml文件,把每一個方法解析成一個MappedStatement對象,存放于Configuration里mappedStatements這個Map集合中。
MapperFactoryBean的getObject()方法
MapperFactoryBean實現了FactoryBean接口,實現了FactoryBean接口的類型在調用getBean(beanName)既通過名稱獲取對象時,返回的對象不是本身類型的對象,而是通過實現接口中的getObject()方法返回的對象。
這里需要對spring的聲明周期有一定的了解:下面是簡化版的MapperFactoryBean的鏈路調用
getObject()--->doGetBean()--->getObjectForBeanInstance()--->getObjectFromFactoryBean()--->doGetObjectFromFactoryBean--->MapperFactoryBean.getObject()方法
protected <T> T doGetBean(final String name, final Object[] args) {Object sharedInstance = getSingleton(name);if (sharedInstance != null) {// 如果是 FactoryBean,則需要調用 FactoryBean#getObjectreturn (T) getObjectForBeanInstance(sharedInstance, name);}BeanDefinition beanDefinition = getBeanDefinition(name);//這里如果是MapperFactoryBean對象,初始化完成以后會進入下面的判斷Object bean = createBean(name, beanDefinition, args);//這里如果是MapperFactoryBean對象,初始化完成以后會進入下面的判斷return (T) getObjectForBeanInstance(bean, name);}private Object getObjectForBeanInstance(Object beanInstance, String beanName) {if (!(beanInstance instanceof FactoryBean)) {return beanInstance;}Object object = getCachedObjectForFactoryBean(beanName);if (object == null) {FactoryBean<?> factoryBean = (FactoryBean<?>) beanInstance;//這里如果是MapperFactoryBean對象,初始化完成以后會進入這里的邏輯object = getObjectFromFactoryBean(factoryBean, beanName);}return object;}
FactoryBeanRegistrySupport的方法getObjectFromFactoryBean--->doGetObjectFromFactoryBean()--->factory.getObject()方法
protected Object getObjectFromFactoryBean(FactoryBean factory, String beanName) {if (factory.isSingleton()) {Object object = this.factoryBeanObjectCache.get(beanName);if (object == null) {//這里如果是MapperFactoryBean對象,初始化完成以后會進入這里的邏輯object = doGetObjectFromFactoryBean(factory, beanName);this.factoryBeanObjectCache.put(beanName, (object != null ? object : NULL_OBJECT));}return (object != NULL_OBJECT ? object : null);} else {return doGetObjectFromFactoryBean(factory, beanName);}}private Object doGetObjectFromFactoryBean(final FactoryBean factory, final String beanName){try {//最終調用MapperFactoryBean的getObject方法獲取實際的對象return factory.getObject();} catch (Exception e) {throw new BeansException("FactoryBean threw exception on object[" + beanName + "] creation", e);}}
MapperFactoryBean的getObject()方法
public T getObject() throws Exception {return this.getSqlSession().getMapper(this.mapperInterface);
}
走到這里,是不是也跟我之前文章超硬核解析Mybatis動態代理原理!只有接口沒實現也能跑?解析的步驟又是一樣的了
getMapper方法的大致調用邏輯鏈是: SqlSession#getMapper() ——> Configuration#getMapper() ——> MapperRegistry#getMapper() ——> MapperProxyFactory#newInstance() ——> Proxy#newProxyInstance()–>MapperProxy#invoke–>MapperMethod#execute
思考聯想
-
在之前的文章中超硬核解析Mybatis動態代理原理!只有接口沒實現也能跑?我們通過
new SqlSessionFactoryBuilder().build(xml)最終調用委托給Configuration#MapperRegistry#addMappper() 方法進行mapper接口的注冊
,而在Spring結合mybatis的過程中,我們通過MapperFactoryBean的checkDaoConfig()最終調用委托給Configuration#MapperRegistry#addMappper() 方法進行mapper接口的注冊
方法實現,本質上是一樣的。 -
在之前的文章中超硬核解析Mybatis動態代理原理!只有接口沒實現也能跑?,我們又通過
SqlSessionFactory的openSession()新建一個SqlSession,然后通過session#getMapper()最終調用委托給MapperRegistry#getMapper()——> MapperProxyFactory#newInstance() 方法實現代理對象的生成
,而在Spring結合mybatis的過程中,我們通過MapperFactoryBean的getObject()調用this.getSqlSession().getMapper(this.mapperInterface)最終也是委托給MapperRegistry#getMapper()——> MapperProxyFactory#newInstance() 方法實現代理對象的生成
,本質也是一樣的道理。
后續
剛剛上面的例子我們可以發現:每配置一個mapper,都需要寫一個對應的MapperFactoryBean,如果mapper多了這樣是很繁瑣的。
<!-- Mapper接口對應的FactoryBean配置 -->
<bean id="userMapper" class="org.mybatis.spring.mapper.MapperFactoryBean"><property name="mapperInterface" value="com.example.mapper.UserMapper" /><property name="sqlSessionFactory" ref="sqlSessionFactory" />
</bean>
為了解決這個問題,我們可以使用MapperScannerConfigurer,讓它掃描特定的包,自動幫我們成批的創建映射器。這樣一來,就能大大減少配置的工作量。具體的實現原理我們后面再進行講解。
<!-- 配置掃描Dao接口包,動態實現Dao接口,注入到spring容器中 --><bean class="org.mybatis.spring.mapper.MapperScannerConfigurer"><!-- 注入sqlSessionFactory --><property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"/><!-- 給出需要掃描Dao接口包 --><property name="basePackage" value="com.joe.dao"/></bean>