Skip to content

Unexpected bean override / inject behavior #32825

Closed
@gavlyukovskiy

Description

@gavlyukovskiy

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.

Metadata

Metadata

Assignees

Labels

in: coreIssues in core modules (aop, beans, core, context, expression)type: documentationA documentation task

Type

No type

Projects

No projects

Milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions