通过@MapperScan源码了解Spring自定义注解扫描器

我们在使用springboot 整合MyBatis时,需要在启动类上添加上@MapperScan注解,并写入mapper接口的包路径,然后我们就能通过从spring IOC容器中取对应的mapper的Bean来进行持久化操作了,那么@MapperScan是如何将mapper接口实例化并注入到Spring IOC容器中的呢?

首先搭建一个spring boot项目,引入mybatis和mysql的相关maven包。在application.properties中配置好连接参数。这些基操我就不写了
新建mapper,当然我这里取名取成dao了,意思到了就行。

package com.p6spy.demop6spy.dao;

import org.apache.ibatis.annotations.Select;
public interface TestDao {
   
   
    @Select("select count(*) from subject_sae_detail")
    String getInfo();
}

启动类上添加注解,指定扫描com.p6spy.demop6spy.dao

@SpringBootApplication
@MapperScan({
   
   "com.p6spy.demop6spy.dao"})
public class Demop6spyApplication {
   
   

    public static void main(String[] args) {
   
   
        SpringApplication.run(Demop6spyApplication.class, args);
    }

}

就能在service实现类中注入TestDao 来进行持久化操作了。

一.@MapperScan

首先让我们来看下@MapperScan注解源码(这里为了清晰的了解扫描注入过程,所以我只选取了部分代码,其余的删除了)。

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

  String[] value() default {
   
   };

  String[] basePackages() default {
   
   };

  Class<? extends Annotation> annotationClass() default Annotation.class;

  Class<?> markerInterface() default Class.class;

  Class<? extends MapperFactoryBean> factoryBean() default MapperFactoryBean.class;
}
1.首先是@Import(MapperScannerRegistrar.class),从名字上看是一个扫描注册器,点击去看下,发现其继承于ImportBeanDefinitionRegistrar

主要作用是:

ImportBeanDefinitionRegistrar接口的作用是当这个接口的实现类(类A)被@Import接口引入某个被标记了@Configuration的注册类(类B)时,可以得到这个类(类B)的所有注解,然后做一些动态注册Bean的事儿。

我们看下@MapperScan,这也没有@Configuration啊,别急,因为@MapperScan是被启动类引用的,所以还要去看下启动类,虽然启动类看上去是只有@SpringBootApplication@MapperScan两个注解,实际上是这样的。
@SpringBootApplication如下

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(excludeFilters = {
   
    @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
		@Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })

@SpringBootConfiguration如下

@Target({
   
   ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Configuration
public @interface SpringBootConfiguration {
   
   
}

找到@Configuration了。

2 . valuebasePackages
	String[] value() default {
   
   };
  	String[] basePackages() default {
   
   };

这两行应该比较好辨认,就是需要我们填写的mapper接口所在的包路径信息。
就像这样写,后面我们会发现这俩填哪个都行,结果是一样的。
在这里插入图片描述
或者这样,这样写的话默认是赋值给value
在这里插入图片描述

3.annotationClass,在这里的作用是:配置了该注解的mapper才会被扫描器扫描,与basePackage是与的作用。默认值是Annotation.class
Class<? extends Annotation> annotationClass() default Annotation.class;
4.markerInterface,在这里的作用是:基于接口的过滤器,实现了该接口的mapper才会被扫描器扫描,与basePackage是与的作用。默认值是Class.class
Class<?> markerInterface() default Class.class;
5.构造工厂
Class<? extends MapperFactoryBean> factoryBean() default MapperFactoryBean.class;

二.MapperScannerRegistrar

在一中我们知道了实现ImportBeanDefinitionRegistrar的大致作用。

public class MapperScannerRegistrar implements ImportBeanDefinitionRegistrar, ResourceLoaderAware {
   
   

  private ResourceLoader resourceLoader;

  /**
   * {@inheritDoc}
   */
  @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 
<think>好的,用户想了解@MapperScan源码解析。首先,我需要确认这个注解的上下文。@MapperScanMyBatis框架中常用的注解,特别是在Spring集成中,比如MyBatis-Spring项目。它的主要作用是指定Mapper接口的扫描路径,让Spring容器能够自动注册这些接口为Bean。用户可能已经知道它的基本用法,但想深入理解其背后的实现机制。 接下来,我需要回忆MyBatisSpring整合的基本原理。通常,Spring通过扫描组件来注册Bean,但Mapper接口是接口,没有实现类,所以常规的@ComponentScan无法处理。这时候@MapperScan就派上用场了,它利用了MyBatisMapperScannerConfigurer或者通过自定义扫描逻辑来生成代理类。 然后,我需要考虑用户可能的真实需求。用户可能是遇到了配置问题,或者想自定义扫描行为,了解源码可以帮助他们解决问题或进行扩展。也有可能用户在面试或学习中需要深入理解框架的工作原理。 接下来,我需要解析@MapperScan源码结构。首先查看注解的定义,通常包括属性如basePackages、value、annotationClass等。然后看这个注解是如何被处理的,可能通过@Import引入了一个配置类,比如MapperScannerRegistrar,该类实现了ImportBeanDefinitionRegistrar接口,动态注册Bean定义。 然后,关键点在于MapperScannerConfigurer,这个类负责配置扫描器,设置基础包、注解过滤器等。扫描器会使用ClassPathMapperScanner,继承自Spring的ClassPathBeanDefinitionScanner,但重写了某些方法以适应MyBatis的特定需求,比如检查是否为接口,并生成对应的Bean定义,使用MapperFactoryBean来创建代理对象。 需要详细说明扫描过程,如何将接口转换为BeanDefinition,以及如何利用MyBatis的SqlSessionTemplate来创建代理。可能还需要提到如何处理多个数据源的情况,或者自定义注解的情况。 还要注意可能的异常情况,比如没有找到Mapper接口时的处理,或者重复扫描的问题。同时,可以提到@MapperScan和@Mapper的区别,前者是批量扫描,后者是单个声明。 最后,总结整个流程,从注解的导入到Bean定义的注册,再到代理对象的生成,确保用户能理解整个链路。用户可能需要了解如何自定义扫描器,或者解决扫描不到Mapper的问题,因此源码解析能帮助他们定位问题。</think>以下是对`@MapperScan`的源码解析(基于MyBatis-Spring 3.0+版本): --- ### 一、@MapperScan 核心作用 用于批量扫描MyBatis Mapper接口,通过`MapperScannerConfigurer`生成BeanDefinition并注册到Spring容器中,最终生成Mapper接口的代理对象。 --- ### 二、源码执行流程 #### 1. 注解定义 ```java @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.TYPE) @Documented @Import(MapperScannerRegistrar.class) // 关键点 public @interface MapperScan { String[] value() default {}; String[] basePackages() default {}; Class<?>[] basePackageClasses() default {}; // ...其他属性 } ``` #### 2. 核心类 MapperScannerRegistrar 实现`ImportBeanDefinitionRegistrar`接口,负责动态注册Bean: ```java public class MapperScannerRegistrar implements ImportBeanDefinitionRegistrar { @Override public void registerBeanDefinitions(...) { // 解析@MapperScan注解属性 AnnotationAttributes mapperScanAttrs = AnnotationAttributes.fromMap(...); // 创建扫描器配置Bean BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(MapperScannerConfigurer.class); // 设置扫描参数 builder.addPropertyValue("processPropertyPlaceHolders", true); builder.addPropertyValue("annotationClass", Mapper.class); builder.addPropertyValue("basePackage", StringUtils.arrayToCommaDelimitedString(basePackages)); // 注册BeanDefinition registry.registerBeanDefinition(beanName, builder.getBeanDefinition()); } } ``` --- ### 三、关键组件解析 #### 1. MapperScannerConfigurer 继承自`BeanDefinitionRegistryPostProcessor`,负责配置扫描器: ```java public class MapperScannerConfigurer implements BeanDefinitionRegistryPostProcessor { public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) { ClassPathMapperScanner scanner = new ClassPathMapperScanner(registry); scanner.registerFilters(); scanner.scan(StringUtils.toStringArray(this.basePackage)); } } ``` #### 2. ClassPathMapperScanner 自定义扫描器(继承自Spring的`ClassPathBeanDefinitionScanner`): ```java public class ClassPathMapperScanner extends ClassPathBeanDefinitionScanner { @Override public Set<BeanDefinitionHolder> doScan(String... basePackages) { // 调用父类扫描 Set<BeanDefinitionHolder> beanDefinitions = super.doScan(basePackages); // 特殊处理:将接口转换为MapperFactoryBean for (BeanDefinitionHolder holder : beanDefinitions) { GenericBeanDefinition definition = (GenericBeanDefinition) holder.getBeanDefinition(); definition.getConstructorArgumentValues().addGenericArgumentValue(definition.getBeanClassName()); definition.setBeanClass(MapperFactoryBean.class); // 关键替换 } return beanDefinitions; } } ``` --- ### 四、代理对象生成流程 1. **MapperFactoryBean**:继承自`SqlSessionDaoSupport` 2. **getObject()** 方法: ```java public T getObject() throws Exception { return getSqlSession().getMapper(this.mapperInterface); // 通过SqlSessionTemplate获取Mapper } ``` 3. **SqlSessionTemplate**:内部通过`MapperProxy`动态代理生成接口实现类 --- ### 五、配置关键参数 通过`@MapperScan`可配置: ```java @MapperScan( basePackages = "com.example.mapper", sqlSessionTemplateRef = "sqlSessionTemplate", annotationClass = Repository.class, markerInterface = BaseMapper.class ) ``` --- ### 六、常见问题 1. **为什么Mapper接口不需要实现类?** 动态代理生成`MapperProxy`对象,通过方法名与XML/sql注解绑定 2. **多数据源如何配置?** 需配合`@Qualifier`指定`sqlSessionTemplateRef` 3. **扫描过滤机制** 默认扫描所有接口,可通过`annotationClass`/`markerInterface`过滤 --- 通过以上源码分析,可以更清晰地定位`@MapperScan`相关的配置问题和扩展需求。
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

团子ing

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值