(六)Mybatis持久化框架原理之MapperScannerConfigurer方式和Spring集成

本文详细解析了Mybatis与Spring集成时使用MapperScannerConfigurer的方式,介绍了如何通过配置applicationContext.xml来扫描包下的mapper接口,避免了手动配置多个MapperFactoryBean的繁琐过程。文章深入分析了MapperScannerConfigurer、ClassPathBeanDefinitionScanner和ClassPathMapperScanner等核心类的工作原理。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

目录

一、Mybatis和Spring集成项目准备

1.applicationContext.xml配置文件改动

二、UML类图和流程分析

1.UML类图分析

2.流程分析

三、原理分析

1.SqlSessionFactoryBean

2.MapperScannerConfigurer

3.ClassPathBeanDefinitionScanner

4.ClassPathMapperScanner


一、Mybatis和Spring集成项目准备

学习本篇前可先看(五)Mybatis持久化框架原理之MapperFactoryBean方式和Spring集成,了解最基本的mybatis-spring集成方式后再来学习此方式效果更好。

看过MapperFactoryBean和Spring的集成方式就可以知道,针对每个mapper接口需要单独写一个对应的配置,如果mapper接口数量很多,就会显得很麻烦,因此Mybatis提供了扫描包的方式来完成读取并封装mapper接口,主要类是MapperScannerConfigurer。

1.applicationContext.xml配置文件改动

除了spring配置文件有改动以外,其他的都和上一篇一致即可,无需改动。

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans 
       http://www.springframework.org/schema/beans/spring-beans.xsd 
       http://www.springframework.org/schema/context 
       http://www.springframework.org/schema/context/spring-context.xsd">

    <!-- 引入properties文件 -->
    <context:property-placeholder location="classpath:database.properties"/>

    <!-- 定义数据源 -->
    <bean id="dataSource" class="oracle.jdbc.pool.OracleDataSource">
        <property name="driverType" value="${connection.driver}"/>
        <property name="URL" value="${connection.url}"/>
        <property name="user" value="${connection.username}"/>
        <property name="password" value="${connection.password}"/>
    </bean>

    <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
        <!-- 数据源 -->
        <property name="dataSource" ref="dataSource"/>
        <!-- mybatis 配置文件 -->
        <property name="configLocation" value="classpath:mybatis-config.xml"/>
        <!-- 指定实体类映射文件,可以同时指定某一包以及子包下面的所有配置文件,
        mapperLocations和configLocation有一个即可,当需要为实体类指定别名时,
        可指定configLocation属性,再在mybatis总配置文件中采用mapper引入实体类映射文件 -->
        <property  name="mapperLocations"  value="classpath*:*Mapper.xml"/>
    </bean>

    <!-- 使用MapperFactoryBean方式和spring集成, -->
    <!--bean id="demoMapper" class="org.mybatis.spring.mapper.MapperFactoryBean">
        <property name="mapperInterface" value="com.iboxpay.mapper.DemoMapper"/>
        <property name="sqlSessionFactory" ref="sqlSessionFactory"/>
    </bean-->

    <!-- 使用MapperScannerConfigurer方式和spring集成 -->
    <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
        <property name="basePackage" value="com.iboxpay.mapper"/>
        <!-- 可有可无,不影响程序的正常使用 -->
        <property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"/>
    </bean>

</beans>

二、UML类图和流程分析

1.UML类图分析

下图是此方法的UML类图结构:

对于SqlSessionFactoryBean、MapperFactoryBean、SqlSessionTemplate这三个类以及其实现的接口在此篇不做过多的介绍,此篇只需要重点关注MapperScannerConfigurer类,其涉及的几个接口介绍分别如下:

  1. ApplicationContextAware接口:spring框架在运行的时候会调用此接口的setApplicationContext方法,通过该方法可以将spring的上下文赋给实现类,但前提是spring需要把该类当成一个bean;
  2. BeanNameAware接口:spring框架运行时调用此接口的setBeanName,为bean赋值名字;
  3. BeanFactoryPostProcessor接口:用来修改spring上下文中的BeanFactory,允许在bean未实例化之前对bean进行添加属性或者重写的操作;
  4. BeanDefinitionRegistryPostProcessor接口:用来修改spring上下文中的bean定义注册中心BeanDefinitionRegistry,允许下一个post-processor开始前为该注册中心添加更多的bean定义。

2.流程分析

其流程图如下:

可以看到其相较于MapperFactoryBean的集成方式只多了一层MapperScannerConfigurer,这个类的主要作用便是扫描basePackage配置下的mapper接口,并将接口使用MapperFactoryBean作为其FactoryBean,在Spring工厂实例化该mapper时,将会调用MapperFactoryBean的getObject,从而完成使用包集成的方式替换了配置多个MapperFactoryBean的对应关系。

需要注意的是MapperScannerConfigurer实现了BeanDefinitionRegistryPostProcessor接口,其实际实现了BeanFactoryPostProcessor接口,因此这些类会比一般的Bean初始化的早,且会被调用的比其它普通Bean早。

三、原理分析

1.SqlSessionFactoryBean

该类的操作流程在(五)Mybatis持久化框架原理之MapperFactoryBean方式和Spring集成文中已经写过,这里便不再复述。

2.MapperScannerConfigurer

该类在applicationContext.xml文件中赋值结束后将会被spring容器实例化,同时在实例化完该类后,将会调用BeanDefinitionRegistryPostProcessor接口方法postProcessBeanDefinitionRegistry,该类做的一些初始化操作就是在此方法中进行的,接下来看下该类的源码:

public class MapperScannerConfigurer
    implements BeanDefinitionRegistryPostProcessor, 
    InitializingBean, ApplicationContextAware, BeanNameAware {
  // XML文件中配置的基础扫描包
  private String basePackage;
  // 将会被传入MapperFactoryBean中
  private boolean addToConfig = true;
  // 是否延迟加载
  private String lazyInitialization;
  // 对应的sqlSessionFactory对象
  private SqlSessionFactory sqlSessionFactory;
  // 对应的sqlSessionTemplate对象
  private SqlSessionTemplate sqlSessionTemplate;
  // 忽略其它不重要的成员属性和方法
  ...
  @Override
  public void afterPropertiesSet() throws Exception {
    // 在被实例化后将被调用,检查基础扫描包basePackage属性是否为空
    notNull(this.basePackage, "Property 'basePackage' is required");
  }
  @Override
  public void postProcessBeanFactory(
          ConfigurableListableBeanFactory beanFactory) {
    // BeanDefinitionRegistryPostProcessor有两个方法,但是只需要用到一个
    // 因此这个方法留白
  }
  @Override
  public void postProcessBeanDefinitionRegistry(
          BeanDefinitionRegistry registry) {
    // 是否处理属性占位符,实际上一般而言这个都不会被用到,因为
    // processPropertyPlaceHolders默认为false,正常的使用不会进入判断方法体
    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);
    // 如果mapperFactoryBeanClass为空,默认为MapperFactoryBean类型
    scanner.setMapperFactoryBeanClass(this.mapperFactoryBeanClass);
    if (StringUtils.hasText(lazyInitialization)) {
      scanner.setLazyInitialization(Boolean.valueOf(lazyInitialization));
    }
    // 注册过滤器,如哪些类不用被扫描等
    scanner.registerFilters();
    // 开始扫描
    scanner.scan(
        StringUtils.tokenizeToStringArray(this.basePackage, 
            ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS));
  }
}

当在applicationContext.xml文件中对MapperScannerConfigurer bean对象进行set赋值之后,afterPropertiesSet方法中将会对basePackage属性进行为空校验,因此可以看出来,该bean的配置只有该属性是必须的,其他的属性都可配可不配,通过之后就会调用postProcessBeanFactory方法和postProcessBeanDefinitionRegistry方法,在此类中,postProcessBeanFactory方法为空,因此只需要把注意点放在postProcessBeanDefinitionRegistry方法上,接下来对该方法进行详细的分析:

  1. 如果processPropertyPlaceHolders属性为true,则会进入processPropertyPlaceHolders方法,在此方法中会将再次从spring容器中拿取beanName相同的bean定义,并从该bean定义中拿取诸如basePackage、sqlSessionFactoryBeanName、sqlSessionTemplateBeanName、lazyInitialization等属性覆盖MapperScannerConfigurer中的属性;
  2. 将MapperScannerConfigurer类中的addToConfig、markerInterface、sqlSessionFactory、sqlSessionTemplate、sqlSessionFactoryBeanName、sqlSessionTemplateBeanName、applicationContext、mapperFactoryBeanClass等属性赋值给ClassPathMapperScanner对象;
  3. registerFilters注册过滤器,如果annotationClass和markerInterface都为空,则默认不设置过滤器;

3.ClassPathBeanDefinitionScanner

这个类的作用便是根据传进来的各种参数对包进行扫描,随后再将扫描获得的对象进行某种处理,具体是如何操作的一起来看看关键源码部分:

public class ClassPathBeanDefinitionScanner 
        extends ClassPathScanningCandidateComponentProvider {
    // spring注册中心
    private final BeanDefinitionRegistry registry;
    // 这个方法在MapperScannerConfigurer类中被调用,实际上调用的是子类
    // ClassPathMapperScanner,但是其并没有重写或重载这个方法,因此会调用
    // 父类的scan方法
    public int scan(String... basePackages) {
        int beanCountAtScanStart = this.registry.getBeanDefinitionCount();
        // 调用本类的soScan方法
        doScan(basePackages);
        if (this.includeAnnotationConfig) {
            AnnotationConfigUtils
                  .registerAnnotationConfigProcessors(this.registry);
        }
        return (this.registry.getBeanDefinitionCount() - 
                beanCountAtScanStart);
    }
    protected Set<BeanDefinitionHolder> doScan(String... basePackages) {
       Set<BeanDefinitionHolder> beanDefinitions = 
               new LinkedHashSet<BeanDefinitionHolder>();
       // 多个包依次进行解析
       for (String basePackage : basePackages) {
          // 根据传入的包路径,扫描该包下的类,同时会根据isCandidateComponent
          // 方法判断什么样的类会被扫描到,该流程实际类是ClassPathMapperScanner
          // 其方法的判断条件为只有接口才能被扫描进来
          Set<BeanDefinition> candidates = 
                  findCandidateComponents(basePackage);
          for (BeanDefinition candidate : candidates) {
             ScopeMetadata scopeMetadata = this.scopeMetadataResolver
                     .resolveScopeMetadata(candidate);
             candidate.setScope(scopeMetadata.getScopeName());
             // 设置候选类名称
             String beanName = this.beanNameGenerator
                     .generateBeanName(candidate, this.registry);
             if (candidate instanceof AbstractBeanDefinition) {
                postProcessBeanDefinition((AbstractBeanDefinition) 
                        candidate, beanName);
             }
             if (candidate instanceof AnnotatedBeanDefinition) {
                AnnotationConfigUtils
                        .processCommonDefinitionAnnotations(
                            (AnnotatedBeanDefinition) candidate);
             }
             // 判断注册中心是否含有该候选类,如果没有则返回true
             if (checkCandidate(beanName, candidate)) {
                BeanDefinitionHolder definitionHolder = 
                        new BeanDefinitionHolder(candidate, beanName);
                definitionHolder = AnnotationConfigUtils
                        .applyScopedProxyMode(scopeMetadata, 
                                definitionHolder, this.registry);
                // 将候选类封装成Bean定义,且注册到Spring注册中心称为Bean
                beanDefinitions.add(definitionHolder);
                registerBeanDefinition(definitionHolder, this.registry);
             }
          }
       }
       // 返回Bean定义集合
       return beanDefinitions;
    }
}

这个类是ClassPathMapperScanner的父类,因此等下分析ClassPathMapperScanner类的父类方法时,只需要知道其大致作用即可。

4.ClassPathMapperScanner

在MapperScannerConfigurer类中调用的实际上是ClassPathMapperScanner类,在前面我们已经知道了scan方法后面会调用到doScan方法来,因此我们看看其关键源码:

public class ClassPathMapperScanner 
        extends ClassPathBeanDefinitionScanner {
  // 上面的成员属性不做过多介绍,实际上和MapperScannerConfigurer类中设置的
  // 属性差不多
  @Override
  protected boolean isCandidateComponent(
          AnnotatedBeanDefinition beanDefinition) {
    // 用来判断父类doScan时哪些类符合条件
    return beanDefinition.getMetadata().isInterface() && 
            beanDefinition.getMetadata().isIndependent();
  }
  private Class<? extends MapperFactoryBean> mapperFactoryBeanClass = 
          MapperFactoryBean.class;
  @Override
  public Set<BeanDefinitionHolder> doScan(String... basePackages) {
    // 调用父类获得候选Bean定义
    Set<BeanDefinitionHolder> beanDefinitions = super.doScan(basePackages);
    if (beanDefinitions.isEmpty()) {
      // 为空则不做什么
    } else {
      // 不为空则调用该方法
      processBeanDefinitions(beanDefinitions);
    }
    return beanDefinitions;
  }
  private void processBeanDefinitions(
          Set<BeanDefinitionHolder> beanDefinitions) {
    GenericBeanDefinition definition;
    // 遍历获得的mapper接口候选bean定义
    for (BeanDefinitionHolder holder : beanDefinitions) {
      definition = (GenericBeanDefinition) holder.getBeanDefinition();
      String beanClassName = definition.getBeanClassName();
      // 使用MapperFactoryBean来封装原来的bean
      definition.getConstructorArgumentValues()
              .addGenericArgumentValue(beanClassName);
      definition.setBeanClass(this.mapperFactoryBeanClass);
      // 下面则是设置一系列属性,诸如addToConfig、sqlSessionFactory、
      // lazyInitialization和AutowireMode等
      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 (!explicitFactoryUsed) {
        definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE);
      }
      definition.setLazyInit(lazyInitialization);
    }
  }
}

至此,我们便已经解析了config.xml文件和mapper.xml文件,且把mapper接口使用MapperFactoryBean封装了起来注册到了Spring工厂中,在后续使用时直接使用@Autowired自动注入则可以完成mapper接口的调用。

 

 

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值