源码
UserMapper
1 | package org.denger.mapper; |
application-context.xml
1 | <?xml version="1.0" encoding="UTF-8"?> |
test
1 | package org.denger.mapper; |
原理分析
对于以上极其简单代码看上去并无特殊之处,主要亮点在于 UserMapper
居然不用实现类,而且在调用 getUser
的时候,也是使用直接调用了UserMapper
实现类,那么Mybatis是如何去实现 UserMapper
的接口的呢?
可能你马上能想到的实现机制就是通过动态代理方式,看看MyBatis整个的代理过程吧。
首先在Spring的配置文件中看到下面的Bean。1
2
3
4<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<property name="sqlSessionFactory" ref="sqlSessionFactory"/>
<property name="basePackage" value="org.denger.mapper"></property>
</bean>
1 | /** |
以上的MapperScannerConfigurer
的注释中描述道。
从base包中搜索所有下面所有interface
,并将其注册到Spring Bean容器中,其注册的class bean是MapperFactoryBean
。
看看它的注册过程,从MapperScannerConfigurer#Scanner
类中抽取,在初始化以上application-content.xml
文件时就会进行调用。
主要用于是搜索base packages
下的所有mapper.class
,并将其注册至spring的benfinitionHolder
中。
MapperScannerConfigurer#Scanner#doScan
1 | /** |
- Spring初始化Bean过程的人可能都知道,Spring首先会将需要初始化的所有class先通过
BeanDefinitionRegistry
进行注册,并且将该Bean的一些属性信息(如scope
、className
、beanName
等)保存至BeanDefinitionHolder
中;MyBatis这里首先会调用Spring中的ClassPathBeanDefinitionScanner#doScan()
,将所有Mapper
接口的class
注册至BeanDefinitionHolder
实例中,然后返回一个Set<BeanDefinitionHolder>
,包含所有搜索到的Mapper#BeanDefinitionHolder
对象。 - 由于#1中注册的都是接口class, 可以肯定的是接口是不能直接初始化的;
for
循环中替换当前所有holder
的className
为MapperFactoryBean.class
,并且将mapper interface
的class name setter
至MapperFactoryBean
属性为mapperInterface
中,也就是 #3代码所看到的。
再看看MapperFactoryBean
,它是直接实现了Spring的FactoryBean
及InitializingBean
接口。其实既使不看这两个接口,当看MapperFactoryBean
的classname
就知道它是一个专门用于创建 Mapper
实例Bean的工厂。
至此,已经完成了Spring与MyBatis的整合的初始化配置的过程。
接着,在以上的Test类中,需要注入UserMapper
接口实例时,由于MyBatis给所有的Mapper
实例注册都是一个MapperFactory
的生成,所以产生UserMapper
实现仍需要MapperFactoryBean
来进行创建。接下来看看 MapperFactoryBean
的处理过程。
先需要创建Mapper
实例时,首先在 MapperFactoryBean
中执行的方法。
MapperFactoryBean#afterPropertiesSet()
1 | /** |
上面方法中先会检测当前需要创建的mapperInterface
在全局的Configuration
中是否存在,如果不存在则添加。等等再说他为什么要将 mapperInterface
添加至 Configuration
中。该方法调用完成之后,就开始调用 getObject()
来产生mapper
实例。
MapperFactoryBean#getObject()
1 | /** |
通过Debug可以看到 sqlSession
及mapperInterface
对象:
到目前为止UserMapper
实例实际上还并未产生,再进入org.mybatis.spring.SqlSessionTemplate#getMapper()
,该方法将this
及mapper interface class
作为参数传入 org.apache.ibatis.session.Configuration#getMapper()
,代码如下。
SqlSessionTemplate#getMapper()
1 | /** |
org.apache.ibatis.session.Configuration#getMapper()
1 | public <T> T getMapper(Class<T> type, SqlSession sqlSession) { |
mapperRegistry
保存着当前所有的mapperInterface class
,那么它在什么时候将 mapperinterface class
保存进入的呢?其实就是在上面的 afterPropertiesSet
中通过 configuration.addMapper(this.mapperInterface)
添加进入的。
org.apache.ibatis.binding.MapperRegistry#getMapper()
1 | public <T> T getMapper(Class<T> type, SqlSession sqlSession) { |
- 首先判断当前
knownMappers
是否存在mapper interface class
,因为前面说到afterPropertiesSet
中已经将当前的mapper interface class
添加进入了。 - 生成
Mapper Proxy
对象。MapperProxy#newMapperProxy()
1
2
3
4
5
6public static <T> T newMapperProxy(Class<T> mapperInterface, SqlSession sqlSession) {
ClassLoader classLoader = mapperInterface.getClassLoader();
Class[] interfaces = new Class[]{mapperInterface};
MapperProxy proxy = new MapperProxy(sqlSession);
return (T) Proxy.newProxyInstance(classLoader, interfaces, proxy);
}
JDK的动态代理就不说,至此,基本Mybatis已经完成了Mapper
实例的整个创建过程,也就是在具体使用 UserMapper#getUser()
时,它实际上调用的是 MapperProxy
,因为此时所返回的 MapperProxy
是实现了UserMapper
接口的。只不过 MapperProxy
拦截了所有对userMapper
中方法的调用,如下。
MapperProxy#invoke
1 | public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { |
为什么说它拦截了呢?可以看到, 它并没有调用 method.invoke(object)
方法,因为实际上 MapperProxy
只是动态的implement UserMapper
接口,但它没有真正实现 UserMapper
中的任何方法。至于结果的返回,它也是通过 MapperMethod.execute
中进行数据库操作来返回结果的。
就是一个实现了 MapperInterface
的 MapperProxy
实例被MapperProxy
代理了,可以Debug看看 userMapper
实例就是 MapperProxy
。
Mybatis版本差异
以上文章很早以前写的,现在用Mybatis3.4.0版本,2个版本存在一些差异。
MapperFactoryBean#checkDaoConfig()
MapperFactoryBean#afterPropertiesSet
方法,在3.4版本中不存在,3.4版本:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17protected void checkDaoConfig() {
super.checkDaoConfig();
notNull(this.mapperInterface, "Property 'mapperInterface' is required");
Configuration configuration = getSqlSession().getConfiguration();
if (this.addToConfig && !configuration.hasMapper(this.mapperInterface)) {
try {
configuration.addMapper(this.mapperInterface);
} catch (Exception e) {
logger.error("Error while adding the mapper '" + this.mapperInterface + "' to configuration.", e);
throw new IllegalArgumentException(e);
} finally {
ErrorContext.instance().reset();
}
}
}
MapperProxy#invoke()
在3.4版本中也不一样。3.4版本:1
2
3
4
5
6
7
8
9
10
11public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if (Object.class.equals(method.getDeclaringClass())) {
try {
return method.invoke(this, args);
} catch (Throwable t) {
throw ExceptionUtil.unwrapThrowable(t);
}
}
final MapperMethod mapperMethod = cachedMapperMethod(method);
return mapperMethod.execute(sqlSession, args);
}