美文网首页
SpringMyBatis解析5-MapperScannerCo

SpringMyBatis解析5-MapperScannerCo

作者: 小陈阿飞 | 来源:发表于2018-12-07 17:54 被阅读8次

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

    背景知识

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

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

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

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

    5 (本文解析重点)XML模式生成的MappedStatement,还必须拥有对应的mapperInterface接口供Service层调用,即mapperInterface接口是需要注册到Configuration的MapperRegistry对象中,方便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 接口注入新配置

    其实新的配置就是动态的将dao接口,转化成MapperFactoryBean包装l类加载到spring容器中

    <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

      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过滤,即用到了annotationClass和markerInterface属性

    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();
          }
        }
      }
    

    总结

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

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

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

    相关文章

      网友评论

          本文标题:SpringMyBatis解析5-MapperScannerCo

          本文链接:https://www.haomeiwen.com/subject/pckxhqtx.html