From 7664a54c933906b97080190e5399b27c3e2d26b5 Mon Sep 17 00:00:00 2001 From: Phillip Webb Date: Fri, 15 Apr 2022 10:36:36 -0700 Subject: [PATCH] Update InitDestroyAnnotationBeanPostProcessor AOT support Update `InitDestroyAnnotationBeanPostProcessor` so that it provides AOT contributions via the `BeanRegistrationAotProcessor` interface. See gh-28414 --- ...nitDestroyAnnotationBeanPostProcessor.java | 22 ++++- ...stroyAnnotationBeanPostProcessorTests.java | 84 +++++++++++++++++-- 2 files changed, 98 insertions(+), 8 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 c87b6674f7f..5e2db74e7c1 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 @@ -39,10 +39,13 @@ import org.apache.commons.logging.LogFactory; import org.springframework.beans.BeansException; import org.springframework.beans.factory.BeanCreationException; +import org.springframework.beans.factory.aot.BeanRegistrationAotContribution; +import org.springframework.beans.factory.aot.BeanRegistrationAotProcessor; import org.springframework.beans.factory.config.DestructionAwareBeanPostProcessor; import org.springframework.beans.factory.generator.AotContributingBeanPostProcessor; import org.springframework.beans.factory.generator.BeanInstantiationContribution; import org.springframework.beans.factory.support.MergedBeanDefinitionPostProcessor; +import org.springframework.beans.factory.support.RegisteredBean; import org.springframework.beans.factory.support.RootBeanDefinition; import org.springframework.core.Ordered; import org.springframework.core.PriorityOrdered; @@ -77,13 +80,15 @@ import org.springframework.util.ReflectionUtils; * * @author Juergen Hoeller * @author Stephane Nicoll + * @author Phillip Webb * @since 2.5 * @see #setInitAnnotationType * @see #setDestroyAnnotationType */ @SuppressWarnings("serial") public class InitDestroyAnnotationBeanPostProcessor implements DestructionAwareBeanPostProcessor, - MergedBeanDefinitionPostProcessor, AotContributingBeanPostProcessor, PriorityOrdered, Serializable { + MergedBeanDefinitionPostProcessor, AotContributingBeanPostProcessor, BeanRegistrationAotProcessor, + PriorityOrdered, Serializable { private final transient LifecycleMetadata emptyLifecycleMetadata = new LifecycleMetadata(Object.class, Collections.emptyList(), Collections.emptyList()) { @@ -170,6 +175,21 @@ public class InitDestroyAnnotationBeanPostProcessor implements DestructionAwareB return null; } + @Override + public BeanRegistrationAotContribution processAheadOfTime(RegisteredBean registeredBean) { + RootBeanDefinition beanDefinition = registeredBean.getMergedBeanDefinition(); + LifecycleMetadata metadata = findInjectionMetadata(beanDefinition, registeredBean.getBeanClass()); + if (!CollectionUtils.isEmpty(metadata.initMethods)) { + String[] initMethodNames = safeMerge(beanDefinition.getInitMethodNames(), metadata.initMethods); + beanDefinition.setInitMethodNames(initMethodNames); + } + if (!CollectionUtils.isEmpty(metadata.destroyMethods)) { + String[] destroyMethodNames = safeMerge(beanDefinition.getDestroyMethodNames(), metadata.destroyMethods); + beanDefinition.setDestroyMethodNames(destroyMethodNames); + } + return null; + } + private LifecycleMetadata findInjectionMetadata(RootBeanDefinition beanDefinition, Class beanType) { LifecycleMetadata metadata = findLifecycleMetadata(beanType); metadata.checkConfigMembers(beanDefinition); 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 e8ac4323f26..46d189703e8 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 @@ -19,6 +19,8 @@ package org.springframework.beans.factory.annotation; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.generator.BeanInstantiationContribution; +import org.springframework.beans.factory.support.DefaultListableBeanFactory; +import org.springframework.beans.factory.support.RegisteredBean; import org.springframework.beans.factory.support.RootBeanDefinition; import org.springframework.beans.testfixture.beans.factory.generator.lifecycle.Destroy; import org.springframework.beans.testfixture.beans.factory.generator.lifecycle.Init; @@ -34,13 +36,16 @@ import static org.mockito.Mockito.verifyNoInteractions; * Tests for {@link InitDestroyAnnotationBeanPostProcessor}. * * @author Stephane Nicoll + * @author Phillip Webb */ class InitDestroyAnnotationBeanPostProcessorTests { + private DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory(); + @Test void contributeWithNoCallbackDoesNotMutateRootBeanDefinition() { RootBeanDefinition beanDefinition = mock(RootBeanDefinition.class); - assertThat(createAotContributingBeanPostProcessor().contribute( + assertThat(createAotBeanPostProcessor().contribute( beanDefinition, String.class, "test")).isNull(); verifyNoInteractions(beanDefinition); } @@ -87,15 +92,80 @@ class InitDestroyAnnotationBeanPostProcessorTests { @Nullable private BeanInstantiationContribution createContribution(RootBeanDefinition beanDefinition) { - InitDestroyAnnotationBeanPostProcessor bpp = createAotContributingBeanPostProcessor(); + InitDestroyAnnotationBeanPostProcessor bpp = createAotBeanPostProcessor(); return bpp.contribute(beanDefinition, beanDefinition.getResolvableType().toClass(), "test"); } - private InitDestroyAnnotationBeanPostProcessor createAotContributingBeanPostProcessor() { - InitDestroyAnnotationBeanPostProcessor bpp = new InitDestroyAnnotationBeanPostProcessor(); - bpp.setInitAnnotationType(Init.class); - bpp.setDestroyAnnotationType(Destroy.class); - return bpp; + @Test + void processAheadOfTimeWhenNoCallbackDoesNotMutateRootBeanDefinition() { + RootBeanDefinition beanDefinition = new RootBeanDefinition(String.class); + processAheadOfTime(beanDefinition); + RootBeanDefinition mergedBeanDefinition = getMergedBeanDefinition(); + assertThat(mergedBeanDefinition.getInitMethodNames()).isNull(); + assertThat(mergedBeanDefinition.getDestroyMethodNames()).isNull(); + } + + @Test + void processAheadOfTimeWhenHasInitDestroyAnnotationsAddsMethodNames() { + RootBeanDefinition beanDefinition = new RootBeanDefinition(InitDestroyBean.class); + processAheadOfTime(beanDefinition); + RootBeanDefinition mergedBeanDefinition = getMergedBeanDefinition(); + assertThat(mergedBeanDefinition.getInitMethodNames()).containsExactly("initMethod"); + assertThat(mergedBeanDefinition.getDestroyMethodNames()).containsExactly("destroyMethod"); + } + + @Test + void processAheadOfTimeWhenHasInitDestroyAnnotationsAndCustomDefinedMethodNamesAddsMethodNames() { + RootBeanDefinition beanDefinition = new RootBeanDefinition(InitDestroyBean.class); + beanDefinition.setInitMethodName("customInitMethod"); + beanDefinition.setDestroyMethodNames("customDestroyMethod"); + processAheadOfTime(beanDefinition); + RootBeanDefinition mergedBeanDefinition = getMergedBeanDefinition(); + assertThat(mergedBeanDefinition.getInitMethodNames()).containsExactly("customInitMethod", "initMethod"); + assertThat(mergedBeanDefinition.getDestroyMethodNames()).containsExactly("customDestroyMethod", "destroyMethod"); + } + + @Test + void processAheadOfTimeWhenHasInitDestroyAnnotationsAndOverlappingCustomDefinedMethodNamesFiltersDuplicates() { + RootBeanDefinition beanDefinition = new RootBeanDefinition(InitDestroyBean.class); + beanDefinition.setInitMethodName("initMethod"); + beanDefinition.setDestroyMethodNames("destroyMethod"); + processAheadOfTime(beanDefinition); + RootBeanDefinition mergedBeanDefinition = getMergedBeanDefinition(); + assertThat(mergedBeanDefinition.getInitMethodNames()).containsExactly("initMethod"); + assertThat(mergedBeanDefinition.getDestroyMethodNames()).containsExactly("destroyMethod"); + } + + @Test + void processAheadOfTimeWhenHasMultipleInitDestroyAnnotationsAddsAllMethodNames() { + RootBeanDefinition beanDefinition = new RootBeanDefinition(MultiInitDestroyBean.class); + processAheadOfTime(beanDefinition); + RootBeanDefinition mergedBeanDefinition = getMergedBeanDefinition(); + assertThat(mergedBeanDefinition.getInitMethodNames()).containsExactly("initMethod", "anotherInitMethod"); + assertThat(mergedBeanDefinition.getDestroyMethodNames()).containsExactly("anotherDestroyMethod", "destroyMethod"); + } + + private void processAheadOfTime(RootBeanDefinition beanDefinition) { + RegisteredBean registeredBean = registerBean(beanDefinition); + assertThat(createAotBeanPostProcessor().processAheadOfTime(registeredBean)).isNull(); + } + + private RegisteredBean registerBean(RootBeanDefinition beanDefinition) { + String beanName = "test"; + this.beanFactory.registerBeanDefinition(beanName, beanDefinition); + RegisteredBean registeredBean = RegisteredBean.of(this.beanFactory, beanName); + return registeredBean; + } + + private RootBeanDefinition getMergedBeanDefinition() { + return (RootBeanDefinition) this.beanFactory.getMergedBeanDefinition("test"); + } + + private InitDestroyAnnotationBeanPostProcessor createAotBeanPostProcessor() { + InitDestroyAnnotationBeanPostProcessor beanPostProcessor = new InitDestroyAnnotationBeanPostProcessor(); + beanPostProcessor.setInitAnnotationType(Init.class); + beanPostProcessor.setDestroyAnnotationType(Destroy.class); + return beanPostProcessor; } }