Spring 整合 MyBatis 原理解析

在 Java 后端开发过程中,我们基本上会选择 Spring 与 MyBatis 这两类主流的框架。这两个框架我当初使用时,大概知道 Spring 是 IOC 框架,帮我们管理项目中的对象,而 MyBatis 是 ORM 框架,帮助我们快速的将数据库的记录映射到 Java 的实体类上。如果需要将两者结合起来,就需要一些特殊的配置,但并不知道 Spring 与 MyBatis 的接入过程。现在对这两个框架已经有了一个清晰的认识,所以很容易就能看懂其中的原理,下面对这次源码解析做一次记录与归纳。

Spring 基础知识点回顾

1.Spring 初始化流程

Spring 初始化的流程是挺复杂的,这里简答归纳一下就是定位、加载、注册、实例化、注入。从中可以看出如果 Spring 想管理我们的类首先就要知道这些类在什么位置。在以前 Spring MVC 的时候我们需要指定一个 component-scan 配置项,用来告诉 Spring 哪些类需要被加载到容器中,后面Spring Boot 为了简化配置项不需要我们手动指定改配置,Spring Boot 默认会去扫描启动类所在包的类以及子包中的类。

2. BeanFactory 与 FactoryBean 的区别

上面两个关键字中都有 Factory 这个单词,从中可以知道跟他们有关的类肯定都是用来生成对象的,在 Spring 中 BeanFactory 指代的容器,可以从中获取指定名称或者指定类型的对象。FactoryBean 也是用来获取对象的,但它只能获取单一类型的对象,因为 BeanFactory 接口使用的是泛型,它们俩的指责是不一样的。

Spring 与 MyBatis 整合

1.Spring 定位 MyBatis 的 DAO 接口

一般我们会使用 @MapperScan 注解来配置,这样 Spring 就知道需要加载哪些包下面的接口。

2.将 DAO 接口定义注册到 Spring

该功能需要使用到 MapperScannerRegistrar 这个类,该类会将指定包下面的接口注册到 Spring 中。

@Override
  public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {

    AnnotationAttributes annoAttrs = AnnotationAttributes.fromMap(importingClassMetadata.getAnnotationAttributes(MapperScan.class.getName()));
    ClassPathMapperScanner scanner = new ClassPathMapperScanner(registry);

    // this check is needed in Spring 3.1
    if (resourceLoader != null) {
      scanner.setResourceLoader(resourceLoader);
    }

    Class<? extends Annotation> 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<? extends BeanNameGenerator> generatorClass = annoAttrs.getClass("nameGenerator");
    if (!BeanNameGenerator.class.equals(generatorClass)) {
      scanner.setBeanNameGenerator(BeanUtils.instantiateClass(generatorClass));
    }

    Class<? extends MapperFactoryBean> 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<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));
  }

这个方法中最终要的是 scanner 的 doScan 方法,这里的 scanner 实例是 ClassPathMapperScanner ,可以看下该实现类中的代码:

@Override
  public Set<BeanDefinitionHolder> doScan(String... basePackages) {
    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 {
      processBeanDefinitions(beanDefinitions);
    }

    return beanDefinitions;
  }

从上面的代码可以看出当前类会调用 ClassPathBeanDefinitionScanner 基类中的 doScan 方法,这个时候会将 DAO 中的接口转换成 BeanDefinitionHolder,最后 spring-mybatis 中的 ClassPathMapperScanner 会调用 processBeanDefinitions 进行额外的处理,下面看一下该方法执行了哪些操作:

private void processBeanDefinitions(Set<BeanDefinitionHolder> beanDefinitions) {
    GenericBeanDefinition definition;
    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
      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)) {
        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;
      }

      if (!explicitFactoryUsed) {
        if (logger.isDebugEnabled()) {
          logger.debug("Enabling autowire by type for MapperFactoryBean with name '" + holder.getBeanName() + "'.");
        }
        definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE);
      }
    }

这个类主要做的工作就是对原来的 BeanDefinition 进行一些额外的配置,其中比较重要的代码是 'definition.setBeanClass(this.mapperFactoryBean.getClass());',该代码的主要作用是重新设置当前 Bean 的 Class,这里将当前接口的 Class 设置成了 MapperFactoryBean.class,所以 Spring 去初始化的时候会去调用 MapperFactoryBean 类的 getObject 方法。

3.实例化 DAO 中的接口

从上面的流程中我们知道 Spring 是调用 MapperFactoryBean 中的 getObject 方法来获取实例的,下面来看看 getObject 方法中具体的实现:

@Override
  public T getObject() throws Exception {
    return getSqlSession().getMapper(this.mapperInterface);
  }

从代码中可以看出是调用 sqlSession中的 getMapper 方法来获取实例的,该方法的具体实现是由 mybatis 完成的,具体流程不在本篇博客的讨论范围。至于这个 sqlSession 是怎么来的,可以去查看上面提到的 processBeanDefinitions 方法,该对象是通过 set 的形式注入到 MapperFactoryBean 中的。

发表评论

电子邮件地址不会被公开。 必填项已用*标注