@SpringBootTest注解分析(一)Found multiple @SpringBootConfiguration annotated classes

14 篇文章 0 订阅

Spring版本

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.0.0.RELEASE</version>
    </parent>

初次使用单元测试时报错如下

java.lang.IllegalStateException: Found multiple @SpringBootConfiguration annotated classes [Generic bean: class [FDFS0.AppTest]; scope=; abstract=false; lazyInit=false; autowireMode=0; dependencyCheck=0; autowireCandidate=true; primary=false; factoryBeanName=null; factoryMethodName=null; initMethodName=null; destroyMethodName=null; defined in file [D:\data\BianCheng\java\20200417\FastDFSClient_Study\target\test-classes\FDFS0\AppTest.class], Generic bean: class [FDFS0.AppFDFS0]; scope=; abstract=false; lazyInit=false; autowireMode=0; dependencyCheck=0; autowireCandidate=true; primary=false; factoryBeanName=null; factoryMethodName=null; initMethodName=null; destroyMethodName=null; defined in file [D:\data\BianCheng\java\20200417\FastDFSClient_Study\target\classes\FDFS0\AppFDFS0.class]]

其中的关键信息是Found multiple @SpringBootConfiguration annotated classes,意思是在包扫描的过程中找到了多个表示有@SpringBootConfiguration的类。

解决方法:
1、删除多余的@SpringBootConfiguration类,只留一个
2、在@SpringBootTest中指定使用的@SpringBootConfiguration类。
3、将指定的@SpringBootConfiguration类移动到与测试类相同的包下

源码分析

遇到问题先尝试通过阅读源码来解决。每一次程序报错都是一次读源码的机会。

产生的错误与@SpringBootTest注解有很大关系。所以重点分析@SpringBootTest注解。

首先@SpringBootTest注解中添加classes和不添加classes是走不同判断路线的。

SpringBootTestContextBootstrapper.processMergedContextConfiguration()
不论哪种路线,都会运行至
SpringBootTestContextBootstrapper.processMergedContextConfiguration()
这个类从名字上来看应该是有关测试环境下上下文以及启动相关的一些东西,而processMergedContextConfiguration()方法是“合并上下文配置”,具体为啥要合并上下文配置,我暂时不是特别清楚。

SpringBootTestContextBootstrapper.getOrFindConfigurationClasses()
紧接着processMergedContextConfiguration()会调用getOrFindConfigurationClasses()获取配置文件相关的类。在这个方法的第一行中有一段代码

Class<?>[] classes = mergedConfig.getClasses();

mergedConfig.classes成员变量中记录了哪些类被@SpringBootTest指定了。
比如下面这种情况

@SpringBootTest(classes = {AppTest.class, AppFDFS0.class})

会有如下结果
在这里插入图片描述
可以看到注册的每个类在mergedConfig.classes成员变量中显示了两次。
如果@SpringBootTest不指定任何类

@SpringBootTest

会有如下结果
在这里插入图片描述
暂时先不去管为啥mergedConfig.classes成员变量中会有两个相同的class对象以及这个成员变量是在何处复制的,继续运行代码会遇到一个判断语句

		if (containsNonTestComponent(classes) || mergedConfig.hasLocations()) {
			return classes;
		}

查看containsNonTestComponent函数的源码可以知道,这个函数会遍历传入的数组,查找没有被@TestConfiguration标识的类,但凡有一个这样的类就会返回true。
mergedConfig.hasLocations()这个函数是用来查看mergedConfig.locations这个成员变量,不论@SpringBootTest指定或者不指定类,都不会对其造成影响。

综合上述两点可以知道当@SpringBootTest指定类时,mergedConfig.classes中非空,如果mergedConfig.classes中起码有一个类没有被@TestConfiguration标识,则containsNonTestComponent(classes)返回true,进而整个SpringBootTestContextBootstrapper.getOrFindConfigurationClasses()方法直接返回mergedConfig.classes

如果@SpringBootTest没有指定类。则mergedConfig.classes为空,SpringBootTestContextBootstrapper.getOrFindConfigurationClasses()方法不会像上面那样直接返回而是去扫描包。

SpringBootTestContextBootstrapper.processMergedContextConfiguration()部分源码

		//判断mergedConfig.classes是否符合要求,如果符合要求,直接返回mergedConfig.classes中的值
		Class<?>[] classes = mergedConfig.getClasses();
		if (containsNonTestComponent(classes) || mergedConfig.hasLocations()) {
			return classes;
		}
		//开始扫描包
		Class<?> found = new SpringBootConfigurationFinder()
				.findFromClass(mergedConfig.getTestClass());

SpringBootConfigurationFinder() .findFromClass()函数会进行一长串的函数调用

scanCandidateComponents:419, ClassPathScanningCandidateComponentProvider (org.springframework.context.annotation)
findCandidateComponents:316, ClassPathScanningCandidateComponentProvider (org.springframework.context.annotation)
scanPackage:67, SpringBootConfigurationFinder (org.springframework.boot.test.context)
findFromPackage:59, SpringBootConfigurationFinder (org.springframework.boot.test.context)
findFromClass:52, SpringBootConfigurationFinder (org.springframework.boot.test.context)
getOrFindConfigurationClasses:239, SpringBootTestContextBootstrapper (org.springframework.boot.test.context)

最后进入到ClassPathScanningCandidateComponentProvider.scanCandidateComponents当中
在其中将真正符合要求的Candidate筛选出来。而决定他们筛选哪个包下的类则是由SpringBootConfigurationFinder.scanPackage()这个函数决定的。
根据SpringBootConfigurationFinder.scanPackage()的算法,在解析不带有classes的@SpringBootTest时,先使用ClassPathScanningCandidateComponentProvider.scanCandidateComponents对测试类中的包进行扫描,如果没有找到一个(不能多不能少,只能是一个)符合要求的类时,就会找其上一级包中有没有相应的类(只招上一级包中的类,不会再递归找上一级包中的包中类)。依照此方法直至找到一个符合要求的类或是没有找到返回null。

至此,了解了@SpringBootTest指定或者不指定类时的类加载情况。不过还有些问题:
1、mergedConfig.classes成员变量何时被赋值?
2、添加@SpringBootTest和不添加@SpringBootTest的对程序的运行流程有啥影响
3、费这么大劲获取到的这些类在之后的流程中起到了什么作用

  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值