逐行阅读Spring5.X源码(八)Mybatis是如何利用MapperScan完成扫描的?

时间:2022-07-24
本文章向大家介绍逐行阅读Spring5.X源码(八)Mybatis是如何利用MapperScan完成扫描的?,主要内容包括其使用实例、应用技巧、基本知识点总结和需要注意事项,具有一定的参考价值,需要的朋友可以参考一下。

mybaits是通过@MapperScan注解完成扫描的,具体是如何完成的呢?首先看一下MapperScan的源码:

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

@MapperScan上加了一个@Import注解,了解注解知识的读者都知道,@MapperScan起始就是@Import的子类。在上一篇博客中我们详细分析了注册扫描后置处理器ConfigurationClassPostProcessor是如何完成扫描的,其中有一个重要的步骤就是解析@Import注解。mybatis就是利用了这个特性完成的扫描。

回顾以下上一篇博文ConfigurationClassPostProcessor解析@Import注解的过程: 定位到ConfigurationClassParser类的doProcessConfigurationClass方法中的如下代码:

processImports(configClass, sourceClass, getImports(sourceClass), true);

这个方法就是处理配置类的@Import注解。起始上一篇博文已经分析过了,这里我们再分析一遍。 首先,通过getImports(sourceClass)方法获取配置类上所有的@Import注解中的类。加入我们有一个配置类如下所示:

@Configuration
@ComponentScan(value = "com")
@Import(User.class)
@MapperScan("net")
public class Config extends ConfigSuperClass implements UserInterface {
    @Value("${demo.name}")
    private String name;
    public String getName() {
        return name;
    }
}

getImports方法就是拿到@Import(User.class)上的User.class和@MapperScan("net")父类上的MapperScannerRegistrar.class,然后将这两个class封装成SourceClass并返回。分析一下getImports源码:

    private Set<SourceClass> getImports(SourceClass sourceClass) throws IOException {
        Set<SourceClass> imports = new LinkedHashSet<>();
        Set<SourceClass> visited = new LinkedHashSet<>();
        collectImports(sourceClass, imports, visited);
        return imports;
    }

这个方法中的核心方法是collectImports(sourceClass, imports, visited);,将找到的class加入到imports集合中,然后返回。

    private void collectImports(SourceClass sourceClass, Set<SourceClass> imports, Set<SourceClass> visited)
            throws IOException {

        if (visited.add(sourceClass)) {
            for (SourceClass annotation : sourceClass.getAnnotations()) {
                String annName = annotation.getMetadata().getClassName();
                if (!annName.equals(Import.class.getName())) {
                    //如果注解本身不是@Import,递归查看注解的父注解是否有@Import注解
                    collectImports(annotation, imports, visited);
                }
            }
            //拿到@Import注解上的value值
            imports.addAll(sourceClass.getAnnotationAttributes(Import.class.getName(), "value"));
        }
    }

总之,就是 1.拿到配置类上所有的注解 2.递归拿到所有注解的父注解 3.在所有的注解中找到@Import注解,拿到value值,放到imports集合中返回。 在本例中,此时我们拿到了两个class,一个是配置类本身的@Import注解中的User.class,另一个是@MapperScan的父注解@Import中的MapperScannerRegistrar.class。

拿到import导入的两个类后,就是真正的进入processImports方法进行处理了,核心代码如下:

for (SourceClass candidate : importCandidates) {
  if (candidate.isAssignable(ImportSelector.class)) {
        // Candidate class is an ImportSelector -> delegate to it to determine imports
        Class<?> candidateClass = candidate.loadClass();
        // 反射创建这个类的实例对象
        ImportSelector selector = BeanUtils.instantiateClass(candidateClass, ImportSelector.class);
        //是否有实现相关Aware接口,如果有,这调用相关方法
        ParserStrategyUtils.invokeAwareMethods(
                                selector, this.environment, this.resourceLoader, this.registry);
        // 延迟加载的ImportSelector
        if (selector instanceof DeferredImportSelector) {
            //  延迟加载的ImportSelector先放到List中,延迟加载
            this.deferredImportSelectorHandler.handle(configClass, (DeferredImportSelector) selector);
        }else {
            // 普通的ImportSelector ,执行其selectImports方法,获取需要导入的类的全限定类名数组
            String[] importClassNames = selector.selectImports(currentSourceClass.getMetadata());
            //获取需要导入类的class
            Collection<SourceClass> importSourceClasses = asSourceClasses(importClassNames);
            // 递归调用
            processImports(configClass, currentSourceClass, importSourceClasses, false);
              }
        }
    else if (candidate.isAssignable(ImportBeanDefinitionRegistrar.class)) {
        // Candidate class is an ImportBeanDefinitionRegistrar ->
        // delegate to it to register additional bean definitions
        Class<?> candidateClass = candidate.loadClass();
        ImportBeanDefinitionRegistrar registrar =
                                BeanUtils.instantiateClass(candidateClass, ImportBeanDefinitionRegistrar.class);
        ParserStrategyUtils.invokeAwareMethods(registrar, this.environment, this.resourceLoader, this.registry);
                        configClass.addImportBeanDefinitionRegistrar(registrar, currentSourceClass.getMetadata());
    }
    else {
        // Candidate class not an ImportSelector or ImportBeanDefinitionRegistrar ->
        // process it as an @Configuration class
        this.importStack.registerImport(currentSourceClass.getMetadata(), candidate.getMetadata().getClassName());
            processConfigurationClass(candidate.asConfigClass(configClass));
     }
}
  • 首先,if (candidate.isAssignable(ImportSelector.class))判断导入的类是否实现了ImportSelector接口。该接口源码如下:
public interface ImportSelector {
    String[] selectImports(AnnotationMetadata importingClassMetadata);
}

接口只定义了一个selectImports(AnnotationMetadata importingClassMetadata)方法,用于指定需要注册到容器中的Class名称。当配置类的@Import注解引入了一个ImportSelector接口实现类后,会根据selectImports方法返回的Class名称生成BeanDefinition加载到spring容器中。

来看一个简单的示例:

public class User2 {
}

@Component
public class User implements ImportSelector ,BeanFactoryAware {

    @Override
    public String[] selectImports(AnnotationMetadata importingClassMetadata) {
        return new String[] {User2.class.getName()};
    }

    @Override
    public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
        System.out.println("实现了BeanFactoryAware接口");
    }
}

上文中,我们在配置类上@Import(User.class)导入了User,此时拿到User的selectImports方法返回的类名,根据类名生成对应的BeanDefinition加载到容器中。在这里,把User2定义成了BeanDefinition,加载到容器中了。看源码如何实现的:

//如果实现了ImportSelector接口
if (candidate.isAssignable(ImportSelector.class)) {
// 获取User的class对象
    Class<?> candidateClass = candidate.loadClass();
// 反射创建这个类的实例对象
    ImportSelector selector = BeanUtils.instantiateClass(candidateClass, ImportSelector.class);
//是否有实现相关Aware接口,如果有,这调用相关方法
    ParserStrategyUtils.invokeAwareMethods(
                                selector, this.environment, this.resourceLoader, this.registry);
// 延迟加载的ImportSelector
    if (selector instanceof DeferredImportSelector) {
//  延迟加载的ImportSelector先放到List中,延迟加载
    this.deferredImportSelectorHandler.handle(configClass, (DeferredImportSelector) selector);
    }else {
// 普通的ImportSelector ,执行其selectImports方法,获取需要导入的类的全限定类名数组
    String[] importClassNames = selector.selectImports(currentSourceClass.getMetadata());
    Collection<SourceClass> importSourceClasses = asSourceClasses(importClassNames);
// 递归调用
    processImports(configClass, currentSourceClass, importSourceClasses, false);
    }
}

代码注释讲的很清楚,相信大家能看懂,但是有这么行代码```

ParserStrategyUtils.invokeAwareMethods(selector, this.environment, this.resourceLoader, this.registry);

判断是否有实现相关Aware接口,如果有,这调用相关方法。Aware,是感应和感知的意思。当User实现了对应的Aware接口时,此处就会User实现该接口的实现方法,此例中就是User的setBeanFactory(BeanFactory beanFactory)方法。该方法传入了一个BeanFactory类型的参数,这个类型的有以下这些方法:

这样我们就能利用这个Bean工厂做很多很多扩展。这就是spring插件或者扩展开发的一个简单案例。

Aware是顶级接口,它有很多子接口:

但是在此处不是所有有Aware接口都可以,invokeAwareMethods源码:

public static void invokeAwareMethods(Object parserStrategyBean, Environment environment,
            ResourceLoader resourceLoader, BeanDefinitionRegistry registry) {

        if (parserStrategyBean instanceof Aware) {
            if (parserStrategyBean instanceof BeanClassLoaderAware) {
                ClassLoader classLoader = (registry instanceof ConfigurableBeanFactory ?
                        ((ConfigurableBeanFactory) registry).getBeanClassLoader() : resourceLoader.getClassLoader());
                if (classLoader != null) {
                    ((BeanClassLoaderAware) parserStrategyBean).setBeanClassLoader(classLoader);
                }
            }
            if (parserStrategyBean instanceof BeanFactoryAware && registry instanceof BeanFactory) {
                ((BeanFactoryAware) parserStrategyBean).setBeanFactory((BeanFactory) registry);
            }
            if (parserStrategyBean instanceof EnvironmentAware) {
                ((EnvironmentAware) parserStrategyBean).setEnvironment(environment);
            }
            if (parserStrategyBean instanceof ResourceLoaderAware) {
                ((ResourceLoaderAware) parserStrategyBean).setResourceLoader(resourceLoader);
            }
        }
    }

只有BeanClassLoaderAware、BeanFactoryAware 、EnvironmentAware、ResourceLoaderAware接口才能被调用。

Aware接口处理完后,就是判断User是否是延迟加载:

if (selector instanceof DeferredImportSelector)

显然,我们的User实现的ImportSelector接口,没有实现DeferredImportSelector接口,所以就不是延迟加载,DeferredImportSelector是ImportSelector的子接口。如果我们要实现延迟加载功能,让User实现DeferredImportSelector接口即可:

public class User  implements DeferredImportSelector {
    @Override
    public String[] selectImports(AnnotationMetadata importingClassMetadata) {
        System.out.println("延迟加载User2");
        return new String[] {User2.class.getName()};
    }
}

启动spring,就会进入上面的if判断里的this.deferredImportSelectorHandler.handle(configClass, (DeferredImportSelector) selector)方法,handle方法内会将selector再封装成DeferredImportSelectorHolder类型的对象,然后保存在数组中以后调用,调用时机在以后的实例化过程中再讲,先记住这点。 如果不是延迟加载的话,if条件不执行,执行else中的语句。String[] importClassNames = selector.selectImports(currentSourceClass.getMetadata());就是调用下面的方法,获取你的返回的类名数组。

else中的最后两行代码就是根据上一步返回的类名数组找到对应的class,然后递归。为什么要递归调用,因为你导入的类可能还有Import注解啊,那也要处理的。

  • 其次,else if (candidate.isAssignable(ImportBeanDefinitionRegistrar.class))判断是否实现ImportBeanDefinitionRegistrar接口。该接口允许我们手动封装BeanDefinition并注册到容器中。我们先修改下User类:
@Component
public class User implements  ImportBeanDefinitionRegistrar {


    @Override
    public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
        System.out.println("实现了ImportBeanDefinitionRegistrar接口");
    }
}

其实,很小儿科,不讲了,在BeanDefinition博文中讲烂了。注意此时并没有立刻调用registerBeanDefinitions这个方法,而是先通过 configClass.addImportBeanDefinitionRegistrar(registrar, currentSourceClass.getMetadata()); 这行代码放到配置类对应的解析器的一个集合中,

private final Map<ImportBeanDefinitionRegistrar, AnnotationMetadata> importBeanDefinitionRegistrars =
            new LinkedHashMap<>();
......
    public void addImportBeanDefinitionRegistrar(ImportBeanDefinitionRegistrar registrar, AnnotationMetadata importingClassMetadata) {
        this.importBeanDefinitionRegistrars.put(registrar, importingClassMetadata);
    }

当配置类都处理完了后,遍历所有配置类解析器,调用这个集合中所有类的addImportBeanDefinitionRegistrar方法。

  • 最后,如果以上接口都没实现。按普通配置类处理,递归调用,跟Config 配置类的处理方式一样,再来一遍上述过程。

也就是说,如果Import进来的类实现了ImportBeanDefinitionRegistrar 或 ImportSelector 接口,则继续递归处理他们生成的类,直到找到没有实现这两个接口的类,找到后就当作配置类按照Config的方式进行处理。

MapperScannerRegistrar

上面讲了User,其实MapperScannerRegistrar 也一样

public class MapperScannerRegistrar implements ImportBeanDefinitionRegistrar, ResourceLoaderAware 

spring会调用MapperScannerRegistrarregisterBeanDefinitions方法。 上面讲了一堆,就是为了说明spring是如何调用MapperScannerRegistrarregisterBeanDefinitions方法的。下一步就是分析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));
    }
  }

先解析MapperScan类的基础配置信息,起始就是拿到MapperScan所有方法的默认返回值。

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Import(MapperScannerRegistrar.class)
@Repeatable(MapperScans.class)
public @interface MapperScan {
  //扫描路径数组
  String[] value() default {};
  //扫描路径数组,另一种表达方式,跟上面一样
  String[] basePackages() default {};
//类数组,根据类获取所在的包路径,作为基础扫描类型
  Class<?>[] basePackageClasses() default {};
//名字生成器,配置路径下的类名根据此名字生成器进行命名
  Class<? extends BeanNameGenerator> nameGenerator() default BeanNameGenerator.class;
 //待扫描的注解,注册基本包中具有指定注解的所有接口。注意这可以与markerInterface结合使用
  Class<? extends Annotation> annotationClass() default Annotation.class;
//扫描指定的类
  Class<?> markerInterface() default Class.class;
//如果spring中有多个sqlSessionTemplate,指定你要用哪一个,通常只有当您有多个数据源时才需要这样做
  String sqlSessionTemplateRef() default "";
//如果spring中有多个SqlSessionFactory,指定你要用哪一个,通常只有当您有多个数据源时才需要这样做
  String sqlSessionFactoryRef() default "";
//Specifies a custom MapperFactoryBean to return a mybatis proxy as spring bean  mapper的创建工厂
  Class<? extends MapperFactoryBean> factoryBean() default MapperFactoryBean.class;
//对于mapper是否懒加载
  String lazyInitialization() default "";

}

然后调用registerBeanDefinitions(AnnotationMetadata annoMeta, AnnotationAttributes annoAttrs, BeanDefinitionRegistry registry, String beanName)方法,

第一步 BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(MapperScannerConfigurer.class); 将MapperScannerConfigurer生成一个GenericBeanDefinition类型的BeanDefinition再返回来备用,看源码:

    public static BeanDefinitionBuilder genericBeanDefinition(Class<?> beanClass) {
//构建一个GenericBeanDefinition
        BeanDefinitionBuilder builder = new BeanDefinitionBuilder(new GenericBeanDefinition());
//GenericBeanDefinition与MapperScannerConfigurer对应
        builder.beanDefinition.setBeanClass(beanClass);
        return builder;
    }

第二步设置属性processPropertyPlaceHolderstrue,下文讲为什么

builder.addPropertyValue("processPropertyPlaceHolders", true);

第三步找到扫描的包路径,放到basePackages集合中,如果没有配置扫描路径,则将@MapperScan所在的配置类的包路径作为基础包路径:

if (basePackages.isEmpty()) {
      basePackages.add(getDefaultBasePackage(annoMeta));
    }

然后就将将MapperScannerConfigurer对应的GenericBeanDefinition注册到容器中

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

    registry.registerBeanDefinition(beanName, builder.getBeanDefinition());

好像,没有扫描啊,只是做了一些配置工作,说实话,如果读者没有读过我之前关于后置处理器的文章,很难搞懂接下来我要讲的内容——自定义后置处理器何时调用? 建议先阅读《逐行阅读Spring5.X源码(五) 初探BeanFactoryPostProcessor后置处理器,难,特别难。》

MapperScannerConfigurer

我们首先要搞清楚,上文注册到容器中的MapperScannerConfigurer到底是个什么鬼?自动扫描 将Mapper接口生成代理注入到Spring。

public class MapperScannerConfigurer
    implements BeanDefinitionRegistryPostProcessor, InitializingBean, ApplicationContextAware, BeanNameAware

顾名思义,mapper扫描配置类,这个类实现了BeanDefinitionRegistryPostProcessor接口! 变成了一个后置处理器。该接口只有一个postProcessBeanDefinitionRegistry方法,spring启动过程中会主动调用MapperScannerConfigurerpostProcessBeanDefinitionRegistry方法。所以接下来就要分析这个方法

这个方法第一步

if (this.processPropertyPlaceHolders) {
      processPropertyPlaceHolders();
    }

processPropertyPlaceHolders,上文讲过这个值确实是true,所以这条判断成立,执行processPropertyPlaceHolders();方法。此函数的官方注解如下:

 /*
   * BeanDefinitionRegistries are called early in application startup, before BeanFactoryPostProcessors. This means that
   * PropertyResourceConfigurers will not have been loaded and any property substitution of this class' properties will
   * fail. To avoid this, find any PropertyResourceConfigurers defined in the context and run them on this class' bean
   * definition. Then update the values.
   */

大概意思就是:BeanDefinitionRegistries会在应用启动的时候调用,并且会早于BeanFactoryPostProcessors的调用(在之前后置处理器博文中讲过,确实是 ),这就意味着PropertiesResourceConfigurers还没有被加载,所有对于属性文件的引用将会失效,为避免此种情况发生,此方法手动地找出定义的PropertyResourceConfigurers并进行调用以以保证对于属性的引用可以正常工作。这个函数主要做以下两件事情:

  1. 找到所有已经注册的PropertyResourceConfigurer类型的bean。
  2. 模拟Spring的环境来用处理器。这里通过使用呢new DefaultlistableBeanFactory()来模拟Spring中的环境(完成处理器的调用后便失效),将映射的bean,也就是MapperScannerConfigurer类型bean注册到环境中来进行后处理器的调用。

可以先不用关注这点,这个会在mybatis源码专题中详解,下一步我们看mybatis如何扫描。

    ClassPathMapperScanner scanner = new ClassPathMapperScanner(registry);

ClassPathMapperScanner类继承了spring的ClassPathBeanDefinitionScanner类,专门用来扫描注册

public class ClassPathMapperScanner extends ClassPathBeanDefinitionScanner

如果读者阅读过本专题之前的文章,肯定对这个类非常了解,如果没读过,可以 参考《逐行阅读Spring5.X源码(六) ClassPathBeanDefinitionScanner扫描器》逐行阅读Spring5.X源码(番外篇)自定义扫描器, Mybatis是如何利用spring完成Mapper扫描的