This is a follow-up from the spring-boot gitter channel. When upgrading the spring-boot bom from 2.2.5.RELEASE to 2.2.6.RELEASE I get this exception:
Caused by: org.springframework.context.annotation.ConflictingBeanDefinitionException: Annotation-specified bean name 'aBean' for bean class [com.example.demo.ABean] conflicts with existing, non-compatible bean definition of same name and class [com.example.demo.ABean]
at org.springframework.context.annotation.ClassPathBeanDefinitionScanner.checkCandidate(ClassPathBeanDefinitionScanner.java:349) ~[spring-context-5.2.5.RELEASE.jar:5.2.5.RELEASE]
at org.springframework.context.annotation.ClassPathBeanDefinitionScanner.doScan(ClassPathBeanDefinitionScanner.java:287) ~[spring-context-5.2.5.RELEASE.jar:5.2.5.RELEASE]
The problem remains even when using 2.2.7.BUILD-SNAPSHOT. Here's what I've discovered while debugging it:
- so what happens is that the classpath scanner detects my test configuration (which has
@ComponentScanannotation) and that adds theaBeanbean, but it also picks up theDemoApplication(the class with the main method). So it does classpath scanning for theDemoApplicationagain so theComponentScanAnnotationParserperforms the scan, and finds theaBeanagain and then calls theisCompatiblemethod here: https://github.com/spring-projects/spring-framework/blob/master/spring-context/src/main/java/org/springframework/context/annotation/ClassPathBeanDefinitionScanner.java#L364
This method returns false so an exception is raised.
-
It is important to have the
spring-context-indexerdependency in your classpath. If you have it then the beans passed toisCompatiblemethod will getgetSource()return null. -
it is also important to name the bean
ABean. If I name itDemoBeanthen the exception does not happen - I think the component scan finds candidates sorted by beanName. So ifABeancomes beforeDemoApplicationthen it will fail if I name itDemoBeanthen it will come afterDemoApplicationand it will not fail.
Here's a repository that reproduces the problem: https://github.com/ptahchiev/conflicting-bean
Comment From: jhoeller
This looks like a side effect of #24638, it seems we need to adapt the isCompatible check accordingly. We also need a backport here since the original change got backported as well (even if not released yet in the backport branches).
Comment From: jhoeller
@ptahchiev I see why the source would not match here... which now kicks in after the previous fix since both the existing and the new bean definition are of type ScannedGenericBeanDefinition now. However, shouldn't the definitions be equal according to the subsequent equals check in isCompatible? Any idea why they might differ?
Comment From: jhoeller
Also, why isn't the index kicking in for the second scan attempt? @snicoll I wonder whether this has to do with some Boot-specific behavior? This wouldn't have materialized before since the index effectively registered its bean definitions like explicit user definitions which always override... Now a mismatch between index and manual scan would show up here.
Comment From: ptahchiev
@jhoeller They differ because ScannedGenricBeanDefinition newDefinition has no attributes, while ScannedGenericBeanDefinition existingDefinition has 1 attribute in the attributes map:
org.springframework.context.annotation.ConfigurationClassPostProcessor.configurationClass -> {BeanMetadataAttribute@5436} "metadata attribute 'org.springframework.context.annotation.ConfigurationClassPostProcessor.configurationClass'"
Comment From: snicoll
@jhoeller there is nothing in Spring Boot that touches the index, it is 100% baked in a single place in the core framework.
Comment From: jhoeller
@ptahchiev Thanks for checking! Our equality check is clearly too rigid then, and we'll have to relax it for 5.2.6. We should also store the source for the index-originated bean definitions there, even if that will not necessarily be identical to the source resouce of an equivalent scan result.
@snicoll I just wonder why a second scan attempt would bypass the index in such a scenario. Is there anything in Boot that could influence this, e.g. custom include filters? The attached repro project looks straightforward enough to me, does it reveal anything out of the ordinary to you?
To be clear, even with a second scan bypassing the index, the outcome should not fail with a conflicting bean definition exception. So this is not immediately relevant for this issue, we'll have to fix in the bean definition compatibility check. It'd just be good to understand how we get there.
Comment From: snicoll
If you configure the scan with an include or exclude filter that the index cannot honour, we will fallback to regular component scan at runtime. But that's not what's happening here, the index is used in both cases.
Comment From: jhoeller
After checking with Stephane here, it turns out that the index is indeed used in both cases... but due to the lack of a source being stored and the difference of the bean definition attributes, it fails with a conflict. For aligned behavior between index and scan, all we need is consistent source comparisons. I'll fix this right away, to be released in 5.2.6 tomorrow.
Thanks for the immediate turnaround to both of you!
Comment From: jhoeller
@snicoll @ptahchiev This should be available in the latest 5.2.6 snapshot now. Please give it a try!
Comment From: snicoll
I did and the test in the sample Petar shared now works. Thank for the quick turnaround Juergen!