一、@Mapper注解

在springboot+mybatis的工程中,如果不做特殊配置,mybatis会查找有@Mapper的接口创建其代理对象添加到spring容器中,接下来就来分析下这个是如何实现的。

关键点就在MybatisAutoConfiguration这个自动配置类中

public class MybatisAutoConfiguration {
  //这个配置会在没有进行其他dao接口扫描的配置时生效,重点在导入的AutoConfiguredMapperScannerRegistrar
  //它是这个类中的一个内部类
  @Configuration
  @Import({ AutoConfiguredMapperScannerRegistrar.class })
  @ConditionalOnMissingBean(MapperFactoryBean.class)
  public static class MapperScannerRegistrarNotFoundConfiguration {

    @PostConstruct
    public void afterPropertiesSet() {
      log.debug(String.format("No %s found.", MapperFactoryBean.class.getName()));
    }
  }
  
  //使用默认配置时,把这个类加入spring容器中,它会自动查找有Mapper注解的接口
  public static class AutoConfiguredMapperScannerRegistrar
      implements BeanFactoryAware, ImportBeanDefinitionRegistrar, ResourceLoaderAware {

    private BeanFactory beanFactory;

    private ResourceLoader resourceLoader;

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

      log.debug("Searching for mappers annotated with @Mapper'");
	  //这个scanner是mybatis继承spring的ClassPathBeanDefinitionScanner后实现的扫描
      //dao接口的扫描器
      ClassPathMapperScanner scanner = new ClassPathMapperScanner(registry);

      try {
        if (this.resourceLoader != null) {
          scanner.setResourceLoader(this.resourceLoader);
        }

        List<String> pkgs = AutoConfigurationPackages.get(this.beanFactory);
        for (String pkg : pkgs) {
          log.debug("Using auto-configuration base package '" + pkg + "'");
        }
	   //这里指定扫描Mapper注解
        scanner.setAnnotationClass(Mapper.class);
        scanner.registerFilters();
        scanner.doScan(StringUtils.toStringArray(pkgs));
      } catch (IllegalStateException ex) {
        log.debug("Could not determine auto-configuration " + "package, automatic mapper scanning disabled.");
      }
    }

}

可以看到是通过ClassPathMapperScanner来扫描有Mapper注解的接口,具体的扫描原理后续会介绍

二、@MapperScan注解

在springboot工程中也可以通过MapperScan注解来指定dao接口的扫描路径,我们来分析下这个注解

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Import(MapperScannerRegistrar.class)
public @interface MapperScan {
    ...省略其他
}

这个注解上导入了一个MapperScannerRegistrar类,此类用来根据配置的注解属性进行扫描。

接下来分析下这个类

public class MapperScannerRegistrar implements ImportBeanDefinitionRegistrar, ResourceLoaderAware {
  //这个方法是具体的扫描逻辑,总的来说就是根据配置的MapperScan注解属性,使用
  //ClassPathMapperScanner注解来扫描classpath路径下满足条件的接口创建代理对象
  @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();
    //执行sql
    scanner.doScan(StringUtils.toStringArray(basePackages));
  }
    
}

三、ClassPathMapperScanner

接下来分析下ClassPathMapperScanner的源码,它继承自spring的ClassPathBeanDefinitionScanner,spring这个scanner扫描时会跳过接口,mybatis继承这个scanner并扩展了它的扫描逻辑

public class ClassPathMapperScanner extends ClassPathBeanDefinitionScanner {
  @Override
  public Set<BeanDefinitionHolder> doScan(String... basePackages) {
    //调用spring的扫描逻辑先把所有符合条件的class都扫描出来
    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 {
      //编辑扫描到的bean定义信息
      processBeanDefinitions(beanDefinitions);
    }

    return beanDefinitions;
  }
  // 编辑bean定义信息,用MapperFactoryBean来替换原来的dao接口
  private void processBeanDefinitions(Set<BeanDefinitionHolder> beanDefinitions) {
    GenericBeanDefinition definition;
    //循环bean定义信息
    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.getBeanClassName()获取到的是dao接口的class,底下这句是把dao接口的class对象
     // 作为了创建这个bean时构造方法的参数,后续会把bean定义的class替换成MapperFactoryBean,
     // 所以也就是创建MapperFactoryBean对象时会传递dao接口的class作为构造方法参数
        definition.getConstructorArgumentValues().addGenericArgumentValue(definition.getBeanClassName()); // issue #59
        
      //替换bean定义的class为MapperFactoryBean
      definition.setBeanClass(this.mapperFactoryBean.getClass());
      //以下这些都是在设置bean定义的一些属性
      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() + "'.");
        }
        //设置这个bean中属性的注入模式为根据类型自动注入,这样当创建这个bean时就会自动给其中的属性
        //赋值
        definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE);
      }
    }
  }
}

所以总的来讲,ClassPathMapperScanner就是把spring扫描到的bean定义信息中的class替换成了MapperFactoryBean ,真正的dao接口代理对象是由它创建的。而它必然实现了spring的FactoryBean 接口

四、 MapperFactoryBean

dao接口的代理对象最终是由这个类创建出来的,它实现了spring的FactoryBean 接口,在其getObject方法中会调用

sqlsession来创建出代理对象

public class MapperFactoryBean<T> extends SqlSessionDaoSupport implements FactoryBean<T> {
  //构造方法传进来的参数是dao接口的class对象,最终会创建它的代理对象
  public MapperFactoryBean(Class<T> mapperInterface) {
    this.mapperInterface = mapperInterface;
  }
    
  @Override
  public T getObject() throws Exception {
    // 通过sqlsession来创建代理对象,sqlsession是父类中的属性
    return getSqlSession().getMapper(this.mapperInterface);
  }
}