Make sure inferred destroy method is set on the original bean definition

This commit updates InitDestroyAnnotationBeanPostProcessor to mutate
the original bean definition rather than the merged one that can be
recreated without it if the cache gets stale.

See gh-28215
This commit is contained in:
Stephane Nicoll
2022-08-08 14:33:00 +02:00
parent 3c76437868
commit 2e1538a20b
5 changed files with 49 additions and 22 deletions

View File

@@ -41,7 +41,9 @@ 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.BeanDefinition;
import org.springframework.beans.factory.config.DestructionAwareBeanPostProcessor;
import org.springframework.beans.factory.support.AbstractBeanDefinition;
import org.springframework.beans.factory.support.MergedBeanDefinitionPostProcessor;
import org.springframework.beans.factory.support.RegisteredBean;
import org.springframework.beans.factory.support.RootBeanDefinition;
@@ -158,20 +160,30 @@ public class InitDestroyAnnotationBeanPostProcessor implements DestructionAwareB
@Override
public BeanRegistrationAotContribution processAheadOfTime(RegisteredBean registeredBean) {
RootBeanDefinition beanDefinition = registeredBean.getMergedBeanDefinition();
beanDefinition.resolveDestroyMethodIfNecessary();
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);
AbstractBeanDefinition beanDefinition = getOriginalBeanDefinition(registeredBean);
if (beanDefinition != null) {
RootBeanDefinition mergedBeanDefinition = registeredBean.getMergedBeanDefinition();
beanDefinition.resolveDestroyMethodIfNecessary();
LifecycleMetadata metadata = findInjectionMetadata(mergedBeanDefinition, 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);
}
registeredBean.getBeanFactory().clearMetadataCache();
}
return null;
}
@Nullable
private AbstractBeanDefinition getOriginalBeanDefinition(RegisteredBean registeredBean) {
BeanDefinition beanDefinition = registeredBean.getBeanFactory().getBeanDefinition(registeredBean.getBeanName());
return (beanDefinition instanceof AbstractBeanDefinition abd ? abd : null);
}
private LifecycleMetadata findInjectionMetadata(RootBeanDefinition beanDefinition, Class<?> beanType) {
LifecycleMetadata metadata = findLifecycleMetadata(beanType);
metadata.checkConfigMembers(beanDefinition);

View File

@@ -1186,6 +1186,14 @@ public abstract class AbstractBeanDefinition extends BeanMetadataAttributeAccess
}
}
/**
* Resolve the inferred destroy method if necessary.
* @since 6.0
*/
public void resolveDestroyMethodIfNecessary() {
setDestroyMethodNames(DisposableBeanAdapter
.inferDestroyMethodsIfNecessary(getResolvableType().toClass(), this));
}
/**
* Public declaration of Object's {@code clone()} method.

View File

@@ -344,13 +344,14 @@ class DisposableBeanAdapter implements DisposableBean, Runnable, Serializable {
* interfaces, reflectively calling the "close" method on implementing beans as well.
*/
@Nullable
static String[] inferDestroyMethodsIfNecessary(Class<?> target, RootBeanDefinition beanDefinition) {
static String[] inferDestroyMethodsIfNecessary(Class<?> target, AbstractBeanDefinition beanDefinition) {
String[] destroyMethodNames = beanDefinition.getDestroyMethodNames();
if (destroyMethodNames != null && destroyMethodNames.length > 1) {
return destroyMethodNames;
}
String destroyMethodName = beanDefinition.resolvedDestroyMethodName;
String destroyMethodName = (beanDefinition instanceof RootBeanDefinition rbd
? rbd.resolvedDestroyMethodName : null);
if (destroyMethodName == null) {
destroyMethodName = beanDefinition.getDestroyMethodName();
boolean autoCloseable = (AutoCloseable.class.isAssignableFrom(target));
@@ -378,7 +379,9 @@ class DisposableBeanAdapter implements DisposableBean, Runnable, Serializable {
}
}
}
beanDefinition.resolvedDestroyMethodName = (destroyMethodName != null ? destroyMethodName : "");
if (beanDefinition instanceof RootBeanDefinition rbd) {
rbd.resolvedDestroyMethodName = (destroyMethodName != null ? destroyMethodName : "");
}
}
return (StringUtils.hasLength(destroyMethodName) ? new String[] {destroyMethodName} : null);
}

View File

@@ -550,15 +550,6 @@ public class RootBeanDefinition extends AbstractBeanDefinition {
}
}
/**
* Resolve the inferred destroy method if necessary.
* @since 6.0
*/
public void resolveDestroyMethodIfNecessary() {
setDestroyMethodNames(DisposableBeanAdapter
.inferDestroyMethodsIfNecessary(getResolvableType().toClass(), this));
}
/**
* Register an externally managed configuration destruction method &mdash;
* for example, a method annotated with JSR-250's

View File

@@ -90,6 +90,19 @@ class InitDestroyAnnotationBeanPostProcessorTests {
assertThat(mergedBeanDefinition.getDestroyMethodNames()).containsExactly("close");
}
@Test
void processAheadOfTimeWhenHasInferredDestroyMethodIsRetainedIfMergedBeanDefinitionIsStale() {
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");
RootBeanDefinition originalBeanDefinition = (RootBeanDefinition) this.beanFactory.getBeanDefinition("test");
assertThat(originalBeanDefinition.getInitMethodNames()).isNull();
assertThat(originalBeanDefinition.getDestroyMethodNames()).containsExactly("close");
}
@Test
void processAheadOfTimeWhenHasInferredDestroyMethodAndNoCandidateDoesNotMutateRootBeanDefinition() {
RootBeanDefinition beanDefinition = new RootBeanDefinition(NoInitDestroyBean.class);