@MapperScan原理探究

news2025/1/12 16:08:38

1. 前言

MyBatis在整合Spring的时候,只需要加如下注解,就可以将Mapper实例注册到IOC容器交给Spring管理,它是怎么做到的呢???

@MapperScan("com.xxx.mapper")

提出几个问题:

  • Mapper接口不能实例化,对象是怎么来的?
  • Mapper接口没有加任何Spring相关注解,Spring凭什么管理这些Bean?

2. ImportBeanDefinitionRegistrar

ImportBeanDefinitionRegistrar是Spring提供的接口,属于Spring的扩展点之一。该接口会暴露BeanDefinitionRegistry对象,Spring允许我们手动往容器注册自定义的BeanDefinition。

public interface ImportBeanDefinitionRegistrar {
	/**
	 * 注册自定义BeanDefinition
	 *
	 * @param importingClassMetadata 导入类的元数据,被谁导入的
	 * @param registry BeanDefinition注册器
	 */
	void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry);
}

使用起来也很简单,新建类实现ImportBeanDefinitionRegistrar接口,重写registerBeanDefinitions()方法实现注册自定义BeanDefinition的相关逻辑,然后通过@Import注解引入即可。

ImportBeanDefinitionRegistrar实例本身并不会注册到容器,Spring仅仅是通过反射实例化对象,然后触发registerBeanDefinitions()方法而已。

3. ConfigurationClassPostProcessor

ImportBeanDefinitionRegistrar扩展点是在哪里被触发的呢???
AnnotationConfigApplicationContext类的构造函数里会创建AnnotatedBeanDefinitionReader对象用来读取并注册基于注解的BeanDefinition,AnnotatedBeanDefinitionReader的构造函数有一个特别重要的功能,就是往容器手动注册Spring内置的几个非常重要的,用来支撑Spring底层核心功能的BeanDefinition,分别是:

  • ConfigurationClassPostProcessor
  • AutowiredAnnotationBeanPostProcessor
  • CommonAnnotationBeanPostProcessor
  • PersistenceAnnotationBeanPostProcessor
  • EventListenerMethodProcessor
  • DefaultEventListenerFactory

ConfigurationClassPostProcessor这个类特别特别重要,它做的事情包括:

  • 解析@ComponentScan注解扫描自定义的Bean。
  • 解析@PropertySources@Value注解读取配置文件属性。
  • 解析@Import注解引入自定义类。
  • 解析@ImportResource注解引入外部Spring配置文件。
  • 处理@Bean注解方法。
    在这里插入图片描述

ConfigurationClassPostProcessor实现了BeanFactoryPostProcessor和BeanDefinitionRegistryPostProcessor接口。BeanFactoryPostProcessor也是Spring的扩展点之一,它允许开发者对BeanFactory进行扩展;BeanDefinitionRegistryPostProcessor扩展的语义更明确一些,它表示要对BeanFactory完成BeanDefinition的注册。BeanDefinitionRegistryPostProcessor#postProcessBeanDefinitionRegistry()会比BeanFactoryPostProcessor#postProcessBeanFactory()先执行。

Spring启动时,准备好BeanFactory后就会开始触发BeanFactoryPostProcessor扩展点,ConfigurationClassPostProcessor因为在构造函数里已经被注册到容器中,所以会被执行到。它会去解析ConfigurationClass是否有加@Import注解,如果加了该注解,且引入的类是ImportBeanDefinitionRegistrar子类,就会去实例化子类对象,然后执行它的registerBeanDefinitions()方法。

4. MapperScannerRegistrar

查看@MapperScan注解发现,它的确加了@Import注解,且引入的MapperScannerRegistrar类就是ImportBeanDefinitionRegistrar的子类。
image.png
在这里插入图片描述

也就是说Spring在启动时,触发ImportBeanDefinitionRegistrar扩展点的时候,会执行MyBatis写的MapperScannerRegistrar的扩展逻辑。其实从名字就可以看的出来,这个类的作用是完成MapperScanner的注册工作,MapperScanner是啥?就是Mapper接口的扫描器了。
MapperScannerRegistrar的扩展逻辑很简单,创建自定义的Bean扫描器ClassPathMapperScanner,然后扫描@MapperScan注解指定的包路径。

public class MapperScannerRegistrar implements ImportBeanDefinitionRegistrar, ResourceLoaderAware {

  private ResourceLoader resourceLoader;

  @Override
  public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
  	// 注解属性
    AnnotationAttributes annoAttrs = AnnotationAttributes.fromMap(importingClassMetadata.getAnnotationAttributes(MapperScan.class.getName()));
    // 创建自定义的Mapper扫描器,用来扫描Mapper接口
    ClassPathMapperScanner scanner = new ClassPathMapperScanner(registry);

    // this check is needed in Spring 3.1
    if (resourceLoader != null) {
      scanner.setResourceLoader(resourceLoader);
    }

    Class<? extends Annotation> annotationClass = annoAttrs.getClass("annotationClass");
    if (!Annotation.class.equals(annotationClass)) {
      // 如果指定了注解,则只扫描加了指定注解的Mapper接口
      scanner.setAnnotationClass(annotationClass);
    }

    Class<?> markerInterface = annoAttrs.getClass("markerInterface");
    if (!Class.class.equals(markerInterface)) {
      scanner.setMarkerInterface(markerInterface);
    }

    Class<? extends BeanNameGenerator> generatorClass = annoAttrs.getClass("nameGenerator");
    if (!BeanNameGenerator.class.equals(generatorClass)) {
      // 指定BeanName生成器,如果有
      scanner.setBeanNameGenerator(BeanUtils.instantiateClass(generatorClass));
    }

    Class<? extends MapperFactoryBean> mapperFactoryBeanClass = annoAttrs.getClass("factoryBean");
    if (!MapperFactoryBean.class.equals(mapperFactoryBeanClass)) {
      scanner.setMapperFactoryBean(BeanUtils.instantiateClass(mapperFactoryBeanClass));
    }

    scanner.setSqlSessionTemplateBeanName(annoAttrs.getString("sqlSessionTemplateRef"));
    scanner.setSqlSessionFactoryBeanName(annoAttrs.getString("sqlSessionFactoryRef"));

    List<String> basePackages = new ArrayList<String>();
    for (String pkg : annoAttrs.getStringArray("value")) {
      if (StringUtils.hasText(pkg)) {
        basePackages.add(pkg);
      }
    }
    for (String pkg : annoAttrs.getStringArray("basePackages")) {
      if (StringUtils.hasText(pkg)) {
        basePackages.add(pkg);
      }
    }
    for (Class<?> clazz : annoAttrs.getClassArray("basePackageClasses")) {
      basePackages.add(ClassUtils.getPackageName(clazz));
    }
    // 注册过滤器,定义Bean的扫描规则
    scanner.registerFilters();
    // 开始扫描
    scanner.doScan(StringUtils.toStringArray(basePackages));
  }

  /**
* {@inheritDoc}
*/
  @Override
  public void setResourceLoader(ResourceLoader resourceLoader) {
    this.resourceLoader = resourceLoader;
  }

}

具体的扫描工作交给了ClassPathMapperScanner类,它继承自Spring提供的ClassPathBeanDefinitionScanner,就不用自己去实现扫描Class的逻辑了,这里用到了模板方法模式,子类通过重写部分方法,来自定义Bean的扫描和注册规则。

@Override
  public Set<BeanDefinitionHolder> doScan(String... basePackages) {
    // 父类完成扫描,得到一组BeanDefinition
    Set<BeanDefinitionHolder> beanDefinitions = super.doScan(basePackages);
    
    if (beanDefinitions.isEmpty()) {
      // 没有符合的Bean,不做处理
      logger.warn("No MyBatis mapper was found in '" + Arrays.toString(basePackages) + "' package. Please check your configuration.");
    } else {
      // 处理BeanDefinition,因为Mapper接口不能被实例化
      processBeanDefinitions(beanDefinitions);
    }
    return beanDefinitions;
  }

调用父类的doScan()方法完成扫描得到一组BeanDefinition,如果有符合规则的BeanDefinition,这里需要做处理,不能直接返回。因为此时BeanDefinition的beanClass指向的是Mapper接口,直接注册到容器的话,Spring不知道怎么实例化Bean。 所以,MyBatis还需要做点小动作,对BeanDefinition做一些修改。主要是重设beanClass,将其指向MapperFactoryBean。因为MapperFactoryBean是类,可以被实例化。

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

    // MapperFactoryBean构造函数需要MapperClass
    // 这里是告诉Spring实例化MapperFactoryBean时构造函数传哪个Class
    definition.getConstructorArgumentValues().addGenericArgumentValue(definition.getBeanClassName());
    // 重设beanClass 指向MapperFactoryBean
    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) {
      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.");
      }
      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);
    }
  }
}

5. MapperFactoryBean

Mapper接口不能被实例化,所以MyBatis会将扫描到的属于MapperClass的BeanDefinition做些修改,将beanClass指向MapperFactoryBean,这样Spring在实例化Bean的时候就会去创建MapperFactoryBean实例了。
MapperFactoryBean实现了FactoryBean接口,SpringgetBean()时会判断,如果一个BeanClass实现了FactoryBean接口,则不直接返回bean,而是返回FactoryBean#getObject()方法返回的对象。也就是说,本该由Spring完成的Bean实例化过程,交给了MyBatis自己来实现。

@Override
  public T getObject() throws Exception {
    // 基于Mapper接口生成代理对象
    return getSqlSession().getMapper(this.mapperInterface);
}

通过MapperFactoryBean#getObject()发现,MyBatis会调用SqlSession#getMapper()方法基于Mapper接口创建JDK动态代理对象。也就是说,Mapper接口对应的BeanDefinition,对应的在Spring容器里的对象是MyBatis生成的代理对象。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/96890.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Snipaste的使用

Snipaste截屏软件的使用&#xff1a; 1、开始截屏 第一种方式&#xff1a;快捷键 &#xff08;默认是F1&#xff09;也就是说按一下F1键就会进入截屏状态。 第二种方式&#xff1a;点击软件在任务栏上的图标。 2、选定截屏区域&#xff1a; 进入截屏状态后移动鼠标&#x…

绩效考核管理方案

第一部分 总 则 第一条&#xff1a;目的 1、通过绩效考核&#xff0c;传递组织目标和压力&#xff0c;促使员工提高工作绩效&#xff0c;达到“培养员工、提高员工的工作能力、纠正员工偏差、使之更好地为公司服务&#xff0c;达到公司与个人之间的双赢”的目的。 2、加强公司的…

四、fs文件系统模块

fs模块是Node.js官方提供用来操作文件的模块&#xff0c;属于核心模块&#xff0c;提供了一些列的方法和属性&#xff0c;用来满足用户的操作需求&#xff1b; 引入fs模块 const fs require(fs); fs.readFile() 读取 读取指定的内容&#xff0c;fs.readFile(path[&#xff…

图文排版 之 line-height

图文排版 之 line-height 设置行盒子的高度. line-height 经常被用来设置多行文本的行间距. 对于块级元素, line-height 制定了行盒子的最小高度. 对于非替换的内联元素, line-height 的值用来计算行盒子的高度. 一般来说, 大家都知道一个 div 的高度默认是由其子元素撑起的, …

史上最全 Java 高频面试合集,命中率高达 95%

进大厂是大部分程序员的梦想&#xff0c;而进大厂的门槛也是比较高的&#xff0c;所以这里整理了一份阿里、美团、滴滴、头条等大厂面试大全&#xff0c;其中概括的知识点有&#xff1a;Java、MyBatis、ZooKeeper、Dubbo、Elasticsearch、Memcached、 Redis、MySQL、Spring、S…

现代控制理论

系统的状态空间表达式的建立 建立系统状态空间表达式的三种方法 &#xff08;1&#xff09;根据系统的方框图列写 &#xff08;2&#xff09;从系统的基本原理推导 &#xff08;3&#xff09;根据传递函数或者高阶微分方程实现 方框图法 有些系统的系统机理还没搞清楚可以使…

nodejs+vue高校教室管理系统

摘 要 1 1 系统概述 4 1.1研究背景 4 1.2研究现状 4 1.3主要内容 5 2 系统开发环境 6 2.3 MySql数据库 6 2.4 B/S结构 7 3 需求分析 8 3.1技术可行性&#xff1a;技术背景 8 3.2经济可行性 8 3.3操作可行性 8 3.4系统设计规则 9 3.5系统流程和逻辑 9 4系统概要设计 13 4.1 概…

Delaunay三角网之分治算法

文章目录 一、简介二、实现代码三、实现效果参考资料一、简介 Delaunay三角网现有的构网算法有很多种,有学者曾对其中一些算法进行了调查和评估(如下图所示)。结果表明,在少量点时,Lawson的增量插入算法、Lee和Schachter的分治算法以及Fortune的平面扫描算法在速度上大致相…

Hexo+stun主题+Gitee5分钟快速搭建你的个人Blog

环境配置 首先要下载Node.js&#xff0c;然后安装Git&#xff0c;接着注册码云&#xff0c;最后安装Hexo&#xff0c;Hexo安装命令行(cmd 或 gitbash): npm install hexo-cli -g速度慢可以先改一下安装源: npm config set registry https://registry.npm.taobao.orgHexo搭建 目…

Linux调试器-gdb介绍

文章目录gdb的基础使用gdb是什么gdb的使用gdb的下载**l 显示代码****b 行号 :打断点****info b :查看断点****d 断点序号 :删除断点****r :运行调试****n&#xff08;next&#xff09; &#xff1a;逐过程****s&#xff08;step&#xff09;&#xff1a;逐语句****c&#xff08…

程序员接私活的那些事

每日坚持一点点&#xff0c;就离目标近一点。 文章目录怎么在空闲时间获得格外的收入接私活的有哪些途径接私活的途中需要注意什么格外福利怎么在空闲时间获得格外的收入 今天我们就不聊技术问题&#xff0c;咱们聊聊一个有意思的话题—>怎么在空闲的时间获得格外的收益&…

设计模式3 - 结构型模式

23种设计模式分析与见解开篇、UML、软件设计原则https://blog.csdn.net/lili40342/article/details/128358435创建型模式https://blog.csdn.net/lili40342/article/details/128358392结构型模式https://blog.csdn.net/lili40342/article/details/128358313行为型模式https://bl…

UNIAPP实战项目笔记54 登录时用state存储用户信息并持久化用户登录和退出登录功能

UNIAPP实战项目笔记54 登录时用state存储用户信息并持久化用户登录和退出登录功能 登录信息各个页面同步使用的是state 登录信息的持久化使用的是本地存储 打开APP自动初始化本地存储数据到state中 实际案例图片 登录页面数据渲染 代码 login.vue页面 登录成功后显示的页面 &l…

论文阅读 - Social Bot-Aware Graph Neural Network for Early Rumor Detection - CCF B

目录 摘要&#xff1a; 1 绪论 2 问题定义 3 SBAG模型 3.1社交机器人检测 3.2 机器人感知图神经网络 3.2.1基于GCN的用户发布 3.2.2 基于GAT的用户交互 3.2.3文本编码器 3.2.4 输出层 3.3 训练 4 实验 4.1 数据集 4.2 实验设置 4.3 基线 4.4 实验结果 4.4.1 谣言…

对时间序列数据(牛仔裤销售数据集)进行LSTM预测(Matlab代码实现)

目录 &#x1f4a5;1 概述 &#x1f4da;2 运行结果 &#x1f389;3 参考文献 &#x1f468;‍&#x1f4bb;4 Matlab代码 &#x1f4a5;1 概述 LSTM模型的一个常见用途是对长时间序列数据进行学习预测&#xff0c;例如得到了某商品前一年的日销量数据&#xff0c;我们可以…

【GPU】Nvidia CUDA 编程高级教程——利用蒙特卡罗法求解近似值(NVSHMEM)

博主未授权任何人或组织机构转载博主任何原创文章&#xff0c;感谢各位对原创的支持&#xff01; 博主链接 本人就职于国际知名终端厂商&#xff0c;负责modem芯片研发。 在5G早期负责终端数据业务层、核心网相关的开发工作&#xff0c;目前牵头6G算力网络技术标准研究。 博客…

MyBatis + SQL Server Using Table-Valued Parameters

一、实现原理 参考文档 Using table-valued parametersSystem requirements for the JDBC driverMicrosoft JDBC Driver for SQL Server1、微软官方封装了 JDBC 驱动 jar 包&#xff0c;提供 SQLServerDataTable 类&#xff1b; 2、Mybatis 官方提供自定义类型处理接口 TypeHa…

Python学习笔记-Pygame

目录 一、Pygame概述 1.安装Pyganme 2.Pygame常用模块介绍 2.1 display模块常用方法 2.2 pygame.event模块常用方法 2.3 Surface对象的常用方法 记述关于Pyganme开发的基本知识。 一、Pygame概述 Pygame是跨平台的python模块&#xff0c;转为电子游戏设计&#xff08;包…

Vue打包后的不同版本解析

vue源码打包版本 这里选取我们开发中常见的几个版本进行说明。 1、vue(.runtime).global(.prod).js 在html页面中通过 <script src“...”> 标签直接使用。通过CDN引入和npm下载的Vue就是这个版本。会暴露一个全局的Vue来使用。&#xff08;.runtime&#xff09;和&…

2022年12月python的字符串常用操作

字符串在整整个开发的过程中&#xff0c;使用频率相对来说是较高的。 在此总结几个字符串的常用操作&#xff0c; 字符串的操作&#xff0c;转换后即生成为新字符串 【长度统计 切片&#xff1a; 【 根据索引进行切片str[开始索引:结束索引:步长] 根据指定标识符进行切片str.sp…