百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 技术分类 > 正文

mybatis-spring注解MapperScan的原理

ztj100 2024-12-04 17:14 21 浏览 0 评论

很多开发者应该都知道,我们只使用@MapperScan这个注解就可以把我们写的Mybatis的Mapper接口加载到Spring的容器中,不需要对每个Mapper接口加@Mapper这个注解了,加快了我们开发的效率。如下:

就可以把我们写在io.renren.mapper这个包下的Mapper接口加载到我们的Spring容器中。当然mybatis-spring能使用这样的注解还是因为的大神开发者给我们提供了大量的可扩展的接口。下面就聊聊它的原理就是@MapperScan这个注解如下:

/*
* @since 1.2.0
 * @see MapperScannerRegistrar  
 * @see MapperFactoryBean // 这个类贴出来它很重要
 */
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Import(MapperScannerRegistrar.class)
@Repeatable(MapperScans.class)
public @interface MapperScan {

好家伙又是@Import这个类帮助我们导入了MapperScannerRegistrar这个类的代码如下:

public class MapperScannerRegistrar implements ImportBeanDefinitionRegistrar, ResourceLoaderAware {
  @Override
  public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
    AnnotationAttributes mapperScanAttrs = AnnotationAttributes
        .fromMap(importingClassMetadata.getAnnotationAttributes(MapperScan.class.getName()));
    if (mapperScanAttrs != null) {
      registerBeanDefinitions(importingClassMetadata, mapperScanAttrs, registry,
          generateBaseBeanName(importingClassMetadata, 0));
    }
  }
  void registerBeanDefinitions(AnnotationMetadata annoMeta, AnnotationAttributes annoAttrs,
      BeanDefinitionRegistry registry, String beanName) {
    BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(MapperScannerConfigurer.class);
    // 注册了一个 MapperScannerConfigurer的BeanDefinition
    registry.registerBeanDefinition(beanName, builder.getBeanDefinition());
  }

它主要是帮我们注入了一个MapperScannerConfigurer类型的BeanDefinition 。那我们就先看这个类的相关代码如下:

public class MapperScannerConfigurer
    implements BeanDefinitionRegistryPostProcessor{
  @Override
  public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) {
    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);

    scanner.registerFilters();
    // 开始扫描指定类下的Mapper接口
    scanner.scan(
        StringUtils.tokenizeToStringArray(this.basePackage, ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS));
  }

如上面的代码 MapperScannerConfigurer这个类实现了BeanDefinitionRegistryPostProcessor

是Spring提供给开发者供开发者实现这个接口然后可以介入Spring的启动周期,具体Spring是什么时候运行这段代码的呢?教大家一个好的方法如下:

我们断点打到上面的位置,然后Debug启动项目,然就可以看到这个方法的调用路径如下,经过5步就调用到这个方法了。

第一个方法AbstractApplicationContext.refresh方法应该是Spring中最重要的方法了,Spring的很多很多功能都是在这个方法里面完成的,有兴趣的可以读一下,下面贴出来直接回调的这个方法如下:

/**
   * Invoke the given BeanDefinitionRegistryPostProcessor beans.
   * 这个方法就会回调MapperScannerConfigurer的
   * postProcessBeanDefinitionRegistry方法
   */
  private static void invokeBeanDefinitionRegistryPostProcessors(
      Collection<? extends BeanDefinitionRegistryPostProcessor> postProcessors, BeanDefinitionRegistry registry) {
    for (BeanDefinitionRegistryPostProcessor postProcessor : postProcessors) {
      postProcessor.postProcessBeanDefinitionRegistry(registry);
    }
  }

帮大家找到Spring回调的方法了,就还回到MapperScannerConfigurer的postProcessBeanDefinitionRegistry方法。我们主要看scan【扫描】这个方法,进入这个方法如下:

Spring大神写代码有个特点方法前面带do就是真正工作的方法【PS我们写复杂业务的时候也可以模仿do....方法】因为do有做,执行的意思。代码如下:

/**
    在我们指定的包内执行
   * Perform a scan within the specified base packages,
   // 返回扫描到的 bean definitions. 
   * returning the registered bean definitions.
   */
  protected Set<BeanDefinitionHolder> doScan(String... basePackages) {
    // 扫描到的beanDefinitions 放到这里
    Set<BeanDefinitionHolder> beanDefinitions = new LinkedHashSet<>();
    for (String basePackage : basePackages) {
    // 获取候选的组件【我们写的Mapper接口】,主要的逻辑都在这里
      Set<BeanDefinition> candidates = findCandidateComponents(basePackage);
      beanDefinitions.add(definitionHolder);
    }
    return beanDefinitions;
  }

findCandidateComponents方法如下:

private Set<BeanDefinition> scanCandidateComponents(String basePackage) {
    Set<BeanDefinition> candidates = new LinkedHashSet<>();
    try {
    // 根据我们写的包名获取Mapper接口在项目中的实际路径
      String packageSearchPath = ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX +
          resolveBasePackage(basePackage) + '/' + this.resourcePattern;
          // 获取包下的所有接口的资源
      Resource[] resources = getResourcePatternResolver().getResources(packageSearchPath);
      for (Resource resource : resources) {
        if (resource.isReadable()) {
          try {
            MetadataReader metadataReader = getMetadataReaderFactory().getMetadataReader(resource);
            if (isCandidateComponent(metadataReader)) {
              ScannedGenericBeanDefinition sbd = new ScannedGenericBeanDefinition(metadataReader);
              if (isCandidateComponent(sbd)) {
                // 把我们写的Dao或者Mapper接口放入candidates中
                candidates.add(sbd);
              }
            }
    }
   // 把封装的Mapper的 BeanDefinition返回
    return candidates;
  }

把封装好的BeanDefinition封装好返回后,后面就会进入Spring Bean的生命周期进行Bean的初始化,然后就提供给开发者直接使用了。到这里有的小伙伴该问了,我们写的是接口Spring怎么能给接口初始化呢?上面这些都是瞎聊的。如果能这样问说明你的Java基础很不错哦,值得鼓励。那小伙伴是否记得我文章开始说 MapperFactoryBean这个类呢?我还特别强调这个类很重要,这个类是FactoryBean。Mybatis是把我们写的接口生成了的代理对象MapperProxy 。由于我使用的是MybatisPlus,它又封装一下MapperProxy变成了MybaitsMapperProxy,这个类直接copy了Mybatis的代码。我们通过MapperFactoryBean的getObject()方法就能获取MapperProxy了,它不是接口

FactoryBean和BeanFactory的区别:

FactoryBean,是Spring提供的一个扩展点,用于复杂Bean的创建。mybatsi在跟Spring做整合时候就用到了这个扩展点。并且FactoryBean所创建的Bean跟普通的Bean不一样。它有getObject()方法才可以获取具体的Bean

BeanFactory是Spring IOC容器的顶级接口,其实现类有XMLBeanFactory ,DefaultListBeanFactory。BeanFactory为Srping管理Bean提供了一套通用的接口规范。

如果不信可以看如下,图中解释的很明白了:

上面就是MapperScan的整个原理,如果跟着上面的代码走一遍流程相信你对它的原理就更懂了,你写代码的时候对写的Mapper接口应该就知道不加@Mapper这个注解Spring也能帮你完成你写的Mapper接口的初始化。

再分享一个Spring生成UUID的很高效的工具类:AlternativeJdkIdGenerator。

大意是它使用了SecureRandom作为种子,来替换调用UUID#randomUUID()。它提供了一个更好、更高性能的表现 发现仅仅循环生成1000万次,Spring提供的算法性能远远高于JDK的。因此建议大家以后使用AlternativeJdkIdGenerator去生成UUID,性能会更好一点

缺点是:还需要new对象才能使用,不能通过类名直接调用静态方法,当然我们可以二次封装。另外,一般输出串我们都会进一步这么处理:.toString().replace("-", "")

如下对比一下就知道它的性能有多好了。

  /**
         * 直接调用java的那个UUID工具 
         */
        JdkIdGenerator jdkIdGenerator = new JdkIdGenerator();
        AlternativeJdkIdGenerator alternativeJdkIdGenerator = new AlternativeJdkIdGenerator();
        Instant start;
        Instant end;
        int count = 10000000;
        //jdkIdGenerator
        start = Instant.now();
        for (int i = 0; i < count; i++) {
            jdkIdGenerator.generateId();
        }
        end = Instant.now();
        System.out.println("jdkIdGenerator循环" + count + "次耗时:" + Duration.between(start, end).toMillis() + "ms");
        //alternativeJdkIdGenerator
        start = Instant.now();
        for (int i = 0; i < count; i++) {
            alternativeJdkIdGenerator.generateId();
        }
        end = Instant.now();
        System.out.println("alternativeJdkIdGenerator循环" + count + "次耗时:" + Duration.between(start, end).toMillis() + "ms");

如下结果如下Spring的性能太高了。

相关推荐

其实TensorFlow真的很水无非就这30篇熬夜练

好的!以下是TensorFlow需要掌握的核心内容,用列表形式呈现,简洁清晰(含表情符号,<300字):1.基础概念与环境TensorFlow架构(计算图、会话->EagerE...

交叉验证和超参数调整:如何优化你的机器学习模型

准确预测Fitbit的睡眠得分在本文的前两部分中,我获取了Fitbit的睡眠数据并对其进行预处理,将这些数据分为训练集、验证集和测试集,除此之外,我还训练了三种不同的机器学习模型并比较了它们的性能。在...

机器学习交叉验证全指南:原理、类型与实战技巧

机器学习模型常常需要大量数据,但它们如何与实时新数据协同工作也同样关键。交叉验证是一种通过将数据集分成若干部分、在部分数据上训练模型、在其余数据上测试模型的方法,用来检验模型的表现。这有助于发现过拟合...

深度学习中的类别激活热图可视化

作者:ValentinaAlto编译:ronghuaiyang导读使用Keras实现图像分类中的激活热图的可视化,帮助更有针对性...

超强,必会的机器学习评估指标

大侠幸会,在下全网同名[算法金]0基础转AI上岸,多个算法赛Top[日更万日,让更多人享受智能乐趣]构建机器学习模型的关键步骤是检查其性能,这是通过使用验证指标来完成的。选择正确的验证指...

机器学习入门教程-第六课:监督学习与非监督学习

1.回顾与引入上节课我们谈到了机器学习的一些实战技巧,比如如何处理数据、选择模型以及调整参数。今天,我们将更深入地探讨机器学习的两大类:监督学习和非监督学习。2.监督学习监督学习就像是有老师的教学...

Python教程(三十八):机器学习基础

...

Python 模型部署不用愁!容器化实战,5 分钟搞定环境配置

你是不是也遇到过这种糟心事:花了好几天训练出的Python模型,在自己电脑上跑得顺顺当当,一放到服务器就各种报错。要么是Python版本不对,要么是依赖库冲突,折腾半天还是用不了。别再喊“我...

超全面讲透一个算法模型,高斯核!!

...

神经网络与传统统计方法的简单对比

传统的统计方法如...

AI 基础知识从0.1到0.2——用“房价预测”入门机器学习全流程

...

自回归滞后模型进行多变量时间序列预测

下图显示了关于不同类型葡萄酒销量的月度多元时间序列。每种葡萄酒类型都是时间序列中的一个变量。假设要预测其中一个变量。比如,sparklingwine。如何建立一个模型来进行预测呢?一种常见的方...

苹果AI策略:慢哲学——科技行业的“长期主义”试金石

苹果AI策略的深度原创分析,结合技术伦理、商业逻辑与行业博弈,揭示其“慢哲学”背后的战略智慧:一、反常之举:AI狂潮中的“逆行者”当科技巨头深陷AI军备竞赛,苹果的克制显得格格不入:功能延期:App...

时间序列预测全攻略,6大模型代码实操

如果你对数据分析感兴趣,希望学习更多的方法论,希望听听经验分享,欢迎移步宝藏公众号...

AI 基础知识从 0.4 到 0.5—— 计算机视觉之光 CNN

...

取消回复欢迎 发表评论: