Spring整合mybatis源码剖析(一)-dao接口加载过程

学习源码过程中随手记录的笔记,仅供参考,有问题欢迎指出交流
可能比较枯燥,耐点心,但是弄懂了,必能知其然而知其所以然
学习源码建议亲手debug调试

使用的源码版本

​	mybatis版本3.5.3

​	spring版本5.2.0

测试代码示例

@Configuration
@MapperScan("com.cheng.mapper")
public class MybatisConfig {

	@Bean
	public SqlSessionFactoryBean sqlSessionFactory() throws IOException {
		SqlSessionFactoryBean factoryBean = new SqlSessionFactoryBean();
		factoryBean.setDataSource(dataSource());
		// 设置 MyBatis 配置文件路径
		//factoryBean.setConfigLocation(new ClassPathResource("mybatis/mybatis-config.xml"));
		// 设置 SQL 映射文件路径
		//factoryBean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources("classpath:com/cheng/mapper/*.xml"));
		//factoryBean.setTypeAliases(User.class);

		return factoryBean;
	}


	public DataSource dataSource() {
		DruidDataSource dataSource = new DruidDataSource();
		dataSource.setUsername("xx");
		dataSource.setPassword("xxx");
		dataSource.setDriverClassName("com.mysql.jdbc.Driver");
		dataSource.setUrl("jdbc:mysql://xxx/mybatis_example?characterEncoding=utf8&useSSL=false");
		return dataSource;
	}
}

通过@MapperScan(“com.cheng.mapper”)注解进行切入分析

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Import(MapperScannerRegistrar.class)
@Repeatable(MapperScans.class)
public @interface MapperScan {}

看到熟悉的@Import 其他三方与Spring无缝链接时,在零xml配置时,都是采用@Import注解进行处理器的注册

点进MapperScannerRegistrar这个类实现ImportBeanDefinitionRegistrar

public class MapperScannerRegistrar implements ImportBeanDefinitionRegistrar, ResourceLoaderAware {}

在bean定义扫描的时候会调用registerBeanDefinitions()方法往我们的容器中添加bean定义对象到 beanDefinitionMap中
AbstractApplicationContext#refresh#invokeBeanFactoryPostProcessors(beanFactory)

private void loadBeanDefinitionsFromRegistrars(Map<ImportBeanDefinitionRegistrar, AnnotationMetadata> registrars) {
		registrars.forEach((registrar, metadata) ->
				registrar.registerBeanDefinitions(metadata, this.registry, this.importBeanNameGenerator));
	}

看下MapperScannerRegistrar的registerBeanDefinitions()方法
先扫描@Mappscan注解

public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {

    /**
     * 从我们传入的配置类中来解析@MapperScan注解信息,然后吧MapperScan注解的属性转化为 AnnotationAttributes类型(Map类型)
     */
    AnnotationAttributes mapperScanAttrs = AnnotationAttributes
        .fromMap(importingClassMetadata.getAnnotationAttributes(MapperScan.class.getName()));
    /**
     * 若上一步解析出来的mapperScanAttrs不为空(说明配置类上加了@MapperScan注解)
     */
    if (mapperScanAttrs != null) {
      /**
       * 调用重写的方法registerBeanDefinitions generateBaseBeanName(importingClassMetadata, 0) 我们即将注册的bean定义的名称
       * com.cheng.config.MyBatisConfig#MapperScannerRegistrar#0
       */
      registerBeanDefinitions(mapperScanAttrs, registry, generateBaseBeanName(importingClassMetadata, 0));
    }
  }

进入registerBeanDefinitions(mapperScanAttrs, registry, generateBaseBeanName(importingClassMetadata, 0));
这边对@MappScan注解属性的解析

void registerBeanDefinitions(AnnotationAttributes annoAttrs, BeanDefinitionRegistry registry, String beanName) {

    /**
     * 创建bean定义构造器 通过够构造器来构建出我们的bean定义<MapperScannerConfigurer> 应用到的设计模式[建造者模式]
     */
    BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(MapperScannerConfigurer.class);

    /**
     * 手动为我们MapperScannerConfigurer 开启processPropertyPlaceHolders属性为true 我们需要着重研究下MapperScannerConfigurer类的继承结构
     */
    builder.addPropertyValue("processPropertyPlaceHolders", true);

    /**
     * 为我们的MapperScannerConfigurer 解析我们@MapperScanner 指定扫描的的注解类型
     */
    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);
    }

    /**
     * 设置MapperScannerConfigurer的beanName 生成器对象
     */
    Class<? extends BeanNameGenerator> generatorClass = annoAttrs.getClass("nameGenerator");
    if (!BeanNameGenerator.class.equals(generatorClass)) {
      builder.addPropertyValue("nameGenerator", BeanUtils.instantiateClass(generatorClass));
    }

    /**
     * 解析@MapperScan注解属性MapperFactoryBean 设置到MapperScannerConfigurer 声明一个自定义的MapperFactoryBean 返回一个代理对象
     */
    Class<? extends MapperFactoryBean> mapperFactoryBeanClass = annoAttrs.getClass("factoryBean");
    if (!MapperFactoryBean.class.equals(mapperFactoryBeanClass)) {
      builder.addPropertyValue("mapperFactoryBeanClass", mapperFactoryBeanClass);
    }

    /**
     * 解析@MapperScan 的sqlSessionTemplateRef到底使用是哪个sqlSessionTemplate 设置到MapperScannerConfigurer 多数据源的情况下需要指定
     */
    String sqlSessionTemplateRef = annoAttrs.getString("sqlSessionTemplateRef");
    if (StringUtils.hasText(sqlSessionTemplateRef)) {
      builder.addPropertyValue("sqlSessionTemplateBeanName", annoAttrs.getString("sqlSessionTemplateRef"));
    }

    /**
     * 解析@MapperScan的sqlSessionFactoryRef属性 设置到 MapperScannerConfigurer 多数据情况下的话 ,需要指定使用哪个 sqlSessionFactory
     */
    String sqlSessionFactoryRef = annoAttrs.getString("sqlSessionFactoryRef");
    if (StringUtils.hasText(sqlSessionFactoryRef)) {
      builder.addPropertyValue("sqlSessionFactoryBeanName", annoAttrs.getString("sqlSessionFactoryRef"));
    }

    /**
     * 解析@MapperScan 扫描的的包或者是class对象
     */
    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()));

    /**
     * 指定MapperScannerConfigurer 是否为懒加载
     */
    String lazyInitialization = annoAttrs.getString("lazyInitialization");
    if (StringUtils.hasText(lazyInitialization)) {
      builder.addPropertyValue("lazyInitialization", lazyInitialization);
    }

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

    /**
     * 为我们的容器中注册了MapperScannerConfigurer的接口
     */
    registry.registerBeanDefinition(beanName, builder.getBeanDefinition());

  }

构建一个MapperScannerConfigurer,将@MapperScan的属性值解析包装到该类中
registry.registerBeanDefinition(beanName, builder.getBeanDefinition());
注册到beanDefinitionMap中

image-20210723155227197

接下来看看MapperScannerConfigurer这个类实现了BeanDefinitionRegistryPostProcessor这个接口

public class MapperScannerConfigurer
    implements BeanDefinitionRegistryPostProcessor, InitializingBean, ApplicationContextAware, BeanNameAware {
}

那么就会在核心方法AbstractApplication#refresh()#invokeBeanFactoryPostProcessors(beanFactory)方法完成处理器的调用

private static void invokeBeanDefinitionRegistryPostProcessors(
			Collection<? extends BeanDefinitionRegistryPostProcessor> postProcessors, BeanDefinitionRegistry registry) {

		for (BeanDefinitionRegistryPostProcessor postProcessor : postProcessors) {
			postProcessor.postProcessBeanDefinitionRegistry(registry);
		}
	}

上面后置处理方法执行时会进入MapperScannerConfigurer#postProcessBeanDefinitionRegistry()

public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) {
    /**
     * 若MapperScannerConfigurer属性的processPropertyPlaceHolders 为ture的时候,就进行 processPropertyPlaceHolders();
     */
    if (this.processPropertyPlaceHolders) {
      processPropertyPlaceHolders();
    }

    /**
     * 显示的new 一个ClassPathMapperScanner 包扫描器对象 这个对象是mybaits继承了spring的 ClassPathBeanDefinitionScanner,有了扫描解析功能
     * 为我们扫描器指定@MapperScan配置的包路径属性
     */
    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();
    /**
     * 真正的去扫描我们@MapperScan指定的路径下的bean定义信息 先会去调用ClassPathMapperScanner.scan()方法
     */
    scanner.scan(
        StringUtils.tokenizeToStringArray(this.basePackage, ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS));
  }

在这去扫描@MapperScan注解中填写的包名,加入到BeanDefinitionMap中

scanner.scan(
        StringUtils.tokenizeToStringArray(this.basePackage, ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS));

image-20210719162304761

ClassPathMapperScanner#doscan

 public Set<BeanDefinitionHolder> doScan(String... basePackages) {
    /**
     * 调用父类ClassPathBeanDefinitionScanner 来进行扫描
     */
    Set<BeanDefinitionHolder> beanDefinitions = super.doScan(basePackages);

    /**
     * 若扫描后 我们mapper包下有接口类,那么扫描bean定义就不会为空
     */
    if (beanDefinitions.isEmpty()) {
      LOGGER.warn(() -> "No MyBatis mapper was found in '" + Arrays.toString(basePackages)
          + "' package. Please check your configuration.");
    } else {
      /**
       * mybaits在切入,将spring的 的bean定义玩到极致(做了偷天换日的操作) 现在我们知道t通过父类扫描出来的mapper是接口类型的
       * 比如我们com.cheng.mapper.UserMapper 他是一个接口 可能会知道我们的bean定义最终会被实例化成
       * 对象,但是我们接口是不能实例化的,所以在processBeanDefinitions 来进行偷天换日
       */
      processBeanDefinitions(beanDefinitions);
    }

ClassPathBeanDefinitionScanner#doScan中会把@MapperScan配置的包路径进行扫描并注册BeanDefinitionMap中
image-20210719162510630
到这,dao层的接口已经扫描完成并加入到beanDefinitionMap中了,此时还没有创建bean哦,不理解的看下spring源码(…)

还有一件事没有做,就是偷天换日,将dao接口转换成FactoryBean(为什么转换?..接口无法直接实例化啊)

简单过下FactoryBean概念
是一个工厂bean,实现该接口可以自定义要创建的bean,spring在创建bean的时候最终会调用它的getObject方法

回到上面processBeanDefinitions(beanDefinitions)方法

private void processBeanDefinitions(Set<BeanDefinitionHolder> beanDefinitions) {
    GenericBeanDefinition definition;
    /**
     * 循环我们所有扫描出mapper的bean定义出来
     */
    for (BeanDefinitionHolder holder : beanDefinitions) {
      // 获取我们的bean定义
      definition = (GenericBeanDefinition) holder.getBeanDefinition();
      // 获取我们的bean定义的名称
      String beanClassName = definition.getBeanClassName();
      LOGGER.debug(() -> "Creating MapperFactoryBean with name '" + holder.getBeanName() + "' and '" + beanClassName
          + "' mapperInterface");

      /**
       * 进行真的偷天换日操作,也就是这二行代码是最最最最最重要的, 关乎我们 spring整合mybaits的整合 definition.setBeanClass(this.mapperFactoryBeanClass);
       */
      // 设置ConstructorArgumentValues 会通过构造器初始化对象
      definition.getConstructorArgumentValues().addGenericArgumentValue(beanClassName); // issue #59
      // 设置成factoryBean
      definition.setBeanClass(this.mapperFactoryBeanClass);

      definition.getPropertyValues().add("addToConfig", this.addToConfig);

      /**
       * 为我们的Mapper对象绑定我们的sqlSessionFactory引用 说白了就是我们的UserMapper(实际上是就是为我们的MapperFactoryBean添加一个sqlSessionFactory的属性)
       * 然后SpringIoc在实例话我们的MapperFactoryBean的时候会经历populate()方法为我么你的UserMapper(MapperFactoryBean)
       * 的sqlSessionFactory赋值(调用set方法)
       */
      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;
      }
      /**
       * 为我们的Mapper对象绑定我们的sqlSessionTemplate属性对象
       * 说白了就是我们的UserMapper(实际上是就是为我们的MapperFactoryBean添加一个sqlSessionTemplate的属性)
       * 然后SpringIoc在实例话我们的MapperFactoryBean的时候会经历populate()方法为UserMapper(MapperFactoryBean)
       * 的sqlSessionTemplate赋值(调用set方法)
       */
      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.");
        }
        // 将sqlSessionTemplate通过AUTOWIRE_BY_TYPE自动装配
        definition.getPropertyValues().add("sqlSessionTemplate", this.sqlSessionTemplate);
        explicitFactoryUsed = true;
      }

     
      if (!explicitFactoryUsed) {
        LOGGER.debug(() -> "Enabling autowire by type for MapperFactoryBean with name '" + holder.getBeanName() + "'.");
        definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE);
      }
      /**
       * 设置bean定义的加载模型(是否为懒加载)
       */
      definition.setLazyInit(lazyInitialization);
    }
  }

将之前扫描的beanDefinitions进行循环

  1. 设置bean为MapperFactoryBean也就是FactoryBean
  2. 为MapperFactoryBean添加SqlSessionTempate属性
  3. 设置注入通过类型注入

至此,dao接口已经扫描完毕并加入到BeanDefinitionMap,并将beanClass设置成MapperFactoryBean了
期待下一篇吧

如果觉得还算凑合,麻烦点个赞哦

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值