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()
, andBeanDefinitionPropertiesCodeGenerator.addInitDestroyHint()
now parse fully-qualified method names to locate the correct init/destroy methods. -
AbstractAutowireCapableBeanFactory
andDisposableBeanAdapter
delegate to a newMethodDescriptor
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 makeMethodDescriptor
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()
andDisposableBean#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.