在刚接触到Mybatis时候,对于Mybatis提供的接口方式调用Mybatis映射文件感到十分地神奇,本文将尝试解开其神秘面纱。
1. 相关配置
首先让我们来看看Mybatis里的相关配置,依然是只贴出关键性的:
<configuration>
...
<mappers>
<!-- 指示Mybatis在启动时候需要扫描的目录 -->
<package name="mybatis.theory.annotation"/>
</mappers>
</configuration>
2. 源码分析
按照之前的研究,我们知道在Mybatis内部,对于XML配置文件的解析工作是从XMLConfigBuilder
开始的,而解析上述配置的准确位置则是位于方法mapperElement()
中:
- 在
XMLConfigBuilder.mapperElement()
方法中Mybatis将最终调用辅助类MapperRegistry
的addMappers()
方法。 - 在
MapperRegistry.addMappers()
方法中,Mybatis将遍历出其下所有的接口类型,对于每个接口类型,将完成以下两项重要操作。
a. 对于每个接口类型都使用一个MapperProxyFactory<T>
进行封装。
b. 使用辅助类MapperAnnotationBuilder
来完成每个接口类型的解析工作。
2.1 MapperProxyFactory<T>
类
此类只看名字就能猜到是个工厂模式,用于构建MapperProxy<T>
实例。通过观察MapperProxyFactory
类的核心方法newInstance()
(其中直接调用了JDK中的Proxy.newProxyInstance()
方法),以及相关的MapperProxy<T>
类(其直接实现了我们非常熟悉的InvocationHandler
接口)。
也正是MapperProxyFactory<T>
和MapperProxy<T>
的相互合作,使得我们可以以强类型的方式调用Mybatis映射文件中的sqlId。
2.2 MapperAnnotationBuilder
类
在 MapperAnnotationBuilder
中,核心逻辑为 parse()方法,而其逻辑大致分为两个部分:
a. 加载接口相匹配的映射文件loadXmlResource()
方法。
b. parseStatement()
方法解析采用注解方式配置的SQL语句,此部分逻辑,和Mybatis中负责解析XML映射文件中的每个<insert>
,<select>
,<update>
,<delete>
节点的XMLStatementBuilder.parseStatementNode()
方法非常相似。而且这两个类都是只负责收集必要参数,而将构造逻辑交给了MapperBuilderAssistant
类来完成。
以上两个逻辑中,我们这次主要关注的是第二个逻辑里回调的getSqlSourceFromAnnotations()
方法,该方法将负责解析我们本次标题里提及的两大类注解—— @Select
系列和@SelectProvider
系列,前者这些注解中的每一个代表了执行的真实 SQL。后者则是允许你指定一个类名和一个方法在执行时来返回运行的SQL —— 这就是使得我们可以使用自己更习惯的语言逻辑来拼接SQL。更详细的区别这里就不叙述了。
2.3 MapperProxy<T>
类
诚如上面提到的,MapperProxy<T>
直接继承自InvocationHandler
,而且这个T
就是我们所定义的Dao接口。所以基于Java中Proxy的实现方式,当我们以接口方法的方式调用Mybatis映射文件中的sql时候,最终都将落到本类中的invoke()
方法上。
// `MapperProxy<T>`中`invoke`的实现
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
try {
// 对于Object类的方法将直接调用
if (Object.class.equals(method.getDeclaringClass())) {
return method.invoke(this, args);
} else if (isDefaultMethod(method)) {
return invokeDefaultMethod(proxy, method, args);
}
} catch (Throwable t) {
throw ExceptionUtil.unwrapThrowable(t);
}
// 其他的方法(即接口方法)将都默认是调用相应的sql。
final MapperMethod mapperMethod = cachedMapperMethod(method);
return mapperMethod.execute(sqlSession, args);
}
3. 时序图
相应的时序图如下:
3. Spring集成
mybatis还提供了与Spring集成的mybatis-spring-1.x.x.jar,基本的配置方法如下:
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<property name="basePackage" value="org.mybatis.spring.sample.mapper" />
<!-- optional unless there are multiple session factories defined -->
<property name="sqlSessionFactoryBeanName" value="sqlSessionFactory" />
</bean>
通观MapperScannerConfigurer
的源码可以发现:
- 其实现了Spring提供的多个扩展接口——
BeanDefinitionRegistryPostProcessor
,InitializingBean
,ApplicationContextAware
,BeanNameAware
。 - 核心逻辑位于实现自
BeanDefinitionRegistryPostProcessor
接口的postProcessBeanDefinitionRegistry()
方法中——使用自定义的ClassPathMapperScanner
类将指定的package下的类型按照约定和配置扫描进Spring容器。 - 在自定义的
ClassPathMapperScanner
类的实现中,针对扫描到的,满足既定要求的类型,将使用MapperFactoryBean<T>
进行封装取代。 - 对于
MapperFactoryBean<T>
,其直接实现FactoryBean<T>
接口,并间接实现了InitializingBean
接口。通过这两个接口,Mybatis将扫描到的接口以Java代理的方式存在于Spring容器中。于是对于初学者而言非常神奇的"无相关实现类的接口,可以调用相应的Mybatis映射文件中的sqlId"的准备工作就算是完成了。 - 更多的细节请参见
Mybatis扩展之通用Mapper源码解析。
4. 参考链接
- Mybatis扩展之通用Mapper源码解析](https://blog.csdn.net/lqzkcx3/article/details/88990792)
- MyBatis注解Annotation介绍及Demo