Mybatis-Spring:@MapperScan注解的使用

引言

Demo: SpringBoot Mybatis的示例中,dao层接口使用了注解@MapperScan:指定扫描com.xuxd.demo.dao.UserDao所在包路径下的所有接口类。

本文分析下@MapperScan注解做了哪些动作。

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

  /**
   *缺省属性(==basePackages),basePackages的别名
   */
  String[] value() default {};

  /**
   * 哪些包路径下的接口被扫描注册(接口至少有一个方法),具体实现类(非接口)忽略
   */
  String[] basePackages() default {};

  /**
   * 指定类所在包下所有接口被扫描注册(接口至少有一个方法),具体实现类(非接口)忽略
   */
  Class<?>[] basePackageClasses() default {};

  /**
   * 扫描到的满足条件的接口,首先要把它们相关bean定义注册到spring容器中吧,注册bean定义
   * 的时候,需要定义bean名称,这个是用来自定方生成bean名称的策略组件,个人觉得很少用
   */
  Class<? extends BeanNameGenerator> nameGenerator() default BeanNameGenerator.class;

  /**
   * 这个注解指定的接口也要被扫描
   */
  Class<? extends Annotation> annotationClass() default Annotation.class;

  /**
   * 继承这个接口的接口也要被扫描
   */
  Class<?> markerInterface() default Class.class;

  /**
   * 多数据源的时候可能用到这个,后面单独说明这个
   */
  String sqlSessionTemplateRef() default "";

  /**
   * 多数据源的时候可能用到这个,后面单独说明这个
   */
  String sqlSessionFactoryRef() default "";

  /**
   * 多数据源的时候可能用到这个,后面单独说明这个
   */
  Class<? extends MapperFactoryBean> factoryBean() default MapperFactoryBean.class;

}

这个注解的重点是@Import(MapperScannerRegistrar.class),使用这个注解导入MapperScannerRegistrar主要完成两件事:

1. 扫描指定接口

2. 注册这些接口的bean定义到spring容器

接下来进入MapperScannerRegistrar类看下是如何完成这两动作:

MapperScannerRegistrar.class
public class MapperScannerRegistrar implements ImportBeanDefinitionRegistrar, ResourceLoaderAware {}

这个类实现了ImportBeanDefinitionRegistrar接口:

public interface ImportBeanDefinitionRegistrar {

	public void registerBeanDefinitions(
			AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry);

}

@MapperScan注解类上使用了@Import注解导入了这个接口的实现类(MapperScannerRegistrar.class),因此spring解析MybatisConfig(源码:Demo: SpringBoot Mybatis)这个类的时候,解析到这个类上使用了注解@MapperScan,从MapperScan注解类上(注解都是一个接口,java会创建代理类)发现了@Import注解及MapperScannerRegistrar类(因为Import注解是导入配置类的)。在加载MybatisConfig配置类的bean定义时候,找到了ImportBeanDefinitionRegistrar 的实现类MapperScannerRegistrar,便会回调这个MapperScannerRegistrar的registerBeanDefinitions方法。

总之一句话,在加载配置类MybatisConfig的bean定义的时候,会调用与之看起来有点关系的MapperScannerRegistrar的registerBeanDefinitions方法。

MapperScannerRegistrar的registerBeanDefinitions方法第一个参数importingClassMetadata指的是MybatisConfig这个类的。

FBECD51F-EA9D-2F3D-3243-D3D484FBF0E4.png

可以debug,看这个参数的信息。

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

看这个方法的源码,主要完成2件事:

1. 解析MapperScan注解的各个字段的值 ,用以初始化类路径扫描器

2. 确定扫描类路径下哪些接口,如指定的包路径、指定的类所在包路径。上面倒数第2行代码,注册过滤器,用来指定包含哪些注解或接口的扫描(@MapperScan的annotationClass的markerInterface属性,如果设置的话)

因此,重点是最后一行代码doScan的调用。

这里不贴源码了,前文提到,MapperScannerRegistrar主要完成两件事,都会在这里完成,解析包路径,扫描指定接口并注册bean定义到spring容器。

definition.getConstructorArgumentValues().addGenericArgumentValue(definition.getBeanClassName()); // issue #59
      definition.setBeanClass(this.mapperFactoryBean.getClass());

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

在ClassPathMapperScanner类的processBeanDefinitions方法内看到这里注册的一个spring的工厂bean:

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

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

  @Override
  public Class<T> getObjectType() {
    return this.mapperInterface;
  }
 
  @Override
  public boolean isSingleton() {
    return true;
  }
...
}

大部分代码删除了, 只留下这几个说明。

不了解 spring的FactoryBean的建议查看下相关文档。

这里用直白的话说,就是:我在service层需要注入这个Dao层接口的bean(比如Demo: SpringBoot Mybatis中UserServiceImpl类的UserDao字段的自动注入),依据类型注入。spring在自己的容器里翻呀翻,如果是普通bean,一看和这个接口类型(UserDao)都不匹配就换一个,找到了这个工厂bean,一看是工厂bean,就不能直接做类型匹配了,而是调用getObjectType方法,把返回的类型和需要被注入字段的类型一比较,正好匹配(都是UserDao类型),就调用这个工厂bean的getObject方法返回这个对象,然后通过反射等操作,把这个值注入到这个字段中。而调用getObject方法,其实就是我们平常直接用mybatis的接口返回的一个MapperProxy的代理对象的操作了。

收藏 (0)
评论列表
正在载入评论列表...
我是有底线的
为您推荐
    暂时没有数据