当前位置: 首页 > article >正文

mybatis如何与spring的结合

1.前言

在现在的java项目开发中,MyBatis和Spring是两个非常流行的框架。MyBatis是一个优秀的持久层框架,它支持定制化SQL、存储过程以及高级映射。而Spring则是一个广泛使用的开源框架,用于构建企业级应用程序。将这两个框架整合在一起,可以充分利用它们各自的优势,构建出高效、易于维护的数据访问层。

2.整合关键组件与技术

我们在代码的开发过程中,只需要引入对应的jar包的pom文件,并在spring的java文件中引入了@MapperScan注解,并在@MapperScan注解中指定对应的基础包类就可以了。剩下的只需要在基础包下进行创建对应的Mapper接口文件就可以了。

那么问题来了,

1.spring是什么时候进行加载这些接口文件呢

2.通过什么方式进行加载文件呢。

3. 加载的文件都是接口,那如何对这些接口进行初始化呢

针对上面的三个问题,我们来点击进入@MapperScan注解查看一些Mybatis与Spring的之间的“勾当”

2.1@MapperScan注解

下面的代码是MapperScan注解的注解,其中不少的代码因为不涉及此次分析,我都进行了隐藏。在下面的代码中我们可以看到一切的解密应该都在MapperScannerRegistrar这个类中。

@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE})
@Documented
@Import({MapperScannerRegistrar.class})
@Repeatable(MapperScans.class)
public @interface MapperScan {
    String[] value() default {};

    String[] basePackages() default {};
}

MapperScannerRegistrar类,这个类继承了ImportBeanDefinitionRegistrar这个类,ImportBeanDefinitionRegistrar这个类呢在Spring的启动的过程中,是一个比较重要的类,现在简单的说,ImportBeanDefinitionRegistrar这个类在spring启动的过程中,会进行执行registerBeanDefinitions方法,感兴趣的同学,可以看我前面发表的文章:spring组件动态注册Bean。那么在MapperScannerRegistrar这个类做了什么呢,我觉得最重要的两行代码是:

BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(MapperScannerConfigurer.class);   
registry.registerBeanDefinition(beanName, builder.getBeanDefinition());

上面两行代码的主要含义是:mybatis组件向spring容器进行注册了一个MapperScannerConfigurer对象。

那么这个MapperScannerConfigurer对象会在spring的容器初始化的时候进行执行一些操作。具体的执行时机,也可以看我前面发表的文章:spring组件动态注册Bean。

MapperScannerRegistrar中的详细代码源码如下:

public class MapperScannerRegistrar implements ImportBeanDefinitionRegistrar, ResourceLoaderAware {

    public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
        AnnotationAttributes mapperScanAttrs = AnnotationAttributes.fromMap(importingClassMetadata.getAnnotationAttributes(MapperScan.class.getName()));
        if (mapperScanAttrs != null) {
            this.registerBeanDefinitions(importingClassMetadata, mapperScanAttrs, registry, generateBaseBeanName(importingClassMetadata, 0));
        }

    }

    void registerBeanDefinitions(AnnotationMetadata annoMeta, AnnotationAttributes annoAttrs, BeanDefinitionRegistry registry, String beanName) {
        BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(MapperScannerConfigurer.class);
        builder.addPropertyValue("processPropertyPlaceHolders", true);
        Class<? extends Annotation> annotationClass = annoAttrs.getClass("annotationClass");
        if (!Annotation.class.equals(annotationClass)) {
            builder.addPropertyValue("annotationClass", annotationClass);
        }

        Class<?> markerInterface = annoAttrs.getClass("markerInterface");
        if (!Class.class.equals(markerInterface)) {
            builder.addPropertyValue("markerInterface", markerInterface);
        }

        Class<? extends BeanNameGenerator> generatorClass = annoAttrs.getClass("nameGenerator");
        if (!BeanNameGenerator.class.equals(generatorClass)) {
            builder.addPropertyValue("nameGenerator", BeanUtils.instantiateClass(generatorClass));
        }

        Class<? extends MapperFactoryBean> mapperFactoryBeanClass = annoAttrs.getClass("factoryBean");
        if (!MapperFactoryBean.class.equals(mapperFactoryBeanClass)) {
            builder.addPropertyValue("mapperFactoryBeanClass", mapperFactoryBeanClass);
        }

        String sqlSessionTemplateRef = annoAttrs.getString("sqlSessionTemplateRef");
        if (StringUtils.hasText(sqlSessionTemplateRef)) {
            builder.addPropertyValue("sqlSessionTemplateBeanName", annoAttrs.getString("sqlSessionTemplateRef"));
        }

        String sqlSessionFactoryRef = annoAttrs.getString("sqlSessionFactoryRef");
        if (StringUtils.hasText(sqlSessionFactoryRef)) {
            builder.addPropertyValue("sqlSessionFactoryBeanName", annoAttrs.getString("sqlSessionFactoryRef"));
        }

        List<String> basePackages = new ArrayList();
        basePackages.addAll((Collection)Arrays.stream(annoAttrs.getStringArray("value")).filter(StringUtils::hasText).collect(Collectors.toList()));
        basePackages.addAll((Collection)Arrays.stream(annoAttrs.getStringArray("basePackages")).filter(StringUtils::hasText).collect(Collectors.toList()));
        basePackages.addAll((Collection)Arrays.stream(annoAttrs.getClassArray("basePackageClasses")).map(ClassUtils::getPackageName).collect(Collectors.toList()));
        if (basePackages.isEmpty()) {
            basePackages.add(getDefaultBasePackage(annoMeta));
        }

        String lazyInitialization = annoAttrs.getString("lazyInitialization");
        if (StringUtils.hasText(lazyInitialization)) {
            builder.addPropertyValue("lazyInitialization", lazyInitialization);
        }

        String defaultScope = annoAttrs.getString("defaultScope");
        if (!"".equals(defaultScope)) {
            builder.addPropertyValue("defaultScope", defaultScope);
        }

        builder.addPropertyValue("basePackage", StringUtils.collectionToCommaDelimitedString(basePackages));
        registry.registerBeanDefinition(beanName, builder.getBeanDefinition());
    }

}

2.2 MapperScannerConfigurer类的说明

MapperScannerConfigurer类 实现了BeanDefinitionRegistryPostProcessor类中的postProcessBeanDefinitionRegistry方法,这个方法会在spring启动的过程中进行执行.

在这个方法中,我们可以看到mybatis进行实例化了一个ClassPathMapperScanner(扫描器),这块不知道大家有没有这样的疑问,那就是mybatis为啥要进行实例化出来一个扫描器,为啥不用spring自己的扫描器呢,原理是因为spring的扫描器,mybatis用不了,为啥用不了呢,因为spring的扫描器,扫描的是加了@Component,@ManagedBean,@Name等注解的非接口类。这点针对mybatis而言是不适用的。所以mybatis自己创建了一个新的扫描器:ClassPathMapperScanner

MapperScannerConfigurer类中的核心代码块:

public class MapperScannerConfigurer implements BeanDefinitionRegistryPostProcessor, InitializingBean, ApplicationContextAware, BeanNameAware {

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

        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.setMapperFactoryBeanClass(this.mapperFactoryBeanClass);
        if (StringUtils.hasText(this.lazyInitialization)) {
            scanner.setLazyInitialization(Boolean.valueOf(this.lazyInitialization));
        }

        if (StringUtils.hasText(this.defaultScope)) {
            scanner.setDefaultScope(this.defaultScope);
        }

        scanner.registerFilters();
        scanner.scan(StringUtils.tokenizeToStringArray(this.basePackage, ",; \t\n"));
    }
}

2.3 ClassPathMapperScanner类

ClassPathMapperScanner这个类是继承于spring的内置的扫描器ClassPathBeanDefinitionScanner,关于spring的ClassPathBeanDefinitionScanner类中有两个比较重要的属性includeFilters和excludeFilters这两个属性都为List集合,从字面意思上我们就可以看出来,includeFilters这个Filter的集合就是决定哪些类能被扫描到,excludeFilters这的Filter的集合来决定哪些类不能被扫描到。

2.3.1 扫描过滤器设置

我们从构造函数中可以看到,ClassPathMapperScanner类在进行初始化的时候,useDefaultFilters属性传入的是false,那么可以证明ClassPathMapperScanner将不使用spring默认的includeFilters和excludeFilters。

在上面MapperScannerConfigurer类中,我们看到了 scanner.registerFilters();这行代码,说明在ClassPathMapperScanner初始化完成之后,调用了registerFilters方法。有自己的includeFilters和excludeFilters。

public class ClassPathMapperScanner extends ClassPathBeanDefinitionScanner{

  public ClassPathMapperScanner(BeanDefinitionRegistry registry) {
        super(registry, false);
        //进行调用父类的构造函数,
        /***
        *   public ClassPathBeanDefinitionScanner(BeanDefinitionRegistry registry, boolean useDefaultFilters) {
        *     this(registry, useDefaultFilters, getOrCreateEnvironment(registry));
        *   }  
        *
        ***/
  }
  /***
    * MapperScan注解的简单示例:
    *
    * @MapperScan(basePackages = "xxx.xxx.xxx",
    *       annotationClass =  A.class,
    *        markerInterface = =TMappper.class)
    ***/
  public void registerFilters() {
      boolean acceptAllInterfaces = true;
     //代表着接口上只有添加了A注解的类 才能被扫描到
      if (this.annotationClass != null) {
        this.addIncludeFilter(new AnnotationTypeFilter(this.annotationClass));
        acceptAllInterfaces = false;
      }
      //设置了markerInterface代表着所有的接口都不会进行扫描,因为下面matchClassName直接返回false
      if (this.markerInterface != null) {
            this.addIncludeFilter(new AssignableTypeFilter(this.markerInterface) {
                protected boolean matchClassName(String className) {
                    return false;
                }
            });
            acceptAllInterfaces = false;
        }
        //为true的时候 代表着包下面的所有接口都会进行扫描
        if (acceptAllInterfaces) {
            this.addIncludeFilter((metadataReader, metadataReaderFactory) -> {
                return true;
            });
        }
        //排除了类名以package-info结尾的类 这种情况感觉很少会用到
        this.addExcludeFilter((metadataReader, metadataReaderFactory) -> {
            String className = metadataReader.getClassMetadata().getClassName();
            return className.endsWith("package-info");
        });
    }
}

2.3.2 doScan方法的执行

在给ClassPathMapperScanner类初始化完成之后,就会执行ClassPathMapperScanner的scan方法进行扫描类的信息,此处要注意调用的是ClassPathMapperScanner的doScan方法。

  public Set<BeanDefinitionHolder> doScan(String... basePackages) {
   //调用父类的doscan方法,进行扫描     
    Set<BeanDefinitionHolder> beanDefinitions = super.doScan(basePackages);
    if (beanDefinitions.isEmpty()) {
            LOGGER.warn(() -> {
                return "No MyBatis mapper was found in '" + Arrays.toString(basePackages) + "' package. Please check your configuration.";
            });
     } else {
        //mybatis扫描的的核心
         this.processBeanDefinitions(beanDefinitions);
     }
     return beanDefinitions;
   }

2.3.3 ClassPathMapperScanner#processBeanDefinitions方法

这个方法主要是针对扫描出来的BeanDefinition对象,进行一个“狸猫换太子”的思想,其主要核心代码如下:

private Class<? extends MapperFactoryBean> mapperFactoryBeanClass = MapperFactoryBean.class;

for (BeanDefinitionHolder holder : beanDefinitions) {
    definition.getConstructorArgumentValues().addGenericArgumentValue(beanClassName); // issue #59
    definition.setBeanClass(this.mapperFactoryBeanClass);
}

这个代表着什么意思呢,举个例子说明一下:

假如现在有两个mapper,AMapper,BMapper,正常情况下被spring扫描完成后,AMapper对应的class的应该是AMapper.class,BMapper对应的class应该是BMapper.class,但是现在不是了都变成了MapperFactoryBean.class,那么spring在针对AMapper,BMapper进行初始化的时候,就不会进行调用AMapper,BMapper的实例化方法,而是会进行调用MapperFactoryBean类中的getObject方法.通过这个MapperFactoryBean工厂来进行生产Mapper对象。

public class MapperFactoryBean<T> extends SqlSessionDaoSupport implements FactoryBean<T> {

   public T getObject() throws Exception {
    return getSqlSession().getMapper(this.mapperInterface);
  }
}

3.总结

那我们来简单总结一下,首先@MapperScan注解中的MapperScannerRegistrar类,往spring容器中添加了MapperScannerConfigurer类;其次 MapperScannerConfigurer类在spring的启动过程中,会进行启动一个ClassPathMapperScanner类的扫描器,该扫描继承于spring内置的扫描器ClassPathBeanDefinitionScanner,覆盖其中的过滤器的逻辑和doscan方法来进行扫描mapper信息;最后将扫描出来的Mapper信息进行“狸猫换太子”,使用mybatis的MapperFactoryBean类来进行替换对用的mapper的Class信息,在进行构建对应的Mapper的时候 其实就是调用MapperFactoryBean工厂类来进行生产对应的Mapper。


http://www.kler.cn/news/330090.html

相关文章:

  • 从构建个性化学习系统维度提高认知转化率
  • 相亲交友系统源码中的数据安全策略
  • 【数据结构】什么是平衡二叉搜索树(AVL Tree)?
  • void类型
  • 【代码随想录Day29】贪心算法Part03
  • 【Python】ftfy 使用指南:修复 Unicode 编码问题
  • elementui 修改el-tabs底部滑块划线的transform滑动距离
  • 【前端开发入门】css快速入门
  • Python机器学习中的模型评估与优化技术
  • 自给自足:手搓了一个睡眠监测仪,用着怎么样?
  • 深入浅出:使用DOM4J结合XPath高效解析XML
  • 【YOLOv8改进[SPPF]】使用SPPFCSPC替换SPPF模块 + 含全部代码和详细修改方式
  • VPN简述
  • C++随心记 续一
  • Vue3 Pinia持久化存储
  • 基于Hive和Hadoop的保险分析系统
  • 【数据结构】线性表
  • CSS3过渡
  • CSP-J 复赛算法 贪心算法练习
  • Elasticsearch学习笔记(2)