@MapperScan 和 @Mapper 源码解读

一.从开发中遇到的问题开始


问题描述 : 在一个springboot+mybatis的项目中,在dao也就是Mapper接口上配置了@Mapper注解,其他同事在启动类还配置了@MapperScan注解(包扫描没有配全面),进行批量指定所生成的Mapper接口动态代理接口类,所以开始的时候没有在@MapperScan直接我新建的dao包,就报错,但是有@Mapper注解。

No qualifying bean of type 'com.xxx.mapper.xxxMapper' 
available: expected at least 1 bean which qualifies as autowire 
candidate. Dependency annotations:{@org.springframework.beans
.factory.annotation.Autowired(required=true)}

1、只使用@Mapper注解,不使用@MapperScan注解。会扫描@Mapper注解所在接口,生成动态代理类,注入到Spring容器中。

2、只使用@MapperScan注解,不使用@Mapper注解。会扫描@MapperScan注解配置的包下面的接口生成动态代理类,注入到Spring容器中。

3、@Mapper、@MapperScan注解都使用,@Mapper接口,在@MapperScan注解中有配置包路径,那么可以正常使用。

4、@Mapper、@MapperScan注解都使用,@Mapper接口,在@MapperScan注解中没有配置包路径,那么会报错,解决办法,就是在@MapperScan注解中配置正确路径下的包即可。

二.@Mapper、@MapperScan注解解释


@Mapperscan:标注在 springboot 的启动类上面,配置 basePackages 属性,可以去扫描指定路径下的接口扫描为 Mapper 接口。

@Mapper:标注在接口上,表明这是一个 Mapper 接口。

工作原理:两者都使普通接口转为 mapper 接口,也即是把接口的beanClass设置为mapperFactoryBean

三.源码分析


1.@MapperScan注解扫描分析
可以发现 @Mapperscan注解类中包含有注解@Import(MapperScannerRegistrar.class)


MapperScannerRegistrar实现了ImportBeanDefinitionRegistrar ,那么在启动时spring容器会调用并执行 registerBeanDefinitions() 方法,扫描对应路径上(@MapperScan注解上带的basepackages)的类扫描到spring容器中.

在这个方法中重点看这行代码:


代码中会实例化 MapperScannerConfigurer,这个类实现了BeanDefinitionRegistryPostProcessor,会容器启动时调用postProcessBeanDefinitionRegistry() 方法,在这方法中设置了接口的 beanClass 。


ClassPathMapperScanner 重写了doScan方法,主要是扫描路径,并将扫描的信息转为beanDefinition,设置其为MapperFactoryBean,最终将扫描好并封装为MapperFactoryBean的类加入到ioc容器中


mapperFactoryBean重写了getObejct()方法。


跟踪 getObject() 方法,发现最终实例化接口的代码如下:


也即是我们写 mapper 接口,然后 mybatis 为我们生成一个 MapperProxy 对象去实现 mapper 接口。

1.1@MapperSca总结
@MapperScan 实际做的事情:
1.扫描指定路径,并将路径下的信息记录为BeanDefinition;
2.将获取的BeanDefinition,设置为MapperFactoryBean,注入IOC;

2.@Mapper注解扫描分析
@Mapper 注解是在 mybatis加载时候起作用的,在 MybatisAutoConfiguration 中:

如果当前IOC容器没有MapperFactoryBean.class, MapperScannerConfigurer.class这两个bean,则会执行如下代码

由该类的头部注解可知,在 spring 上下文中没有 MapperScannerConfigurer 实例时候会进行对@Mapper注解类的初始化。由此可知 @Mapper和@Mapperscan只有一个起作用,而 @Mapperscan 优先级较高。因此当 @Mapperscan 不存在时候,MybatisAutoConfiguration 该类的头部注解@import,会实例化 AutoConfiguredMapperScannerRegistrar ,这个类 会调用registerBeanDefinitions方法,将MapperScannerConfigurer这个类注入到了IOC容器中。

可以看到,和之前@MapperScan对应上了,都是注册了MapperScannerConfigurer,也就是两种注解方式都是通过MapperScannerConfigurer扫描mapper注册的

下面我们仔细翻一下源码,看下MapperScannerConfigurer到底怎么处理@MapperScan和@Mapper

registerFilters方法指定了是走@Mapper注解扫描,还是@MapperScan包扫描

 public void registerFilters() {
  boolean acceptAllInterfaces = true;
 
  // 如果指定了扫描类型(@Mapper走这里)
  // annotationClass在前面的AutoConfiguredMapperScannerRegistrar#registerBeanDefinitions被注入
  // 就是这段builder.addPropertyValue("annotationClass", Mapper.class);
  if (this.annotationClass != null) {
    addIncludeFilter(new AnnotationTypeFilter(this.annotationClass));
    acceptAllInterfaces = false;
  }
 
  ......
  // 如果没指定扫描类型,则扫描全部(@MapperScan走这里)
  if (acceptAllInterfaces) {
    addIncludeFilter((metadataReader, metadataReaderFactory) -> true);
  }
 
  // exclude package-info.java
  addExcludeFilter((metadataReader, metadataReaderFactory) -> {
    String className = metadataReader.getClassMetadata().getClassName();
    return className.endsWith("package-info");
  });
}


四.@MapperScan和@Mapper的使用建议


mapper接口所在包比较集中式可以在启动类上加@MapperScan注解指定mapper接口所在的包路径。

如果mapper接口所在的包路径比较分散(多部门,多人开发),建议直接在接口上加@Mapper注解,去掉启动类上的@MapperScan包扫描

你可能感兴趣的:(java后端,Mybatis,SpringBoot,java,spring,boot,spring,mybatis)