Mybatis源码阅读(四)-Spring集成Mybatis-Mapper接口的注入:MapperScannerConfigurer

2023-11-18

一、Spring包扫描注解注入会过滤掉接口类

在Spring IOC容器启动的过程中,Spring会在扫描@CompopnentScan指定的路径时,会将被@Component,@Service等注解的类自动注册BeanDefinition到Spring IOC容器中,但是会过滤掉接口、抽象类,无法生成BeanDefinition,具体的源码可以查看之前的一篇文章《Spring源码阅读(四)-注册BeanDefinition-ConfigurationClassPostProcessor》的第三点。但是Mybatis 的Mapper类是接口,那么mybatis-spring又是如何将Mapper接口类注入到Sprig IOC容器的呢?接下来就要看下这个类-MapperScannerConfigurer做了什么。

二、Mapper接口的注入-MapperScannerConfigurer

注意:MapperScannerConfigurer是xml配置的Mapper接口包扫描解析类,如果是注解配置的Mapper接口则用的包扫描类是MapperScannerRegistrar,扫描解析逻辑都是一样的。我们就来分析下MapperScannerConfigurer。

常见的Mybatis.xml配置文件中,一般是这样配置包扫描的:

<!-- 扫描dao -->  
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">  
	<property name="basePackage" value="com.test.dao"/>  
</bean>

再来看下MapperScannerConfigurer的类的继承关系:
在这里插入图片描述

由上图可知,MapperScannerConfigurer和上面第一点说的ConfigurationClassPostProcessor类一样都是BeanFactoryPostProcessor,所以都会在Spring IOC容器在启动刷新的时候,执行AbstractApplicationContext#invokeBeanFactoryPostProcessors()方法时调用它的postProcessBeanDefinitionRegistry()方法。所以MapperScannerConfigurer是专门给Mybatis的Mapper接口类进行包扫描的类,和Spring IOC自己的包扫描类ConfigurationClassPostProcessor的处理逻辑是有区别的,那我们就来看下具体是什么区别?

看下MapperScannerConfigurer#postProcessBeanDefinitionRegistry源码:

public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) {
    if (this.processPropertyPlaceHolders) {
      processPropertyPlaceHolders();
    }

	// 创建一个mybaits-spring 自己的BeanDefinition扫描器来扫描Mapper接口
    ClassPathMapperScanner scanner = new ClassPathMapperScanner(registry);
    scanner.setAddToConfig(this.addToConfig);
    scanner.setAnnotationClass(this.annotationClass);
    scanner.setMarkerInterface(this.markerInterface);
    scanner.setSqlSessionFactory(this.sqlSessionFactory);
    scanner.setSqlSessionTemplate(this.sqlSessionTemplate);
    scanner.setSqlSessionFactoryBeanName(this.sqlSessionFactoryBeanName);
    scanner.setSqlSessionTemplateBeanName(this.sqlSessionTemplateBeanName);
    scanner.setResourceLoader(this.applicationContext);
    scanner.setBeanNameGenerator(this.nameGenerator);
    scanner.registerFilters();
    scanner.scan(StringUtils.tokenizeToStringArray(this.basePackage, ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS));
}

再来看下ClassPathMapperScanner的继承关系:
在这里插入图片描述

由图可知,ClassPathMapperScanner也是ClassPathBeanDefinitionScanner的子类,它分别重写了ClassPathBeanDefinitionScanner的doScan()和ClassPathScanningCandidateComponentProvider的isCandidateComponent()方法,而重点就是这个重写方法isCandidateComponent(),它允许Mapper接口注册成BeanDefinition!ClassPathMapperScanner#isCandidateComponent()方法源码如下:

protected boolean isCandidateComponent(AnnotatedBeanDefinition beanDefinition) {
    return beanDefinition.getMetadata().isInterface() && beanDefinition.getMetadata().isIndependent();
  }

将Mpapper接口注册成BeanDefinition之后,再来看下它又是如何"偷梁换柱"把他从接口类,变成代理类的。看下ClassPathMapperScanner#doScan()方法源码:

public Set<BeanDefinitionHolder> doScan(String... basePackages) {

	// 重写isCandidateComponent,将Mapper接口注册成BeanDefinition
    Set<BeanDefinitionHolder> beanDefinitions = super.doScan(basePackages);

    if (beanDefinitions.isEmpty()) {
      logger.warn("No MyBatis mapper was found in '" + Arrays.toString(basePackages) + "' package. Please check your configuration.");
    } else {
	
	  // "偷梁换柱"将Mapper接口变成代理类
      processBeanDefinitions(beanDefinitions);
    }

    return beanDefinitions;
}

ClassPathMapperScanner#processBeanDefinitions()方法源码如下:

private void processBeanDefinitions(Set<BeanDefinitionHolder> beanDefinitions) {
    GenericBeanDefinition definition;
    for (BeanDefinitionHolder holder : beanDefinitions) {
      definition = (GenericBeanDefinition) holder.getBeanDefinition();

      if (logger.isDebugEnabled()) {
        logger.debug("Creating MapperFactoryBean with name '" + holder.getBeanName() 
          + "' and '" + definition.getBeanClassName() + "' mapperInterface");
      }
      
	  // 将Mapper接口传入MapperFactoryBean的构造函数中,后面MapperProxy创建Mapper的动态代理时会用到。
      // the mapper interface is the original class of the bean
      // but, the actual class of the bean is MapperFactoryBean
      definition.getConstructorArgumentValues().addGenericArgumentValue(definition.getBeanClassName()); // issue #59
	  
	  // “偷梁换柱”
      definition.setBeanClass(this.mapperFactoryBean.getClass());

      definition.getPropertyValues().add("addToConfig", this.addToConfig);

      boolean explicitFactoryUsed = false;
      if (StringUtils.hasText(this.sqlSessionFactoryBeanName)) {
        definition.getPropertyValues().add("sqlSessionFactory", new RuntimeBeanReference(this.sqlSessionFactoryBeanName));
        explicitFactoryUsed = true;
      } else if (this.sqlSessionFactory != null) {
      
      	// 给MapperFactoryBean的sqlSessionFactory属性赋值
        definition.getPropertyValues().add("sqlSessionFactory", this.sqlSessionFactory);
        explicitFactoryUsed = true;
      }

      if (StringUtils.hasText(this.sqlSessionTemplateBeanName)) {
        if (explicitFactoryUsed) {
          logger.warn("Cannot use both: sqlSessionTemplate and sqlSessionFactory together. sqlSessionFactory is ignored.");
        }
        definition.getPropertyValues().add("sqlSessionTemplate", new RuntimeBeanReference(this.sqlSessionTemplateBeanName));
        explicitFactoryUsed = true;
      } else if (this.sqlSessionTemplate != null) {
        if (explicitFactoryUsed) {
          logger.warn("Cannot use both: sqlSessionTemplate and sqlSessionFactory together. sqlSessionFactory is ignored.");
        }
 		// 给MapperFactoryBean的sqlSessionTemplate属性赋值
        definition.getPropertyValues().add("sqlSessionTemplate", this.sqlSessionTemplate);
        explicitFactoryUsed = true;
      }

      if (!explicitFactoryUsed) {
        if (logger.isDebugEnabled()) {
          logger.debug("Enabling autowire by type for MapperFactoryBean with name '" + holder.getBeanName() + "'.");
        }
        definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE);
      }
    }
}
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Mybatis源码阅读(四)-Spring集成Mybatis-Mapper接口的注入:MapperScannerConfigurer 的相关文章

随机推荐

  • QML-消息提示框

    QML 消息提示框 前言 一 提示框 二 警告提示框 三 错误提示框 四 属性介绍 前言 介绍常用的消息提示框 包括提示 错误 报警等 一 提示框 import QtQuick 2 12 import QtQuick Window 2 12
  • Qt creator出现mainwindow.ui does not exist,导致无法编译通过

    遇到这个问题 首先是之前做过删除ui mainwindow h的操作步骤 导致出现这样的问题 参考博客 https blog csdn net mhw828 article details 104143881 解决的方法就如这位博主说的 需
  • redis集群模式

    redis单机版 出现单机故障后 导致redis无法使用 如果程序使用redis 间接导致程序出错 redis的集群模式 主从复制模式 哨兵模式 集群化模式 1 主从复制模式 一主多从模式 一个主节点 多个从节点 那么主节点可以负责 读操作
  • MySQL拾遗之数据类型的默认值-default

    MySQL 中 所有的数据类型 都可以显式或隐式的拥有默认值 我们可以使用 DEFAULT 约束显式的为列指定一个默认值 比如 CREATE TABLE t1 i INT DEFAULT 1 c VARCHAR 10 DEFAULT pri
  • 正则,JS:this,同步异步,原型链笔记整理

    一 正则表达式 正则表达式 regular expression 是一种表达文本模式 即字符串结构 的方法 有点像字符串的模板 常常用来按照 给定模式 匹配文本 正则表达式可以用于以下常见操作 匹配 判断一个字符串是否符合某个模式 搜索 在
  • DCA和DTS关系

    On the consumer level DTS is the oft used shorthand for the DTS Coherent Acoustics DCA codec transportable through S PDI
  • 深信服C++开发岗笔试记录

    如果大家也在找面试笔试题目内容 可以看我的总结文章 正在更新之中 有没涉及到的内容 欢迎大家指出 附链接 数据库 C C HTML OS 计网面试准备 更新中 笔试共有三部分 不定项选择 8道 填空 7道 编程 3道 选择和填空的部分内容涉
  • Mybatis left join 一对多及多对多查询配置

    一对一查询配置
  • 90-50-010-源码-hbase的rowkey设计

    1 视界 1 rowKey编码概述 注 Kylin源码分析系列基于Kylin的2 6 0版本的源码 其他版本可以类比 2 相关概念 前面介绍了Kylin中Cube构建的流程 但Cube数据具体是以什么样的形式存在 可能还不是特别清晰明了 这
  • 拷贝构造函数调用总结

    拷贝构造函数主要在以下三种情况下起初始化作用 1 在声明语句中用一个对象初始化另一个对象 2 将一个对象作为参数按值调用方式传递给另一个对象时生成对象副本 3 生成一个临时对象作为函数的返回结果 那么接着就看一下在这三种情况下拷贝构造函数分
  • python自动化课程笔记(六)函数

    函数 类 模块 包 项目 包与文件夹的区别在于 包中有很多模块 和init文件 函数 提高编码的效率及代码的重用 把独立功能的代码块组成一个小模块 def printInfo 定义一个函数 print 10 print 人生苦短 我用pyt
  • Microsoft Office 2021安装

    哈喽 大家好 今天一起学习的是office2021的安装 有兴趣的小伙伴也可以来一起试试手 一 测试演示参数 演示操作系统 Windows 11 支持Win10安装 不支持Win7 XP系统 系统类型 64位 演示版本 cn office
  • PMP估算方法对比:参数估算、类比估算、自下而上估算、三点估算和粗略量级估算

    目录 1 类比估算 2 参数估算 3 自下而上估算 4 三点估算 5 粗略量级估算 1 类比估算 英文全称 Analogous Estimating Technique 定义 与过去类似活动的参数值 如范围 成本 预算和持续时间等 或规模指
  • 快乐E栈项目实战第四阶段

    快乐E栈项目实战第四阶段 文章目录 快乐E栈项目实战第四阶段 1 思路 2 代码 3 结果 学完Java的IO操作 我们使用文件将快递信息存储起来 这样程序重新启动起来存储的快递信息也不会丢失 暂时不使用序列化进行存储 使用Properti
  • MySQL C/C++ API libmysqlclient-dev

    完整的API内容地址 https www mysqlzh com doc 196 html 接口 接口 解释 mysql library init 初始化MySQL C API库 mysql library end 最终确定MySQL C
  • Calendar的使用

    Calendar Calendar是一个抽象类 构造器被protected修饰 需要通过getInstance 获取实例 public static void main String args Calendar instance Calen
  • IDEA2021中VUE代码爆红解决方案

    1 在IDEA中安装vue js插件 找到vue js安装即可 之后点击apply gt OK 2 安装完成后打开cmd 输入npm v npm命令集成在node js里面 如果这条命令失败则前往node js进行相关的安装 3 爆红的主要
  • 解决Ubuntu 20.04网络无法连接,没有网络图标

    现象 网络适配器处于NAT 并且电脑主机有网络 但是Ubuntu20 04中没有网络 或者初次启动Ubuntu20 04时是由网络的 但后来不知是何原因导致网络不通 无法用浏览器访问百度 如下图右上角有线网络图标消失了 解决方法 删除Net
  • 【教程分享】Docker搭建Zipkin,实现数据持久化到MySQL、ES

    1 拉取镜像 指定版本 在git查看相应版本 参考 https github com openzipkin zipkin 如2 21 7 docker pull openzipkin zipkin 2 21 7 2 启动 Zipkin默认端
  • Mybatis源码阅读(四)-Spring集成Mybatis-Mapper接口的注入:MapperScannerConfigurer

    一 Spring包扫描注解注入会过滤掉接口类 在Spring IOC容器启动的过程中 Spring会在扫描 CompopnentScan指定的路径时 会将被 Component Service等注解的类自动注册BeanDefinition到