From e8bbcd0538e1e2173cdd9a003c344ede97f6331a Mon Sep 17 00:00:00 2001 From: Sam Brannen Date: Tue, 20 Jun 2023 17:47:01 +0200 Subject: [PATCH] Support private init/destroy methods in AOT mode Closes gh-30692 --- ...nitDestroyAnnotationBeanPostProcessor.java | 80 +++++++-------- ...BeanDefinitionPropertiesCodeGenerator.java | 2 + .../AbstractAutowireCapableBeanFactory.java | 32 ++++-- .../support/DisposableBeanAdapter.java | 19 +++- ...stroyAnnotationBeanPostProcessorTests.java | 75 +++++++++++++- .../InitDestroyMethodLifecycleTests.java | 98 ++++++++++++++++++- 6 files changed, 246 insertions(+), 60 deletions(-) diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/annotation/InitDestroyAnnotationBeanPostProcessor.java b/spring-beans/src/main/java/org/springframework/beans/factory/annotation/InitDestroyAnnotationBeanPostProcessor.java index 952f84afe1f6..4ba155e8853b 100644 --- a/spring-beans/src/main/java/org/springframework/beans/factory/annotation/InitDestroyAnnotationBeanPostProcessor.java +++ b/spring-beans/src/main/java/org/springframework/beans/factory/annotation/InitDestroyAnnotationBeanPostProcessor.java @@ -153,7 +153,7 @@ public int getOrder() { @Override public void postProcessMergedBeanDefinition(RootBeanDefinition beanDefinition, Class beanType, String beanName) { - findInjectionMetadata(beanDefinition, beanType); + findLifecycleMetadata(beanDefinition, beanType); } @Override @@ -161,7 +161,7 @@ public void postProcessMergedBeanDefinition(RootBeanDefinition beanDefinition, C public BeanRegistrationAotContribution processAheadOfTime(RegisteredBean registeredBean) { RootBeanDefinition beanDefinition = registeredBean.getMergedBeanDefinition(); beanDefinition.resolveDestroyMethodIfNecessary(); - LifecycleMetadata metadata = findInjectionMetadata(beanDefinition, registeredBean.getBeanClass()); + LifecycleMetadata metadata = findLifecycleMetadata(beanDefinition, registeredBean.getBeanClass()); if (!CollectionUtils.isEmpty(metadata.initMethods)) { String[] initMethodNames = safeMerge(beanDefinition.getInitMethodNames(), metadata.initMethods); beanDefinition.setInitMethodNames(initMethodNames); @@ -173,16 +173,16 @@ public BeanRegistrationAotContribution processAheadOfTime(RegisteredBean registe return null; } - private LifecycleMetadata findInjectionMetadata(RootBeanDefinition beanDefinition, Class beanType) { + private LifecycleMetadata findLifecycleMetadata(RootBeanDefinition beanDefinition, Class beanType) { LifecycleMetadata metadata = findLifecycleMetadata(beanType); metadata.checkInitDestroyMethods(beanDefinition); return metadata; } - private String[] safeMerge(@Nullable String[] existingNames, Collection detectedElements) { - Stream detectedNames = detectedElements.stream().map(LifecycleElement::getIdentifier); + private String[] safeMerge(@Nullable String[] existingNames, Collection detectedElements) { + Stream detectedNames = detectedElements.stream().map(LifecycleMethod::getIdentifier); Stream mergedNames = (existingNames != null ? - Stream.concat(Stream.of(existingNames), detectedNames) : detectedNames); + Stream.concat(detectedNames, Stream.of(existingNames)) : detectedNames); return mergedNames.distinct().toArray(String[]::new); } @@ -257,24 +257,23 @@ private LifecycleMetadata buildLifecycleMetadata(final Class clazz) { return this.emptyLifecycleMetadata; } - List initMethods = new ArrayList<>(); - List destroyMethods = new ArrayList<>(); + List initMethods = new ArrayList<>(); + List destroyMethods = new ArrayList<>(); Class targetClass = clazz; do { - final List currInitMethods = new ArrayList<>(); - final List currDestroyMethods = new ArrayList<>(); + final List currInitMethods = new ArrayList<>(); + final List currDestroyMethods = new ArrayList<>(); ReflectionUtils.doWithLocalMethods(targetClass, method -> { if (this.initAnnotationType != null && method.isAnnotationPresent(this.initAnnotationType)) { - LifecycleElement element = new LifecycleElement(method); - currInitMethods.add(element); + currInitMethods.add(new LifecycleMethod(method)); if (logger.isTraceEnabled()) { logger.trace("Found init method on class [" + clazz.getName() + "]: " + method); } } if (this.destroyAnnotationType != null && method.isAnnotationPresent(this.destroyAnnotationType)) { - currDestroyMethods.add(new LifecycleElement(method)); + currDestroyMethods.add(new LifecycleMethod(method)); if (logger.isTraceEnabled()) { logger.trace("Found destroy method on class [" + clazz.getName() + "]: " + method); } @@ -312,18 +311,18 @@ private class LifecycleMetadata { private final Class targetClass; - private final Collection initMethods; + private final Collection initMethods; - private final Collection destroyMethods; + private final Collection destroyMethods; @Nullable - private volatile Set checkedInitMethods; + private volatile Set checkedInitMethods; @Nullable - private volatile Set checkedDestroyMethods; + private volatile Set checkedDestroyMethods; - public LifecycleMetadata(Class targetClass, Collection initMethods, - Collection destroyMethods) { + public LifecycleMetadata(Class targetClass, Collection initMethods, + Collection destroyMethods) { this.targetClass = targetClass; this.initMethods = initMethods; @@ -331,8 +330,8 @@ public LifecycleMetadata(Class targetClass, Collection init } public void checkInitDestroyMethods(RootBeanDefinition beanDefinition) { - Set checkedInitMethods = new LinkedHashSet<>(this.initMethods.size()); - for (LifecycleElement element : this.initMethods) { + Set checkedInitMethods = new LinkedHashSet<>(this.initMethods.size()); + for (LifecycleMethod element : this.initMethods) { String methodIdentifier = element.getIdentifier(); if (!beanDefinition.isExternallyManagedInitMethod(methodIdentifier)) { beanDefinition.registerExternallyManagedInitMethod(methodIdentifier); @@ -342,8 +341,8 @@ public void checkInitDestroyMethods(RootBeanDefinition beanDefinition) { } } } - Set checkedDestroyMethods = new LinkedHashSet<>(this.destroyMethods.size()); - for (LifecycleElement element : this.destroyMethods) { + Set checkedDestroyMethods = new LinkedHashSet<>(this.destroyMethods.size()); + for (LifecycleMethod element : this.destroyMethods) { String methodIdentifier = element.getIdentifier(); if (!beanDefinition.isExternallyManagedDestroyMethod(methodIdentifier)) { beanDefinition.registerExternallyManagedDestroyMethod(methodIdentifier); @@ -358,11 +357,11 @@ public void checkInitDestroyMethods(RootBeanDefinition beanDefinition) { } public void invokeInitMethods(Object target, String beanName) throws Throwable { - Collection checkedInitMethods = this.checkedInitMethods; - Collection initMethodsToIterate = + Collection checkedInitMethods = this.checkedInitMethods; + Collection initMethodsToIterate = (checkedInitMethods != null ? checkedInitMethods : this.initMethods); if (!initMethodsToIterate.isEmpty()) { - for (LifecycleElement element : initMethodsToIterate) { + for (LifecycleMethod element : initMethodsToIterate) { if (logger.isTraceEnabled()) { logger.trace("Invoking init method on bean '" + beanName + "': " + element.getMethod()); } @@ -372,11 +371,11 @@ public void invokeInitMethods(Object target, String beanName) throws Throwable { } public void invokeDestroyMethods(Object target, String beanName) throws Throwable { - Collection checkedDestroyMethods = this.checkedDestroyMethods; - Collection destroyMethodsToUse = + Collection checkedDestroyMethods = this.checkedDestroyMethods; + Collection destroyMethodsToUse = (checkedDestroyMethods != null ? checkedDestroyMethods : this.destroyMethods); if (!destroyMethodsToUse.isEmpty()) { - for (LifecycleElement element : destroyMethodsToUse) { + for (LifecycleMethod element : destroyMethodsToUse) { if (logger.isTraceEnabled()) { logger.trace("Invoking destroy method on bean '" + beanName + "': " + element.getMethod()); } @@ -386,8 +385,8 @@ public void invokeDestroyMethods(Object target, String beanName) throws Throwabl } public boolean hasDestroyMethods() { - Collection checkedDestroyMethods = this.checkedDestroyMethods; - Collection destroyMethodsToUse = + Collection checkedDestroyMethods = this.checkedDestroyMethods; + Collection destroyMethodsToUse = (checkedDestroyMethods != null ? checkedDestroyMethods : this.destroyMethods); return !destroyMethodsToUse.isEmpty(); } @@ -395,17 +394,17 @@ public boolean hasDestroyMethods() { /** - * Class representing injection information about an annotated method. + * Class representing an annotated init or destroy methods. */ - private static class LifecycleElement { + private static class LifecycleMethod { private final Method method; private final String identifier; - public LifecycleElement(Method method) { + public LifecycleMethod(Method method) { if (method.getParameterCount() != 0) { - throw new IllegalStateException("Lifecycle method annotation requires a no-arg method: " + method); + throw new IllegalStateException("Lifecycle annotation requires a no-arg method: " + method); } this.method = method; this.identifier = (Modifier.isPrivate(method.getModifiers()) ? @@ -422,18 +421,13 @@ public String getIdentifier() { public void invoke(Object target) throws Throwable { ReflectionUtils.makeAccessible(this.method); - this.method.invoke(target, (Object[]) null); + this.method.invoke(target); } @Override public boolean equals(@Nullable Object other) { - if (this == other) { - return true; - } - if (!(other instanceof LifecycleElement otherElement)) { - return false; - } - return (this.identifier.equals(otherElement.identifier)); + return (this == other || (other instanceof LifecycleMethod that && + this.identifier.equals(that.identifier))); } @Override diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/aot/BeanDefinitionPropertiesCodeGenerator.java b/spring-beans/src/main/java/org/springframework/beans/factory/aot/BeanDefinitionPropertiesCodeGenerator.java index 72dbf9ebd580..2f1fcbcef9d3 100644 --- a/spring-beans/src/main/java/org/springframework/beans/factory/aot/BeanDefinitionPropertiesCodeGenerator.java +++ b/spring-beans/src/main/java/org/springframework/beans/factory/aot/BeanDefinitionPropertiesCodeGenerator.java @@ -72,6 +72,7 @@ * * @author Phillip Webb * @author Stephane Nicoll + * @author Sam Brannen * @since 6.0 */ class BeanDefinitionPropertiesCodeGenerator { @@ -138,6 +139,7 @@ private void addInitDestroyMethods(Builder code, } private void addInitDestroyHint(Class beanUserClass, String methodName) { + // TODO Handle fully-qualified method names Method method = ReflectionUtils.findMethod(beanUserClass, methodName); if (method != null) { this.hints.reflection().registerMethod(method, ExecutableMode.INVOKE); diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/support/AbstractAutowireCapableBeanFactory.java b/spring-beans/src/main/java/org/springframework/beans/factory/support/AbstractAutowireCapableBeanFactory.java index 7e264e411e1a..d8abe69b93bc 100644 --- a/spring-beans/src/main/java/org/springframework/beans/factory/support/AbstractAutowireCapableBeanFactory.java +++ b/spring-beans/src/main/java/org/springframework/beans/factory/support/AbstractAutowireCapableBeanFactory.java @@ -1832,26 +1832,40 @@ protected void invokeInitMethods(String beanName, Object bean, @Nullable RootBea /** * Invoke the specified custom init method on the given bean. - * Called by invokeInitMethods. - *

Can be overridden in subclasses for custom resolution of init - * methods with arguments. + *

Called by {@link #invokeInitMethods(String, Object, RootBeanDefinition)}. + *

Can be overridden in subclasses for custom resolution of init methods + * with arguments. * @see #invokeInitMethods */ protected void invokeCustomInitMethod(String beanName, Object bean, RootBeanDefinition mbd, String initMethodName) throws Throwable { + Class beanClass = bean.getClass(); + Class methodDeclaringClass = beanClass; + String methodName = initMethodName; + + // Parse fully-qualified method name if necessary. + int indexOfDot = initMethodName.lastIndexOf('.'); + if (indexOfDot > 0) { + String className = initMethodName.substring(0, indexOfDot); + methodName = initMethodName.substring(indexOfDot + 1); + if (!beanClass.getName().equals((className))) { + methodDeclaringClass = ClassUtils.forName(className, beanClass.getClassLoader()); + } + } + Method initMethod = (mbd.isNonPublicAccessAllowed() ? - BeanUtils.findMethod(bean.getClass(), initMethodName) : - ClassUtils.getMethodIfAvailable(bean.getClass(), initMethodName)); + BeanUtils.findMethod(methodDeclaringClass, methodName) : + ClassUtils.getMethodIfAvailable(beanClass, methodName)); if (initMethod == null) { if (mbd.isEnforceInitMethod()) { throw new BeanDefinitionValidationException("Could not find an init method named '" + - initMethodName + "' on bean with name '" + beanName + "'"); + methodName + "' on bean with name '" + beanName + "'"); } else { 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 + "'"); } // Ignore non-existent default lifecycle methods. @@ -1860,9 +1874,9 @@ protected void invokeCustomInitMethod(String beanName, Object bean, RootBeanDefi } 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 { ReflectionUtils.makeAccessible(methodToInvoke); diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/support/DisposableBeanAdapter.java b/spring-beans/src/main/java/org/springframework/beans/factory/support/DisposableBeanAdapter.java index 0d2bc2adeb22..4d503a07255f 100644 --- a/spring-beans/src/main/java/org/springframework/beans/factory/support/DisposableBeanAdapter.java +++ b/spring-beans/src/main/java/org/springframework/beans/factory/support/DisposableBeanAdapter.java @@ -255,19 +255,32 @@ else if (this.destroyMethodNames != null) { private Method determineDestroyMethod(String name) { try { Class beanClass = this.bean.getClass(); - Method destroyMethod = findDestroyMethod(beanClass, name); + Class methodDeclaringClass = beanClass; + String methodName = name; + + // Parse fully-qualified method name if necessary. + int indexOfDot = name.lastIndexOf('.'); + if (indexOfDot > 0) { + String className = name.substring(0, indexOfDot); + methodName = name.substring(indexOfDot + 1); + if (!beanClass.getName().equals((className))) { + methodDeclaringClass = ClassUtils.forName(className, beanClass.getClassLoader()); + } + } + + Method destroyMethod = findDestroyMethod(methodDeclaringClass, methodName); if (destroyMethod != null) { return destroyMethod; } for (Class beanInterface : beanClass.getInterfaces()) { - destroyMethod = findDestroyMethod(beanInterface, name); + destroyMethod = findDestroyMethod(beanInterface, methodName); if (destroyMethod != null) { return destroyMethod; } } return null; } - catch (IllegalArgumentException ex) { + catch (ClassNotFoundException | IllegalArgumentException ex) { throw new BeanDefinitionValidationException("Could not find unique destroy method on bean with name '" + this.beanName + ": " + ex.getMessage()); } diff --git a/spring-beans/src/test/java/org/springframework/beans/factory/annotation/InitDestroyAnnotationBeanPostProcessorTests.java b/spring-beans/src/test/java/org/springframework/beans/factory/annotation/InitDestroyAnnotationBeanPostProcessorTests.java index 9856e9d1cf9c..2f6787f0cd0f 100644 --- a/spring-beans/src/test/java/org/springframework/beans/factory/annotation/InitDestroyAnnotationBeanPostProcessorTests.java +++ b/spring-beans/src/test/java/org/springframework/beans/factory/annotation/InitDestroyAnnotationBeanPostProcessorTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2022 the original author or authors. + * 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. @@ -18,6 +18,8 @@ import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.DisposableBean; +import org.springframework.beans.factory.InitializingBean; import org.springframework.beans.factory.support.AbstractBeanDefinition; import org.springframework.beans.factory.support.DefaultListableBeanFactory; import org.springframework.beans.factory.support.RegisteredBean; @@ -29,12 +31,15 @@ import org.springframework.beans.testfixture.beans.factory.generator.lifecycle.MultiInitDestroyBean; import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.SoftAssertions.assertSoftly; /** * Tests for {@link InitDestroyAnnotationBeanPostProcessor}. * + * @since 6.0 * @author Stephane Nicoll * @author Phillip Webb + * @author Sam Brannen */ class InitDestroyAnnotationBeanPostProcessorTests { @@ -109,6 +114,29 @@ void processAheadOfTimeWhenHasMultipleInitDestroyAnnotationsAddsAllMethodNames() assertThat(mergedBeanDefinition.getDestroyMethodNames()).containsExactly("anotherDestroyMethod", "destroyMethod"); } + @Test + void processAheadOfTimeWithMultipleLevelsOfPublicAndPrivateInitAndDestroyMethods() { + RootBeanDefinition beanDefinition = new RootBeanDefinition(CustomAnnotatedPrivateSameNameInitDestroyBean.class); + beanDefinition.setInitMethodNames("afterPropertiesSet", "customInit"); + beanDefinition.setDestroyMethodNames("destroy", "customDestroy"); + processAheadOfTime(beanDefinition); + RootBeanDefinition mergedBeanDefinition = getMergedBeanDefinition(); + assertSoftly(softly -> { + softly.assertThat(mergedBeanDefinition.getInitMethodNames()).containsExactly( + "afterPropertiesSet", + "customInit", + CustomAnnotatedPrivateInitDestroyBean.class.getName() + ".privateInit", // fully-qualified private method + CustomAnnotatedPrivateSameNameInitDestroyBean.class.getName() + ".privateInit" // fully-qualified private method + ); + softly.assertThat(mergedBeanDefinition.getDestroyMethodNames()).containsExactly( + "destroy", + "customDestroy", + CustomAnnotatedPrivateSameNameInitDestroyBean.class.getName() + ".privateDestroy", // fully-qualified private method + CustomAnnotatedPrivateInitDestroyBean.class.getName() + ".privateDestroy" // fully-qualified private method + ); + }); + } + private void processAheadOfTime(RootBeanDefinition beanDefinition) { RegisteredBean registeredBean = registerBean(beanDefinition); assertThat(createAotBeanPostProcessor().processAheadOfTime(registeredBean)).isNull(); @@ -133,4 +161,49 @@ private InitDestroyAnnotationBeanPostProcessor createAotBeanPostProcessor() { static class NoInitDestroyBean {} + static class CustomInitDestroyBean { + + public void customInit() { + } + + public void customDestroy() { + } + } + + static class CustomInitializingDisposableBean extends CustomInitDestroyBean + implements InitializingBean, DisposableBean { + + @Override + public void afterPropertiesSet() { + } + + @Override + public void destroy() { + } + } + + static class CustomAnnotatedPrivateInitDestroyBean extends CustomInitializingDisposableBean { + + @Init + private void privateInit() { + } + + @Destroy + private void privateDestroy() { + } + } + + static class CustomAnnotatedPrivateSameNameInitDestroyBean extends CustomAnnotatedPrivateInitDestroyBean { + + @Init + @SuppressWarnings("unused") + private void privateInit() { + } + + @Destroy + @SuppressWarnings("unused") + private void privateDestroy() { + } + } + } diff --git a/spring-context/src/test/java/org/springframework/context/annotation/InitDestroyMethodLifecycleTests.java b/spring-context/src/test/java/org/springframework/context/annotation/InitDestroyMethodLifecycleTests.java index a481ce58b54c..b9d9c06b9f3f 100644 --- a/spring-context/src/test/java/org/springframework/context/annotation/InitDestroyMethodLifecycleTests.java +++ b/spring-context/src/test/java/org/springframework/context/annotation/InitDestroyMethodLifecycleTests.java @@ -18,15 +18,24 @@ 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.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; @@ -112,11 +121,58 @@ void jsr250AnnotationsWithCustomPrivateInitDestroyMethods() { @Test void jsr250AnnotationsWithCustomSameMethodNames() { Class beanClass = CustomAnnotatedPrivateSameNameInitDestroyBean.class; - DefaultListableBeanFactory beanFactory = createBeanFactoryAndRegisterBean(beanClass, "customInit1", "customDestroy1"); + DefaultListableBeanFactory beanFactory = createBeanFactoryAndRegisterBean(beanClass, "customInit", "customDestroy"); CustomAnnotatedPrivateSameNameInitDestroyBean bean = beanFactory.getBean(CustomAnnotatedPrivateSameNameInitDestroyBean.class); - assertThat(bean.initMethods).as("init-methods").containsExactly("@PostConstruct.privateCustomInit1", "@PostConstruct.sameNameCustomInit1", "afterPropertiesSet"); + + assertThat(bean.initMethods).as("init-methods").containsExactly( + "@PostConstruct.privateCustomInit1", + "@PostConstruct.sameNameCustomInit1", + "afterPropertiesSet", + "customInit" + ); + beanFactory.destroySingletons(); - assertThat(bean.destroyMethods).as("destroy-methods").containsExactly("@PreDestroy.sameNameCustomDestroy1", "@PreDestroy.privateCustomDestroy1", "destroy"); + assertThat(bean.destroyMethods).as("destroy-methods").containsExactly( + "@PreDestroy.sameNameCustomDestroy1", + "@PreDestroy.privateCustomDestroy1", + "destroy", + "customDestroy" + ); + } + + @Test + @CompileWithForkedClassLoader + void jsr250AnnotationsWithCustomSameMethodNamesWithAotProcessingAndAotRuntime() { + Class 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 @@ -134,14 +190,48 @@ private static DefaultListableBeanFactory createBeanFactoryAndRegisterBean(Class String initMethodName, String destroyMethodName) { DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory(); + beanFactory.addBeanPostProcessor(new CommonAnnotationBeanPostProcessor()); + + // Configure and register an InitDestroyAnnotationBeanPostProcessor as + // done in AnnotationConfigUtils.registerAnnotationConfigProcessors() + // for an ApplicatonContext. + InitDestroyAnnotationBeanPostProcessor initDestroyBpp = new InitDestroyAnnotationBeanPostProcessor(); + initDestroyBpp.setInitAnnotationType(javax.annotation.PostConstruct.class); + initDestroyBpp.setDestroyAnnotationType(javax.annotation.PreDestroy.class); + beanFactory.addBeanPostProcessor(initDestroyBpp); + RootBeanDefinition beanDefinition = new RootBeanDefinition(beanClass); beanDefinition.setInitMethodName(initMethodName); beanDefinition.setDestroyMethodName(destroyMethodName); - beanFactory.addBeanPostProcessor(new CommonAnnotationBeanPostProcessor()); beanFactory.registerBeanDefinition("lifecycleTestBean", beanDefinition); return beanFactory; } + private static GenericApplicationContext createApplicationContext( + ApplicationContextInitializer initializer) { + + GenericApplicationContext context = new GenericApplicationContext(); + initializer.initialize(context); + context.refresh(); + return context; + } + + @SuppressWarnings("unchecked") + private static void testCompiledResult(GenericApplicationContext applicationContext, + BiConsumer, 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 InitDestroyBean {