Skip to content

MyBatis 之 @Mapper 扫描和 实例化到 Spring

从 Spring-Boot 自动配置类可以看出,是用 MapperScannerConfigurer 扫描 @Mapper 并在 MyBatis 中生成代理对象,然后注册到 Spring 容器。

MapperScannerConfigurer

可以看出,是一个 BeanDefinitionRegistryPostProcessor,那么它肯定是用来扫描类,最终注册 bean 到 Spring 容器的。

java
public class MapperScannerConfigurer
    implements BeanDefinitionRegistryPostProcessor, InitializingBean, ApplicationContextAware, BeanNameAware {
    @Override
    public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) {
        if (this.processPropertyPlaceHolders) {
            processPropertyPlaceHolders();
        }
        // 这个类的用法可以参考 Spring-Boot 中的博客:ClassPathBeanDefinitionScanner 自定义注解扫描器
        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));
        }
        if (StringUtils.hasText(defaultScope)) {
            scanner.setDefaultScope(defaultScope);
        }
        /**
         * 这个扫描器注册了拦截 @Mapper 注解的拦截器。
         * 注解 @Mapper 类是在 AutoConfiguredMapperScannerRegistrar 类中设置的,然后传到这里面的。
         */
        scanner.registerFilters();
        /**
         * 扫描所有 @Mapper 接口,并注册为 Spring 中的 BeanDefinition
         */
        scanner.scan(
                StringUtils.tokenizeToStringArray(this.basePackage, ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS));
    }
}

ClassPathMapperScanner.scan()

java
@Override
public Set<BeanDefinitionHolder> doScan(String... basePackages) {
    // Spring 的 ClassPathBeanDefinitionScanner.doScan()
    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 {
        // 扫描到 Mapper 接口的 BeanDefinition 后,在这里做进一步处理
        processBeanDefinitions(beanDefinitions);
    }

    return beanDefinitions;
}

processBeanDefinitions(beanDefinitions);

this.mapperFactoryBeanClass = org.mybatis.spring.mapper.MapperFactoryBean

所以,每个 Mapper 接口的 BeanDefinition 实际上是 MapperFactoryBean 类型的,而 MapperFactoryBean 实现了 FactoryBean, 因此,生成 Mapper 接口的代理对象的生成实际上主要是 MapperFactoryBean.getObject(); 方法。

有关 Spring 中的 FactoryBean 可以参考文章: Spring-Boot 之 FactoryBean

java
private void processBeanDefinitions(Set<BeanDefinitionHolder> beanDefinitions) {
    AbstractBeanDefinition definition;
    BeanDefinitionRegistry registry = getRegistry();
    for (BeanDefinitionHolder holder : beanDefinitions) {
      definition = (AbstractBeanDefinition) holder.getBeanDefinition();
      boolean scopedProxy = false;
      if (ScopedProxyFactoryBean.class.getName().equals(definition.getBeanClassName())) {
        definition = (AbstractBeanDefinition) Optional
            .ofNullable(((RootBeanDefinition) definition).getDecoratedDefinition())
            .map(BeanDefinitionHolder::getBeanDefinition).orElseThrow(() -> new IllegalStateException(
                "The target bean definition of scoped proxy bean not found. Root bean definition[" + holder + "]"));
        scopedProxy = true;
      }
      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

      /**
       * this.mapperFactoryBeanClass = org.mybatis.spring.mapper.MapperFactoryBean
       * 所以,每个 Mapper 接口的 BeanDefinition 实际上是 MapperFactoryBean 类型的,
       * 而 MapperFactoryBean 实现了 FactoryBean,因此,生成 Mapper 接口的代理对象实际上主要是 MapperFactoryBean.getObject(); 方法。
       */
      definition.setBeanClass(this.mapperFactoryBeanClass);

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

      // Attribute for MockitoPostProcessor
      // https://github.com/mybatis/spring-boot-starter/issues/475
      definition.setAttribute(FACTORY_BEAN_OBJECT_TYPE, beanClassName);

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

      if (scopedProxy) {
        continue;
      }

      if (ConfigurableBeanFactory.SCOPE_SINGLETON.equals(definition.getScope()) && defaultScope != null) {
        definition.setScope(defaultScope);
      }

      if (!definition.isSingleton()) {
        BeanDefinitionHolder proxyHolder = ScopedProxyUtils.createScopedProxy(holder, registry, true);
        if (registry.containsBeanDefinition(proxyHolder.getBeanName())) {
          registry.removeBeanDefinition(proxyHolder.getBeanName());
        }
        registry.registerBeanDefinition(proxyHolder.getBeanName(), proxyHolder.getBeanDefinition());
      }

    }
  }

MapperFactoryBean.getObject();

在这里,就看到了我们熟悉的 MyBatis 中的 SqlSession 对象了,这里就通过 SqlSession 生成 Mapper 接口的代理对象,最后放到 Spring 容器中。

java
public class MapperFactoryBean<T> extends SqlSessionDaoSupport implements FactoryBean<T> {
    @Override
    public T getObject() throws Exception {
        return getSqlSession().getMapper(this.mapperInterface);
    }
}

那么,getSqlSession().getMapper(this.mapperInterface) 是如何生成代理对象的呢?接着看。

getMapper

java
public class SqlSessionTemplate implements SqlSession, DisposableBean {
  @Override
  public <T> T getMapper(Class<T> type) {
    return getConfiguration().getMapper(type, this);
  }
}

public class Configuration {
    public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
        return mapperRegistry.getMapper(type, sqlSession);
    }
}

public class MapperRegistry {
    public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
        // 可以看到,从 MyBatis 中的 knownMappers 容器中,根据 Mapper.class 获取到一个 MapperProxyFactory 对象。
        // 为什么这里可以拿到 MapperProxyFactory?因为在建立 MyBatis 映射关系时,放进去的。
        final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type);
        if (mapperProxyFactory == null) {
            throw new BindingException("Type " + type + " is not known to the MapperRegistry.");
        }
        try {
            // 在这里,创建了 Mapper 接口的代理对象。实际上全都是 MapperProxy 类型的
            return mapperProxyFactory.newInstance(sqlSession);
        } catch (Exception e) {
            throw new BindingException("Error getting mapper instance. Cause: " + e, e);
        }
    }
}

public class MapperProxyFactory<T> {
    protected T newInstance(MapperProxy<T> mapperProxy) {
        return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
    }

    public T newInstance(SqlSession sqlSession) {
        /**
         * 调到这里,创建了一个 MapperProxy 对象, MapperProxy<T> implements InvocationHandler,因此,它是一个动态代理的增强器,
         * 当方法调用 Mapper 接口中的方法时,就会调用增强器 MapperProxy 的 invoke 方法。
         */
        final MapperProxy<T> mapperProxy = new MapperProxy<>(sqlSession, mapperInterface, methodCache);
        // 这里创建了 Mapper 接口的代理对象。
        return newInstance(mapperProxy);
    }
}

至此,应用程序启动完成,所有 MyBatis 生成的的 @Mapper 接口的代理对象都被 Spring 容器所管理。