Spring 扩展点实践:整合 MyBATIS

Spring 扩展点实践:整合 MyBATIS

在上一篇文章 Spring 扩展点概览及实践 中介绍了 Spring 内部存在的扩展点。学以致用,现在来分析一下 Spring 与 MyBATIS 的整合流程。

示例程序

为了方便分析源码,先根据官方文档 mybatis-spring – MyBatis-Spring | Getting Started 搭建起一个简单实例。

数据库方面,直接使用功能了 MySQL 示例数据库: MySQL : Employees Sample Database,需要的话,自行下载。

package com.diguage.truman.mybatis;

import com.mysql.cj.jdbc.Driver;
import com.zaxxer.hikari.HikariDataSource;
import org.apache.ibatis.session.Configuration;
import org.junit.jupiter.api.Test;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Bean;

import javax.sql.DataSource;

/**
 * @author D瓜哥, https://www.diguage.com/
 * @since 2020-05-29 17:11
 */
public class MybatisTest {
  @Test
  public void test() {
    AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
    context.register(Config.class);
    context.refresh();
    EmployeesMapper employeesMapper = context.getBean(EmployeesMapper.class);
    Employees employees = employeesMapper.getById(10001);
    System.out.println(employees);
  }

  @org.springframework.context.annotation.Configuration
  @MapperScan(basePackages = "com.diguage.truman.mybatis")
  public static class Config {
    @Bean
    public DataSource dataSource() {
      HikariDataSource dataSource = new HikariDataSource();
      dataSource.setUsername("root");
      dataSource.setPassword("123456");
      dataSource.setDriverClassName(Driver.class.getName());
      dataSource.setJdbcUrl("jdbc:mysql://localhost:3306/employees?useUnicode=true&characterEncoding=utf-8&autoReconnectForPools=true&autoReconnect=true");
      return dataSource;
    }

    @Bean
    public SqlSessionFactoryBean sqlSessionFactory(@Autowired DataSource dataSource) {
      SqlSessionFactoryBean factoryBean = new SqlSessionFactoryBean();
      factoryBean.setDataSource(dataSource);
      Configuration configuration = new Configuration();
      configuration.setMapUnderscoreToCamelCase(true);
      factoryBean.setConfiguration(configuration);
      return factoryBean;
    }
  }
}
EmployeesMapper
package com.diguage.truman.mybatis;

import org.apache.ibatis.annotations.Param;
import org.apache.ibatis.annotations.Select;

/**
 * @author D瓜哥, https://www.diguage.com/
 * @since 2020-05-29 17:23
 */
public interface EmployeesMapper {
  @Select("SELECT * FROM employees WHERE emp_no = #{id}")
  Employees getById(@Param("id") Integer id);
}
Employees
package com.diguage.truman.mybatis;

import java.util.Date;

/**
 * @author D瓜哥, https://www.diguage.com/
 * @since 2020-05-29 17:24
 */
public class Employees {
  Integer empNo;
  Date birthDate;
  String firstName;
  String lastName;
  String gender;
  Date hireDate;

  @Override
  public String toString() {
    return "Employees{" +
      "empNo=" + empNo +
      ", birthDate=" + birthDate +
      ", firstName='" + firstName + '\'' +
      ", lastName='" + lastName + '\'' +
      ", gender='" + gender + '\'' +
      ", hireDate=" + hireDate +
      '}';
  }
}

整个实例代码中,只有 @MapperScan(basePackages = "com.diguage.truman.mybatis") 这个注解和 MyBATIS 的配置相关,我们就从这里开始吧。

@MapperScan 处理

D瓜哥在 Spring 扩展点概览及实践:BeanDefinitionRegistryPostProcessor 中已经指出 ConfigurationClassPostProcessor 负责处理 @Configuration 注解。所以,可以直接去看这个类的代码。

ConfigurationClassPostProcessor 的处理流程都是在 processConfigBeanDefinitions(BeanDefinitionRegistry registry) 方法中完成的。在这个方法中,可以看到如下代码:

ConfigurationClassPostProcessor#processConfigBeanDefinitions
// Parse each @Configuration class
ConfigurationClassParser parser = new ConfigurationClassParser(
    this.metadataReaderFactory, this.problemReporter, this.environment,
    this.resourceLoader, this.componentScanBeanNameGenerator, registry);

Set<BeanDefinitionHolder> candidates = new LinkedHashSet<>(configCandidates);
Set<ConfigurationClass> alreadyParsed = new HashSet<>(configCandidates.size());
do {
  parser.parse(candidates);

parser.parse(candidates); 这行代码打一个断点,然后一步一步跟下去,就到了 ConfigurationClassParserdoProcessConfigurationClass 方法里:

ConfigurationClassParser#doProcessConfigurationClass
/**
 * Apply processing and build a complete {@link ConfigurationClass} by reading the
 * annotations, members and methods from the source class. This method can be called
 * multiple times as relevant sources are discovered.
 * @param configClass the configuration class being build
 * @param sourceClass a source class
 * @return the superclass, or {@code null} if none found or previously processed
 */
@Nullable
protected final SourceClass doProcessConfigurationClass(
    ConfigurationClass configClass, SourceClass sourceClass, Predicate<String> filter)
    throws IOException {

  //...此处省去 N 行代码

  // Process any @Import annotations
  processImports(configClass, sourceClass, getImports(sourceClass), filter, true);

请注意这里的 getImports(sourceClass),我们看一下这个方法:

/**
 * Returns {@code @Import} class, considering all meta-annotations.
 */
private Set<SourceClass> getImports(SourceClass sourceClass) throws IOException {
  Set<SourceClass> imports = new LinkedHashSet<>();
  Set<SourceClass> visited = new LinkedHashSet<>();
  collectImports(sourceClass, imports, visited);
  return imports;
}

/**
 * Recursively collect all declared {@code @Import} values. Unlike most
 * meta-annotations it is valid to have several {@code @Import}s declared with
 * different values; the usual process of returning values from the first
 * meta-annotation on a class is not sufficient.
 * <p>For example, it is common for a {@code @Configuration} class to declare direct
 * {@code @Import}s in addition to meta-imports originating from an {@code @Enable}
 * annotation.
 * @param sourceClass the class to search
 * @param imports the imports collected so far
 * @param visited used to track visited classes to prevent infinite recursion
 * @throws IOException if there is any problem reading metadata from the named class
 */
private void collectImports(SourceClass sourceClass, Set<SourceClass> imports, Set<SourceClass> visited)
    throws IOException {

  if (visited.add(sourceClass)) {
    for (SourceClass annotation : sourceClass.getAnnotations()) {
      String annName = annotation.getMetadata().getClassName();
      if (!annName.equals(Import.class.getName())) {
        collectImports(annotation, imports, visited);
      }
    }
    imports.addAll(sourceClass.getAnnotationAttributes(Import.class.getName(), "value"));
  }
}

String annName = annotation.getMetadata().getClassName(); 这行代码打断点,然后调试,注意观察 annName 变量的值,相信肯定可以看到 org.mybatis.spring.annotation.MapperScan,接着就可以看到,通过 sourceClass.getAnnotationAttributes(Import.class.getName(), "value") 解析 @Import 注解,把其中的 org.mybatis.spring.annotation.MapperScannerRegistrar 的相关信息(被封装成了 SourceClass 对象)加入到了 imports 变量中。

下面看一下是如何处理 MapperScannerRegistrar 的。

MapperScannerRegistrar

我们接着看 processImports 方法:

private void processImports(ConfigurationClass configClass, SourceClass currentSourceClass,
  Collection<SourceClass> importCandidates, Predicate<String> exclusionFilter,
  boolean checkForCircularImports) {

    //...此处省去 N 行代码
      else if (candidate.isAssignable(ImportBeanDefinitionRegistrar.class)) {
        // 很明显,会进入到这个分支
        // Candidate class is an ImportBeanDefinitionRegistrar ->
        // delegate to it to register additional bean definitions
        Class<?> candidateClass = candidate.loadClass();
        ImportBeanDefinitionRegistrar registrar =
            ParserStrategyUtils.instantiateClass(candidateClass, ImportBeanDefinitionRegistrar.class,
                this.environment, this.resourceLoader, this.registry);
                    // 创建一个实例,然后加入到 configClass 中
        configClass.addImportBeanDefinitionRegistrar(registrar, currentSourceClass.getMetadata());
    //...此处省去 N 行代码
}

接着,回到 processConfigBeanDefinitions 方法:

ConfigurationClassPostProcessor#processConfigBeanDefinitions
parser.parse(candidates);
parser.validate();

Set<ConfigurationClass> configClasses = new LinkedHashSet<>(parser.getConfigurationClasses());
configClasses.removeAll(alreadyParsed);

// Read the model and create bean definitions based on its content
if (this.reader == null) {
  this.reader = new ConfigurationClassBeanDefinitionReader(
      registry, this.sourceExtractor, this.resourceLoader, this.environment,
      this.importBeanNameGenerator, parser.getImportRegistry());
}
this.reader.loadBeanDefinitions(configClasses);

进入 this.reader.loadBeanDefinitions(configClasses); 方法:

ConfigurationClassBeanDefinitionReader#loadBeanDefinitions
/**
 * Read {@code configurationModel}, registering bean definitions
 * with the registry based on its contents.
 */
public void loadBeanDefinitions(Set<ConfigurationClass> configurationModel) {
  TrackedConditionEvaluator trackedConditionEvaluator = new TrackedConditionEvaluator();
  for (ConfigurationClass configClass : configurationModel) {
    loadBeanDefinitionsForConfigurationClass(configClass, trackedConditionEvaluator);
  }
}

/**
 * Read a particular {@link ConfigurationClass}, registering bean definitions
 * for the class itself and all of its {@link Bean} methods.
 */
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;
  }

  if (configClass.isImported()) {
    registerBeanDefinitionForImportedConfigurationClass(configClass);
  }
  for (BeanMethod beanMethod : configClass.getBeanMethods()) {
    loadBeanDefinitionsForBeanMethod(beanMethod);
  }

  loadBeanDefinitionsFromImportedResources(configClass.getImportedResources());
  loadBeanDefinitionsFromRegistrars(configClass.getImportBeanDefinitionRegistrars());
}

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

到这里就调用到了 MapperScannerRegistrarregisterBeanDefinitions 方法:

MapperScannerRegistrar#registerBeanDefinitions(AnnotationMetadata, BeanDefinitionRegistry)
/**
 * {@inheritDoc}
 */
@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
  AnnotationAttributes mapperScanAttrs = AnnotationAttributes
      .fromMap(importingClassMetadata.getAnnotationAttributes(MapperScan.class.getName()));
  if (mapperScanAttrs != null) {
    registerBeanDefinitions(importingClassMetadata, mapperScanAttrs, registry,
        generateBaseBeanName(importingClassMetadata, 0));
  }
}

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

  // 注意这行代码:
  BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(MapperScannerConfigurer.class);
  builder.addPropertyValue("processPropertyPlaceHolders", true);

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

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

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

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

}

其实只干了一件事情,就是在想容器中注册了一个类为 MapperScannerConfigurerBeanDefinition,在创建过程中,还把 @MapperScan 注解中的属性给添加到了 BeanDefinition 属性中。下面,来看看 MapperScannerConfigurer 是何方神圣。

MapperScannerConfigurer

先看一下 MapperScannerConfigurer 的类型定义:

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

结合上一篇文章 Spring 扩展点概览及实践:BeanDefinitionRegistryPostProcessor 中的介绍,可以知道 BeanDefinitionRegistryPostProcessor 也是 Spring 生命周期中的一环,将其注册到容器中,就可以通过对 postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) 来实现注册自定义 BeanDefinition 的功能。

来看看 postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) 的定义:

MapperScannerConfigurer#postProcessBeanDefinitionRegistry
@Override
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) {
  if (this.processPropertyPlaceHolders) {
    processPropertyPlaceHolders();
  }

  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();
  scanner.scan(
      StringUtils.tokenizeToStringArray(this.basePackage, ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS));
}

代码已经非常明确了,就是注册了一个 ClassPathMapperScanner,同事调用了 scanner.scan 方法。下面,来看一下 ClassPathMapperScanner

ClassPathMapperScanner

老规矩,先看看 ClassPathMapperScanner 的定义:

public class ClassPathMapperScanner extends ClassPathBeanDefinitionScanner {

  //...此处省去 N 行代码

  private Class<? extends MapperFactoryBean> mapperFactoryBeanClass = MapperFactoryBean.class;

  public ClassPathMapperScanner(BeanDefinitionRegistry registry) {
    super(registry, false);
  }

从这里可以看出,ClassPathMapperScanner 就是一个 ClassPathBeanDefinitionScanner,根据类名可以得知,扫描 class path 并生成 BeanDefinition。来看一下 scan(String…​ basePackages)

ClassPathBeanDefinitionScanner#scan
/**
 * Perform a scan within the specified base packages.
 * @param basePackages the packages to check for annotated classes
 * @return number of beans registered
 */
public int scan(String... basePackages) {
  int beanCountAtScanStart = this.registry.getBeanDefinitionCount();

  doScan(basePackages);

  // Register annotation config processors, if necessary.
  if (this.includeAnnotationConfig) {
    AnnotationConfigUtils.registerAnnotationConfigProcessors(this.registry);
  }

  return (this.registry.getBeanDefinitionCount() - beanCountAtScanStart);
}

这里把实际扫描工作委托给了 doScan(basePackages) 方法,而这个方法被 ClassPathMapperScanner 重写了,来看一下它的实现:

ClassPathMapperScanner#doScan
/**
 * Calls the parent search that will search and register all the candidates. Then the registered objects are post
 * processed to set them as MapperFactoryBeans
 */
@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;
}

实际的扫描工作还是由父类 super.doScan(basePackages) 完成,只是又对扫描结果做了进一步处理: processBeanDefinitions(beanDefinitions)

ClassPathMapperScanner#processBeanDefinitions
private void processBeanDefinitions(Set<BeanDefinitionHolder> beanDefinitions) {
  GenericBeanDefinition definition;
  for (BeanDefinitionHolder holder : beanDefinitions) {
    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
    // 注意这行代码
    definition.setBeanClass(this.mapperFactoryBeanClass);

    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) {
      LOGGER.debug(() -> "Enabling autowire by type for MapperFactoryBean with name '" + holder.getBeanName() + "'.");
      definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE);
    }
    definition.setLazyInit(lazyInitialization);
  }
}

这里特别需要注意的是 definition.setBeanClass(this.mapperFactoryBeanClass); 这行代码。为什么把扫描出来的 MapperBean Class 给设置成 mapperFactoryBeanClass 呢?通过上面的 ClassPathMapperScanner 类型定义可以知道,mapperFactoryBeanClass 就是 MapperFactoryBean

另外,还有一点值得思考,扫描出来的是接口,怎么生成对应的实例呢?带着这两个问题,来看一下 MapperFactoryBean

MapperFactoryBean

来看一下 MapperFactoryBean 的类型定义:

public class MapperFactoryBean<T> extends SqlSessionDaoSupport implements FactoryBean<T> {

  private Class<T> mapperInterface;

  private boolean addToConfig = true;

  public MapperFactoryBean() {
    // intentionally empty
  }

  public MapperFactoryBean(Class<T> mapperInterface) {
    this.mapperInterface = mapperInterface;
  }

  /**
   * {@inheritDoc}
   */
  @Override
  protected void checkDaoConfig() {
    super.checkDaoConfig();

    notNull(this.mapperInterface, "Property 'mapperInterface' is required");

    Configuration configuration = getSqlSession().getConfiguration();
    if (this.addToConfig && !configuration.hasMapper(this.mapperInterface)) {
      try {
        configuration.addMapper(this.mapperInterface);
      } catch (Exception e) {
        logger.error("Error while adding the mapper '" + this.mapperInterface + "' to configuration.", e);
        throw new IllegalArgumentException(e);
      } finally {
        ErrorContext.instance().reset();
      }
    }
  }

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

可以看出 MapperFactoryBean 是一个 FactoryBean,上一篇文章 Spring 扩展点概览及实践:FactoryBean 中提到,FactoryBean 就是专门生产 Bean 的工厂。

再看构造函数 public MapperFactoryBean(Class<T> mapperInterface),结合上一个片段代码中注意的地方可以看出,从 Class Path 扫描出来的 BeanDefinition,把扫描出来的接口设置为构造函数参数 definition.getConstructorArgumentValues().addGenericArgumentValue(beanClassName); 然后通过实例化 FactoryBean,然后调用 getObject() 就可以获得接口对应的实例对象。

实例化对象的过程是由 MyBATIS 完成的,以后单独开篇来介绍,这里不再多做介绍。

还有个疑问,MyBATIS 是怎么知道 Mapper 接口信息呢?这个问题就要看 checkDaoConfig() 方法了,单步调试代码可以知道父类 DaoSupport#afterPropertiesSet 调用的,在这个方法中,把 Mapper 接口信息条件到了 MyBATIS 中 configuration.addMapper(this.mapperInterface)

自此,MyBATIS 和 Spring 的整个流程就全部介绍完毕了。下面做个小节。

小节

本文从源码角度,深入绍了 MyBATIS 和 Spring 整合过程。整个过程中,用到了 Spring 的如下扩展点:

  1. @Import

  2. MapperScannerRegistrar - ImportBeanDefinitionRegistrar

  3. MapperScannerConfigurer - BeanDefinitionRegistryPostProcessor

  4. ClassPathMapperScanner - ClassPathBeanDefinitionScanner

  5. MapperFactoryBean - FactoryBean

  6. InitializingBean

可见,和 Spring 整合并不是只靠一个扩展点就可以完成的,需要多个扩展点多方配合才能更好地完成整合过程。

下一篇文章中,D瓜哥来介绍一下 Apache Dubbo 和 Spring 的整合过程。