首页 > 其他分享 >springboot项目中mybatis的dao接口实现类是如何添加到spring容器中的

springboot项目中mybatis的dao接口实现类是如何添加到spring容器中的

时间:2024-08-18 16:38:29浏览次数:14  
标签:definition MapperFactoryBean scanner spring dao 接口 public class springboot

一、@Mapper注解

在springboot+mybatis的工程中,如果不做特殊配置,mybatis会查找有@Mapper的接口创建其代理对象添加到spring容器中,接下来就来分析下这个是如何实现的。

关键点就在MybatisAutoConfiguration这个自动配置类中

public class MybatisAutoConfiguration {
  //这个配置会在没有进行其他dao接口扫描的配置时生效,重点在导入的AutoConfiguredMapperScannerRegistrar
  //它是这个类中的一个内部类
  @Configuration
  @Import({ AutoConfiguredMapperScannerRegistrar.class })
  @ConditionalOnMissingBean(MapperFactoryBean.class)
  public static class MapperScannerRegistrarNotFoundConfiguration {

    @PostConstruct
    public void afterPropertiesSet() {
      log.debug(String.format("No %s found.", MapperFactoryBean.class.getName()));
    }
  }
  
  //使用默认配置时,把这个类加入spring容器中,它会自动查找有Mapper注解的接口
  public static class AutoConfiguredMapperScannerRegistrar
      implements BeanFactoryAware, ImportBeanDefinitionRegistrar, ResourceLoaderAware {

    private BeanFactory beanFactory;

    private ResourceLoader resourceLoader;

    @Override
    public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {

      log.debug("Searching for mappers annotated with @Mapper'");
	  //这个scanner是mybatis继承spring的ClassPathBeanDefinitionScanner后实现的扫描
      //dao接口的扫描器
      ClassPathMapperScanner scanner = new ClassPathMapperScanner(registry);

      try {
        if (this.resourceLoader != null) {
          scanner.setResourceLoader(this.resourceLoader);
        }

        List<String> pkgs = AutoConfigurationPackages.get(this.beanFactory);
        for (String pkg : pkgs) {
          log.debug("Using auto-configuration base package '" + pkg + "'");
        }
	   //这里指定扫描Mapper注解
        scanner.setAnnotationClass(Mapper.class);
        scanner.registerFilters();
        scanner.doScan(StringUtils.toStringArray(pkgs));
      } catch (IllegalStateException ex) {
        log.debug("Could not determine auto-configuration " + "package, automatic mapper scanning disabled.");
      }
    }

}

可以看到是通过ClassPathMapperScanner来扫描有Mapper注解的接口,具体的扫描原理后续会介绍

二、@MapperScan注解

在springboot工程中也可以通过MapperScan注解来指定dao接口的扫描路径,我们来分析下这个注解

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Import(MapperScannerRegistrar.class)
public @interface MapperScan {
    ...省略其他
}

这个注解上导入了一个MapperScannerRegistrar类,此类用来根据配置的注解属性进行扫描。

接下来分析下这个类

public class MapperScannerRegistrar implements ImportBeanDefinitionRegistrar, ResourceLoaderAware {
  //这个方法是具体的扫描逻辑,总的来说就是根据配置的MapperScan注解属性,使用
  //ClassPathMapperScanner注解来扫描classpath路径下满足条件的接口创建代理对象
  @Override
  public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {

    AnnotationAttributes annoAttrs = AnnotationAttributes.fromMap(importingClassMetadata.getAnnotationAttributes(MapperScan.class.getName()));
    //创建扫描器
    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)) {
      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)) {
      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));
    }
    scanner.registerFilters();
    //执行sql
    scanner.doScan(StringUtils.toStringArray(basePackages));
  }
    
}

三、ClassPathMapperScanner

接下来分析下ClassPathMapperScanner的源码,它继承自spring的ClassPathBeanDefinitionScanner,spring这个scanner扫描时会跳过接口,mybatis继承这个scanner并扩展了它的扫描逻辑

public class ClassPathMapperScanner extends ClassPathBeanDefinitionScanner {
  @Override
  public Set<BeanDefinitionHolder> doScan(String... basePackages) {
    //调用spring的扫描逻辑先把所有符合条件的class都扫描出来
    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 {
      //编辑扫描到的bean定义信息
      processBeanDefinitions(beanDefinitions);
    }

    return beanDefinitions;
  }
  // 编辑bean定义信息,用MapperFactoryBean来替换原来的dao接口
  private void processBeanDefinitions(Set<BeanDefinitionHolder> beanDefinitions) {
    GenericBeanDefinition definition;
    //循环bean定义信息
    for (BeanDefinitionHolder holder : beanDefinitions) {
      definition = (GenericBeanDefinition) holder.getBeanDefinition();

      if (logger.isDebugEnabled()) {
        logger.debug("Creating MapperFactoryBean with name '" + holder.getBeanName() 
          + "' and '" + definition.getBeanClassName() + "' mapperInterface");
      }

      // the mapper interface is the original class of the bean
      // but, the actual class of the bean is MapperFactoryBean
     // definition.getBeanClassName()获取到的是dao接口的class,底下这句是把dao接口的class对象
     // 作为了创建这个bean时构造方法的参数,后续会把bean定义的class替换成MapperFactoryBean,
     // 所以也就是创建MapperFactoryBean对象时会传递dao接口的class作为构造方法参数
        definition.getConstructorArgumentValues().addGenericArgumentValue(definition.getBeanClassName()); // issue #59
        
      //替换bean定义的class为MapperFactoryBean
      definition.setBeanClass(this.mapperFactoryBean.getClass());
      //以下这些都是在设置bean定义的一些属性
      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() + "'.");
        }
        //设置这个bean中属性的注入模式为根据类型自动注入,这样当创建这个bean时就会自动给其中的属性
        //赋值
        definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE);
      }
    }
  }
}

所以总的来讲,ClassPathMapperScanner就是把spring扫描到的bean定义信息中的class替换成了MapperFactoryBean ,真正的dao接口代理对象是由它创建的。而它必然实现了spring的FactoryBean 接口

四、 MapperFactoryBean

dao接口的代理对象最终是由这个类创建出来的,它实现了spring的FactoryBean 接口,在其getObject方法中会调用

sqlsession来创建出代理对象

public class MapperFactoryBean<T> extends SqlSessionDaoSupport implements FactoryBean<T> {
  //构造方法传进来的参数是dao接口的class对象,最终会创建它的代理对象
  public MapperFactoryBean(Class<T> mapperInterface) {
    this.mapperInterface = mapperInterface;
  }
    
  @Override
  public T getObject() throws Exception {
    // 通过sqlsession来创建代理对象,sqlsession是父类中的属性
    return getSqlSession().getMapper(this.mapperInterface);
  }
}

标签:definition,MapperFactoryBean,scanner,spring,dao,接口,public,class,springboot
From: https://www.cnblogs.com/chengxuxiaoyuan/p/18365768

相关文章

  • 基于SpringBoot3框架-数据库乐观锁、悲观锁、Redis、Zookeeper分布式锁的简单案例实现
    1.分布式锁的定义分布式锁是一种在分布式系统中用来协调多个进程或线程对共享资源进行访问的机制。它确保在分布式环境下,多个节点(如不同的服务器或进程)不会同时访问同一个共享资源,从而避免数据不一致、资源竞争等问题。2.分布式锁的工作原理分布式锁的工作原理与单机锁......
  • springboot+vue前后端分离项目-项目搭建19-ElementUI图标+聊天室
    一、ElementUI图标按照官网这两步,注册所有图标,然后就能直接使用 1.安装后在vue/package.json里能看到包 2.注册所有图标 3.点击自动复制,直接就能使用 4.效果: ......
  • 【防忘笔记】Spring+Struts2古董框架学习
    Spring+Struts2项目框架梳理若基于Spring+Struts2的方式进行开发,前后端的交互逻辑会与boot系以及MCV的组织结构有所不同这里是对于学习过程的一些记录前置通用知识Struts2框架资料Struts2基础篇之基本概念Java之struts2框架学习一般情况的Spring前后端调试流程要理解基于......
  • Springboot项目的War包部署在tomcat上
    使用场景:使用springboot框架+mybatis+html开发的项目将软件服务打成war包,将war包部署在tomcat上。使用前提:电脑已经安装jdk1.8、tomcat8.5环境。开始部署:步骤1:Java启动类上加SpringApplicationBuilder()方法,且需继承类SpringBootServletInitializer@SpringBootApplication@Ma......
  • 基于Java+SpringBoot+Mysql实现的共享厨房平台功能设计与实现六
    一、前言介绍:1.1项目摘要随着城市化进程的加快和人们对生活品质要求的提升,共享经济模式在全球范围内迅速兴起。共享厨房平台作为共享经济的一种创新形式,旨在通过整合闲置的厨房资源,为用户提供一个便捷、经济且富有创意的烹饪空间。现代都市生活中,许多年轻人、创业者及小......
  • 基于Java+SpringBoot+Mysql实现的共享厨房平台功能设计与实现七
    一、前言介绍:1.1项目摘要随着城市化进程的加快和人们对生活品质要求的提升,共享经济模式在全球范围内迅速兴起。共享厨房平台作为共享经济的一种创新形式,旨在通过整合闲置的厨房资源,为用户提供一个便捷、经济且富有创意的烹饪空间。现代都市生活中,许多年轻人、创业者及小......
  • java guide Spring Cloud Gateway 答疑6
    使用SpringCloudGateway的时候,官方文档提供的方案总是基于配置文件或代码配置的方式。SpringCloudGateway作为微服务的入口,需要尽量避免重启,而现在配置更改需要重启服务不能满足实际生产过程中的动态刷新、实时变更的业务需求,所以我们需要在SpringCloudGateway运行......
  • 基于Java+SpringBoot+Mysql实现的共享厨房平台功能设计与实现四
    一、前言介绍:1.1项目摘要随着城市化进程的加快和人们对生活品质要求的提升,共享经济模式在全球范围内迅速兴起。共享厨房平台作为共享经济的一种创新形式,旨在通过整合闲置的厨房资源,为用户提供一个便捷、经济且富有创意的烹饪空间。现代都市生活中,许多年轻人、创业者及小......
  • 基于Java+SpringBoot+Mysql实现的共享厨房平台功能设计与实现六
    一、前言介绍:1.1项目摘要随着城市化进程的加快和人们对生活品质要求的提升,共享经济模式在全球范围内迅速兴起。共享厨房平台作为共享经济的一种创新形式,旨在通过整合闲置的厨房资源,为用户提供一个便捷、经济且富有创意的烹饪空间。现代都市生活中,许多年轻人、创业者及小......
  • 【问题记录】【Spring】Spring-framework 源码环境搭建
    1 前言换了个电脑,这不是得倒腾代码嘛,这Spring源码还是Gradle管理的依赖,平时接触Gradle就比较少,这家伙这环境给我整的大半天,最后也算是整好了,把中间遇到的各种问题就下,希望大家少走弯路。需要用到的地址我先贴出来,有的需要下载的可以先下载下来:源码:源码下载Gradle:腾讯各......