Mybatis源码解析(三):Spring 整合 Mybatis 原理

452 阅读9分钟

Mybatis源码解析(一):MyBatis 是如何执行的
Mybatis源码解析(二):Mybatis 插件原理
Mybatis源码解析(三):Spring 整合 Mybatis 原理

1 关于 Spring 的前置知识

MyBatis 与 Spring 框架整合需要借助于 MyBatis Spring 模块,在理解 MyBatis Spring 模块的实现原理之前,需要先了解一下 Spring 中的一些概念。

1.1 一些 Spring 中的概念

BeanDefinition

BeanDefinition 用于描述 Spring Bean 的配置信息,Spring 配置 Bean 的方式通常有3种:

  1. XML 配置文件;
  2. Java 注解,例如@Service@Component 等注解;
  3. Java Config 方式,Spring 从 3.0 版本开始支持使 @Configuration 注解,通过 Java Config 方式配置 Bean。

BeanDefinitionRegistry

BeanDefinitionRegistryBeanDefinition 容器,所有的 Bean 配置解析后生成的 BeanDefinition 对象都会注册到 BeanDefinitionRegistry 对象中。Spring 提供了扩展机制,允许用户在 Spring 框架启动时,动态地往 BeanDefinitionRegistry 容器中注册 BeanDefinition 对象。

BeanFactory

BeanFactory 是 Spring 的 Bean 工厂,负责 Bean 的创建及属性注入。它同时是一个 Bean 容器,Spring 框架启动后,会根据 BeanDefinition 对象创建 Bean 实例,所有的单例 Bean 都会注册到 BeanFactory 容器中。

BeanFactoryPostProcessor

BeanFactoryPostProcessor 是 Spring 提供的扩展机制,用于在所有的 Bean 配置信息解析完成后修改 BeanFactory 信息。例如,向 BeanDefinitionRegistry 容器中增加额外的 BeanDefinition 对象,或者修改原有的 BeanDefinition 对象。BeanFactoryPostProcessor 是一个接口,该接口中只有一个方法:

public interface BeanFactoryPostProcessor {
    void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException;
}

当我们配置的 Bean 实现该接口时,Spring 解析 Bean 配置完成后,就会调用所有 BeanFactoryPostProcessor 实现类的 postProcessBeanFactory() 方法。

ImportBeanDefinitionRegistrar

该接口的实现类作用于 Spring 解析 Bean 的配置阶段,当解析 @Configuration 注解时,可以通过 ImportBeanDefinitionRegistrar 接口的实现类向 BeanDefinitionRegistry 容器中添加额外的 BeanDefinition 对象:

public interface ImportBeanDefinitionRegistrar {
	default void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {}
}

ImportBeanDefinitionRegistrar 接口实现类的 registerBeanDefinitions() 方法会在 Spring 解析 @Configuration 注解时调用。ImportBeanDefinitionRegistrar 接口需要配合 @Import 注解使用,importingClassMetadata 参数为 @Import 所在注解的配置信息,registry 参数为 BeanDefinition 容器。

BeanPostProcessor

Bean 的后置处理器,在 Bean 初始化方法(init-method 属性指定的方法或 afterPropertiesSet() 方法)调用前后,会执行 BeanPostProcessor 中定义的拦截逻辑:

public interface BeanPostProcessor {
    @Nullable
    default Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
        return bean;
    }
    @Nullable
    default Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        return bean;
    }
}

BeanPostProcessor 接口中定义了两个方法,postProcessBeforelnitialization() 方法会在所有 Bean 初始化方法调用之前执行,postProcessAfterlnitialization() 方法会在所有 Bean 的初始化方法调用之后执行,BeanPostProcessor 通常用于处理 Spring Bean 对应的 Java 类中的注解信息或者创建 Bean 的代理对象。

ClassPathBeanDefinitionScanner

ClassPathBeanDefinitionScannerBeanDefinition 扫描器,能够对指定包下的 Class 进行扫描,将 Class 信息转换为 BeanDefinition 对象注册到 BeanDefinitionRegistry 容器中。ClassPathBeanDefinitionScanner 支持自定义的过滤规则,例如我们可以只对使用某种注解的类进行扫描。Spring 中的 @Service@Component 等注解配置 Bean 都是通过 ClassPathBeanDefinitionScanner 实现的。MyBatis Spring 模块中 Mapper 接口的扫描使用到了 ClassPathBeanDefinitionScanner 类。

FactoryBean

FactoryBean 是 Spring 中的工厂 Bean,通常用于处理 Spring 中配置较为复杂或者由动态代理生成的 Bean 实例,实现了该接口的 Bean 不能作为普通的 Bean 使用,而是作为单个对象的工厂。当我们通过 Bean 名称获取 FactoryBean 实例时,获取到的并不是 FactoryBean 对象本身,而是 FactoryBean 对象的 getObject() 方法返回的实例。例如如下 MybatisSqlSessionFactoryBean 配置:

<bean id="sqlSessionFactory" 
  		class="com.baomidou.mybatisplus.extension.spring.MybatisSqlSessionFactoryBean"
  <property name="dataSource" ref="dataSource" />
</bean>

SqlSessionFactoryBean 是一个 FactoryBean,通过名称 sqlSessionFactory 从 Spring 容器中获取 Bean 时,获取到的实际上是 SqlSessionFactoryBean 对象的 getObject() 方法返回的 SqlSessionFactory 对象。

1.2 Spring 容器启动过程

除了上一节的这些概念外,还需要熟悉一下 Spring 框架的启动过程,Spring 框架的启动过程大致可以分为以下几步:

  1. 对所有 Bean 的配置信息进行解析,其中包括 XML 配置文件、Java 注解以及 Java Config 方式配置的 Bean ,将 Bean 的配置信息转换为 BeanDefinition 对象,注册到 BeanDefinitionRegistry 容器中;
  2. BeanDefinitionRegistry 容器中获取实现了 BeanFactoryPostProcessor 接口的 Bean 定义,然后实例化 Bean,调用所有 BeanFactoryPostProcessor 对象的 postProcessBeanFactory() 方法,在 postProcessBeanFactory() 方法中可以对 Bean 工厂的信息进行修改;
  3. 根据 BeanDefinitionRegistry 容器中的 BeanDefinition 对象实例化所有的单例 Bean,并对Bean 的属性进行填充;
  4. 执行所有实现了 BeanPostProcessor 接口的 Bean 的 postProcessBeforelnitialization() 方法。该方法中可以对原始的 Bean 进行包装;
  5. 执行 Bean 的初始化方法,初始化方法包括配置 Bean 时通过 init-method 属性指定的方法,或者通过实现 InitializingBean 接口重写的 afterPropertiesSet() 方法;
  6. 执行所有实现了 BeanPostProcessor 接口的 Bean 的 postProcessAfterinitialization() 方法。

2 Mybatis Spring 简单配置案例

Spring 配置 Bean 的方式有多种,例如 XML 文件、Java 注解以及 JavaConfig等方式。这里我们使用 JavaConfig 方式配置 Mybatis 需要的 Bean。

DataSourceConfig.class

@Configuration
@MapperScan(basePackages = {"com.example.learn.common.mapper"}, sqlSessionFactoryRef = "sqlSessionFactory")
public class DataSourceConfig {

    @Bean("dataSource")
    @ConfigurationProperties(prefix = "spring.datasource") // 引入 application.properties 中数据源配置信息
    public DataSource dataSource() {
        // 注入数据源
        return DruidDataSourceBuilder.create().build();
    }

    @Bean("sqlSessionFactory")
    public SqlSessionFactory sqlSessionFactory(@Qualifier("dataSource") DataSource dataSource) throws Exception {
        // 通过 Mybatis Spring 模块提供的 SqlSessionFactoryBean 创建 MyBatis 的 SqlSessionFactory 对象
        MybatisSqlSessionFactoryBean sqlSessionFactoryBean = new MybatisSqlSessionFactoryBean();
        sqlSessionFactoryBean.setDataSource(dataSource);
        return sqlSessionFactoryBean.getObject();
    }

    @Bean("sqlSessionTemplate")
    public SqlSessionTemplate secondSqlSessionTemplate(@Qualifier("sqlSessionFactory") SqlSessionFactory sqlSessionFactory) {
        // 创建MybatisSpring模块中的Sq1sessionTemplate对象
        return new SqlSessionTemplate(sqlSessionFactory);
    }
}

application.properties

spring.datasource.url = jdbc:mysql://localhost:3306/test
spring.datasource.username = root
spring.datasource.password = root
spring.datasource.driverClassName = com.mysql.cj.jdbc.Driver

上面代码通过 JavaConfig 方式配置 MyBatis,主要完成以下功能:

  1. 创建 DataSource 实例。通过 DruidDataSourceBuilder 构建了一个 DruidDataSource 实例;
  2. 配置 SqlSessionFactory 对象。SqlSession 是 MyBatis 提供的与数据库交互的接口,SqlSessionFactory 通过工厂模式创建 SqlSession,并通过 Spring 来管理 SqlSessionFactory 对象的生命周期。上面的代码中使用 MyBatis Spring 模块中提供的 SqlSessionFactoryBean 来构建 SqlSessionFactory 对象;
  3. 配置 SqlSessionTemplate 对象。使用 MyBatis 时可以通过 SqlSessionFactory 对象的 openSession() 方法获取一个 SqlSession 对象与数据库进行交互,MyBatis Spring 模块提供了SqlSessionTemplate 用于完成数据库交互,在整合 Spring 容器中只存在一个 SqlSessionTemplate 实例;
  4. 通过 MapperScan 注解扫描 Mapper 接口。在 MyBatisMapper 对象是通过动态代理生成的,调用 SqlSession 对象的 getMapper() 方法每次返回的是一个新的代理对象。MyBatis Spring 模块提供了一个 MapperScan 注解,用于扫描特定包下的 Mapper 接口,并创建 Mapper 代理对象,然后将 Mapper 对象添加到 Spring 容器中。

3 Mybatis Spring 原理分析

前面介绍了 Spring 框架中的一些概念以及 Spring 框架的启动过程。在理解 Spring 的基础上,下面介绍 MyBatis 中动态代理创建的 Mapper 对象是如何与 Spring 容器进行关联的。 MyBatis 与 Spring 框架整合需要借助 MyBatis Spring 模块,该模块中提供了一个 MapperScan 注解,该注解用于扫描指定包中的 Mapper 接口并创建 Mapper 动态代理对象,其关键代码如下:

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

  // 扫描包路径
  String[] value() default {};

  // 扫描包路径
  String[] basePackages() default {};

  // 扫描 Mapper 接口对应的 Class 对象
  Class<?>[] basePackageClasses() default {};

  // Bean 名称生成策略
  Class<? extends BeanNameGenerator> nameGenerator() default BeanNameGenerator.class;

  // 只扫描使用某种注解修饰的类或接口
  Class<? extends Annotation> annotationClass() default Annotation.class;

  // 只扫描某种类型的子类型
  Class<?> markerInterface() default Class.class;

  // 指定使用哪个 SqlSessionTemplate 对象
  String sqlSessionTemplateRef() default "";

  // 指定使用哪个 SqlSessionFactory 对象
  String sqlSessionFactoryRef() default "";

  // 指定使用自定义的 MapperFactoryBean 返回 Mybatis代理对象作为 Spring 的 Bean
  Class<? extends MapperFactoryBean> factoryBean() default MapperFactoryBean.class;

}

MapperScan 注解通过 @Import 注解导入了一个 BeanDefinition 注册类 MapperScannerRegistrarMapperScannerRegistrar 类实现了 ImportBeanDefinitionRegistrar 接口。

在上文提到过,Spring中的 ImportBeanDefinitionRegistrar 用于在 Spring 解析 Bean 的配置阶段往 BeanDefinitionRegistry 容器中注册额外的 BeanDefinition 对象。

public class MapperScannerRegistrar implements ImportBeanDefinitionRegistrar, ResourceLoaderAware {
  
  @Override
  public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
    // 获取 MapperScan 注解中的配置信息
    AnnotationAttributes mapperScanAttrs = AnnotationAttributes
        .fromMap(importingClassMetadata.getAnnotationAttributes(MapperScan.class.getName()));
    if (mapperScanAttrs != null) {
      // 调用 registerBeanDefinitions() 方法注册 BeanDefinition 对象
      registerBeanDefinitions(importingClassMetadata, mapperScanAttrs, registry,
          generateBaseBeanName(importingClassMetadata, 0));
    }
  }

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

    // MapperScannerConfigurer 用于整合 Mybatis Spring 模块自定义的 BeanDefinition 扫描器 ClassPathMapperScanner
    BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(MapperScannerConfigurer.class);
    builder.addPropertyValue("processPropertyPlaceHolders", true);

    // 以下是解析 Mapper 注解中的信息,添加到 MapperScannerConfigurer 对象中

    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);
    }

    Class<? extends BeanNameGenerator> generatorClass = annoAttrs.getClass("nameGenerator");
    if (!BeanNameGenerator.class.equals(generatorClass)) {
      builder.addPropertyValue("nameGenerator", BeanUtils.instantiateClass(generatorClass));
    }

    Class<? extends MapperFactoryBean> mapperFactoryBeanClass = annoAttrs.getClass("factoryBean");
    if (!MapperFactoryBean.class.equals(mapperFactoryBeanClass)) {
      builder.addPropertyValue("mapperFactoryBeanClass", mapperFactoryBeanClass);
    }

    String sqlSessionTemplateRef = annoAttrs.getString("sqlSessionTemplateRef");
    if (StringUtils.hasText(sqlSessionTemplateRef)) {
      builder.addPropertyValue("sqlSessionTemplateBeanName", annoAttrs.getString("sqlSessionTemplateRef"));
    }

    String sqlSessionFactoryRef = annoAttrs.getString("sqlSessionFactoryRef");
    if (StringUtils.hasText(sqlSessionFactoryRef)) {
      builder.addPropertyValue("sqlSessionFactoryBeanName", annoAttrs.getString("sqlSessionFactoryRef"));
    }

    // 获取需要扫描的包
    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()));

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

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

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

如上面的代码所示,MapperScannerRegistrar 类实现了 ImportBeanDefinitionRegistrar 接口的registerBeanDefinitions() 方法,该方法调用了重载的 registerBeanDefinitions() 进行处理。在重载方法中,首先创建了一个 MapperScannerConfigurer 的构建者对象,然后获取 MapperScan 注解的属性信息,加到 MapperScannerConfigurer 对象的属性中,最终将该 MapperScannerConfigurer 对象注册到 BeanDefinitionRegistry 中。

public class MapperScannerConfigurer implements BeanDefinitionRegistryPostProcessor, InitializingBean, ApplicationContextAware, BeanNameAware {
  @Override
  public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) {
    if (this.processPropertyPlaceHolders) {
      processPropertyPlaceHolders();
    }
    // Mybatis Spring 模块自定义的 BeanDefinition 扫描器
    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);
    // MapperFactoryBeanClass 默认是 MapperFactoryBean.class
    scanner.setMapperFactoryBeanClass(this.mapperFactoryBeanClass);
    if (StringUtils.hasText(lazyInitialization)) {
      scanner.setLazyInitialization(Boolean.valueOf(lazyInitialization));
    }
    // 注册扫描过滤规则
    scanner.registerFilters();
    // 对包中的类进行扫描生成 BeanDefinition 对象
    scanner.scan(
        StringUtils.tokenizeToStringArray(this.basePackage, ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS));
  }
}
public void setMapperFactoryBeanClass(Class<? extends MapperFactoryBean> mapperFactoryBeanClass) {
  this.mapperFactoryBeanClass = mapperFactoryBeanClass == null ? MapperFactoryBean.class : mapperFactoryBeanClass;
}

MapperScannerConfigurer 实现了 BeanDefinitionRegistryPostProcessor 接口,该接口是标准BeanFactoryPostProcessor SPI 的扩展,允许在常规 BeanFactoryPostProcess 检测开始之前注册更多的 bean 定义,其执行逻辑在 postProcessBeanDefinitionRegistry 方法中; postProcessBeanDefinitionRegistry 中,使用 ClassPathMapperScanner 扫描 MapperbasePackage注册 MapperBeanDefinationClassPathMapperScanner 是 Spring 中 ClassPathBeanDefinitionScanner 的子类,用于扫描特定包下 的 Mapper 接口,将 Mapper 接口信息转换为对应的 BeanDefinition 对象。下面是ClassPathMapperScannerdoScan() 方法的实现:

public class ClassPathMapperScanner extends ClassPathBeanDefinitionScanner {
  
  // mapperFactoryBeanClass 属性为 MapperFactoryBean.class;
	private Class<? extends MapperFactoryBean> mapperFactoryBeanClass = MapperFactoryBean.class;
  
  @Override
  public Set<BeanDefinitionHolder> doScan(String... basePackages) {
    // 调用父类的 doscan() 方法,将包中的 Class 转换为 BeanDefinitionHolder 对象
    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;
  }


  private void processBeanDefinitions(Set<BeanDefinitionHolder> beanDefinitions) {
    GenericBeanDefinition definition;
    for (BeanDefinitionHolder holder : beanDefinitions) {
      // 获取 BeanDefinition 对象
      definition = (GenericBeanDefinition) holder.getBeanDefinition();
      String beanClassName = definition.getBeanClassName();
      LOGGER.debug(() -> "Creating MapperFactoryBean with name '" + holder.getBeanName() + "' and '" + beanClassName
                   + "' mapperInterface");
  
      // the mapper interface is the original class of the bean
      // but, the actual class of the bean is MapperFactoryBean
      definition.getConstructorArgumentValues().addGenericArgumentValue(beanClassName); // issue #59
      // 将 BeanDefinition 对象的 beanclass 属性设置为 MapperFactoryBean.class
      definition.setBeanClass(this.mapperFactoryBeanClass);
      ......
      
    } 
  }
}

ClassPathMapperScanner 类的 doScan() 方法中,首先调用父类的 doScan() 方法,将指定包下的 Mapper 接口信息转换为 BeanDefinitionHolder 对象,BeanDefinitionHolder 中持有一个 BeanDefinition 对象及 Bean 的名称和所有别名;

所有的 Mapper 接口转换为 BeanDefinitionHolder 对象后,接着调用 processBeanDefinitions() 方法,对所有 BeanDefinitionHolder 对象进行处理; 在 processBeanDefinitions() 方法中,对所有 BeanDefinitionHolder 对象进行遍历,获取BeanDefinitionHolder 对象中持有的 BeanDefinition 对象,然后对 BeanDefinition 对象的信息进行修改,将 BeanDefinition 对象的 beanClass 属性设置为 MapperFactoryBean.class

BeanDefinition 对象的 beanClass 属性设置为 MapperFactoryBean.class 这一步很重要,当 Spring 将所有的 Bean 配置信息转换为 BeanDefinition 对象后,就会根据 BeanDefinition 对象来实例化 Bean,由于 BeanDefinition 对象的 beanClass 属性被设置为 MapperFactoryBean.class,因此 Spring 在创建 Bean 时实例化的是 MapperFactoryBean 对象;

MapperFactoryBean 实现了 FactoryBean 接口,FactoryBean 是单个 Bean 的工厂 Bean,当我们根据 Mapper 类型从 Spring 容器中获取 FactoryBean 时,获取到的并不是 FactoryBean 本身,而是 FactoryBeangetObject() 方法返回的对象。 看下 MapperFactoryBeangetObject() 方法的实现:

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

这里我们找到了想要的答案,在 MapperFactoryBeangetObject() 方法中,调用 SqlSession 对 象的 getMapper() 方法返回一个 Mapper 动态代理对象。