您当前的位置: 首页 >  mybatis

止步前行

暂无认证

  • 0浏览

    0关注

    247博文

    0收益

  • 0浏览

    0点赞

    0打赏

    0留言

私信
关注
热门博文

Spring源码系列(八)——Mybatis是如何整合进Spring源码分析

止步前行 发布时间:2021-11-06 16:28:49 ,浏览量:0

文章目录
  • 一、Spring启动流程再分析
    • 1. 处理Import过程
    • 2. 执行Import导入类
  • 二、Mybatis的入口类MapperScannerRegistrar
    • 1. scanner.doScan()
    • 2. processBeanDefinitions()
  • 三、小结

一、Spring启动流程再分析

配置类

@Configuration
@ComponentScan("com.scorpios")
@MapperScan("com.scorpios.mapper")
public class AppConfig {
    
    @Bean
    public DataSource dataSource() {
        DruidDataSource dataSource = new DruidDataSource();
        dataSource.setUsername("root");
        dataSource.setPassword("123456");
        dataSource.setUrl("jdbc:mysql://localhost:3306/test");
        dataSource.setDriverClassName("com.mysql.cj.jdbc.Driver");
        return dataSource;
    }

    @Bean
    public SqlSessionFactoryBean sqlSessionFactoryBean(DataSource dataSource) {
        SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();
        sqlSessionFactoryBean.setDataSource(dataSource);
        return sqlSessionFactoryBean;
    }
}

启动类

public static void main( String[] args )
{
    AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext();
    ac.register(AppConfig.class);
    ac.refresh();
    StudentMapper studentMapper = (StudentMapper) ac.getBean("studentMapper");
    System.out.println(studentMapper.selectAll());
}
1. 处理Import过程

Spring源码系列(四)——ConfigurationClassPostProcessor功能解析

https://blog.csdn.net/zxd1435513775/article/details/120935494?spm=1001.2014.3001.5501

在上面这篇文章中,我们分析了parser.parse(candidates);这个解析配置类的方法,也重点分析了Spring是如何解析并扫描@ComponentScan注解的,这部分核心代码主要在doProcessConfigurationClass()方法中。

Spring扫描完@ComponentScan注解后,就去调用下面这行代码processImports()来处理@Import注解导入的类。

// 处理 @Import 注解
processImports(configClass, sourceClass, getImports(sourceClass), true);

我们按照上面的启动类和配置类来运行程序,并断点到processImports()方法这一行,如下图。这个sourceClassAppConfig.class配置类,getImports(sourceClass)方法是拿到这个配置类上面的@Import注解信息。

从断点处可以看到,拿到了@MapperScan("com.scorpios.mapper")注解中的@Import(MapperScannerRegistrar.class)信息,MapperScannerRegistrar 实现 ImportBeanDefinitionRegistrar接口。

public class MapperScannerRegistrar implements ImportBeanDefinitionRegistrar

在这里插入图片描述

我们知道@Import注解的使用有三种方式,其中有一种使用方式是导入ImportBeanDefinitionRegistrar实现类。

@Import注解就是把Class类转化为BeanDefinition,然后把这个BeanDefinition添加到Spring容器中

关于@Import注解的使用可以看下面这篇文章:

https://blog.csdn.net/zxd1435513775/article/details/100625879

向Spring容器中注册组件的几种方式

下面来看下代码中处理@Import注解的方法逻辑:

private void processImports(ConfigurationClass configClass, SourceClass currentSourceClass,
                            Collection importCandidates, boolean checkForCircularImports) {

    if (importCandidates.isEmpty()) {
        return;
    }

    if (checkForCircularImports && isChainedImportOnStack(configClass)) {
        this.problemReporter.error(new CircularImportProblem(configClass, this.importStack));
    } else {
        this.importStack.push(configClass);
        try {
            for (SourceClass candidate : importCandidates) {
                if (candidate.isAssignable(ImportSelector.class)) {
                    // Candidate class is an ImportSelector -> delegate to it to determine imports
                    // 处理ImportSelector类
                    Class candidateClass = candidate.loadClass();
                    ImportSelector selector = BeanUtils.instantiateClass(candidateClass, ImportSelector.class);
                    ParserStrategyUtils.invokeAwareMethods(
                        selector, this.environment, this.resourceLoader, this.registry);
                    if (this.deferredImportSelectors != null && selector instanceof DeferredImportSelector) {
                        this.deferredImportSelectors.add(
                            new DeferredImportSelectorHolder(configClass, (DeferredImportSelector) selector));
                    } else {
                        String[] importClassNames = selector.selectImports(currentSourceClass.getMetadata());
                        Collection importSourceClasses = asSourceClasses(importClassNames);
                        processImports(configClass, currentSourceClass, importSourceClasses, false);
                    }
                } else if (candidate.isAssignable(ImportBeanDefinitionRegistrar.class)) {
                    // 处理ImportBeanDefinitionRegistrar类
                    // ImportBeanDefinitionRegistrar类暴露出BeanDefinitionRegistry
                    // 这个BeanDefinitionRegistry就是可以向spring容器中添加BeanDefinition
                    Class candidateClass = candidate.loadClass();
                    // 实例化,创建出来对象,并不需要放到bdMap中,后面直接调用,Mybatis与Spring集成就是在此处引入的
                    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());
                    // 此处又是一个递归调用,把import进来的类再次进行解析
                    processConfigurationClass(candidate.asConfigClass(configClass));
                }
            }
        } catch (BeanDefinitionStoreException ex) {
            throw ex;
        } catch (Throwable ex) {
            
        } finally {
            this.importStack.pop();
        }
    }
}

上面处理ImportBeanDefinitionRegistrar类的方法只有四行:

  • 获取到org.mybatis.spring.annotation.MapperScannerRegistrar
  • 实例化MapperScannerRegistrar
  • 把实例化好的MapperScannerRegistrar放入到由AppConfig转化的ConfigurationClass类中的importBeanDefinitionRegistrars属性中,后面会从这个Map中拿取并执行

注意:此处MapperScannerRegistrar创建出来对象,并没有放到bdMap中,而是放到了ConfigurationClass类中的importBeanDefinitionRegistrars属性中,后面会直接调用,MybatisSpring集成就是在此!!!! AOPImportBeanDefinitionRegistrar也是在此导入!!!

2. 执行Import导入类

上面完成了对ImportBeanDefinitionRegistrars接口类的扫描工作,并也已经实例化,但我们知道,并没有把这些类转为BeanDefinition,而是直接进行了实例化。==那在哪里进行实现这个接口类方法的调用呢?==答案是在parser.parse(candidates);这行代码的下面第9行,就是下图打断点的地方。this.reader.loadBeanDefinitions()这个方法中进行了调用。

在这里插入图片描述

从上面的分析中,我们知道,实现ImportBeanDefinitionRegistrars接口的类会被放到ConfigurationClassMap属性的importBeanDefinitionRegistrars中,上面这个断点处,就是要去这个Map中拿出这些接口实现类,逐个遍历,并进行方法调用。

下面来看下这个方法的处理逻辑:

public void loadBeanDefinitions(Set configurationModel) {
    TrackedConditionEvaluator trackedConditionEvaluator = new TrackedConditionEvaluator();
    for (ConfigurationClass configClass : configurationModel) {
        // 具体看下这个方法
        loadBeanDefinitionsForConfigurationClass(configClass, trackedConditionEvaluator);
    }
}

private void loadBeanDefinitionsForConfigurationClass(
    ConfigurationClass configClass, TrackedConditionEvaluator trackedConditionEvaluator) {

    if (trackedConditionEvaluator.shouldSkip(configClass)) {
        String beanName = configClass.getBeanName();
        if (StringUtils.hasLength(beanName) && this.registry.containsBeanDefinition(beanName)) {
            this.registry.removeBeanDefinition(beanName);
        }
        this.importRegistry.removeImportingClass(configClass.getMetadata().getClassName());
        return;
    }

    // 如果一个类是被import,会被spring标注,在这里完成注册
    if (configClass.isImported()) {
        registerBeanDefinitionForImportedConfigurationClass(configClass);
    }
    
    // 此处是处理配置类中@Bean注解修饰的方法
    for (BeanMethod beanMethod : configClass.getBeanMethods()) {
        loadBeanDefinitionsForBeanMethod(beanMethod);
    }

    // 处理配置类上@ImportResource导入的资源 ,xml中的bean
    loadBeanDefinitionsFromImportedResources(configClass.getImportedResources());
    
    // 处理配置类上@Import导入的ImportBeanDefinitionRegistrar接口实现类
    // 调用ImportBeanDefinitionRegistrar接口中的registerBeanDefinitions()方法
    loadBeanDefinitionsFromRegistrars(configClass.getImportBeanDefinitionRegistrars());
}

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

上面是处理配置类中具有@Bean注解的方法,下面就是来处理实现ImportBeanDefinitionRegistrar接口类的方法了。Mybatis整合的入口就在这里!!!!

在这里插入图片描述

二、Mybatis的入口类MapperScannerRegistrar

从上面的分析顺下来,程序调用到了MapperScannerRegistrar类中的registerBeanDefinitions()方法,这个类是由Mybatis提供的,也是Spring整合Mybatis的入口。

// 注意下第二个参数BeanDefinitionRegistry
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {

    // 此处要注意下根据MapperScan注解,返回的annoAttrs的值
    AnnotationAttributes annoAttrs = AnnotationAttributes.fromMap(importingClassMetadata.getAnnotationAttributes(MapperScan.class.getName()));
    
    // 创建一个ClassPathMapperScanner扫描器,这个扫描器继承ClassPathBeanDefinitionScanner
    // 这个ClassPathBeanDefinitionScanner扫描器熟悉吧,Spring完成扫描工作就是用的这个扫描器
    // 注意一下,此处的扫描器是拥有spring容器的,即registry,这样就可以往spring容器中放东西了
    ClassPathMapperScanner scanner = new ClassPathMapperScanner(registry);

    // 下面一系列代码都是为这个scanner赋值一些属性
    if (resourceLoader != null) {
        scanner.setResourceLoader(resourceLoader);
    }

    Class annotationClass = annoAttrs.getClass("annotationClass");
    if (!Annotation.class.equals(annotationClass)) {
        scanner.setAnnotationClass(annotationClass);
    }

    Class markerInterface = annoAttrs.getClass("markerInterface");
    if (!Class.class.equals(markerInterface)) {
        scanner.setMarkerInterface(markerInterface);
    }

    Class generatorClass = annoAttrs.getClass("nameGenerator");
    if (!BeanNameGenerator.class.equals(generatorClass)) {
        scanner.setBeanNameGenerator(BeanUtils.instantiateClass(generatorClass));
    }

    Class 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 basePackages = new ArrayList();
    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();
    // 开始扫描@MapperScan("com.scorpios.mapper")指定的路径
    scanner.doScan(StringUtils.toStringArray(basePackages));
}

此处要注意下根据@MapperScan注解,返回的annoAttrs的值,其中factoryBean的属性值是个FactoryBean

在这里插入图片描述

上面方法的最后一行scanner.doScan()方法,打个断点看下,这个scanner扫描器里面的具体情况:

注意,此处的scanner扫描器是拥有Spring容器的,即registry,这样就可以把扫描到类转化为BeanDefinition,然后放入到Spring容器中了

在这里插入图片描述

1. scanner.doScan()
public Set doScan(String... basePackages) {
    // 调用父类的doScan()方法,此时basePackages是@MapperScan注解所指定的包
    // 将扫描到的class转化为BeanDefinition返回
    Set beanDefinitions = super.doScan(basePackages);

    if (beanDefinitions.isEmpty()) {
		// 打印代码略
    } else {
        // 处理扫描出来并转化成的BeanDefinition
        processBeanDefinitions(beanDefinitions);
    }
    return beanDefinitions;
}

由上面的分析知道,这个ClassPathMapperScanner扫描器,继承ClassPathBeanDefinitionScanner,所以此处调用父类的doScan()方法就是调用ClassPathBeanDefinitionScanner类中的doScan()方法。

再仔细一看这个ClassPathBeanDefinitionScanner,不就是Spring自己扫描包时候的扫描器么?确实是的,这地方连调用的方法都是一样的。所以此处又把Spring的包扫描流程走了一遍,只不过扫描包的路径是@MapperScan注解指定的值了。

那么问题来了?

既然此处又把Spring包扫描的流程走了一遍,那为什么在第一次扫描的时候,没有把这些Mapper文件扫描进去呢?那Spring进行包扫描的时候是怎么判断一个类是否符合条件呢?下面来分析一下。

下面来逐遍调试,断点打在ClassPathScanningCandidateComponentProvider#scanCandidateComponents()方法。

下面这张图是Spring第一次自己扫描com.scorpios包的结果。我们可以看到,会把com.scorpios包下面的所有类都拿到放到这个Resource数组中,然后逐个resource去遍历,看看是否满足条件转化为BeanDefinition

在这里插入图片描述

下面来看看是如何进行判断是不是符合将类转化为BeanDefinition的,方法就是上图打断点的地方,isCandidateComponent(),下面来看下这个方法代码:

protected boolean isCandidateComponent(MetadataReader metadataReader) throws IOException {
    for (TypeFilter tf : this.excludeFilters) {
        // 是否在排除范围之内,满足直接返回
        if (tf.match(metadataReader, getMetadataReaderFactory())) {
            return false;
        }
    }
    for (TypeFilter tf : this.includeFilters) {
        // 如果匹配成功,就标记为满足条件,然后把类转为BeanDefinition
        if (tf.match(metadataReader, getMetadataReaderFactory())) {
            return isConditionMatch(metadataReader);
        }
    }
    return false;
}

下面要来看一下这个TypeFilter,因为两次扫描的区别就在这!!!!!

this.excludeFiltersmatch()的判断是在AbstractTypeHierarchyTraversingFilter#match()方法。

在这里插入图片描述

matchSelf()方法也是在AbstractTypeHierarchyTraversingFilter类中,这个方法直接返回的是false,表示不满足。

protected boolean matchSelf(MetadataReader metadataReader) {
    return false;
}

this.includeFiltersmatch()的判断也是在AbstractTypeHierarchyTraversingFilter#match()方法。

但此处的matchSelf()方法是在AnnotationTypeFilter类中

// 此处的判断就是类上是否有@Component注解,如果有,则返回true
protected boolean matchSelf(MetadataReader metadataReader) {
    // 拿到注解信息,metadata存放的就是注解信息
    AnnotationMetadata metadata = metadataReader.getAnnotationMetadata();
    
    return metadata.hasAnnotation(this.annotationType.getName()) ||
        (this.considerMetaAnnotations && metadata.hasMetaAnnotation(this.annotationType.getName()));
}

这里知道为什么Spring第一次扫描能扫描到哪些类了吧。

下面再来分析下从Mybatis中第二次触发的包扫描:

在这里插入图片描述

在这里插入图片描述

扫描的包路径是com.scorpios.mapper,即接口文件所在的位置。从上图中可以看出来拿到了Mapper文件。

下面要来验证下这个文件是否需要转化为BeanDefinition

下面又到了match()方法,但此时的match()方法是在ClassPathMapperScanner类中,而且代码中是返回的true,所以此处的所有扫描文件都满足,都会被转化为BeanDefinition

在这里插入图片描述

此处需要注意的是,BeanDefinition里面的beanClass属性现在是com.scorpios.mapper.StudentMapper

但后面会被替换为org.mybatis.spring.mapper.MapperFactoryBean

这个替换的地方在哪呢?

答案在ClassPathMapperScanner类中的processBeanDefinitions()

为什么需要替换呢?

2. processBeanDefinitions()
// 这个方法的入参是扫描到的Mapper文件转化为BeanDefinition的集合
private void processBeanDefinitions(Set beanDefinitions) {
    GenericBeanDefinition definition;
    
    // 循环扫描到的BeanDefinition
    for (BeanDefinitionHolder holder : beanDefinitions) {
        definition = (GenericBeanDefinition) holder.getBeanDefinition();

       // 映射器接口是bean的原始类,但是bean的实际类是MapperFactoryBean 
        definition.getConstructorArgumentValues().addGenericArgumentValue(definition.getBeanClassName()); // issue #59
        
        // 将扫描到的所有Mapper类转化为BeanDefinition中的class设置为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)) {
            definition.getPropertyValues().add("sqlSessionTemplate", new RuntimeBeanReference(this.sqlSessionTemplateBeanName));
            explicitFactoryUsed = true;
        } else if (this.sqlSessionTemplate != null) {
            definition.getPropertyValues().add("sqlSessionTemplate", this.sqlSessionTemplate);
            explicitFactoryUsed = true;
        }
        // 设置自动注入的类型,按type进行注入
        if (!explicitFactoryUsed) {
            definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE);
        }
    }
}

到此处就分析完了,Spring是如何整合Mybatis框架的了,而且在什么地方进行了从接口到类的替换。

三、小结

Spring整合Mybatis主要就是通过@MapperScan注解,该注解背后的原理就是通过@Import向容器中导入MapperScannerRegistrar类,而该类实现了ImportBeanDefinitionRegistrar接口。

本文通过分析@Import注解是如何处理实现ImportBeanDefinitionRegistrar接口的类源代码,来熟悉Mybatis的整合过程,并知道了将接口替换为MappperFactoryBean的位置。

其实,SpringAOP的实现原理也是这个ImportBeanDefinitionRegistrar接口,留在下篇分析吧。

关注
打赏
1657848381
查看更多评论
立即登录/注册

微信扫码登录

0.2210s