Skip to content

Nondeterministic behaviour when replacing @Context bean #11758

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

Open
sdedic opened this issue Apr 17, 2025 · 0 comments
Open

Nondeterministic behaviour when replacing @Context bean #11758

sdedic opened this issue Apr 17, 2025 · 0 comments

Comments

@sdedic
Copy link

sdedic commented Apr 17, 2025

Expected Behavior

In my code, I wanted to replace a hikari DataSource, which is a @Context bean; this is by definition a singleton auto-initialized with the context start. My factory replaced it as

@Replaces(DataSource.class)
@Primary
@EachBean(DataSourceConfiguration.class)

Note the missing @Contextqualifier in this replacement; I would expect that the created bean definition has @Singleton (default) scope. This kind of replacement usually works - as the BeanDefinition is replaced incl. its construction procedure.
The expected outcome was that my replacement Bean will be seen as DataSource; subject to BeanCreation listeners etc. and that the original DataSource implementation will not be instantiated at all.

Actual Behaviour

The observed behaviour with @Context scoped beans is that:

  • both replacement, but also the original bean instances are created. Any unwanted side effect of the replaced bean's init code will happen.
  • sometimes, the original (not replacement) bean is injected
  • whether one or two instanceof of the bean are created depends on bean initialization order.

The example application demonstrates, that @Replaces changes scope, under regular circumstances. Not documented explcitly, but is implied the concept of "replacing the bean definition" (that includes the scope spec). This can be seen in ConsumerBean that injects Prototype beans that replace Singleton original definition. If it is not indended to work that way, it should be prominently documented and checked during the replacement process with at least warning log.

The example application demonstrates, in the default provided configuration, that both the replacement (ReplacementMultiBean) AND original (MultiBean) instances are created during startup. Only the proper (ReplacementMultiBean) is however injectable. The MultiBean should have not been created at all.

Next, if the example app is modified so that ReplacementConsumer is @Order(100) (instead of -1000), the original bean is injected and startup fails on assertion. The ReplacementMultiBean is never created:

  • beanCandidateCache is populated with ReplacementBeanFactory definition by findBeanCandidatesInternal (OK)
  • DefaultBeanContext.BeanKey equals for both ReplacementBeanFactory and MultiBeanFactory methods
  • the MultiBean (earlier initialized singleton) is already in the singletonByArgumentAndQualifier map

Therefore, even though SingletonScope.findBeanRegistration is called with ReplacementBeanFactory bean definition, the OTHER factory's bean instance is actually returned.

Note that the @Order is provided to demonstrate the outcome depends on eager initialization order. If @Order is absent (in most cases it is), the initialization order is random/unspecified and in my case even depended on FQN of the bean/factory: mere refactoring of example app classes into different package changed the behaviour from non-working to working.

Finally, if the code is changed so that ReplacementBeanFactory produces @Context-scoped bean, the original MultiBean definition is properly replaced even during eager singleton initialization at the context startup, and the MultiBean instance is never created at all.

Steps To Reproduce

  1. Clone https://github.com/sdedic/micronaut-dbbugs2 example application, branch contextbean-errors.
  2. Run the application, observe the logs. Both replaced and the replacement bean is created, creation is logged.
  3. change the @Order of ReplacementConsumer as described above, and re-run. Wrong bean is injected, startup fails
  4. change the @Order back,uncomment @Context scope in ReplacementBeanFactory. Now only the proper instance of the bean is created.

Environment Information

  • Ubuntu Linux 22.04
  • Micronaut 4.7.6

Example Application

https://github.com/sdedic/micronaut-dbbugs2

Version

4.7.6

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant