Description
Affects: 6.1.2
It is not present in spring-framework
version 6.0.15 (shipped with Spring Boot 3.1.7)
When upgrading from any Spring Boot 3.1 version (tried 3.1.2 and 3.1.7) of Spring Boot to Spring Boot 3.2 (both 3.2.0 and 3.2.1 tried) our tests break with issues such as expected single matching bean but found 2: clientAdminDataSource,dataSource
or expected single matching bean but found 2: clientAdminTemplateJdbcTemplate, jdbcTemplate
. This has worked through all Spring Boot versions up until 3.2.0
I then went and tried to reproduce the issue, thinking the issue was that autowiring in 3.2.0 somehow was not excluding the @Configuration
bean that was set to be excluded in our @TestConfiguration
bean, thus ending up with multiple candidate beans. The reproduction (at fatso83/issue-reproductions/spring-3.2.0-autowire-issue) failed to display the issue, which forced me to do some more debugging, comparing test runs in 3.1 and 3.2. It turns out
- exclusion seems to work fine in 3.2
- there were multiple candidate beans in the 3.1 tests as well, but the autowire code managed to figure out which was the right one (based on the heuristics of name matching, etc)
My attempt at reproducing was unsuccessful, so my only clues must be in what is different.
The main difference I can see is that in our production setup, the beans that fails to map up are either DataSource
's or JdbcTemplates
. So type/class seems to matter somehow. Both of these types have @Bean
's supplied by Spring itself (the default ones). This is of course not the case for my Foo
and Bar
classes in the repro.
Where the bug manifests in Spring: DefaultListableBeanFactory#determineAutowireCandidate()
.
When DefaultListableBeanFactory#doResolveDependency()
calls that method the code looks like this:
// Step 4: determine single candidate
if (matchingBeans.size() > 1) {
autowiredBeanName = determineAutowireCandidate(matchingBeans, descriptor);
if (autowiredBeanName == null) {
In Spring 3.1.7 (and 3.1.2), autowiredBeanName
returns one bean. In Spring 3.2.0 and 3.2.1, it returns null
. The reason is this code:
if ((beanInstance != null && this.resolvableDependencies.containsValue(beanInstance)) ||
matchesBeanName(candidateName, descriptor.getDependencyName())) {
return candidateName;
}
descriptor.getDependencyName()
returns null in 3.2, but not 3.1, so naturally this fails.
It was at this point I got dizzy trying to debug this, diving further into a rabbit hole I had no idea where was going, so
that's when I gave up and just submitted the bug without knowing what goes wrong.