Spring mybatis源码篇章-MapperScannerConfigurer关联dao接口

前言:Spring针对Mybatis的XML方式的加载MappedStatement,通过引入MapperScannerConfigurer扫描类来关联相应的dao接口以供Service层调用。承接前文Spring mybatis源码篇章-NodeHandler实现类具体解析保存Dynamic sql节点信息

背景知识

  1. MappedStatement是mybatis操作sql语句的持久层对象,其id由注解模式的${mapperInterface类全名}.${methodName}或者XML模式的${namespace}.${CRUD标签的id}确定,且是唯一的

  2. Mybatis对每个CRUD语句都会生成唯一的MappedStatement保存至ConfigurationmappedStatements(Map集合)中

  3. Mybatis提供注解模式和XML模式生成MappedStatement,在两者同时存在的情况下,注解模式的MappedStatement会覆盖同id的XML模式的MappedStatement

  4. 注解模式生成MappedStatement的途径有两个,一个是在其同目录下存在与类名一致的mapper文件;另一个是其方法名实现了CRUD的注解,如果被注解的方法名与mapper文件存在同id,遵循第3点特性

  5. (本文解析重点)XML模式生成的MappedStatement,还必须拥有对应的mapperInterface接口供Service层调用,即mapperInterface接口是需要注册到ConfigurationMapperRegistry对象中,方便Service层找寻调用

Spring Mybatis 接口注入老配置

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

上述的配置是针对单个的mapperInterface注入到应用程序中,试想如果有很多的接口则会导致Spring主配置文件臃肿,所以上述的办法已过时

Spring Mybatis 接口注入新配置

<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
      <property name="basePackage" value="org.mybatis.spring.sample.mapper" />
      <!-- optional unless there are multiple session factories defined -->
      <property name="sqlSessionFactoryBeanName" value="sqlSessionFactory" />
  </bean>

采用MapperScannerConfigurer扫描类来实现对basePackage指定的包进行接口的注入,节省了之前老配置很多的代码空间。

MapperScannerConfigurer

直接一睹MapperScannerConfigurer类的源码风采,优先查看其内部属性

    public class MapperScannerConfigurer implements BeanDefinitionRegistryPostProcessor, InitializingBean, ApplicationContextAware, BeanNameAware {

      private String basePackage;

      private boolean addToConfig = true;

      private SqlSessionFactory sqlSessionFactory;

      private SqlSessionTemplate sqlSessionTemplate;

      private String sqlSessionFactoryBeanName;
    
      private String sqlSessionTemplateBeanName;

      private Class<? extends Annotation> annotationClass;

      private Class<?> markerInterface;
    
      private ApplicationContext applicationContext;

      private String beanName;

      private boolean processPropertyPlaceHolders;

      private BeanNameGenerator nameGenerator;
      ....
     }

其源码上的注释其实写的很清楚了,注释篇幅过长,就不在这里展示了,稍微提下这个类的相关使用:

  • basePackage 基本属性,接口类扫描的包路径,支持,;分隔

  • sqlSessionFactoryBeanName 当有多个SqlSessionFactory环境时,官方通过其来指定加载特定的sqlSessionFactory,value即为bean的id值(建议使用此属性)

  • sqlSessionFactoty 默认是不用填的,其会去寻找id为sqlSessionFactory的sqlSessionFactory实例,sqlSessionTemplate的操作类似

  • annotationClass 注解类,其会去Spring环境下寻找拥有此注解的接口类,并忽略basePackage的属性,默认为null

  • markerInterface 父类接口类,其会去寻找继承此接口类的子接口类但不包括父类接口,并忽略basePackage的属性,与annotationClass并存,默认为null

MapperScannerConfigurer#postProcessBeanDefinitionRegistry()

直接进入其关键方法,观察下是如何进行扫描的

  public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {
    //支持${basePackage}形式
    if (this.processPropertyPlaceHolders) {
      processPropertyPlaceHolders();
    }
    //base classpath environment to scan
    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);
    //base annotationClass and markerInterface properties to register interface filters
    scanner.registerFilters();
    // scan 
    scanner.scan(StringUtils.tokenizeToStringArray(this.basePackage, ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS));
  }

上述代码也就是设置相应的属性给ClassPathMapperScanner,具体的如何扫描我们继续往下看

ClassPathBeanDefinitionScanner

扫描的具体操作由ClassPathMapperScanner的父类ClassPathBeanDefinitionScanner来完成,我们简单的看下其中的逻辑

    protected Set<BeanDefinitionHolder> doScan(String... basePackages) {
        Assert.notEmpty(basePackages, "At least one base package must be specified");
        Set<BeanDefinitionHolder> beanDefinitions = new LinkedHashSet<BeanDefinitionHolder>();
        for (String basePackage : basePackages) {
            // 找寻classpath对应的目录,其中的解析帮助类为PathMatchingResourcePatternResolver
            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);
                // 设置基本的属性,比如lazy-init/autowire-mode等
                if (candidate instanceof AbstractBeanDefinition) {
                    postProcessBeanDefinition((AbstractBeanDefinition) candidate, beanName);
                }
                // 对针对@mapper注解的bean
                if (candidate instanceof AnnotatedBeanDefinition) {
                    AnnotationConfigUtils.processCommonDefinitionAnnotations((AnnotatedBeanDefinition) candidate);
                }
                // 确保不重复注册到bean工厂
                if (checkCandidate(beanName, candidate)) {
                    BeanDefinitionHolder definitionHolder = new BeanDefinitionHolder(candidate, beanName);
                    definitionHolder =
                            AnnotationConfigUtils.applyScopedProxyMode(scopeMetadata, definitionHolder, this.registry);
                    beanDefinitions.add(definitionHolder);
                    registerBeanDefinition(definitionHolder, this.registry);
                }
            }
        }
        return beanDefinitions;
    }
  • 上述代码最关键的便是找寻对应目录的所有interface接口,其是通过PathMatchingResourcePatternResolver帮助类来完成的,后续笔者独立成篇加以分析

  • 对找寻的beanDefinitions集合过程中也会进行filters过滤,即用到了annotationClassmarkerInterface属性

ClassPathMapperScanner

针对上述的符合条件后获取的beanDefinitions集合,子类便要进行最后的加工处理

  /**
   * Calls the parent search that will search and register all the candidates.
   * Then the registered objects are post processed to set them as
   * MapperFactoryBeans
   */
  @Override
  public Set<BeanDefinitionHolder> doScan(String... basePackages) {
    //由父类去找到符合条件的interface类,并转化为bean类
    Set<BeanDefinitionHolder> beanDefinitions = super.doScan(basePackages);

    if (beanDefinitions.isEmpty()) {
      logger.warn("No MyBatis mapper was found in '" + Arrays.toString(basePackages) + "' package. Please check your configuration.");
    } else {
      for (BeanDefinitionHolder holder : beanDefinitions) {
        GenericBeanDefinition definition = (GenericBeanDefinition) holder.getBeanDefinition();

        if (logger.isDebugEnabled()) {
          logger.debug("Creating MapperFactoryBean with name '" + holder.getBeanName() 
              + "' and '" + definition.getBeanClassName() + "' mapperInterface");
        }

        // the mapper interface is the original class of the bean
        // but, the actual class of the bean is MapperFactoryBean
        //最终将definition包装成MapperFactoryBean,beanClass设置为其内部属性MapperInterface
        definition.getPropertyValues().add("mapperInterface", definition.getBeanClassName());
        definition.setBeanClass(MapperFactoryBean.class);

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

        boolean explicitFactoryUsed = false;
        //根据sqlsessionFactoryBeanName寻找运行状态的SqlsessionFactory的虚引用,但并没有去真实加载
        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 (StringUtils.hasText(this.sqlSessionTemplateBeanName)) {
          if (explicitFactoryUsed) {
            logger.warn("Cannot use both: sqlSessionTemplate and sqlSessionFactory together. sqlSessionFactory is ignored.");
          }
          definition.getPropertyValues().add("sqlSessionTemplate", new RuntimeBeanReference(this.sqlSessionTemplateBeanName));
          explicitFactoryUsed = true;
        } else if (this.sqlSessionTemplate != null) {
          if (explicitFactoryUsed) {
            logger.warn("Cannot use both: sqlSessionTemplate and sqlSessionFactory together. sqlSessionFactory is ignored.");
          }
          definition.getPropertyValues().add("sqlSessionTemplate", this.sqlSessionTemplate);
          explicitFactoryUsed = true;
        }
        //当没有指定SqlSession对象,则设置MapperFactoryBean自动去找寻
        if (!explicitFactoryUsed) {
          if (logger.isDebugEnabled()) {
            logger.debug("Enabling autowire by type for MapperFactoryBean with name '" + holder.getBeanName() + "'.");
          }
          definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE);
        }
      }
    }

    return beanDefinitions;
  }

根据上述代码得知,其最终被包装的类为MapperFactoryBean,由其完成最终的MappedStatement关联

MapperFactoryBean

笔者此处只关注其关键方法checkDaoConfig(),源码如下

  /**
   * {@inheritDoc}
   */
  @Override
  protected void checkDaoConfig() {
    super.checkDaoConfig();

    notNull(this.mapperInterface, "Property 'mapperInterface' is required");

    Configuration configuration = getSqlSession().getConfiguration();
    // 判断mapperInterface接口是否已被注册过
    if (this.addToConfig && !configuration.hasMapper(this.mapperInterface)) {
      try {
        // 此处就跟mybatis加载主配置文件时对mapper节点指定package属性或者mapperClass属性的入口是一样的
        configuration.addMapper(this.mapperInterface);
      } catch (Throwable t) {
        logger.error("Error while adding the mapper '" + this.mapperInterface + "' to configuration.", t);
        throw new IllegalArgumentException(t);
      } finally {
        ErrorContext.instance().reset();
      }
    }
  }

上述主要是调用Configuration#addMapper()方法来完成对MapperInterface接口的关联注册,此处来个传送门>>>Spring mybatis源码篇章-MybatisDAO文件解析(一)

总结

  1. MapperScannerConfigurer类主要实现将basePackage包下的所有接口类注册到Configuration的内部属性MapperRegister#knowMappers(HashMap集合)中

  2. MapperScannerConfigurer类默认情况下在形成MappedStatement的过程中会优先去找寻与接口同目录下的XML文件来加载生成。
    如果想应用XML配置文件且可以任意放置,则可以结合sqlSessionFactoryBeanmapperLocations属性来完成自由化绑定的过程

  3. MappedStatement对象的生成与MapperInterfaces接口类是一一对应的
    • MapperInterfaces接口类可通过注解例如@Select方式生成注解,即脱离XML配置方式
    • MapperInterfaces接口类如果在SqlsessionFactory不使用mapperLocations属性时且不使用注解方式,则必须在其同目录下有同名字的XML mapper文件,否则无法访问数据库;
      反之使用mapperLocations属性,则mapper文件只需放在classpath路径下即可

转载于:https://www.cnblogs.com/question-sky/p/6654101.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值