Spring源码系列(十)MapperScan注解的原理(二)

1.写在前面

上篇博客中笔者介绍怎么Import注解中的类给变成一个BeanDefinition添加到Spring的IOC的容器中,主要调用的是这个类(MapperScannerRegistrar)的registerBeanDefinitions(),由于篇幅的原因,笔者只介绍了如何调用的MapperScannerRegistrar类中的registerBeanDefinitions()的方法。具体方法中的逻辑还没有讲,由于Mybatis-Spring这个有两个版本,所以笔者今天就打算讲下这两个版本的实现逻辑和原理。

2.老版本的Mybatis-Spring(1.3)

由于上篇博客笔者已经介绍了怎么调用到对应的registerBeanDefinitions(),笔者这篇博客就不赘述了。直接来看registerBeanDefinitions()方法的代码,具体的代码如下:

//importingClassMetadata 注解的元数据
//registry DefaultListableBeanFactory
@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
    //获取MapperScan注解数据
	AnnotationAttributes annoAttrs = 
        AnnotationAttributes.fromMap(importingClassMetadata.getAnnotationAttributes(MapperScan.class.getName()));
    //ClassPathMapperScanner继承的是ClassPathBeanDefinitionScanner有篇博客中笔者介绍了自定义注解的时候,介绍过这个类
    //这个类笔者等下说 自定义的扫描规则
    ClassPathMapperScanner scanner = new ClassPathMapperScanner(registry);

    // this check is needed in Spring 3.1
    // 这个属性在上篇博客中实例化MapperScannerRegistrar的时候设置的
    if (resourceLoader != null) {
      scanner.setResourceLoader(resourceLoader);
    }

    //此属性指定扫描程序将扫描的注解,一般都是默认的Annotation。
    Class<? extends Annotation> annotationClass = annoAttrs.getClass("annotationClass");
    if (!Annotation.class.equals(annotationClass)) {
      scanner.setAnnotationClass(annotationClass);
    }

    //此属性指定扫描器将扫描的父对象。一般默认都是Class
    Class<?> markerInterface = annoAttrs.getClass("markerInterface");
    if (!Class.class.equals(markerInterface)) {
      scanner.setMarkerInterface(markerInterface);
    }

    //名字生成策略,一般都是默认的BeanNameGenerator
    Class<? extends BeanNameGenerator> generatorClass = annoAttrs.getClass("nameGenerator");
    if (!BeanNameGenerator.class.equals(generatorClass)) {
      scanner.setBeanNameGenerator(BeanUtils.instantiateClass(generatorClass));
    }

    //指定一个自定义MapperFactoryBean以将mybatis代理返回为spring bean 一般默认就是MapperFactoryBean
    Class<? extends MapperFactoryBean> mapperFactoryBeanClass = annoAttrs.getClass("factoryBean");
    if (!MapperFactoryBean.class.equals(mapperFactoryBeanClass)) {
      scanner.setMapperFactoryBean(BeanUtils.instantiateClass(mapperFactoryBeanClass));
    }

    //指定在spring上下文中有多个数据源的情况下使用哪个SqlSessionTemplate。通常只有在您拥有多个数据源时才需要这样做,默认是“”
    scanner.setSqlSessionTemplateBeanName(annoAttrs.getString("sqlSessionTemplateRef"));
    //指定在spring上下文中有多个数据源的情况下使用哪个SqlSessionFactory。通常只有在您拥有多个数据源时才需要这样做。默认是“”
    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();
    //通过自定义的扫描器,然后进行扫描
    scanner.doScan(StringUtils.toStringArray(basePackages));
  }

上面的代码主要就是解析对应的@MapperScan中的数据,同时将这些数据填充到一个自定义的扫描器中去,主要是ClassPathMapperScanner这个类,这个类是继承ClassPathBeanDefinitionScanner类,有篇博客笔者介绍了自定义注解,具体是Spring源码系列(四)Spring扫描机制(一),读者可以参考一下这篇博客,这样看ClassPathMapperScanner类的代码会比较好理解点,这个时候我们直接来看ClassPathMapperScanner的代码,具体的代码如下:

public class ClassPathMapperScanner extends ClassPathBeanDefinitionScanner {
    
    //三个构造函数,真好有BeanDefinitionRegistry,所以就调用这个构造方法
    public ClassPathMapperScanner(BeanDefinitionRegistry registry) {
       super(registry, false);
    }
    
    public void registerFilters() {
        boolean acceptAllInterfaces = true;
        
        // if specified, use the given annotation and / or marker interface
        // 如果给了自己的自定义的注解,直接加到第一次过滤条件中,这个是包括的
        if (this.annotationClass != null) {
            addIncludeFilter(new AnnotationTypeFilter(this.annotationClass));
            acceptAllInterfaces = false;
        }

        // override AssignableTypeFilter to ignore matches on the actual marker interface
        // 覆盖AssignableTypeFilter(简单的过滤器)也是添加对应的过滤器
        if (this.markerInterface != null) {
            addIncludeFilter(new AssignableTypeFilter(this.markerInterface) {
                @Override
                protected boolean matchClassName(String className) {
                    return false;
                }
            });
            acceptAllInterfaces = false;
        }

        //如果上面设置annotation或者是markerInterface就不会采用默认的过滤机制
        if (acceptAllInterfaces) {
            // default include filter that accepts all classes
            addIncludeFilter(new TypeFilter() {
                @Override
                public boolean match(MetadataReader metadataReader, MetadataReaderFactory metadataReaderFactory) throws IOException {
                    return true;
                }
            });
        }

        // exclude package-info.java
        // 排除 package-info.java
        addExcludeFilter(new TypeFilter() {
            @Override
            public boolean match(MetadataReader metadataReader, MetadataReaderFactory metadataReaderFactory) throws IOException {
                String className = metadataReader.getClassMetadata().getClassName();
                return className.endsWith("package-info");
            }
        });
    }
    
    @Override
    public Set<BeanDefinitionHolder> doScan(String... basePackages) {
        //调用父类的扫描的方法,前面的博客已经说过了,这儿会执行两个过滤的方法,过滤的规则我们上面已经写了,这儿第一次过滤扫描出来所有的class
        //第二次过滤会扫描出来是接口的类,在下面我们重写了对应的方法。
        //由于返回的set集合,引用,我们对set集合中的BeanDefinition修改同样也会作用到spring的容器中去。
        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 {
            //将扫描出来的BeanDefinition进行相应的处理,最后添加到spring容器中去
            processBeanDefinitions(beanDefinitions);
        }
	 
        //返回对应的BeanDefinition
        return beanDefinitions;
    }
    
    private void processBeanDefinitions(Set<BeanDefinitionHolder> beanDefinitions) {
        GenericBeanDefinition definition;
        //遍历所有的BeanDefinition
        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.getConstructorArgumentValues().addGenericArgumentValue(definition.getBeanClassName()); // issue #59
            // 设置对应的BeanClass
            definition.setBeanClass(this.mapperFactoryBean.getClass());

            //设置对应的属性值 默认为true
            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;
            }

            //如果上面有一个参数设置了值,注入的模型就不是byType
            if (!explicitFactoryUsed) {
                if (logger.isDebugEnabled()) {
                    logger.debug("Enabling autowire by type for MapperFactoryBean with name '" + holder.getBeanName() + "'.");
                }
                definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE);
            }
        }
    }
    
    //第二次过滤,返回的是接口同时是独立的类
    @Override
  	protected boolean isCandidateComponent(AnnotatedBeanDefinition beanDefinition) {
    	return beanDefinition.getMetadata().isInterface() && beanDefinition.getMetadata().isIndependent();
  	}
}

上面的代码就是mybatis重写了ClassPathBeanDefinitionScanner类中的方法,默认的情况下,将第一次过滤的条件改成了所有的class都会扫描出来。同时重写了第二次过滤的方法,将第二次过滤的条件改成了是接口同时也是一个独立类的情况。最后扫描出来的BeanDefinition已经注册到Spring容器中去了,由于返回的是set集合,mybatis又写了一个方法processBeanDefinitions,将原来的BeanDefinition进行了修改。以达到能对接口能代理的目的。而这里注册BeanDefinitionBeanClass的值是MapperFactoryBean类型,这个类继承了FactoryBean,至于Spring怎么将这个继承FactoryBean的类转换成Bean,后面的博客会讲到。然后后面的代理就是mybatis的事,至于怎么代理的,可以看Mybatis源码解析的博客。至此老版本的Mybatis-Spring的原理就讲完了。

3.新版本的Mybatis-Spring(2.0)

还是和上面一样,我们走来先看registerBeanDefinitions()方法,具体的代码如下:

@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
    //获取MapperScan的注解的元数据
    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) {

    //调用spring写的BeanDefinition的生成器,直接将MapperScannerConfigurer生成一个BeanDefinition,这个MapperScannerConfigurer实现了	
    //BeanDefinitionRegistryPostProcessor类
    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(
        Arrays.stream(annoAttrs.getStringArray("value")).filter(StringUtils::hasText).collect(Collectors.toList()));

    basePackages.addAll(Arrays.stream(annoAttrs.getStringArray("basePackages")).filter(StringUtils::hasText)
        .collect(Collectors.toList()));

    basePackages.addAll(Arrays.stream(annoAttrs.getClassArray("basePackageClasses")).map(ClassUtils::getPackageName)
        .collect(Collectors.toList()));

    //如果获取的值都是为空的话,直接将当前加了@MapperScan注解的类的包名添加进去。
    if (basePackages.isEmpty()) {
      basePackages.add(getDefaultBasePackage(annoMeta));
    }

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

    builder.addPropertyValue("basePackage", StringUtils.collectionToCommaDelimitedString(basePackages));

    //将这个BeanDefinition注册到容器中去
    registry.registerBeanDefinition(beanName, builder.getBeanDefinition());

  }

//生成对应的名字,生成的规则就是加了这个的注解的全类名#MapperScannerRegistrar#0
private static String generateBaseBeanName(AnnotationMetadata importingClassMetadata, int index) {
    return importingClassMetadata.getClassName() + "#" + MapperScannerRegistrar.class.getSimpleName() + "#" + index;
}

private static String getDefaultBasePackage(AnnotationMetadata importingClassMetadata) {
    return ClassUtils.getPackageName(importingClassMetadata.getClassName());
}

上面就是代码就是注册了一个BeanDefinition到容器中去,而这个BeanDefinition对应的BeanClassMapperScannerConfigurer类实现了BeanDefinitionRegistryPostProcessor这个接口,这个接口的实现类的执行时机,笔者在Spring源码系列(五)Spring扫描机制(二)博客中有讲到,这儿就不阐述了。可以去看下那篇博客,然后Spring会调用到MapperScannerConfigurer类中的postProcessBeanDefinitionRegistry方法,具体的代码如下:

@Override
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) {
    //这个值为true,前面传入进来的
    if (this.processPropertyPlaceHolders) {
      processPropertyPlaceHolders();
    }

    //创建对应ClassPathMapperScanner对象,设置好对应的属性
    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(lazyInitialization)) {
      scanner.setLazyInitialization(Boolean.valueOf(lazyInitialization));
    }
    //和老的版本是一样的
    scanner.registerFilters();
    //最终会调用到子类的doScan的方法,就是和上面的是一样的
    scanner.scan(
        StringUtils.tokenizeToStringArray(this.basePackage, ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS));
}

//BeanDefinitionRegistries在应用程序启动的早期,即BeanFactoryPostProcessors之前被调用。
//这意味着PropertyResourceConfigurers将不会被加载,并且此类属性的任何属性替换都将失败。
//为了避免这种情况,请找到上下文中定义的所有PropertyResourceConfigurers并在此类的bean定义上运行它们。然后更新值
private void processPropertyPlaceHolders() {
    Map<String, PropertyResourceConfigurer> prcs = applicationContext.getBeansOfType(PropertyResourceConfigurer.class,
        false, false);

    //一般的情况下都是为空
    if (!prcs.isEmpty() && applicationContext instanceof ConfigurableApplicationContext) {
      BeanDefinition mapperScannerBean = ((ConfigurableApplicationContext) applicationContext).getBeanFactory()
          .getBeanDefinition(beanName);

      // PropertyResourceConfigurer does not expose any methods to explicitly perform
      // property placeholder substitution. Instead, create a BeanFactory that just
      // contains this mapper scanner and post process the factory.
      DefaultListableBeanFactory factory = new DefaultListableBeanFactory();
      factory.registerBeanDefinition(beanName, mapperScannerBean);

      for (PropertyResourceConfigurer prc : prcs.values()) {
        prc.postProcessBeanFactory(factory);
      }

      PropertyValues values = mapperScannerBean.getPropertyValues();

      this.basePackage = updatePropertyValue("basePackage", values);
      this.sqlSessionFactoryBeanName = updatePropertyValue("sqlSessionFactoryBeanName", values);
      this.sqlSessionTemplateBeanName = updatePropertyValue("sqlSessionTemplateBeanName", values);
      this.lazyInitialization = updatePropertyValue("lazyInitialization", values);
    }
    //强制设置对应的属性
    this.basePackage = Optional.ofNullable(this.basePackage).map(getEnvironment()::resolvePlaceholders).orElse(null);
    this.sqlSessionFactoryBeanName = Optional.ofNullable(this.sqlSessionFactoryBeanName)
        .map(getEnvironment()::resolvePlaceholders).orElse(null);
    this.sqlSessionTemplateBeanName = Optional.ofNullable(this.sqlSessionTemplateBeanName)
        .map(getEnvironment()::resolvePlaceholders).orElse(null);
    this.lazyInitialization = Optional.ofNullable(this.lazyInitialization).map(getEnvironment()::resolvePlaceholders)
        .orElse(null);
  }

上面的代码就是通过spring的机制最后执行postProcessBeanDefinitionRegistry方法,最后在这个方法中完成了扫描和注册。

新老版本的区别:

  • 老版本是在调用MapperScannerRegistrarregisterBeanDefinitions方法的时候完成扫描和将接口转成对应的BeanDefinition注册到spring的IOC容器中。
  • 新版本是在调用MapperScannerRegistrarregisterBeanDefinitions方法的时候添加了一个BeanClassMapperScannerConfigurerBeanDefinition,然后利用了spring调用BeanDefinitionRegistryPostProcessor接口实现类的机制,在MapperScannerConfigurer中的postProcessBeanDefinitionRegistry的方法中完成了扫描和将接口转成对应的BeanDefinition注册到spring的IOC容器中。
  • 新版本的接口对应的BeanDefinition的注册比老版本的接口对应的BeanDefinition注册要晚点。

4.写在最后

至此MapperScan注解的原理就讲完了。这种方式,为我们以后开发我们自己的插件整合spring,提供思路。至于新版本的优点是什么?笔者自己也不是很清楚,如果有人知道,可以告诉笔者。下篇博客笔者可能会讲用dbutils模拟Mybatis整合spring。

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值