MyBatis入门系列(25) -MyBatis-Spring核心之@MapperScan使用及源码分析

2023-04-26 07:58:37

注册映射器

在使用Mybatis中,我们需要将Mapper接口注册到Spring中,这叫注册映射器。

注册映射器的方法根据你的配置方法,即经典的 XML 配置或新的 3.0 以上版本的 Java 配置(也就是常说的 @Configuration),而有所不同。

1. 使用 XML 配置

在你的 XML 中加入 MapperFactoryBean 以便将映射器注册到 Spring 中。就像下面一样:

<bean id="userMapper" class="org.mybatis.spring.mapper.MapperFactoryBean">
  <property name="mapperInterface" value="org.mybatis.spring.sample.mapper.UserMapper" />
  <property name="sqlSessionFactory" ref="sqlSessionFactory" />
</bean>

如果映射器接口 UserMapper 在相同的类路径下有对应的 MyBatis XML 映射器配置文件,将会被 MapperFactoryBean 自动解析。不需要在 MyBatis 配置文件中显式配置映射器,除非映射器配置文件与接口类不在同一个类路径下。 参考 SqlSessionFactoryBean 的 configLocation 属性以获取更多信息。

注意 MapperFactoryBean 需要配置一个 SqlSessionFactory 或 SqlSessionTemplate。它们可以分别通过 sqlSessionFactory 和 sqlSessionTemplate 属性来进行设置。 如果两者都被设置,SqlSessionFactory 将被忽略。由于 SqlSessionTemplate 已经设置了一个 session 工厂,MapperFactoryBean 将使用那个工厂。

2. 使用Java 配置

@Configuration
public class MyBatisConfig {
  @Bean
  public MapperFactoryBean<UserMapper> userMapper() throws Exception {
    MapperFactoryBean<UserMapper> factoryBean = new MapperFactoryBean<>(UserMapper.class);
    factoryBean.setSqlSessionFactory(sqlSessionFactory());
    return factoryBean;
  }
}

3. 使用@MapperScan

不需要一个个地注册你的所有映射器。你可以让 MyBatis-Spring 对类路径进行扫描来发现它们。

有几种办法来发现映射器:

  • 使用 < mybatis:scan/> 元素
  • 使用 @MapperScan 注解
  • 在经典 Spring XML 配置文件中注册一个 MapperScannerConfigurer

< mybatis:scan/> 和 @MapperScan 都在 MyBatis-Spring 1.2.0 中被引入。@MapperScan 需要你使用 Spring 3.1+。

当你正在使用 Spring 的基于 Java 的配置时(也就是 @Configuration),相比于使用 < mybatis:scan/>,你会更喜欢用 @MapperScan。

@MapperScan 注解的使用方法如下:

@Configuration
@MapperScan("org.mybatis.spring.sample.mapper")
public class AppConfig {
  // ...
}

核心源码解读

@MapperScan

@MapperScan是MyBatis-Spring提供的注解,其源码及属性说明如下

@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE})
@Documented
@Import({MapperScannerRegistrar.class})
@Repeatable(MapperScans.class)
public @interface MapperScan {
	// basePackages属性的别名。允许更简洁的注释声明,例如:@MapperScan(“org.my.pkg”)
    String[] value() default {};
	// 基于包下面的扫描MyBatis的接口。注意是,只有是接口的将会被扫描注册,如果是具体的类将会被忽略。
    String[] basePackages() default {};
	// 这是一个安全替代basePackages()作为指定组件的扫描包。包下面的所有配置接口都将会被扫描。
    Class<?>[] basePackageClasses() default {};
	// 在Spring的容器中,将使用BeanNameGenerator去命名检测到的组件
    Class<? extends BeanNameGenerator> nameGenerator() default BeanNameGenerator.class;
	//  这个是基于包下面扫描所有的接口类并注册,也有指定的属性。   
    Class<? extends Annotation> annotationClass() default Annotation.class;
	// 基于包下面扫描所有接口类并注册,也可以指定特殊的接口为父类。
    Class<?> markerInterface() default Class.class;
	// 指定在spring上下文中有多个{@code SqlSessionTemplate}的情况下使用哪个{@code SqlSessionTemplate}
    String sqlSessionTemplateRef() default "";
	// 指定在spring上下文中存在多个{@code SqlSessionFactory}的情况下使用哪个{@code SqlSessionFactory}通常,只有当您有多个数据源时才需要这样做。
    String sqlSessionFactoryRef() default "";
	//  指定一个自定义MapperFactoryBean,以将mybatis代理作为SpringBean返回
    Class<? extends MapperFactoryBean> factoryBean() default MapperFactoryBean.class;
	// 是否启用映射器bean的延迟初始化
    String lazyInitialization() default "";
	// 指定扫描映射器的默认范围
    String defaultScope() default "";
}

MapperScannerRegistrar

MapperScannerRegistrar和@MapperSca位于同一目录,实现了ImportBeanDefinitionRegistrar和ResourceLoaderAware接口。

ImportBeanDefinitionRegistrar接口主要用来注册beanDefinition。实现了该接口,说明只能通过其@Import的方式来加载,而@MapperScan注解中就使用了@Import来加载这个类。

public interface ImportBeanDefinitionRegistrar {
    default void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry, BeanNameGenerator importBeanNameGenerator) {
        this.registerBeanDefinitions(importingClassMetadata, registry);
    }

    default void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
    }
}

registerBeanDefinitions有重要的几个参数:

  • 参数AnnotationMetadata注解元数据,会获取到@MapperScan注解上配置的属性
  • BeanDefinitionRegistry ,用于将BeanDefinition注册到spring容器中
  • BeanNameGenerator,(bean名字生成器)

registerBeanDefinitions重写了这些方法,主要是作用是获取@MapperScan中的配置,然后将这些配置传递给一个BeanDefinition。
在这里插入图片描述

MapperScannerConfigurer

MapperScannerConfigurer实现了以下接口:

  • BeanDefinitionRegistryPostProcessor
  • InitializingBean
  • ApplicationContextAware
  • BeanNameAware

Spring容器初始化时,从资源中读取到bean的相关定义后,保存在BeanDefinitionMap,在实例化bean的操作就是依据这些bean的定义来做的,而在实例化之前,Spring允许我们通过自定义扩展来改变bean的定义,而beanFactory后置处理器,即BeanFactoryPostProcessor就是用来改变bean定义的。

BeanDefinitionRegistryPostProcessor继承自BeanFactoryPostProcessor,其中有两个接口,postProcessBeanDefinitionRegistry是BeanDefinitionRegistryPostProcessor自带的,postProcessBeanFactory是从BeanFactoryPostProcessor继承过来的。

public interface BeanDefinitionRegistryPostProcessor extends BeanFactoryPostProcessor {
	// 此方法在所有Bean定义将要被加载,Bean实例还未创建的时候运行,它优先于postProcessBeanFactory方法执行。
    void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry var1) throws BeansException;
}

MapperScannerConfigurer拥有很多属性值,基本和@MapperScan能对应起来。

其中的postProcessBeanDefinitionRegistry方法,创建了一个扫描器,并执行扫描操作。

  @Override
  public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) {
    if (this.processPropertyPlaceHolders) {
      processPropertyPlaceHolders();
    }

    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);
    }
    scanner.registerFilters();
    scanner.scan(
        StringUtils.tokenizeToStringArray(this.basePackage, ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS));
  }

ClassPathMapperScanner

ClassPathMapperScanner继承自ClassPathBeanDefinitionScanner(Spring 的类扫描器),

其doScan会根据配置扫描所有的BeanDefinition,并将其注册到容器中。
在这里插入图片描述

Debug 分析

1. 创建IOC容器

1.1 创建IOC容器代码

首先我们在配置类上,添加了@MapperScan注解,配置了其value属性值。

@MapperScan(basePackages = {"org.pearl.spring.mybatis.demo.dao"})

然后手动创建了一个AnnotationConfigApplicationContext类型的IOC容器,并传入了配置类参数。

AnnotationConfigApplicationContext annotationConfigApplicationContext = new AnnotationConfigApplicationContext(MyBatisConfig.class, UserService.class);

1.2 注册BeanDefinition

Spring在BeanFactory基础上提供了一些列具体容器的实现,其中AnnotationConfigApplicationContext是一个用来管理基于注解配置bean的容器。

spring在创建容器及Bean对象时,首先会创建BeanDefinition,也就是会根据注解或者XML配置的Bean的元数据,读写这些配置,生成对应的信息,统一放入一个beanDefinitionMap中,可以看到之前配置的MyBatisConfig类的BeanDefinition,被AnnotationConfigApplicationContext加载到了一个Map结构中:
在这里插入图片描述

1.3 刷新上下文

BeanDefinition被注册以后,进入到refreshh()方法,该方法是整个Spring容器的核心,其体现了Spring容器的工作流程。

public void refresh() throws BeansException, IllegalStateException {

    synchronized (this.startupShutdownMonitor) {
        // Prepare this context for refreshing.
        // 准备刷新的上下文环境
        prepareRefresh();

        // Tell the subclass to refresh the internal bean factory.
        // 获取beanFactory
        ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();

        // Prepare the bean factory for use in this context.

        // 设置beanFactory的各个属性
        prepareBeanFactory(beanFactory);

        try {
            // Allows post-processing of the bean factory in context subclasses.
            // 模板模式,留给子类通过重写这个方法来做其他处理
            postProcessBeanFactory(beanFactory);

            // Invoke factory processors registered as beans in the context.
            // BeanFactoryPostProcessorc,BeanFactory的后置处理器处理
            invokeBeanFactoryPostProcessors(beanFactory);

            // Register bean processors that intercept bean creation.
            // 注册BeanPostProcessors,拦截bean的创建过程,bean在这个时候还没创建
            registerBeanPostProcessors(beanFactory);

            // Initialize message source for this context.
            // 国际化处理
            initMessageSource();

            // Initialize event multicaster for this context.
            // 初始化事件派发器
            initApplicationEventMulticaster();

            // Initialize other special beans in specific context subclasses.
            // 模板模式,留给子类通过重写这个方法来做其他处理
            onRefresh();

            // Check for listener beans and register them.
            // 注册ApplicationListener到applicationEventMulticaster
            registerListeners();

            // Instantiate all remaining (non-lazy-init) singletons.
            // 初始化所有的单例,除了延迟加载的bean
            finishBeanFactoryInitialization(beanFactory);

            // Last step: publish corresponding event.
            // 通知别人说已经完成了
            finishRefresh();
        }

        catch (BeansException ex) {
            if (logger.isWarnEnabled()) {
                logger.warn("Exception encountered during context initialization - " +
                        "cancelling refresh attempt: " + ex);
            }

            // Destroy already created singletons to avoid dangling resources.
            destroyBeans();

            // Reset 'active' flag.
            cancelRefresh(ex);

            // Propagate exception to caller.
            throw ex;
        }

        finally {
            // Reset common introspection caches in Spring's core, since we
            // might not ever need metadata for singleton beans anymore...
            resetCommonCaches();
        }
    }
}

我们这里只关注invokeBeanFactoryPostProcessors方法,这个方法的作用是用来执行Bean工厂的后置处理器。根据反射机制从BeanDefinitionRegistry(bean定义注册中心)中找到所有实现了BeanFactoryPostProcessor接口bean,并调用其postProcessBeanFactory()

            // Invoke factory processors registered as beans in the context.
            // BeanFactoryPostProcessorc,BeanFactory的后置处理器处理
            invokeBeanFactoryPostProcessors(beanFactory);

2. MapperScannerRegistrar注册

在IOC创建刷新上下文的过程中,会进行创建Bean工厂,获取BeanDefinition,后置处理器,实例化Bean等等操作。

因为MapperScannerRegistrar实现了ImportBeanDefinitionRegistrar接口,并且在@MapperScan中import了该类,所以,在加载BeanDefinition时,会进入该类的registerBeanDefinitions方法。

registerBeanDefinitions主要是获取@MapperScan配置信息,然后注册MapperScannerRegistrar类的BeanDefinition到容器中。

registerBeanDefinitions传入了两个参数,AnnotationMetadata是通过注解注入Bean的相关元数据,也就是@Configuration注解标识类的相关信息,BeanDefinitionRegistry 是BeanDefinition注册器,也就是所有的BeanDefinition都在这里。

registerBeanDefinitions或获取@MapperScan上配置的信息,然后调用generateBaseBeanName生成一个bean的名称。

  @Override
  public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
    // 获取MapperScan注解上的配置
    AnnotationAttributes mapperScanAttrs = AnnotationAttributes
      .fromMap(importingClassMetadata.getAnnotationAttributes(MapperScan.class.getName()));
    // 导入元数据
    if (mapperScanAttrs != null) {
      registerBeanDefinitions(importingClassMetadata, mapperScanAttrs, registry,
        generateBaseBeanName(importingClassMetadata, 0));
    }
  }

生成的Bean名称如下:

org.pearl.spring.mybatis.demo.config.MyBatisConfig#MapperScannerRegistrar#0

接着调用registerBeanDefinitions方法,该方法

    void registerBeanDefinitions(AnnotationMetadata annoMeta, AnnotationAttributes annoAttrs, BeanDefinitionRegistry registry, String beanName) {
    	// 1. 使用MapperScannerConfigurer类,创建BeanDefinition构建者
        BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(MapperScannerConfigurer.class);
        builder.addPropertyValue("processPropertyPlaceHolders", true);
        // 省略大量代码,将相关@MapperScann配置添加到BeanDefinition中
        builder.addPropertyValue("basePackage", StringUtils.collectionToCommaDelimitedString(basePackages));
        // 将当前BeanDefinition添加到注册器中。
        registry.registerBeanDefinition(beanName, builder.getBeanDefinition());
    }

最后,可以在BeanDefinitionRegistry中看到添加了当前MapperScannerRegistrar类的BeanDefinition被添加了。
在这里插入图片描述

3. 扫描

MapperScannerRegistrar注册之后,IOC就会进行后置处理操作,因为MapperScannerConfigurer实现了ImportBeanDefinitionRegistrar接口,所以会执行BeanDefinition后置处理。

后置处理会进入到postProcessBeanDefinitionRegistry方法,主要是创建扫描器,执行扫描操作。

    public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) {
        if (this.processPropertyPlaceHolders) {
        	// 1. processPropertyPlaceHolders 属性处理
            this.processPropertyPlaceHolders();
        }
		// 2. 创建classpath路径扫描器
        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(this.lazyInitialization)) {
            scanner.setLazyInitialization(Boolean.valueOf(this.lazyInitialization));
        }

        if (StringUtils.hasText(this.defaultScope)) {
            scanner.setDefaultScope(this.defaultScope);
        }
		// 3. 扫描
        scanner.registerFilters();
        scanner.scan(StringUtils.tokenizeToStringArray(this.basePackage, ",; \t\n"));
    }

创建扫描器后,会设置相关配置,最后当前扫描器维护了扫描路径,BeanDefinitionRegistry等信息。
在这里插入图片描述
首先调用扫描器registerFilters的方法,进行过滤操作,配置哪些需要扫描,哪些不需要扫描。

    public void registerFilters() {
        boolean acceptAllInterfaces = true;
        if (this.annotationClass != null) {
            this.addIncludeFilter(new AnnotationTypeFilter(this.annotationClass));
            acceptAllInterfaces = false;
        }

        if (this.markerInterface != null) {
            this.addIncludeFilter(new AssignableTypeFilter(this.markerInterface) {
                protected boolean matchClassName(String className) {
                    return false;
                }
            });
            acceptAllInterfaces = false;
        }

        if (acceptAllInterfaces) {
            this.addIncludeFilter((metadataReader, metadataReaderFactory) -> {
                return true;
            });
        }

        this.addExcludeFilter((metadataReader, metadataReaderFactory) -> {
            String className = metadataReader.getClassMetadata().getClassName();
            return className.endsWith("package-info");
        });
    }

接着进入到扫描器的scan方法,进行扫描,参数会传入当前配置的扫描包路径。

    public int scan(String... basePackages) {
    	// BeanDefinition总数
        int beanCountAtScanStart = this.registry.getBeanDefinitionCount();
        // 执行扫描
        this.doScan(basePackages);
        if (this.includeAnnotationConfig) {
      	//       AnnotationConfigUtils.registerAnnotationConfigProcessors(this.registry);
        }

        return this.registry.getBeanDefinitionCount() - beanCountAtScanStart;
    }

doScan方法最终调用的是父类ClassPathBeanDefinitionScanner的doScan方法。

    protected Set<BeanDefinitionHolder> doScan(String... basePackages) {
        Assert.notEmpty(basePackages, "At least one base package must be specified");
        Set<BeanDefinitionHolder> beanDefinitions = new LinkedHashSet();
        String[] var3 = basePackages;
        int var4 = basePackages.length;
		// 循环配置的扫描路							
  • 作者:云烟成雨TD
  • 原文链接:https://yunyanchengyu.blog.csdn.net/article/details/121021136
    更新时间:2023-04-26 07:58:37