Skip to content

Conversation

@Jarvx
Copy link

@Jarvx Jarvx commented Oct 24, 2025

Issue link: #11656

Issue description

  1. The test reproduces the issue: it creates a bean via ApplicationContext.createBean (which should invoke @PostConstruct once), then registers the same instance via registerSingleton and asserts that @PostConstruct is not invoked a second time. On the original code, the test failed with initCount=2, confirming the double @PostConstruct invocation.

  2. The patch resolves the issue by tracking instances created by the context (via createBean) in an identity-based set. When registerSingleton is called with inject=true, it checks this set: if the instance was already initialized by the context, it performs injection only (without initialization), avoiding a second @PostConstruct call. The test passes after the patch, and the full test suite builds successfully.

Note: this approach introduces a global set of initialized instances, which may have implications (e.g., strong references and non-concurrent collection);

Solution

please run the command

./gradlew :test-suite-kotlin-ksp:test

@Jarvx Jarvx changed the title add a bug fix and a test case for #11656 Fixing 11656: Handling the issue with @PostConstruct annotated bean Oct 24, 2025
@Jarvx Jarvx changed the title Fixing 11656: Handling the issue with @PostConstruct annotated bean [GenAI] Fixing 11656: Handling the issue with @PostConstruct annotated bean Oct 24, 2025
// If this instance was created by this context earlier, it may already have been injected/initialized.
// In such case skip duplicate injection/initialization. Use the beansCreatedByContext set to detect this.
boolean createdByThisContext;
synchronized (beansCreatedByContext) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why you use synchronizedSet together with synchronized block?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh. Collections.synchronizedSet(...) already ensures that all accesses

so the block is redundant. I’ll remove the extra synchronization. Thanks.

@dstepanov
Copy link
Contributor

It shouldn't matter if the bean is created by the bean context or not, the triggering of the post construct should act the same if the bean is create manually or not. Not sure for the current behaviour. I would expect that createBean will trigger all the life-cycle methods and register will not.

public class DoublePostConstructTest {

@Test
void postConstructCalledOnceWhenCreateAndRegisterSingleton() {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This PR may result in a memory leak since each call to createBean will create a new instance to be tracked forever. This is not the current behaviour of createBean which relies on the user to invoke post construct hooks.

It is actually debatable whether this is even a bug

// If this instance was created by this context earlier, it may already have been injected/initialized.
// In such case skip duplicate injection/initialization. Use the beansCreatedByContext set to detect this.
boolean createdByThisContext;
synchronized (beansCreatedByContext) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

introduction of synchronisation on the createBean path will likely result in performance degradation

@Jarvx
Copy link
Author

Jarvx commented Oct 31, 2025

I have updated this PR by adding new fixes and new reproducer test file.

@dstepanov
Copy link
Contributor

As I wrote before, tests need to be in Java for the core functionality

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

Successfully merging this pull request may close these issues.

4 participants