文章详情

短信预约-IT技能 免费直播动态提醒

请输入下面的图形验证码

提交验证

短信预约提醒成功

深入探究Java @MapperScan实现原理

2023-01-04 12:02

关注

1. 前言

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

@MapperScan("com.xxx.mapper")

提出几个问题:

2. ImportBeanDefinitionRegistrar

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

public interface ImportBeanDefinitionRegistrar {
	
	void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry);
}

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

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

3. ConfigurationClassPostProcessor

ImportBeanDefinitionRegistrar扩展点是在哪里被触发的呢???

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

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

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的子类。

@Retention(RetentionPolicy . RUNTIME)

@Target(ElementType.TYPE)

@Documented

@Import(MapperScannerRegistrar.class)

public @interface MapperScan {

也就是说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));
  }
  
  @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生成的代理对象。

到此这篇关于深入探究Java @MapperScan实现原理的文章就介绍到这了,更多相关Java @MapperScan内容请搜索编程网以前的文章或继续浏览下面的相关文章希望大家以后多多支持编程网!

阅读原文内容投诉

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

软考中级精品资料免费领

  • 历年真题答案解析
  • 备考技巧名师总结
  • 高频考点精准押题
  • 2024年上半年信息系统项目管理师第二批次真题及答案解析(完整版)

    难度     807人已做
    查看
  • 【考后总结】2024年5月26日信息系统项目管理师第2批次考情分析

    难度     351人已做
    查看
  • 【考后总结】2024年5月25日信息系统项目管理师第1批次考情分析

    难度     314人已做
    查看
  • 2024年上半年软考高项第一、二批次真题考点汇总(完整版)

    难度     433人已做
    查看
  • 2024年上半年系统架构设计师考试综合知识真题

    难度     221人已做
    查看

相关文章

发现更多好内容

猜你喜欢

AI推送时光机
位置:首页-资讯-后端开发
咦!没有更多了?去看看其它编程学习网 内容吧
首页课程
资料下载
问答资讯