Imagine, you have a bean with an init method, annotated with @PostConstruct and a configuration class, referencing the same init method on its own.

public class MyBean {

    @PostConstruct
    public void init() {
        ...
    }

}
@Configuration
public class MyConfiguration {

    @Bean(initMethod = "init")
    public MyBean myBean() {
        return new MyBean();
    }
}

The init method will be invoked only once (what is pretty well).

This is true for all method modifiers except private. As soon as you change the method modifier to private the init method is invoked twice.

public class MyBean {

    @PostConstruct
-    public void init() {
+    private void init() {
        ...
    }
}

Comment From: mroccyen

Hi, I test it and maked a same result, I found the problem.

1、when it is private Spring Private init/destroy method may be invoked twice Spring Private init/destroy method may be invoked twice Spring Private init/destroy method may be invoked twice

externallyManagedInitMethods value is 'org.springframework.issues.issue_28083.MyBean.init', but initMethod value is 'init', invokeCustomInitMethod method will be invoked

2、when it is not private Spring Private init/destroy method may be invoked twice Spring Private init/destroy method may be invoked twice

externallyManagedInitMethods value is 'init', initMethod value is 'init', invokeCustomInitMethod method will not be invoked

I found that the value of the location setting is as follows: Spring Private init/destroy method may be invoked twice

Is there a problem here?

Comment From: mroccyen

Spring Private init/destroy method may be invoked twice

Spring Private init/destroy method may be invoked twice

Both of the above modifications can get the correct answer.

Comment From: snicoll

@MrocCyen thanks but please do not share screenshots as it bloats the issue report. I've hidden those for now.

Comment From: mroccyen

@MrocCyen thanks but please do not share screenshots as it bloats the issue report. I've hidden those for now.

Sorry, I'll pay attention next time.

Excuse me, is it correct for me to fix it like that above?

Comment From: snicoll

Excuse me, is it correct for me to fix it like that above?

I haven't investigated this in detail so I can't say for sure.

Comment From: sbrannen

I reproduced the described behavior with the following test class.

@SpringJUnitConfig
class InitMethodTests {

    @Test
    @DirtiesContext
    void test() {
        assertSoftly(softly -> {
            softly.assertThat(PublicLifecycleMethodBean.initCounter).as("public init-method").hasValue(1);
            softly.assertThat(PrivateLifecycleMethodBean.initCounter).as("private init-method").hasValue(1);
        });
    }

    @AfterAll
    static void afterAll() {
        assertSoftly(softly -> {
            softly.assertThat(PublicLifecycleMethodBean.destroyCounter).as("public destroy-method").hasValue(1);
            softly.assertThat(PrivateLifecycleMethodBean.destroyCounter).as("private destroy-method").hasValue(1);
        });
    }

    @Configuration
    static class Config {

        @Bean(initMethod = "publicInit", destroyMethod = "publicDestroy")
        Object publicLifecycleMethodBean() {
            return new PublicLifecycleMethodBean();
        }

        @Bean(initMethod = "privateInit", destroyMethod = "privateDestroy")
        Object privateLifecycleMethodBean() {
            return new PrivateLifecycleMethodBean();
        }

    }

    static class PublicLifecycleMethodBean {

        static final AtomicInteger initCounter = new AtomicInteger();
        static final AtomicInteger destroyCounter = new AtomicInteger();

        @PostConstruct
        public void publicInit() {
            initCounter.incrementAndGet();
        }

        @PreDestroy
        public void publicDestroy() {
            destroyCounter.incrementAndGet();
        }

    }

    static class PrivateLifecycleMethodBean {

        static final AtomicInteger initCounter = new AtomicInteger();
        static final AtomicInteger destroyCounter = new AtomicInteger();

        @PostConstruct
        private void privateInit() {
            initCounter.incrementAndGet();
        }

        @PreDestroy
        private void privateDestroy() {
            destroyCounter.incrementAndGet();
        }

    }

}

The "private init-method" and "private destroy-method" assertions fail due to 2 invocations.

Comment From: sbrannen

Superseded by #28113

Comment From: sbrannen

Related Issues

  • 8455

  • 10762

  • 27449

Comment From: sbrannen

Excuse me, is it correct for me to fix it like that above?

No, that would constitute a breaking change in behavior for private init/destroy methods with the same name declared at multiple levels in a class hierarchy.

We have decided to approach the fix differently, and I have therefore assigned this issue to myself.