源码
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);
  }

