Support [package-]private init/destroy methods in AOT mode
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 vastly 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. Closes gh-30692
This commit is contained in:
@@ -18,18 +18,26 @@ package org.springframework.context.annotation;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.function.BiConsumer;
|
||||
|
||||
import jakarta.annotation.PostConstruct;
|
||||
import jakarta.annotation.PreDestroy;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.aot.test.generate.TestGenerationContext;
|
||||
import org.springframework.beans.factory.DisposableBean;
|
||||
import org.springframework.beans.factory.InitializingBean;
|
||||
import org.springframework.beans.factory.annotation.InitDestroyAnnotationBeanPostProcessor;
|
||||
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
|
||||
import org.springframework.beans.factory.support.RootBeanDefinition;
|
||||
import org.springframework.context.ApplicationContextInitializer;
|
||||
import org.springframework.context.annotation.lifecyclemethods.InitDestroyBean;
|
||||
import org.springframework.context.annotation.lifecyclemethods.PackagePrivateInitDestroyBean;
|
||||
import org.springframework.context.aot.ApplicationContextAotGenerator;
|
||||
import org.springframework.context.support.GenericApplicationContext;
|
||||
import org.springframework.core.test.tools.CompileWithForkedClassLoader;
|
||||
import org.springframework.core.test.tools.Compiled;
|
||||
import org.springframework.core.test.tools.TestCompiler;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
@@ -156,6 +164,79 @@ class InitDestroyMethodLifecycleTests {
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @see org.springframework.context.aot.ApplicationContextAotGeneratorTests#processAheadOfTimeWhenHasMultipleInitDestroyMethods
|
||||
*/
|
||||
@Test
|
||||
@CompileWithForkedClassLoader
|
||||
void jakartaAnnotationsWithCustomSameMethodNamesWithAotProcessingAndAotRuntime() {
|
||||
Class<CustomAnnotatedPrivateSameNameInitDestroyBean> beanClass = CustomAnnotatedPrivateSameNameInitDestroyBean.class;
|
||||
GenericApplicationContext applicationContext = new GenericApplicationContext();
|
||||
|
||||
DefaultListableBeanFactory beanFactory = applicationContext.getDefaultListableBeanFactory();
|
||||
AnnotationConfigUtils.registerAnnotationConfigProcessors(beanFactory);
|
||||
|
||||
RootBeanDefinition beanDefinition = new RootBeanDefinition(beanClass);
|
||||
beanDefinition.setInitMethodName("customInit");
|
||||
beanDefinition.setDestroyMethodName("customDestroy");
|
||||
beanFactory.registerBeanDefinition("lifecycleTestBean", beanDefinition);
|
||||
|
||||
testCompiledResult(applicationContext, (initializer, compiled) -> {
|
||||
GenericApplicationContext aotApplicationContext = createApplicationContext(initializer);
|
||||
CustomAnnotatedPrivateSameNameInitDestroyBean bean = aotApplicationContext.getBean("lifecycleTestBean", beanClass);
|
||||
|
||||
assertThat(bean.initMethods).as("init-methods").containsExactly(
|
||||
"afterPropertiesSet",
|
||||
"@PostConstruct.privateCustomInit1",
|
||||
"@PostConstruct.sameNameCustomInit1",
|
||||
"customInit"
|
||||
);
|
||||
|
||||
aotApplicationContext.close();
|
||||
assertThat(bean.destroyMethods).as("destroy-methods").containsExactly(
|
||||
"destroy",
|
||||
"@PreDestroy.sameNameCustomDestroy1",
|
||||
"@PreDestroy.privateCustomDestroy1",
|
||||
"customDestroy"
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
@CompileWithForkedClassLoader
|
||||
void jakartaAnnotationsWithPackagePrivateInitDestroyMethodsWithAotProcessingAndAotRuntime() {
|
||||
Class<SubPackagePrivateInitDestroyBean> beanClass = SubPackagePrivateInitDestroyBean.class;
|
||||
GenericApplicationContext applicationContext = new GenericApplicationContext();
|
||||
|
||||
DefaultListableBeanFactory beanFactory = applicationContext.getDefaultListableBeanFactory();
|
||||
AnnotationConfigUtils.registerAnnotationConfigProcessors(beanFactory);
|
||||
|
||||
RootBeanDefinition beanDefinition = new RootBeanDefinition(beanClass);
|
||||
beanDefinition.setInitMethodName("initMethod");
|
||||
beanDefinition.setDestroyMethodName("destroyMethod");
|
||||
beanFactory.registerBeanDefinition("lifecycleTestBean", beanDefinition);
|
||||
|
||||
testCompiledResult(applicationContext, (initializer, compiled) -> {
|
||||
GenericApplicationContext aotApplicationContext = createApplicationContext(initializer);
|
||||
SubPackagePrivateInitDestroyBean bean = aotApplicationContext.getBean("lifecycleTestBean", beanClass);
|
||||
|
||||
assertThat(bean.initMethods).as("init-methods").containsExactly(
|
||||
"InitializingBean.afterPropertiesSet",
|
||||
"PackagePrivateInitDestroyBean.postConstruct",
|
||||
"SubPackagePrivateInitDestroyBean.postConstruct",
|
||||
"initMethod"
|
||||
);
|
||||
|
||||
aotApplicationContext.close();
|
||||
assertThat(bean.destroyMethods).as("destroy-methods").containsExactly(
|
||||
"DisposableBean.destroy",
|
||||
"SubPackagePrivateInitDestroyBean.preDestroy",
|
||||
"PackagePrivateInitDestroyBean.preDestroy",
|
||||
"destroyMethod"
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void allLifecycleMechanismsAtOnce() {
|
||||
Class<?> beanClass = AllInOneBean.class;
|
||||
@@ -188,6 +269,31 @@ class InitDestroyMethodLifecycleTests {
|
||||
return beanFactory;
|
||||
}
|
||||
|
||||
private static GenericApplicationContext createApplicationContext(
|
||||
ApplicationContextInitializer<GenericApplicationContext> initializer) {
|
||||
|
||||
GenericApplicationContext context = new GenericApplicationContext();
|
||||
initializer.initialize(context);
|
||||
context.refresh();
|
||||
return context;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private static void testCompiledResult(GenericApplicationContext applicationContext,
|
||||
BiConsumer<ApplicationContextInitializer<GenericApplicationContext>, Compiled> result) {
|
||||
|
||||
TestCompiler.forSystem().with(processAheadOfTime(applicationContext)).compile(compiled ->
|
||||
result.accept(compiled.getInstance(ApplicationContextInitializer.class), compiled));
|
||||
}
|
||||
|
||||
private static TestGenerationContext processAheadOfTime(GenericApplicationContext applicationContext) {
|
||||
ApplicationContextAotGenerator generator = new ApplicationContextAotGenerator();
|
||||
TestGenerationContext generationContext = new TestGenerationContext();
|
||||
generator.processAheadOfTime(applicationContext, generationContext);
|
||||
generationContext.writeGeneratedContent();
|
||||
return generationContext;
|
||||
}
|
||||
|
||||
|
||||
static class InitializingDisposableWithShadowedMethodsBean extends InitDestroyBean implements
|
||||
InitializingBean, DisposableBean {
|
||||
|
||||
@@ -257,9 +257,9 @@ class ApplicationContextAotGeneratorTests {
|
||||
GenericApplicationContext freshApplicationContext = toFreshApplicationContext(initializer);
|
||||
assertThat(freshApplicationContext.getBeanDefinitionNames()).containsOnly("initDestroyComponent");
|
||||
InitDestroyComponent bean = freshApplicationContext.getBean(InitDestroyComponent.class);
|
||||
assertThat(bean.events).containsExactly("customInit", "init");
|
||||
assertThat(bean.events).containsExactly("init", "customInit");
|
||||
freshApplicationContext.close();
|
||||
assertThat(bean.events).containsExactly("customInit", "init", "customDestroy", "destroy");
|
||||
assertThat(bean.events).containsExactly("init", "customInit", "destroy", "customDestroy");
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user