文章目录
- 前言
- 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 {
@Bean
public 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();
}
}
}
@Override
public 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#getObject
return (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>