Mybatis源码研究之@SelectProvider系列注解

在刚接触到Mybatis时候,对于Mybatis提供的接口方式调用Mybatis映射文件感到十分地神奇,本文将尝试解开其神秘面纱。

1. 相关配置

首先让我们来看看Mybatis里的相关配置,依然是只贴出关键性的:

<configuration>
	...
	<mappers>
		<!-- 指示Mybatis在启动时候需要扫描的目录 -->
		<package name="mybatis.theory.annotation"/>
	</mappers>
</configuration>

2. 源码分析

按照之前的研究,我们知道在Mybatis内部,对于XML配置文件的解析工作是从XMLConfigBuilder开始的,而解析上述配置的准确位置则是位于方法mapperElement()中:

  1. XMLConfigBuilder.mapperElement()方法中Mybatis将最终调用辅助类MapperRegistryaddMappers()方法。
  2. 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的源码可以发现:

  1. 其实现了Spring提供的多个扩展接口——BeanDefinitionRegistryPostProcessorInitializingBeanApplicationContextAwareBeanNameAware
  2. 核心逻辑位于实现自BeanDefinitionRegistryPostProcessor接口的postProcessBeanDefinitionRegistry()方法中——使用自定义的ClassPathMapperScanner类将指定的package下的类型按照约定和配置扫描进Spring容器。
  3. 在自定义的ClassPathMapperScanner类的实现中,针对扫描到的,满足既定要求的类型,将使用MapperFactoryBean<T>进行封装取代。
  4. 对于MapperFactoryBean<T>,其直接实现FactoryBean<T>接口,并间接实现了InitializingBean接口。通过这两个接口,Mybatis将扫描到的接口以Java代理的方式存在于Spring容器中。于是对于初学者而言非常神奇的"无相关实现类的接口,可以调用相应的Mybatis映射文件中的sqlId"的准备工作就算是完成了。
  5. 更多的细节请参见
    Mybatis扩展之通用Mapper源码解析

4. 参考链接

  1. Mybatis扩展之通用Mapper源码解析](https://blog.csdn.net/lqzkcx3/article/details/88990792)
  2. MyBatis注解Annotation介绍及Demo
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值