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
1. We have explicitly disabled bean overriding
2. Had the bean (method) name been different (i.e. Dependency beanDependency
), we could have gotten the NoUniqueBeanDefinitionException
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.
Comment From: jhoeller
I can certainly understand that even subtle impact here is going to show somewhere in a scenario with 3000 modules...
On the upside, the new behavior in 6.1 should not break anywhere, just enable scenarios that previously did not work at all. Since the @Bean
method always wins next to an annotated component class, it can be used to explicitly override injection behavior for pre-annotated beans (e.g. selecting a different constructor than the regular autowired algorithm). In that sense, this is a feature, not a bug. I'll try to add some explicit documentation notes around this.
As for parameter names, we embrace them more and more, in particular in the upcoming 6.2 (see https://github.com/spring-projects/spring-framework/issues/28122). So I would not recommend turning it off, rather arranging everything for parameter names to match bean names, and for the bean name generator setup to use a shared naming policy.