• Spring mybatis源码篇章-MapperScannerConfigurer


    承接前文Spring mybatis源码篇章-Mybatis的XML文件加载,本文将在前文的基础上讲解Spring在Mybatis整合方面的另一动作

    前话

    根据前文的分析可得到以下结论

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

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

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

    4. XML模式生成的MappedStatement,是保存在org.apache.ibatis.session.Configuration对象中的mappedStatements属性中(Map),并且也将${namespace}对应的DAO类存放至mapperRegistry属性里,供外部调用。

    本文主要分析的是第四点,因为第四点的mapperRegistry类有一个特别的方法

      // SqlSession就是关键
      public <T> T getMapper(Class<T> type, SqlSession sqlSession) {}
    

    我们都知道Spring在整合Mybatis的时候都会配置一个SqlSessionFactoryBean对象来生成一个SqlSessionFactory,而这个SqlSessionFactory就是作为SqlSession(数据库会话)的关键部分,那么Spring又怎么把这个对象与DAO接口类关联放在mapperRegistry里呢????

    老方式

    <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主配置文件臃肿,所以上述的办法已过时

    新方式

    <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指定的DAO接口集合统一关联SqlSession,这就节省了之前老配置很多的代码空间。

    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,由其完成最终的关联

    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属性的入口是一样的;xml方式的加载也是会走这一步
            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()方法来将相应的DAO接口放入mapperRegistry中的knownMappers属性,我们可以看下这个属性的类型

    private final Map<Class<?>, MapperProxyFactory<?>> knownMappers = new HashMap<Class<?>, MapperProxyFactory<?>>();
    

    恩,有动态代理的味道~~~

    怎么取相应的DAO接口呢供外界调用呢,就是getObejct()方法

      public T getObject() throws Exception {
    	// 间接关联了SqlSession与mapperInterface
        return getSqlSession().getMapper(this.mapperInterface);
      }
    

    通过上述的代码便调用了上文所提及问题的mapperRegistry#getMapper()方法了,但具体的如何调用我们后文分析~~

    总结

    作下简单的小结

    1. MapperScannerConfigurer类主要实现将basePackage包下的所有接口类都包装成MapperFactoryBean对象,内含相应的SqlSessionFactory数据库会话

    2. MapperScannerConfigurer类默认情况下在形成MappedStatement的过程中会优先去找寻与接口同目录下的XML文件来加载生成。这点与Mybatis的主文件加载方式类同~~~只不过其更方便

    3. MapperScannerConfigurer一般结合sqlSessionFactoryBeanmapperLocations属性来完成Mybatis主文件的mappers的工作~~

    4. 官方以及笔者推荐大家使用MapperScannerConfigurer与SqlSessionFactoryBean搭配使用~~~~

  • 相关阅读:
    精选30道Java笔试题解答
    ASM
    Java Decompiler Plugin For Eclipse IDE
    AMQ5540, AMQ5541 and AMQ5542, application did not supply a user ID and password, 2035 MQRC_NOT_AUTHORIZED
    Shell脚本中的export
    Linux set unset命令
    shell中${}的妙用
    ubuntu alsa2
    ubuntu alsa
    计算机启动boot
  • 原文地址:https://www.cnblogs.com/question-sky/p/6654101.html
Copyright © 2020-2023  润新知