Mybatis扫描全包(包含Service、Dao的接口)导致抛出BindingException异常,引发的一系列思考(含源码分析)

转载请注明原文链接:Mybatis扫描全包(包含Service、Dao的接口)导致抛出BindingException异常,引发的一系列思考(含源码分析)

在Spring中配置Mybatis接口类的接口扫描basePackage的时候,将其设置为全部的包,然后就抛出了异常,我当时感觉很诧异,怎么会这样?明明可以扫描到我的Mapper接口类的,怎么会抛出绑定异常呢?而且这个异常是service层的接口的绑定异常,我又改回了扫描dao包,这回就可以了,虽然是程序可以了,但是我不明白到底发生了什么,就这样配置改了一下就是质的区别。下面先看下抛出的异常,我先给出解释,再给出在读自动扫描源码的过程中的一些认知。


经过一番的努力最终发现在源码的注释中,有下面这样一段话:

源码链接地址

BeanDefinitionRegistryPostProcessor that searches recursively starting from a base package for
 * interfaces and registers them as {@code MapperFactoryBean}. Note that only interfaces with at
 * least one method will be registered; concrete classes will be ignored.
 * <p>

这段话的意思很明确,我觉得一般人仔细读读的话应该会懂的,意思大概为:BeanDefinitionRegistryPostProcessor这个类会扫描你设置的BasePackage中的所有的接口(注意这里的所有),然后将他们注册到MapperFactoryBean中。然后还特意给出了提示:只有至少有一个方法的接口才会被注册,实现类会被忽略。

那么,跟我遇到的异常有什么关系呢?

我的项目中分为三层,dao层由Mybatis来负责管理,Service层也有接口和实现类,由Spring管理,上面也说了,这个类在扫描的时候会扫描所有的至少一个方法的接口,也就是只要符合条件的,都会被他扫描到并且任务是Mybatis的接口,那么在进行注册的时候因为找不到service层接口的xml文件而抛出这个绑定异常。


源码分析

这里MapperScannerConfigurer实现了一堆的接口,前面提到的BeanDefinitionRegistryPostProcessor,然后你会看到有一个Spring里面见到过很多次的接口(Spring进阶之路(3)-bean获得Spring的容器):ApplicationContextAware,实现这个接口之后,我就可以直接拿到Spring容器本身也就是applicationContext。这样我就可以把生成的代理实现类直接注入到Spring容器中去,交给了Spring管理。

public class MapperScannerConfigurer implements BeanDefinitionRegistryPostProcessor, 
InitializingBean, ApplicationContextAware, BeanNameAware 

该方法重写了父类的方法,在父类初始化时调用的方法。

  public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {
    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.registerFilters();
    scanner.scan(StringUtils.tokenizeToStringArray(this.basePackage, ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS));
  }

这里对Scanner进行了一系列的设值,然后调用了scan方法。

	public int scan(String... basePackages) {
		int beanCountAtScanStart = this.registry.getBeanDefinitionCount();

		doScan(basePackages);

		// Register annotation config processors, if necessary.
		if (this.includeAnnotationConfig) {
			AnnotationConfigUtils.registerAnnotationConfigProcessors(this.registry);
		}

		return this.registry.getBeanDefinitionCount() - beanCountAtScanStart;
	}
scan方法并不是在 ClassPathMapperScanner这个类中,而是再其父类ClassPathBeanDefinitionScanner中,ClassPathBeanDefinitionScanner是在Spring包中的类。


ClassPathMapperScanner类中的doScan方法。重写了父类的方法,然后调用了父类中的方法。

public Set<BeanDefinitionHolder> doScan(String... basePackages) {
	  //调用父类扫描函数
    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();

        //此处的mapperInterface就是我们写的mapper接口一般是个空接口,实际上关联到它
        //的BeanDefinition中的Bean是MapperFactoryBean,MapperFactoryBean很重要
        definition.getPropertyValues().add("mapperInterface", definition.getBeanClassName());
        definition.setBeanClass(MapperFactoryBean.class);

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

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


ClassPathBeanDefinitionScanner中的doScan方法。


protected Set<BeanDefinitionHolder> doScan(String... basePackages) {
		Assert.notEmpty(basePackages, "At least one base package must be specified");
		//BeanDefinitionHolder的容器
		Set<BeanDefinitionHolder> beanDefinitions = new LinkedHashSet<BeanDefinitionHolder>();
		for (String basePackage : basePackages) {
			//选择候选者
			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);
				//如果是基包下的一个抽象类,我们配置的mapper接口如果不是注解方式也会走这处理
				if (candidate instanceof AbstractBeanDefinition) {
					postProcessBeanDefinition((AbstractBeanDefinition) candidate, beanName);
				}
				//如果基包下的类是有注解的
				if (candidate instanceof AnnotatedBeanDefinition) {
					AnnotationConfigUtils.processCommonDefinitionAnnotations((AnnotatedBeanDefinition) candidate);
				}
				//如果dao包(一般配置的basePakage是这个)下的类是符合mybaits要求的则向spring IOC容器中注册它的BeanDefinition
				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;
	}


这里应该是排除了实现类的地方,前面的这个if语句就可以实现这个功能。
protected boolean checkCandidate(String beanName, BeanDefinition beanDefinition) throws IllegalStateException {
		//如果spring中没注册这个beanName对应的bean那么就是mybaits需要的类,一般mybaits的mapper类是一个接口不会被spring实例化加载到IOC容器中
		if (!this.registry.containsBeanDefinition(beanName)) {
			return true;
		}
		//如果spring中有这个类的bean定义
		BeanDefinition existingDef = this.registry.getBeanDefinition(beanName);
		BeanDefinition originatingDef = existingDef.getOriginatingBeanDefinition();
		if (originatingDef != null) {
			existingDef = originatingDef;
		}
		//如果重复扫描此类或者或者重复获取同一个类的源文件或者是其它类的子类则是不兼容的,这样的类是不符合mybaits加载要求返回false
		if (isCompatible(beanDefinition, existingDef)) {
			return false;
		}
		throw new ConflictingBeanDefinitionException("Annotation-specified bean name '" + beanName +
				"' for bean class [" + beanDefinition.getBeanClassName() + "] conflicts with existing, " +
				"non-compatible bean definition of same name and class [" + existingDef.getBeanClassName() + "]");
	}

转载请注明原文链接: Mybatis扫描全包(包含Service、Dao的接口)导致抛出BindingException异常,引发的一系列思考(含源码分析)


评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值