Resolve infer destroy method at build-time

This commit allows a RootBeanDefinition to resolve its infer destroy
method if necessary. Contrary to BeanInstanceAdapter that uses the
actual bean instance, the new method works against the type exposed
in the bean definition.

The AOT contribution of InitDestroyAnnotationBeanPostProcessor uses
the new method to make sure the special '(inferred)' placeholder is
handled prior to code generation.

Closes gh-28215
This commit is contained in:
Stephane Nicoll
2022-07-27 11:33:06 +02:00
parent 3f3e37e66c
commit 735051bf7d
8 changed files with 109 additions and 38 deletions

View File

@@ -18,10 +18,12 @@ package org.springframework.beans.factory.annotation;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.support.AbstractBeanDefinition;
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.InferredDestroyBean;
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;
@@ -40,7 +42,7 @@ class InitDestroyAnnotationBeanPostProcessorTests {
@Test
void processAheadOfTimeWhenNoCallbackDoesNotMutateRootBeanDefinition() {
RootBeanDefinition beanDefinition = new RootBeanDefinition(String.class);
RootBeanDefinition beanDefinition = new RootBeanDefinition(NoInitDestroyBean.class);
processAheadOfTime(beanDefinition);
RootBeanDefinition mergedBeanDefinition = getMergedBeanDefinition();
assertThat(mergedBeanDefinition.getInitMethodNames()).isNull();
@@ -78,6 +80,26 @@ class InitDestroyAnnotationBeanPostProcessorTests {
assertThat(mergedBeanDefinition.getDestroyMethodNames()).containsExactly("destroyMethod");
}
@Test
void processAheadOfTimeWhenHasInferredDestroyMethodAddsDestroyMethodName() {
RootBeanDefinition beanDefinition = new RootBeanDefinition(InferredDestroyBean.class);
beanDefinition.setDestroyMethodNames(AbstractBeanDefinition.INFER_METHOD);
processAheadOfTime(beanDefinition);
RootBeanDefinition mergedBeanDefinition = getMergedBeanDefinition();
assertThat(mergedBeanDefinition.getInitMethodNames()).isNull();
assertThat(mergedBeanDefinition.getDestroyMethodNames()).containsExactly("close");
}
@Test
void processAheadOfTimeWhenHasInferredDestroyMethodAndNoCandidateDoesNotMutateRootBeanDefinition() {
RootBeanDefinition beanDefinition = new RootBeanDefinition(NoInitDestroyBean.class);
beanDefinition.setDestroyMethodNames(AbstractBeanDefinition.INFER_METHOD);
processAheadOfTime(beanDefinition);
RootBeanDefinition mergedBeanDefinition = getMergedBeanDefinition();
assertThat(mergedBeanDefinition.getInitMethodNames()).isNull();
assertThat(mergedBeanDefinition.getDestroyMethodNames()).isNull();
}
@Test
void processAheadOfTimeWhenHasMultipleInitDestroyAnnotationsAddsAllMethodNames() {
RootBeanDefinition beanDefinition = new RootBeanDefinition(MultiInitDestroyBean.class);
@@ -110,4 +132,6 @@ class InitDestroyAnnotationBeanPostProcessorTests {
return beanPostProcessor;
}
static class NoInitDestroyBean {}
}

View File

@@ -36,7 +36,6 @@ import org.springframework.beans.factory.config.BeanReference;
import org.springframework.beans.factory.config.ConstructorArgumentValues.ValueHolder;
import org.springframework.beans.factory.config.RuntimeBeanNameReference;
import org.springframework.beans.factory.config.RuntimeBeanReference;
import org.springframework.beans.factory.support.AbstractBeanDefinition;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.beans.factory.support.ManagedList;
import org.springframework.beans.factory.support.ManagedMap;
@@ -225,9 +224,8 @@ class BeanDefinitionPropertiesCodeGeneratorTests {
}
@Test
void setInitMethodWhenSingleInferredInitMethod() {
void setInitMethodWhenNoInitMethod() {
this.beanDefinition.setTargetType(InitDestroyBean.class);
this.beanDefinition.setInitMethodName(AbstractBeanDefinition.INFER_METHOD);
compile((actual, compiled) -> assertThat(actual.getInitMethodNames()).isNull());
}
@@ -241,13 +239,6 @@ class BeanDefinitionPropertiesCodeGeneratorTests {
assertHasMethodInvokeHints(InitDestroyBean.class, methodNames);
}
@Test
void setInitMethodWithInferredMethodFirst() {
this.beanDefinition.setInitMethodNames(AbstractBeanDefinition.INFER_METHOD, "init");
compile((actual, compiled) -> assertThat(compiled.getSourceFile().getContent())
.contains("beanDefinition.setInitMethodNames(\"init\");"));
}
@Test
void setDestroyMethodWhenDestroyInitMethod() {
this.beanDefinition.setTargetType(InitDestroyBean.class);
@@ -260,9 +251,8 @@ class BeanDefinitionPropertiesCodeGeneratorTests {
}
@Test
void setDestroyMethodWhenSingleInferredInitMethod() {
void setDestroyMethodWhenNoDestroyMethod() {
this.beanDefinition.setTargetType(InitDestroyBean.class);
this.beanDefinition.setDestroyMethodName(AbstractBeanDefinition.INFER_METHOD);
compile((actual, compiled) -> assertThat(actual.getDestroyMethodNames()).isNull());
}
@@ -277,13 +267,6 @@ class BeanDefinitionPropertiesCodeGeneratorTests {
assertHasMethodInvokeHints(InitDestroyBean.class, methodNames);
}
@Test
void setDestroyMethodWithInferredMethodFirst() {
this.beanDefinition.setDestroyMethodNames(AbstractBeanDefinition.INFER_METHOD, "destroy");
compile((actual, compiled) -> assertThat(compiled.getSourceFile().getContent())
.contains("beanDefinition.setDestroyMethodNames(\"destroy\");"));
}
private void assertHasMethodInvokeHints(Class<?> beanType, String... methodNames) {
assertThat(methodNames).allMatch(methodName -> RuntimeHintsPredicates.reflection()
.onMethod(beanType, methodName).invoke()

View File

@@ -57,4 +57,39 @@ class RootBeanDefinitionTests {
verify(instanceSupplier).getFactoryMethod();
}
@Test
void resolveDestroyMethodWithMatchingCandidateReplacedInferredVaue() {
RootBeanDefinition beanDefinition = new RootBeanDefinition(BeanWithCloseMethod.class);
beanDefinition.setDestroyMethodName(AbstractBeanDefinition.INFER_METHOD);
beanDefinition.resolveDestroyMethodIfNecessary();
assertThat(beanDefinition.getDestroyMethodNames()).containsExactly("close");
}
@Test
void resolveDestroyMethodWithNoCandidateSetDestroyMethodNameToNull() {
RootBeanDefinition beanDefinition = new RootBeanDefinition(BeanWithNoDestroyMethod.class);
beanDefinition.setDestroyMethodName(AbstractBeanDefinition.INFER_METHOD);
beanDefinition.resolveDestroyMethodIfNecessary();
assertThat(beanDefinition.getDestroyMethodNames()).isNull();
}
@Test
void resolveDestroyMethodWithNoResolvableType() {
RootBeanDefinition beanDefinition = new RootBeanDefinition();
beanDefinition.setDestroyMethodName(AbstractBeanDefinition.INFER_METHOD);
beanDefinition.resolveDestroyMethodIfNecessary();
assertThat(beanDefinition.getDestroyMethodNames()).isNull();
}
static class BeanWithCloseMethod {
public void close() {
}
}
static class BeanWithNoDestroyMethod {
}
}