栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Java

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

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

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

一、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()方法这一行,如下图。这个sourceClass是AppConfig.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属性中,后面会直接调用,Mybatis与Spring集成就是在此!!!!
AOP的importBeanDefinitionRegistrar也是在此导入!!!

2. 执行import导入类

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

从上面的分析中,我们知道,实现importBeanDefinitionRegistrars接口的类会被放到ConfigurationClass的Map属性的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.excludeFilters中match()的判断是在AbstractTypeHierarchyTraversingFilter#match()方法。

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

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

this.includeFilters中match()的判断也是在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的位置。

其实,Spring中AOP的实现原理也是这个importBeanDefinitionRegistrar接口,留在下篇分析吧。

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/434280.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号