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 e615931bcc9..c87b6674f7f 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 @@ -32,6 +32,7 @@ import java.util.List; import java.util.Map; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; +import java.util.stream.Stream; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; @@ -39,6 +40,8 @@ import org.apache.commons.logging.LogFactory; import org.springframework.beans.BeansException; import org.springframework.beans.factory.BeanCreationException; 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.RootBeanDefinition; import org.springframework.core.Ordered; @@ -46,6 +49,7 @@ import org.springframework.core.PriorityOrdered; import org.springframework.core.annotation.AnnotationUtils; import org.springframework.lang.Nullable; import org.springframework.util.ClassUtils; +import org.springframework.util.CollectionUtils; import org.springframework.util.ReflectionUtils; /** @@ -72,13 +76,14 @@ import org.springframework.util.ReflectionUtils; * for annotation-driven injection of named beans. * * @author Juergen Hoeller + * @author Stephane Nicoll * @since 2.5 * @see #setInitAnnotationType * @see #setDestroyAnnotationType */ @SuppressWarnings("serial") -public class InitDestroyAnnotationBeanPostProcessor - implements DestructionAwareBeanPostProcessor, MergedBeanDefinitionPostProcessor, PriorityOrdered, Serializable { +public class InitDestroyAnnotationBeanPostProcessor implements DestructionAwareBeanPostProcessor, + MergedBeanDefinitionPostProcessor, AotContributingBeanPostProcessor, PriorityOrdered, Serializable { private final transient LifecycleMetadata emptyLifecycleMetadata = new LifecycleMetadata(Object.class, Collections.emptyList(), Collections.emptyList()) { @@ -146,8 +151,36 @@ public class InitDestroyAnnotationBeanPostProcessor @Override public void postProcessMergedBeanDefinition(RootBeanDefinition beanDefinition, Class beanType, String beanName) { + findInjectionMetadata(beanDefinition, beanType); + } + + @Override + public BeanInstantiationContribution contribute(RootBeanDefinition beanDefinition, Class beanType, String beanName) { + LifecycleMetadata metadata = findInjectionMetadata(beanDefinition, beanType); + 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); + return metadata; + } + + private String[] safeMerge(@Nullable String[] existingNames, Collection detectedElements) { + Stream detectedNames = detectedElements.stream().map(LifecycleElement::getIdentifier); + Stream mergedNames = (existingNames != null + ? Stream.concat(Stream.of(existingNames), detectedNames) : detectedNames); + return mergedNames.distinct().toArray(String[]::new); } @Override diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/generator/BeanRegistrationBeanFactoryContribution.java b/spring-beans/src/main/java/org/springframework/beans/factory/generator/BeanRegistrationBeanFactoryContribution.java index fe47a2521c1..7caf9214289 100644 --- a/spring-beans/src/main/java/org/springframework/beans/factory/generator/BeanRegistrationBeanFactoryContribution.java +++ b/spring-beans/src/main/java/org/springframework/beans/factory/generator/BeanRegistrationBeanFactoryContribution.java @@ -55,6 +55,7 @@ import org.springframework.javapoet.support.MultiStatement; import org.springframework.lang.Nullable; import org.springframework.util.ClassUtils; import org.springframework.util.ObjectUtils; +import org.springframework.util.ReflectionUtils; import org.springframework.util.StringUtils; /** @@ -121,6 +122,14 @@ public class BeanRegistrationBeanFactoryContribution implements BeanFactoryContr * @param runtimeHints the runtime hints to use */ void registerRuntimeHints(RuntimeHints runtimeHints) { + String[] initMethodNames = this.beanDefinition.getInitMethodNames(); + if (!ObjectUtils.isEmpty(initMethodNames)) { + registerInitDestroyMethodsRuntimeHints(initMethodNames, runtimeHints); + } + String[] destroyMethodNames = this.beanDefinition.getDestroyMethodNames(); + if (!ObjectUtils.isEmpty(destroyMethodNames)) { + registerInitDestroyMethodsRuntimeHints(destroyMethodNames, runtimeHints); + } registerPropertyValuesRuntimeHints(runtimeHints); } @@ -191,6 +200,15 @@ public class BeanRegistrationBeanFactoryContribution implements BeanFactoryContr return this.beanInstantiationGenerator.generateBeanInstantiation(runtimeHints); } + private void registerInitDestroyMethodsRuntimeHints(String[] methodNames, RuntimeHints runtimeHints) { + for (String methodName : methodNames) { + Method method = ReflectionUtils.findMethod(getUserBeanClass(), methodName); + if (method != null) { + runtimeHints.reflection().registerMethod(method, hint -> hint.withMode(ExecutableMode.INVOKE)); + } + } + } + private void registerPropertyValuesRuntimeHints(RuntimeHints runtimeHints) { if (!this.beanDefinition.hasPropertyValues()) { return; @@ -357,6 +375,14 @@ public class BeanRegistrationBeanFactoryContribution implements BeanFactoryContr private void handleBeanDefinitionMetadata(Builder code) { String bdVariable = determineVariableName("bd"); MultiStatement statements = new MultiStatement(); + String[] initMethodNames = this.beanDefinition.getInitMethodNames(); + if (!ObjectUtils.isEmpty(initMethodNames)) { + handleInitMethodNames(statements, bdVariable, initMethodNames); + } + String[] destroyMethodNames = this.beanDefinition.getDestroyMethodNames(); + if (!ObjectUtils.isEmpty(destroyMethodNames)) { + handleDestroyMethodNames(statements, bdVariable, destroyMethodNames); + } if (this.beanDefinition.isPrimary()) { statements.addStatement("$L.setPrimary(true)", bdVariable); } @@ -399,6 +425,26 @@ public class BeanRegistrationBeanFactoryContribution implements BeanFactoryContr code.add(")"); } + private void handleInitMethodNames(MultiStatement statements, String bdVariable, String[] initMethodNames) { + if (initMethodNames.length == 1) { + statements.addStatement("$L.setInitMethodName($S)", bdVariable, initMethodNames[0]); + } + else { + statements.addStatement("$L.setInitMethodNames($L)", bdVariable, + this.parameterGenerator.generateParameterValue(initMethodNames)); + } + } + + private void handleDestroyMethodNames(MultiStatement statements, String bdVariable, String[] destroyMethodNames) { + if (destroyMethodNames.length == 1) { + statements.addStatement("$L.setDestroyMethodName($S)", bdVariable, destroyMethodNames[0]); + } + else { + statements.addStatement("$L.setDestroyMethodNames($L)", bdVariable, + this.parameterGenerator.generateParameterValue(destroyMethodNames)); + } + } + private void handleArgumentValues(MultiStatement statements, String bdVariable, Map indexedArgumentValues) { if (indexedArgumentValues.size() == 1) { 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 new file mode 100644 index 00000000000..e8ac4323f26 --- /dev/null +++ b/spring-beans/src/test/java/org/springframework/beans/factory/annotation/InitDestroyAnnotationBeanPostProcessorTests.java @@ -0,0 +1,101 @@ +/* + * Copyright 2002-2022 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.annotation; + +import org.junit.jupiter.api.Test; + +import org.springframework.beans.factory.generator.BeanInstantiationContribution; +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; +import org.springframework.beans.testfixture.beans.factory.generator.lifecycle.InitDestroyBean; +import org.springframework.beans.testfixture.beans.factory.generator.lifecycle.MultiInitDestroyBean; +import org.springframework.lang.Nullable; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verifyNoInteractions; + +/** + * Tests for {@link InitDestroyAnnotationBeanPostProcessor}. + * + * @author Stephane Nicoll + */ +class InitDestroyAnnotationBeanPostProcessorTests { + + @Test + void contributeWithNoCallbackDoesNotMutateRootBeanDefinition() { + RootBeanDefinition beanDefinition = mock(RootBeanDefinition.class); + assertThat(createAotContributingBeanPostProcessor().contribute( + beanDefinition, String.class, "test")).isNull(); + verifyNoInteractions(beanDefinition); + } + + @Test + void contributeWithInitDestroyCallback() { + RootBeanDefinition beanDefinition = new RootBeanDefinition(InitDestroyBean.class); + assertThat(createContribution(beanDefinition)).isNull(); + assertThat(beanDefinition.getInitMethodNames()).containsExactly("initMethod"); + assertThat(beanDefinition.getDestroyMethodNames()).containsExactly("destroyMethod"); + } + + @Test + void contributeWithInitDestroyCallbackRetainCustomMethods() { + RootBeanDefinition beanDefinition = new RootBeanDefinition(InitDestroyBean.class); + beanDefinition.setInitMethodName("customInitMethod"); + beanDefinition.setDestroyMethodNames("customDestroyMethod"); + assertThat(createContribution(beanDefinition)).isNull(); + assertThat(beanDefinition.getInitMethodNames()) + .containsExactly("customInitMethod", "initMethod"); + assertThat(beanDefinition.getDestroyMethodNames()) + .containsExactly("customDestroyMethod", "destroyMethod"); + } + + @Test + void contributeWithInitDestroyCallbackFilterDuplicates() { + RootBeanDefinition beanDefinition = new RootBeanDefinition(InitDestroyBean.class); + beanDefinition.setInitMethodName("initMethod"); + beanDefinition.setDestroyMethodNames("destroyMethod"); + assertThat(createContribution(beanDefinition)).isNull(); + assertThat(beanDefinition.getInitMethodNames()).containsExactly("initMethod"); + assertThat(beanDefinition.getDestroyMethodNames()).containsExactly("destroyMethod"); + } + + @Test + void contributeWithMultipleInitDestroyCallbacks() { + RootBeanDefinition beanDefinition = new RootBeanDefinition(MultiInitDestroyBean.class); + assertThat(createContribution(beanDefinition)).isNull(); + assertThat(beanDefinition.getInitMethodNames()) + .containsExactly("initMethod", "anotherInitMethod"); + assertThat(beanDefinition.getDestroyMethodNames()) + .containsExactly("anotherDestroyMethod", "destroyMethod"); + } + + @Nullable + private BeanInstantiationContribution createContribution(RootBeanDefinition beanDefinition) { + InitDestroyAnnotationBeanPostProcessor bpp = createAotContributingBeanPostProcessor(); + 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; + } + +} diff --git a/spring-beans/src/test/java/org/springframework/beans/factory/generator/BeanRegistrationBeanFactoryContributionTests.java b/spring-beans/src/test/java/org/springframework/beans/factory/generator/BeanRegistrationBeanFactoryContributionTests.java index 689d7360a71..e3451f5ebda 100644 --- a/spring-beans/src/test/java/org/springframework/beans/factory/generator/BeanRegistrationBeanFactoryContributionTests.java +++ b/spring-beans/src/test/java/org/springframework/beans/factory/generator/BeanRegistrationBeanFactoryContributionTests.java @@ -56,6 +56,7 @@ import org.springframework.beans.testfixture.beans.factory.generator.InnerCompon import org.springframework.beans.testfixture.beans.factory.generator.SimpleConfiguration; import org.springframework.beans.testfixture.beans.factory.generator.factory.SampleFactory; import org.springframework.beans.testfixture.beans.factory.generator.injection.InjectionComponent; +import org.springframework.beans.testfixture.beans.factory.generator.lifecycle.InitDestroyBean; import org.springframework.beans.testfixture.beans.factory.generator.property.ConfigurableBean; import org.springframework.beans.testfixture.beans.factory.generator.visibility.ProtectedConstructorComponent; import org.springframework.beans.testfixture.beans.factory.generator.visibility.ProtectedFactoryMethod; @@ -208,6 +209,30 @@ class BeanRegistrationBeanFactoryContributionTests { PublicFactoryBean.class.getPackageName() + ".Test.registerTest(beanFactory);\n"); } + @Test + void generateWithBeanDefinitionHavingInitMethodName() { + compile(simpleConfigurationRegistration(bd -> bd.setInitMethodName("someMethod")), + hasBeanDefinition(generatedBd -> assertThat(generatedBd.getInitMethodNames()).containsExactly("someMethod"))); + } + + @Test + void generateWithBeanDefinitionHavingInitMethodNames() { + compile(simpleConfigurationRegistration(bd -> bd.setInitMethodNames("i1", "i2")), + hasBeanDefinition(generatedBd -> assertThat(generatedBd.getInitMethodNames()).containsExactly("i1", "i2"))); + } + + @Test + void generateWithBeanDefinitionHavingDestroyMethodName() { + compile(simpleConfigurationRegistration(bd -> bd.setDestroyMethodName("someMethod")), + hasBeanDefinition(generatedBd -> assertThat(generatedBd.getDestroyMethodNames()).containsExactly("someMethod"))); + } + + @Test + void generateWithBeanDefinitionHavingDestroyMethodNames() { + compile(simpleConfigurationRegistration(bd -> bd.setDestroyMethodNames("d1", "d2")), + hasBeanDefinition(generatedBd -> assertThat(generatedBd.getDestroyMethodNames()).containsExactly("d1", "d2"))); + } + @Test void generateWithBeanDefinitionHavingSyntheticFlag() { compile(simpleConfigurationRegistration(bd -> bd.setSynthetic(true)), @@ -392,6 +417,28 @@ class BeanRegistrationBeanFactoryContributionTests { })); } + @Test + void registerRuntimeHintsWithInitMethodNames() { + RootBeanDefinition bd = new RootBeanDefinition(InitDestroyBean.class); + bd.setInitMethodNames("customInitMethod", "initMethod"); + RuntimeHints runtimeHints = new RuntimeHints(); + getDefaultContribution(new DefaultListableBeanFactory(), bd).registerRuntimeHints(runtimeHints); + assertThat(runtimeHints.reflection().getTypeHint(InitDestroyBean.class)).satisfies(hint -> + assertThat(hint.methods()).anySatisfy(invokeMethodHint("customInitMethod")) + .anySatisfy(invokeMethodHint("initMethod")).hasSize(2)); + } + + @Test + void registerRuntimeHintsWithDestroyMethodNames() { + RootBeanDefinition bd = new RootBeanDefinition(InitDestroyBean.class); + bd.setDestroyMethodNames("customDestroyMethod", "destroyMethod"); + RuntimeHints runtimeHints = new RuntimeHints(); + getDefaultContribution(new DefaultListableBeanFactory(), bd).registerRuntimeHints(runtimeHints); + assertThat(runtimeHints.reflection().getTypeHint(InitDestroyBean.class)).satisfies(hint -> + assertThat(hint.methods()).anySatisfy(invokeMethodHint("customDestroyMethod")) + .anySatisfy(invokeMethodHint("destroyMethod")).hasSize(2)); + } + @Test void registerRuntimeHintsWithNoPropertyValuesDoesNotAccessRuntimeHints() { RootBeanDefinition bd = new RootBeanDefinition(String.class); @@ -432,12 +479,12 @@ class BeanRegistrationBeanFactoryContributionTests { assertThat(typeHint.getType()).isEqualTo(TypeReference.of(BaseFactoryBean.class)); assertThat(typeHint.constructors()).isEmpty(); assertThat(typeHint.methods()).singleElement() - .satisfies(methodHint("setName", String.class)); + .satisfies(invokeMethodHint("setName", String.class)); assertThat(typeHint.fields()).isEmpty(); }).anySatisfy(typeHint -> { assertThat(typeHint.getType()).isEqualTo(TypeReference.of(IntegerFactoryBean.class)); assertThat(typeHint.constructors()).singleElement() - .satisfies(constructorHint(Environment.class)); + .satisfies(introspectConstructorHint(Environment.class)); assertThat(typeHint.methods()).isEmpty(); assertThat(typeHint.fields()).isEmpty(); }).hasSize(2); @@ -453,8 +500,8 @@ class BeanRegistrationBeanFactoryContributionTests { assertThat(reflectionHints.typeHints()).singleElement().satisfies(typeHint -> { assertThat(typeHint.getType()).isEqualTo(TypeReference.of(NameAndCountersComponent.class)); assertThat(typeHint.constructors()).isEmpty(); - assertThat(typeHint.methods()).anySatisfy(methodHint("setName", String.class)) - .anySatisfy(methodHint("setCounter", Integer.class)).hasSize(2); + assertThat(typeHint.methods()).anySatisfy(invokeMethodHint("setName", String.class)) + .anySatisfy(invokeMethodHint("setCounter", Integer.class)).hasSize(2); assertThat(typeHint.fields()).isEmpty(); }); } @@ -473,14 +520,14 @@ class BeanRegistrationBeanFactoryContributionTests { assertThat(reflectionHints.typeHints()).anySatisfy(typeHint -> { assertThat(typeHint.getType()).isEqualTo(TypeReference.of(NameAndCountersComponent.class)); assertThat(typeHint.constructors()).isEmpty(); - assertThat(typeHint.methods()).singleElement().satisfies(methodHint("setCounter", Integer.class)); + assertThat(typeHint.methods()).singleElement().satisfies(invokeMethodHint("setCounter", Integer.class)); assertThat(typeHint.fields()).isEmpty(); }).anySatisfy(typeHint -> { assertThat(typeHint.getType()).isEqualTo(TypeReference.of(BaseFactoryBean.class)); - assertThat(typeHint.methods()).singleElement().satisfies(methodHint("setName", String.class)); + assertThat(typeHint.methods()).singleElement().satisfies(invokeMethodHint("setName", String.class)); }).anySatisfy(typeHint -> { assertThat(typeHint.getType()).isEqualTo(TypeReference.of(IntegerFactoryBean.class)); - assertThat(typeHint.constructors()).singleElement().satisfies(constructorHint(Environment.class)); + assertThat(typeHint.constructors()).singleElement().satisfies(introspectConstructorHint(Environment.class)); }).hasSize(3); } @@ -499,32 +546,37 @@ class BeanRegistrationBeanFactoryContributionTests { assertThat(reflectionHints.typeHints()).anySatisfy(typeHint -> { assertThat(typeHint.getType()).isEqualTo(TypeReference.of(NameAndCountersComponent.class)); assertThat(typeHint.constructors()).isEmpty(); - assertThat(typeHint.methods()).singleElement().satisfies(methodHint("setCounters", List.class)); + assertThat(typeHint.methods()).singleElement().satisfies(invokeMethodHint("setCounters", List.class)); assertThat(typeHint.fields()).isEmpty(); }).anySatisfy(typeHint -> { assertThat(typeHint.getType()).isEqualTo(TypeReference.of(BaseFactoryBean.class)); - assertThat(typeHint.methods()).singleElement().satisfies(methodHint("setName", String.class)); + assertThat(typeHint.methods()).singleElement().satisfies(invokeMethodHint("setName", String.class)); }).anySatisfy(typeHint -> { assertThat(typeHint.getType()).isEqualTo(TypeReference.of(IntegerFactoryBean.class)); - assertThat(typeHint.constructors()).singleElement().satisfies(constructorHint(Environment.class)); + assertThat(typeHint.constructors()).singleElement().satisfies(introspectConstructorHint(Environment.class)); }).anySatisfy(typeHint -> { assertThat(typeHint.getType()).isEqualTo(TypeReference.of(AnotherIntegerFactoryBean.class)); - assertThat(typeHint.constructors()).singleElement().satisfies(constructorHint(Environment.class)); + assertThat(typeHint.constructors()).singleElement().satisfies(introspectConstructorHint(Environment.class)); }).hasSize(4); } - private Consumer methodHint(String name, Class... parameterTypes) { + private Consumer invokeMethodHint(String name, Class... parameterTypes) { + return executableHint(ExecutableMode.INVOKE, name, parameterTypes); + } + + private Consumer introspectConstructorHint(Class... parameterTypes) { + return executableHint(ExecutableMode.INTROSPECT, "", parameterTypes); + } + + private Consumer executableHint(ExecutableMode mode, String name, Class... parameterTypes) { return executableHint -> { assertThat(executableHint.getName()).isEqualTo(name); assertThat(executableHint.getParameterTypes()).containsExactly(Arrays.stream(parameterTypes) .map(TypeReference::of).toArray(TypeReference[]::new)); + assertThat(executableHint.getModes()).containsExactly(mode); }; } - private Consumer constructorHint(Class... parameterTypes) { - return methodHint("", parameterTypes); - } - private Consumer hasBeanDefinition(Consumer bd) { return beanFactory -> { assertThat(beanFactory.getBeanDefinitionNames()).contains("test"); diff --git a/spring-beans/src/testFixtures/java/org/springframework/beans/testfixture/beans/factory/generator/lifecycle/Destroy.java b/spring-beans/src/testFixtures/java/org/springframework/beans/testfixture/beans/factory/generator/lifecycle/Destroy.java new file mode 100644 index 00000000000..89db8539e28 --- /dev/null +++ b/spring-beans/src/testFixtures/java/org/springframework/beans/testfixture/beans/factory/generator/lifecycle/Destroy.java @@ -0,0 +1,30 @@ +/* + * Copyright 2002-2022 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.testfixture.beans.factory.generator.lifecycle; + +import java.lang.annotation.Documented; +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +import static java.lang.annotation.ElementType.METHOD; +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +@Documented +@Retention(RUNTIME) +@Target(METHOD) +public @interface Destroy { +} diff --git a/spring-beans/src/testFixtures/java/org/springframework/beans/testfixture/beans/factory/generator/lifecycle/Init.java b/spring-beans/src/testFixtures/java/org/springframework/beans/testfixture/beans/factory/generator/lifecycle/Init.java new file mode 100644 index 00000000000..8c56616b747 --- /dev/null +++ b/spring-beans/src/testFixtures/java/org/springframework/beans/testfixture/beans/factory/generator/lifecycle/Init.java @@ -0,0 +1,30 @@ +/* + * Copyright 2002-2022 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.testfixture.beans.factory.generator.lifecycle; + +import java.lang.annotation.Documented; +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +import static java.lang.annotation.ElementType.METHOD; +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +@Documented +@Retention(RUNTIME) +@Target(METHOD) +public @interface Init { +} diff --git a/spring-beans/src/testFixtures/java/org/springframework/beans/testfixture/beans/factory/generator/lifecycle/InitDestroyBean.java b/spring-beans/src/testFixtures/java/org/springframework/beans/testfixture/beans/factory/generator/lifecycle/InitDestroyBean.java new file mode 100644 index 00000000000..838b16783e4 --- /dev/null +++ b/spring-beans/src/testFixtures/java/org/springframework/beans/testfixture/beans/factory/generator/lifecycle/InitDestroyBean.java @@ -0,0 +1,35 @@ +/* + * Copyright 2002-2022 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.testfixture.beans.factory.generator.lifecycle; + +public class InitDestroyBean { + + @Init + public void initMethod() { + } + + public void customInitMethod() { + } + + @Destroy + public void destroyMethod() { + } + + public void customDestroyMethod() { + } + +} diff --git a/spring-beans/src/testFixtures/java/org/springframework/beans/testfixture/beans/factory/generator/lifecycle/MultiInitDestroyBean.java b/spring-beans/src/testFixtures/java/org/springframework/beans/testfixture/beans/factory/generator/lifecycle/MultiInitDestroyBean.java new file mode 100644 index 00000000000..47cbde92628 --- /dev/null +++ b/spring-beans/src/testFixtures/java/org/springframework/beans/testfixture/beans/factory/generator/lifecycle/MultiInitDestroyBean.java @@ -0,0 +1,29 @@ +/* + * Copyright 2002-2022 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.testfixture.beans.factory.generator.lifecycle; + +public class MultiInitDestroyBean extends InitDestroyBean { + + @Init + void anotherInitMethod() { + } + + @Destroy + void anotherDestroyMethod() { + } + +} diff --git a/spring-context/src/test/java/org/springframework/context/generator/ApplicationContextAotGeneratorTests.java b/spring-context/src/test/java/org/springframework/context/generator/ApplicationContextAotGeneratorTests.java index 4935de29654..38574ef707c 100644 --- a/spring-context/src/test/java/org/springframework/context/generator/ApplicationContextAotGeneratorTests.java +++ b/spring-context/src/test/java/org/springframework/context/generator/ApplicationContextAotGeneratorTests.java @@ -43,9 +43,11 @@ import org.springframework.beans.factory.support.DefaultListableBeanFactory; import org.springframework.beans.factory.support.RootBeanDefinition; import org.springframework.context.ApplicationContextInitializer; import org.springframework.context.annotation.AnnotationConfigUtils; +import org.springframework.context.annotation.CommonAnnotationBeanPostProcessor; import org.springframework.context.support.GenericApplicationContext; import org.springframework.context.testfixture.context.generator.SimpleComponent; import org.springframework.context.testfixture.context.generator.annotation.AutowiredComponent; +import org.springframework.context.testfixture.context.generator.annotation.InitDestroyComponent; import org.springframework.javapoet.ClassName; import org.springframework.javapoet.CodeBlock; import org.springframework.javapoet.JavaFile; @@ -94,6 +96,41 @@ class ApplicationContextAotGeneratorTests { })); } + @Test + void generateApplicationContextWithInitDestroyMethods() { + GenericApplicationContext context = new GenericApplicationContext(); + context.registerBeanDefinition(AnnotationConfigUtils.COMMON_ANNOTATION_PROCESSOR_BEAN_NAME, + BeanDefinitionBuilder.rootBeanDefinition(CommonAnnotationBeanPostProcessor.class) + .setRole(BeanDefinition.ROLE_INFRASTRUCTURE).getBeanDefinition()); + context.registerBeanDefinition("initDestroyComponent", new RootBeanDefinition(InitDestroyComponent.class)); + compile(context, toFreshApplicationContext(GenericApplicationContext::new, aotContext -> { + assertThat(aotContext.getBeanDefinitionNames()).containsOnly("initDestroyComponent"); + InitDestroyComponent bean = aotContext.getBean(InitDestroyComponent.class); + assertThat(bean.events).containsExactly("init"); + aotContext.close(); + assertThat(bean.events).containsExactly("init", "destroy"); + })); + } + + @Test + void generateApplicationContextWithMultipleInitDestroyMethods() { + GenericApplicationContext context = new GenericApplicationContext(); + context.registerBeanDefinition(AnnotationConfigUtils.COMMON_ANNOTATION_PROCESSOR_BEAN_NAME, + BeanDefinitionBuilder.rootBeanDefinition(CommonAnnotationBeanPostProcessor.class) + .setRole(BeanDefinition.ROLE_INFRASTRUCTURE).getBeanDefinition()); + RootBeanDefinition beanDefinition = new RootBeanDefinition(InitDestroyComponent.class); + beanDefinition.setInitMethodName("customInit"); + beanDefinition.setDestroyMethodName("customDestroy"); + context.registerBeanDefinition("initDestroyComponent", beanDefinition); + compile(context, toFreshApplicationContext(GenericApplicationContext::new, aotContext -> { + assertThat(aotContext.getBeanDefinitionNames()).containsOnly("initDestroyComponent"); + InitDestroyComponent bean = aotContext.getBean(InitDestroyComponent.class); + assertThat(bean.events).containsExactly("customInit", "init"); + aotContext.close(); + assertThat(bean.events).containsExactly("customInit", "init", "customDestroy", "destroy"); + })); + } + @Test void generateApplicationContextWitNoContributors() { GeneratedTypeContext generationContext = createGenerationContext(); diff --git a/spring-context/src/testFixtures/java/org/springframework/context/testfixture/context/generator/annotation/InitDestroyComponent.java b/spring-context/src/testFixtures/java/org/springframework/context/testfixture/context/generator/annotation/InitDestroyComponent.java new file mode 100644 index 00000000000..ac8e5b159aa --- /dev/null +++ b/spring-context/src/testFixtures/java/org/springframework/context/testfixture/context/generator/annotation/InitDestroyComponent.java @@ -0,0 +1,47 @@ +/* + * Copyright 2002-2022 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.context.testfixture.context.generator.annotation; + +import java.util.ArrayList; +import java.util.List; + +import jakarta.annotation.PostConstruct; +import jakarta.annotation.PreDestroy; + +public class InitDestroyComponent { + + public final List events = new ArrayList<>(); + + @PostConstruct + public void init() { + this.events.add("init"); + } + + public void customInit() { + this.events.add("customInit"); + } + + @PreDestroy + public void destroy() { + this.events.add("destroy"); + } + + public void customDestroy() { + this.events.add("customDestroy"); + } + +}