源码解析mapper接口代理

突然想到 mybatis 的 mapper 接口没有实现类,却能在 service 层直接注入并调用其方法操作数据库,之前只是知道这么做就行,出于好奇网上找了一下答案跟着源码走了一遍。

1
2
3
4
<bean id="userMapper" class="org.mybatis.spring.mapper.MapperFactoryBean">
<property name="mapperInterface" value="org.mybatis.spring.sample.mapper.UserMapper" />
<property name="sqlSessionFactory" ref="sqlSessionFactory" />
</bean>

如官方文档中所示,为了代替手工使用 SqlSessionDaoSupport 或 SqlSessionTemplate 编写数据访问对象 (DAO)的代码,MyBatis-Spring 提供了一个动态代理的实现:MapperFactoryBean。这个类 可以让你直接注入数据映射器接口到你的 service 层 bean 中。当使用映射器时,你仅仅如调用你的 DAO 一样调用它们就可以了,但是你不需要编写任何 DAO 实现的代码,因为 MyBatis-Spring 将会为你创建代理。

MapperFactoryBean 创建的代理类实现了 UserMapper 接口,并且注入到应用程序中。因为代理创建在运行时环境中(Runtime,译者注) ,那么指定的映射器必须是一个接口,而不是一个具体的实现类。

当映射器很多的时候,没有必要一个个的去注册,我们可以使用 MapperScannerConfigurer 来扫描一个或多个包路径(使用逗号或分号作为分隔符),递归搜索每个包路径下的每个映射器。

1
2
3
4
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<property name="basePackage" value="org.mybatis.spring.sample.mapper" />
<property name="sqlSessionFactoryBeanName" value="sqlSessionFactory" />
</bean>

下面我们来看下 MapperScannerConfigurer 的源码,MapperScannerConfigurer 实现了 BeanDefinitionRegistryPostProcessor 接口,BeanDefinitionRegistryPostProcessor 接口是一个可以修改 spring 工厂中已定义的 bean 的接口,该接口有个 postProcessBeanDefinitionRegistry 方法。


MapperScannerCnfigurer源码

重点看蓝色这行,从 scan() 就可以看出来这是对前面配置文件中定义时的包进行扫描,这个 scan()方法是从 ClassPathMapperScanner 的父类 ClassPathBeanDefinitionScanner 继承的,进去看看先。


ClassPathMapperScanner源码
ClassPathMapperScanner源码

父类的 doScan() 方法就不看了,返回一个 BeanDefinitionHolder 的Set集合,然后进 processBeanDefinitions() 方法看下


ClassPathMapperScanner源码

蓝色部分很明显了,对于每个 beanDefinition,将 mapperInterface 作为属性设置进去,并把 beanClass 设置为 MapperFactoryBean(原类型为mapper接口)。这也与前面用 MapperFactoryBean 的定义方式相对应。

我们再来看下 MapperFctoryBean,MapperFactoryBean 继承了 SqlSessionDaoSupport 类,SqlSessionDaoSupport 类继承 DaoSupport 抽象类,DaoSupport 抽象类实现了 InitializingBean 接口,因此实例个 MapperFactoryBean 的时候,都会调用 InitializingBean 接口的 afterPropertiesSet 方法。( InitializingBean 接口为 bean 提供了初始化方法的方式,它只包括 afterPropertiesSet 方法,凡是继承该接口的类,在初始化 bean 的时候会执行该方法。)


DaoSupport源码

MapperFactoryBean重写了checkDaoConfig():


MapperFactoryBean源码

如果 mapperInterface 不在 configuration 中则添加进去,调用的是 Configuration 里的 MapperRegistry 对象的 addMapper()方法:


MapperRegistry源码

然后通过Spring工厂获取对应bean的时候:


MapperFactoryBean源码

这里的SqlSession是SqlSessionTemplate,SqlSessionTemplate的getMapper方法:


SqlSessionTemplate源码

Configuration的getMapper方法,会使用MapperRegistry的getMapper方法:


Configuration源码

MapperRegistry的getMapper方法:


MapperRegistry源码

MapperProxyFactory构造MapperProxy:


MapperProxyFactory源码

再来看下MapperProxy:


MapperProxy源码

MapperProxy 实现了 InvocationHandler,这就很明显了啊,用的是 JDK 动态代理啊,具体与 CGLIB 代理区别见JDK动态代理与CGLIB动态代理。然后再看 mapperMethod 的
execute()方法:


MapperMethod源码

整了半天原来还是sqlSession在操作啊(虽然以前就知道,但还是要表现的很惊讶哈哈)