Description
Affects: 6.1.x, partly 5.3.x, 6.0.x
Spring Framework 6.1 introduced a change to allow overriding auto-scanned beans silently as those are guaranteed to be equivalent, the issue and the solution completely makes sense for the problem it solved, but we have found a small problem with that behavior.
This change allows to subtly override the behavior from a @Configuration
class in a case like this:
@Component
public class Dependency {
@Autowired
public Dependency(ApplicationContext context) { // context as a placeholder dependency
this(context, "from @Component");
}
public Dependency(ApplicationContext context, String origin) {
System.out.println(origin);
}
}
the stereotype annotation indicates that the first constructor will be called, but it can be overridden by using explicit configuration:
@Bean
public Dependency dependency(ApplicationContext context) {
return new Dependency(context, "from @Bean");
}
(the code prints from @Bean
, before 6.1 results in BeanDefinitionOverrideException
)
This problem isn't as severe since both declarations are, without a doubt, explicit, however it is slightly unexpected because
- We have explicitly disabled bean overriding
- Had the bean (method) name been different (i.e.
Dependency beanDependency
), we could have gotten theNoUniqueBeanDefinitionException
Another somewhat related to unexpected bean overriding (but likely different :)), is the fact that Spring uses the field name at the injection point as a qualifier in case of conflicts, which also creates the same problem when used together with FullyQualifiedAnnotationBeanNameGenerator
. In this case, we would have 2 distinct beans named dependency
and com.example.Dependency
, but since most code injecting the bean looks like this
@Service
public class MyService {
private final Dependency dependency;
public MyService(Dependency dependency) {
this.dependency = dependency;
}
}
The added configuration effectively results in bean overriding for all the dependents (this also affects Spring before 6.1). In regards to this one, I wonder if you'd recommend doing this:
beanFactory.setParameterNameDiscoverer(null)
to disable the field name matching (so that we require explicit @Qualifier
) or can it have some unexpected consequences?
For some context: these issues might look quite synthetic, but we're encountering these on a monthly basis while maintaining a large mono-repository with around 3,000 shared modules, many of which declare spring beans (99.99% rely on auto-scanned bean definitions) and having a configuration is some rogue module often overrides the bean for everyone else.