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:
parent
f86a69ebfb
commit
3181dca5ef
|
@ -182,7 +182,7 @@ public class InitDestroyAnnotationBeanPostProcessor implements DestructionAwareB
|
||||||
private static String[] safeMerge(@Nullable String[] existingNames, Collection<LifecycleMethod> detectedMethods) {
|
private static String[] safeMerge(@Nullable String[] existingNames, Collection<LifecycleMethod> detectedMethods) {
|
||||||
Stream<String> detectedNames = detectedMethods.stream().map(LifecycleMethod::getIdentifier);
|
Stream<String> detectedNames = detectedMethods.stream().map(LifecycleMethod::getIdentifier);
|
||||||
Stream<String> mergedNames = (existingNames != null ?
|
Stream<String> mergedNames = (existingNames != null ?
|
||||||
Stream.concat(Stream.of(existingNames), detectedNames) : detectedNames);
|
Stream.concat(detectedNames, Stream.of(existingNames)) : detectedNames);
|
||||||
return mergedNames.distinct().toArray(String[]::new);
|
return mergedNames.distinct().toArray(String[]::new);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -72,6 +72,7 @@ import org.springframework.util.StringUtils;
|
||||||
*
|
*
|
||||||
* @author Phillip Webb
|
* @author Phillip Webb
|
||||||
* @author Stephane Nicoll
|
* @author Stephane Nicoll
|
||||||
|
* @author Sam Brannen
|
||||||
* @since 6.0
|
* @since 6.0
|
||||||
*/
|
*/
|
||||||
class BeanDefinitionPropertiesCodeGenerator {
|
class BeanDefinitionPropertiesCodeGenerator {
|
||||||
|
@ -138,7 +139,25 @@ class BeanDefinitionPropertiesCodeGenerator {
|
||||||
}
|
}
|
||||||
|
|
||||||
private void addInitDestroyHint(Class<?> beanUserClass, String methodName) {
|
private void addInitDestroyHint(Class<?> beanUserClass, String methodName) {
|
||||||
Method method = ReflectionUtils.findMethod(beanUserClass, methodName);
|
Class<?> methodDeclaringClass = beanUserClass;
|
||||||
|
|
||||||
|
// Parse fully-qualified method name if necessary.
|
||||||
|
int indexOfDot = methodName.lastIndexOf('.');
|
||||||
|
if (indexOfDot > 0) {
|
||||||
|
String className = methodName.substring(0, indexOfDot);
|
||||||
|
methodName = methodName.substring(indexOfDot + 1);
|
||||||
|
if (!beanUserClass.getName().equals(className)) {
|
||||||
|
try {
|
||||||
|
methodDeclaringClass = ClassUtils.forName(className, beanUserClass.getClassLoader());
|
||||||
|
}
|
||||||
|
catch (Throwable ex) {
|
||||||
|
throw new IllegalStateException("Failed to load Class [" + className +
|
||||||
|
"] from ClassLoader [" + beanUserClass.getClassLoader() + "]", ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Method method = ReflectionUtils.findMethod(methodDeclaringClass, methodName);
|
||||||
if (method != null) {
|
if (method != null) {
|
||||||
this.hints.reflection().registerMethod(method, ExecutableMode.INVOKE);
|
this.hints.reflection().registerMethod(method, ExecutableMode.INVOKE);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1841,18 +1841,22 @@ public abstract class AbstractAutowireCapableBeanFactory extends AbstractBeanFac
|
||||||
protected void invokeCustomInitMethod(String beanName, Object bean, RootBeanDefinition mbd, String initMethodName)
|
protected void invokeCustomInitMethod(String beanName, Object bean, RootBeanDefinition mbd, String initMethodName)
|
||||||
throws Throwable {
|
throws Throwable {
|
||||||
|
|
||||||
|
Class<?> beanClass = bean.getClass();
|
||||||
|
MethodDescriptor descriptor = MethodDescriptor.create(beanName, beanClass, initMethodName);
|
||||||
|
String methodName = descriptor.methodName();
|
||||||
|
|
||||||
Method initMethod = (mbd.isNonPublicAccessAllowed() ?
|
Method initMethod = (mbd.isNonPublicAccessAllowed() ?
|
||||||
BeanUtils.findMethod(bean.getClass(), initMethodName) :
|
BeanUtils.findMethod(descriptor.declaringClass(), methodName) :
|
||||||
ClassUtils.getMethodIfAvailable(bean.getClass(), initMethodName));
|
ClassUtils.getMethodIfAvailable(beanClass, methodName));
|
||||||
|
|
||||||
if (initMethod == null) {
|
if (initMethod == null) {
|
||||||
if (mbd.isEnforceInitMethod()) {
|
if (mbd.isEnforceInitMethod()) {
|
||||||
throw new BeanDefinitionValidationException("Could not find an init method named '" +
|
throw new BeanDefinitionValidationException("Could not find an init method named '" +
|
||||||
initMethodName + "' on bean with name '" + beanName + "'");
|
methodName + "' on bean with name '" + beanName + "'");
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
if (logger.isTraceEnabled()) {
|
if (logger.isTraceEnabled()) {
|
||||||
logger.trace("No default init method named '" + initMethodName +
|
logger.trace("No default init method named '" + methodName +
|
||||||
"' found on bean with name '" + beanName + "'");
|
"' found on bean with name '" + beanName + "'");
|
||||||
}
|
}
|
||||||
// Ignore non-existent default lifecycle methods.
|
// Ignore non-existent default lifecycle methods.
|
||||||
|
@ -1861,9 +1865,9 @@ public abstract class AbstractAutowireCapableBeanFactory extends AbstractBeanFac
|
||||||
}
|
}
|
||||||
|
|
||||||
if (logger.isTraceEnabled()) {
|
if (logger.isTraceEnabled()) {
|
||||||
logger.trace("Invoking init method '" + initMethodName + "' on bean with name '" + beanName + "'");
|
logger.trace("Invoking init method '" + methodName + "' on bean with name '" + beanName + "'");
|
||||||
}
|
}
|
||||||
Method methodToInvoke = ClassUtils.getInterfaceMethodIfPossible(initMethod, bean.getClass());
|
Method methodToInvoke = ClassUtils.getInterfaceMethodIfPossible(initMethod, beanClass);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
ReflectionUtils.makeAccessible(methodToInvoke);
|
ReflectionUtils.makeAccessible(methodToInvoke);
|
||||||
|
|
|
@ -255,12 +255,15 @@ class DisposableBeanAdapter implements DisposableBean, Runnable, Serializable {
|
||||||
private Method determineDestroyMethod(String destroyMethodName) {
|
private Method determineDestroyMethod(String destroyMethodName) {
|
||||||
try {
|
try {
|
||||||
Class<?> beanClass = this.bean.getClass();
|
Class<?> beanClass = this.bean.getClass();
|
||||||
Method destroyMethod = findDestroyMethod(beanClass, destroyMethodName);
|
MethodDescriptor descriptor = MethodDescriptor.create(this.beanName, beanClass, destroyMethodName);
|
||||||
|
String methodName = descriptor.methodName();
|
||||||
|
|
||||||
|
Method destroyMethod = findDestroyMethod(descriptor.declaringClass(), methodName);
|
||||||
if (destroyMethod != null) {
|
if (destroyMethod != null) {
|
||||||
return destroyMethod;
|
return destroyMethod;
|
||||||
}
|
}
|
||||||
for (Class<?> beanInterface : beanClass.getInterfaces()) {
|
for (Class<?> beanInterface : beanClass.getInterfaces()) {
|
||||||
destroyMethod = findDestroyMethod(beanInterface, destroyMethodName);
|
destroyMethod = findDestroyMethod(beanInterface, methodName);
|
||||||
if (destroyMethod != null) {
|
if (destroyMethod != null) {
|
||||||
return destroyMethod;
|
return destroyMethod;
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,73 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2002-2023 the original author or authors.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* https://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.springframework.beans.factory.support;
|
||||||
|
|
||||||
|
import org.springframework.util.ClassUtils;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Descriptor for a {@link java.lang.reflect.Method Method} which holds a
|
||||||
|
* reference to the method's {@linkplain #declaringClass declaring class},
|
||||||
|
* {@linkplain #methodName name}, and {@linkplain #parameterTypes parameter types}.
|
||||||
|
*
|
||||||
|
* @param declaringClass the method's declaring class
|
||||||
|
* @param methodName the name of the method
|
||||||
|
* @param parameterTypes the types of parameters accepted by the method
|
||||||
|
* @author Sam Brannen
|
||||||
|
* @since 6.0.11
|
||||||
|
*/
|
||||||
|
record MethodDescriptor(Class<?> declaringClass, String methodName, Class<?>... parameterTypes) {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a {@link MethodDescriptor} for the supplied bean class and method name.
|
||||||
|
* <p>The supplied {@code methodName} may be a {@linkplain Method#getName()
|
||||||
|
* simple method name} or a
|
||||||
|
* {@linkplain org.springframework.util.ClassUtils#getQualifiedMethodName(Method)
|
||||||
|
* qualified method name}.
|
||||||
|
* <p>If the method name is fully qualified, this utility will parse the
|
||||||
|
* method name and its declaring class from the qualified method name and then
|
||||||
|
* attempt to load the method's declaring class using the {@link ClassLoader}
|
||||||
|
* of the supplied {@code beanClass}. Otherwise, the returned descriptor will
|
||||||
|
* reference the supplied {@code beanClass} and {@code methodName}.
|
||||||
|
* @param beanName the bean name in the factory (for debugging purposes)
|
||||||
|
* @param beanClass the bean class
|
||||||
|
* @param methodName the name of the method
|
||||||
|
* @return a new {@code MethodDescriptor}; never {@code null}
|
||||||
|
*/
|
||||||
|
static MethodDescriptor create(String beanName, Class<?> beanClass, String methodName) {
|
||||||
|
try {
|
||||||
|
Class<?> declaringClass = beanClass;
|
||||||
|
String methodNameToUse = methodName;
|
||||||
|
|
||||||
|
// Parse fully-qualified method name if necessary.
|
||||||
|
int indexOfDot = methodName.lastIndexOf('.');
|
||||||
|
if (indexOfDot > 0) {
|
||||||
|
String className = methodName.substring(0, indexOfDot);
|
||||||
|
methodNameToUse = methodName.substring(indexOfDot + 1);
|
||||||
|
if (!beanClass.getName().equals(className)) {
|
||||||
|
declaringClass = ClassUtils.forName(className, beanClass.getClassLoader());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return new MethodDescriptor(declaringClass, methodNameToUse);
|
||||||
|
}
|
||||||
|
catch (Exception | LinkageError ex) {
|
||||||
|
throw new BeanDefinitionValidationException(
|
||||||
|
"Could not create MethodDescriptor for method '%s' on bean with name '%s': %s"
|
||||||
|
.formatted(methodName, beanName, ex.getMessage()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -70,8 +70,8 @@ class InitDestroyAnnotationBeanPostProcessorTests {
|
||||||
beanDefinition.setDestroyMethodNames("customDestroyMethod");
|
beanDefinition.setDestroyMethodNames("customDestroyMethod");
|
||||||
processAheadOfTime(beanDefinition);
|
processAheadOfTime(beanDefinition);
|
||||||
RootBeanDefinition mergedBeanDefinition = getMergedBeanDefinition();
|
RootBeanDefinition mergedBeanDefinition = getMergedBeanDefinition();
|
||||||
assertThat(mergedBeanDefinition.getInitMethodNames()).containsExactly("customInitMethod", "initMethod");
|
assertThat(mergedBeanDefinition.getInitMethodNames()).containsExactly("initMethod", "customInitMethod");
|
||||||
assertThat(mergedBeanDefinition.getDestroyMethodNames()).containsExactly("customDestroyMethod", "destroyMethod");
|
assertThat(mergedBeanDefinition.getDestroyMethodNames()).containsExactly("destroyMethod", "customDestroyMethod");
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@ -129,16 +129,16 @@ class InitDestroyAnnotationBeanPostProcessorTests {
|
||||||
RootBeanDefinition mergedBeanDefinition = getMergedBeanDefinition();
|
RootBeanDefinition mergedBeanDefinition = getMergedBeanDefinition();
|
||||||
assertSoftly(softly -> {
|
assertSoftly(softly -> {
|
||||||
softly.assertThat(mergedBeanDefinition.getInitMethodNames()).containsExactly(
|
softly.assertThat(mergedBeanDefinition.getInitMethodNames()).containsExactly(
|
||||||
"afterPropertiesSet",
|
|
||||||
"customInit",
|
|
||||||
CustomAnnotatedPrivateInitDestroyBean.class.getName() + ".privateInit", // fully-qualified private method
|
CustomAnnotatedPrivateInitDestroyBean.class.getName() + ".privateInit", // fully-qualified private method
|
||||||
CustomAnnotatedPrivateSameNameInitDestroyBean.class.getName() + ".privateInit" // fully-qualified private method
|
CustomAnnotatedPrivateSameNameInitDestroyBean.class.getName() + ".privateInit", // fully-qualified private method
|
||||||
|
"afterPropertiesSet",
|
||||||
|
"customInit"
|
||||||
);
|
);
|
||||||
softly.assertThat(mergedBeanDefinition.getDestroyMethodNames()).containsExactly(
|
softly.assertThat(mergedBeanDefinition.getDestroyMethodNames()).containsExactly(
|
||||||
"destroy",
|
|
||||||
"customDestroy",
|
|
||||||
CustomAnnotatedPrivateSameNameInitDestroyBean.class.getName() + ".privateDestroy", // fully-qualified private method
|
CustomAnnotatedPrivateSameNameInitDestroyBean.class.getName() + ".privateDestroy", // fully-qualified private method
|
||||||
CustomAnnotatedPrivateInitDestroyBean.class.getName() + ".privateDestroy" // fully-qualified private method
|
CustomAnnotatedPrivateInitDestroyBean.class.getName() + ".privateDestroy", // fully-qualified private method
|
||||||
|
"destroy",
|
||||||
|
"customDestroy"
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -376,6 +376,9 @@ class BeanDefinitionPropertiesCodeGeneratorTests {
|
||||||
@Nested
|
@Nested
|
||||||
class InitDestroyMethodTests {
|
class InitDestroyMethodTests {
|
||||||
|
|
||||||
|
private final String privateInitMethod = InitDestroyBean.class.getName() + ".privateInit";
|
||||||
|
private final String privateDestroyMethod = InitDestroyBean.class.getName() + ".privateDestroy";
|
||||||
|
|
||||||
@BeforeEach
|
@BeforeEach
|
||||||
void setTargetType() {
|
void setTargetType() {
|
||||||
beanDefinition.setTargetType(InitDestroyBean.class);
|
beanDefinition.setTargetType(InitDestroyBean.class);
|
||||||
|
@ -393,11 +396,18 @@ class BeanDefinitionPropertiesCodeGeneratorTests {
|
||||||
assertHasMethodInvokeHints(InitDestroyBean.class, "init");
|
assertHasMethodInvokeHints(InitDestroyBean.class, "init");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void privateInitMethod() {
|
||||||
|
beanDefinition.setInitMethodName(privateInitMethod);
|
||||||
|
compile((beanDef, compiled) -> assertThat(beanDef.getInitMethodNames()).containsExactly(privateInitMethod));
|
||||||
|
assertHasMethodInvokeHints(InitDestroyBean.class, "privateInit");
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void multipleInitMethods() {
|
void multipleInitMethods() {
|
||||||
beanDefinition.setInitMethodNames("init", "init2");
|
beanDefinition.setInitMethodNames("init", privateInitMethod);
|
||||||
compile((beanDef, compiled) -> assertThat(beanDef.getInitMethodNames()).containsExactly("init", "init2"));
|
compile((beanDef, compiled) -> assertThat(beanDef.getInitMethodNames()).containsExactly("init", privateInitMethod));
|
||||||
assertHasMethodInvokeHints(InitDestroyBean.class, "init", "init2");
|
assertHasMethodInvokeHints(InitDestroyBean.class, "init", "privateInit");
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@ -412,11 +422,18 @@ class BeanDefinitionPropertiesCodeGeneratorTests {
|
||||||
assertHasMethodInvokeHints(InitDestroyBean.class, "destroy");
|
assertHasMethodInvokeHints(InitDestroyBean.class, "destroy");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void privateDestroyMethod() {
|
||||||
|
beanDefinition.setDestroyMethodName(privateDestroyMethod);
|
||||||
|
compile((beanDef, compiled) -> assertThat(beanDef.getDestroyMethodNames()).containsExactly(privateDestroyMethod));
|
||||||
|
assertHasMethodInvokeHints(InitDestroyBean.class, "privateDestroy");
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void multipleDestroyMethods() {
|
void multipleDestroyMethods() {
|
||||||
beanDefinition.setDestroyMethodNames("destroy", "destroy2");
|
beanDefinition.setDestroyMethodNames("destroy", privateDestroyMethod);
|
||||||
compile((beanDef, compiled) -> assertThat(beanDef.getDestroyMethodNames()).containsExactly("destroy", "destroy2"));
|
compile((beanDef, compiled) -> assertThat(beanDef.getDestroyMethodNames()).containsExactly("destroy", privateDestroyMethod));
|
||||||
assertHasMethodInvokeHints(InitDestroyBean.class, "destroy", "destroy2");
|
assertHasMethodInvokeHints(InitDestroyBean.class, "destroy", "privateDestroy");
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -461,13 +478,15 @@ class BeanDefinitionPropertiesCodeGeneratorTests {
|
||||||
void init() {
|
void init() {
|
||||||
}
|
}
|
||||||
|
|
||||||
void init2() {
|
@SuppressWarnings("unused")
|
||||||
|
private void privateInit() {
|
||||||
}
|
}
|
||||||
|
|
||||||
void destroy() {
|
void destroy() {
|
||||||
}
|
}
|
||||||
|
|
||||||
void destroy2() {
|
@SuppressWarnings("unused")
|
||||||
|
private void privateDestroy() {
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,18 +18,26 @@ package org.springframework.context.annotation;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.function.BiConsumer;
|
||||||
|
|
||||||
import jakarta.annotation.PostConstruct;
|
import jakarta.annotation.PostConstruct;
|
||||||
import jakarta.annotation.PreDestroy;
|
import jakarta.annotation.PreDestroy;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
import org.springframework.aot.test.generate.TestGenerationContext;
|
||||||
import org.springframework.beans.factory.DisposableBean;
|
import org.springframework.beans.factory.DisposableBean;
|
||||||
import org.springframework.beans.factory.InitializingBean;
|
import org.springframework.beans.factory.InitializingBean;
|
||||||
import org.springframework.beans.factory.annotation.InitDestroyAnnotationBeanPostProcessor;
|
import org.springframework.beans.factory.annotation.InitDestroyAnnotationBeanPostProcessor;
|
||||||
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
|
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
|
||||||
import org.springframework.beans.factory.support.RootBeanDefinition;
|
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.InitDestroyBean;
|
||||||
import org.springframework.context.annotation.lifecyclemethods.PackagePrivateInitDestroyBean;
|
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;
|
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
|
@Test
|
||||||
void allLifecycleMechanismsAtOnce() {
|
void allLifecycleMechanismsAtOnce() {
|
||||||
Class<?> beanClass = AllInOneBean.class;
|
Class<?> beanClass = AllInOneBean.class;
|
||||||
|
@ -188,6 +269,31 @@ class InitDestroyMethodLifecycleTests {
|
||||||
return beanFactory;
|
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
|
static class InitializingDisposableWithShadowedMethodsBean extends InitDestroyBean implements
|
||||||
InitializingBean, DisposableBean {
|
InitializingBean, DisposableBean {
|
||||||
|
|
|
@ -257,9 +257,9 @@ class ApplicationContextAotGeneratorTests {
|
||||||
GenericApplicationContext freshApplicationContext = toFreshApplicationContext(initializer);
|
GenericApplicationContext freshApplicationContext = toFreshApplicationContext(initializer);
|
||||||
assertThat(freshApplicationContext.getBeanDefinitionNames()).containsOnly("initDestroyComponent");
|
assertThat(freshApplicationContext.getBeanDefinitionNames()).containsOnly("initDestroyComponent");
|
||||||
InitDestroyComponent bean = freshApplicationContext.getBean(InitDestroyComponent.class);
|
InitDestroyComponent bean = freshApplicationContext.getBean(InitDestroyComponent.class);
|
||||||
assertThat(bean.events).containsExactly("customInit", "init");
|
assertThat(bean.events).containsExactly("init", "customInit");
|
||||||
freshApplicationContext.close();
|
freshApplicationContext.close();
|
||||||
assertThat(bean.events).containsExactly("customInit", "init", "customDestroy", "destroy");
|
assertThat(bean.events).containsExactly("init", "customInit", "destroy", "customDestroy");
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue