Overview

Prior to this commit, private (and non-visible package-private) init/destroy methods were not supported in AOT mode. The reason is that such methods are tracked using their fully-qualified method names, and the AOT support for init/destroy methods previously did not take fully-qualified method names into account. In addition, the invocation order of init/destroy methods differed between standard JVM mode and AOT mode.

This commit addresses these issues in the following ways.

  • AbstractAutowireCapableBeanFactory.invokeCustomInitMethod(), DisposableBeanAdapter.determineDestroyMethod(), and BeanDefinitionPropertiesCodeGenerator.addInitDestroyHint() now parse fully-qualified method names to locate the correct init/destroy methods.

  • AbstractAutowireCapableBeanFactory and DisposableBeanAdapter delegate to a new MethodDescriptor record which encapsulates the parsing of fully qualified method names; however, BeanDefinitionPropertiesCodeGenerator duplicates this logic since it resides in a different package, and we do not currently want to make MethodDescriptor public.

  • Init/destroy methods detected via annotations (such as @PostConstruct and @PreDestroy) are now invoked prior to init/destroy methods that are explicitly configured by name or convention. This aligns with the invocation order in standard JVM mode; however, InitializingBean#afterPropertiesSet() and DisposableBean#destroy() are still invoked before annotated init/destroy methods in AOT mode which differs from standard JVM mode.

  • Unit and integration tests have been updated to test the revised behavior.

Related Issues

  • 10614

  • 28083

  • 30654

  • 30755

Comment From: sbrannen

@jhoeller and @snicoll, if either of you have time, I would appreciate a quick review of this PR.

In any case, I think we should decide if we want to address the following (with a separate GitHub issue).

however, InitializingBean#afterPropertiesSet() and DisposableBean#destroy() are still invoked before annotated init/destroy methods in AOT mode which differs from standard JVM mode.

This difference in behavior stems from the fact that we merge init/destroy method names during AOT processing, and that causes @PostConstruct/@PreDestroy methods to be invoked after InitializingBean#afterPropertiesSet() and DisposableBean#destroy() when running in AOT mode.

Comment From: sbrannen

Fixed in 3181dca5efe48b74b017d5461d7572af000ab52c

Comment From: sbrannen

Reopening to ensure this has been tested in a native image, regarding reflection hints, etc.

Comment From: sbrannen

Reopening to ensure this has been tested in a native image, regarding reflection hints, etc.

I have confirmed that the fix works within a GraalVM native image by creating the sample application as described in #30654 and modifying the build.gradle script as follows.

ext['spring-framework.version'] = '6.0.11-SNAPSHOT'

repositories {
    mavenCentral()
    maven {
        url "https://repo.spring.io/snapshot"
    }
}

To be thorough, I also verified the recently introduced support for shadowed package-private init/destroy methods (see #30718).

Using the following classes...

// resides in a different package than the other beans
public class SubpackageSuperBean {

    @PostConstruct
    void init() {
        System.out.println("Init SubpackageSuperBean");
    }

    @PreDestroy
    void destroy() {
        System.out.println("Destroy SubpackageSuperBean");
    }

}
public class SuperBean extends SubpackageSuperBean {

    @PostConstruct
    private void init() {
        System.out.println("Init SuperBean");
    }

    @PreDestroy
    private void destroy() {
        System.out.println("Destroy SuperBean");
    }

}
public class DemoBean extends SuperBean {

    @PostConstruct
    private void init() {
        System.out.println("Init DemoBean");
    }

    @PreDestroy
    private void destroy() {
        System.out.println("Destroy DemoBean");
    }

}

... the output from the native executable is:

Init SubpackageSuperBean
Init SuperBean
Init DemoBean

Destroy DemoBean
Destroy SuperBean
Destroy SubpackageSuperBean

In light of that, I am closing this issue.