Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Unexpected bean override / inject behavior #32825

Closed
gavlyukovskiy opened this issue May 14, 2024 · 1 comment
Closed

Unexpected bean override / inject behavior #32825

gavlyukovskiy opened this issue May 14, 2024 · 1 comment
Assignees
Labels
in: core Issues in core modules (aop, beans, core, context, expression) type: documentation A documentation task
Milestone

Comments

@gavlyukovskiy
Copy link

gavlyukovskiy commented May 14, 2024

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.

@spring-projects-issues spring-projects-issues added the status: waiting-for-triage An issue we've not yet triaged or decided on label May 14, 2024
@jhoeller jhoeller added the in: core Issues in core modules (aop, beans, core, context, expression) label May 14, 2024
@jhoeller jhoeller self-assigned this May 14, 2024
@jhoeller jhoeller added type: documentation A documentation task and removed status: waiting-for-triage An issue we've not yet triaged or decided on labels Jun 7, 2024
@jhoeller jhoeller added this to the 6.1.9 milestone Jun 7, 2024
@jhoeller
Copy link
Contributor

jhoeller commented Jun 7, 2024

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 #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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
in: core Issues in core modules (aop, beans, core, context, expression) type: documentation A documentation task
Projects
None yet
Development

No branches or pull requests

3 participants