Support for registering multiple init/destroy methods on AbstractBeanDefinition

Closes gh-28013
This commit is contained in:
Juergen Hoeller 2022-02-17 18:14:09 +01:00
parent 8506778608
commit 41ee23345d
6 changed files with 238 additions and 145 deletions

View File

@ -74,7 +74,6 @@ import org.springframework.core.ParameterNameDiscoverer;
import org.springframework.core.PriorityOrdered; import org.springframework.core.PriorityOrdered;
import org.springframework.core.ResolvableType; import org.springframework.core.ResolvableType;
import org.springframework.lang.Nullable; import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils; import org.springframework.util.ClassUtils;
import org.springframework.util.ObjectUtils; import org.springframework.util.ObjectUtils;
import org.springframework.util.ReflectionUtils; import org.springframework.util.ReflectionUtils;
@ -1789,11 +1788,15 @@ public abstract class AbstractAutowireCapableBeanFactory extends AbstractBeanFac
} }
if (mbd != null && bean.getClass() != NullBean.class) { if (mbd != null && bean.getClass() != NullBean.class) {
String initMethodName = mbd.getInitMethodName(); String[] initMethodNames = mbd.getInitMethodNames();
if (initMethodNames != null) {
for (String initMethodName : initMethodNames) {
if (StringUtils.hasLength(initMethodName) && if (StringUtils.hasLength(initMethodName) &&
!(isInitializingBean && "afterPropertiesSet".equals(initMethodName)) && !(isInitializingBean && "afterPropertiesSet".equals(initMethodName)) &&
!mbd.isExternallyManagedInitMethod(initMethodName)) { !mbd.isExternallyManagedInitMethod(initMethodName)) {
invokeCustomInitMethod(beanName, bean, mbd); invokeCustomInitMethod(beanName, bean, mbd, initMethodName);
}
}
} }
} }
} }
@ -1805,11 +1808,9 @@ public abstract class AbstractAutowireCapableBeanFactory extends AbstractBeanFac
* methods with arguments. * methods with arguments.
* @see #invokeInitMethods * @see #invokeInitMethods
*/ */
protected void invokeCustomInitMethod(String beanName, Object bean, RootBeanDefinition mbd) protected void invokeCustomInitMethod(String beanName, Object bean, RootBeanDefinition mbd, String initMethodName)
throws Throwable { throws Throwable {
String initMethodName = mbd.getInitMethodName();
Assert.state(initMethodName != null, "No init method set");
Method initMethod = (mbd.isNonPublicAccessAllowed() ? Method initMethod = (mbd.isNonPublicAccessAllowed() ?
BeanUtils.findMethod(bean.getClass(), initMethodName) : BeanUtils.findMethod(bean.getClass(), initMethodName) :
ClassUtils.getMethodIfAvailable(bean.getClass(), initMethodName)); ClassUtils.getMethodIfAvailable(bean.getClass(), initMethodName));

View File

@ -1,5 +1,5 @@
/* /*
* Copyright 2002-2021 the original author or authors. * Copyright 2002-2022 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -184,10 +184,10 @@ public abstract class AbstractBeanDefinition extends BeanMetadataAttributeAccess
private MethodOverrides methodOverrides = new MethodOverrides(); private MethodOverrides methodOverrides = new MethodOverrides();
@Nullable @Nullable
private String initMethodName; private String[] initMethodNames;
@Nullable @Nullable
private String destroyMethodName; private String[] destroyMethodNames;
private boolean enforceInitMethod = true; private boolean enforceInitMethod = true;
@ -262,9 +262,9 @@ public abstract class AbstractBeanDefinition extends BeanMetadataAttributeAccess
setInstanceSupplier(originalAbd.getInstanceSupplier()); setInstanceSupplier(originalAbd.getInstanceSupplier());
setNonPublicAccessAllowed(originalAbd.isNonPublicAccessAllowed()); setNonPublicAccessAllowed(originalAbd.isNonPublicAccessAllowed());
setLenientConstructorResolution(originalAbd.isLenientConstructorResolution()); setLenientConstructorResolution(originalAbd.isLenientConstructorResolution());
setInitMethodName(originalAbd.getInitMethodName()); setInitMethodNames(originalAbd.getInitMethodNames());
setEnforceInitMethod(originalAbd.isEnforceInitMethod()); setEnforceInitMethod(originalAbd.isEnforceInitMethod());
setDestroyMethodName(originalAbd.getDestroyMethodName()); setDestroyMethodNames(originalAbd.getDestroyMethodNames());
setEnforceDestroyMethod(originalAbd.isEnforceDestroyMethod()); setEnforceDestroyMethod(originalAbd.isEnforceDestroyMethod());
setSynthetic(originalAbd.isSynthetic()); setSynthetic(originalAbd.isSynthetic());
setResource(originalAbd.getResource()); setResource(originalAbd.getResource());
@ -338,12 +338,12 @@ public abstract class AbstractBeanDefinition extends BeanMetadataAttributeAccess
setInstanceSupplier(otherAbd.getInstanceSupplier()); setInstanceSupplier(otherAbd.getInstanceSupplier());
setNonPublicAccessAllowed(otherAbd.isNonPublicAccessAllowed()); setNonPublicAccessAllowed(otherAbd.isNonPublicAccessAllowed());
setLenientConstructorResolution(otherAbd.isLenientConstructorResolution()); setLenientConstructorResolution(otherAbd.isLenientConstructorResolution());
if (otherAbd.getInitMethodName() != null) { if (otherAbd.getInitMethodNames() != null) {
setInitMethodName(otherAbd.getInitMethodName()); setInitMethodNames(otherAbd.getInitMethodNames());
setEnforceInitMethod(otherAbd.isEnforceInitMethod()); setEnforceInitMethod(otherAbd.isEnforceInitMethod());
} }
if (otherAbd.getDestroyMethodName() != null) { if (otherAbd.getDestroyMethodNames() != null) {
setDestroyMethodName(otherAbd.getDestroyMethodName()); setDestroyMethodNames(otherAbd.getDestroyMethodNames());
setEnforceDestroyMethod(otherAbd.isEnforceDestroyMethod()); setEnforceDestroyMethod(otherAbd.isEnforceDestroyMethod());
} }
setSynthetic(otherAbd.isSynthetic()); setSynthetic(otherAbd.isSynthetic());
@ -919,21 +919,41 @@ public abstract class AbstractBeanDefinition extends BeanMetadataAttributeAccess
} }
/** /**
* Set the name of the initializer method. * Specify the names of multiple initializer methods.
* <p>The default is {@code null} in which case there is no initializer method. * <p>The default is {@code null} in which case there are no initializer methods.
* @since 6.0
* @see #setInitMethodName
*/ */
@Override public void setInitMethodNames(@Nullable String... initMethodNames) {
public void setInitMethodName(@Nullable String initMethodName) { this.initMethodNames = initMethodNames;
this.initMethodName = initMethodName;
} }
/** /**
* Return the name of the initializer method. * Return the names of the initializer methods.
* @since 6.0
*/
@Nullable
public String[] getInitMethodNames() {
return this.initMethodNames;
}
/**
* Set the name of the initializer method.
* <p>The default is {@code null} in which case there is no initializer method.
* @see #setInitMethodNames
*/
@Override
public void setInitMethodName(@Nullable String initMethodName) {
this.initMethodNames = (initMethodName != null ? new String[] {initMethodName} : null);
}
/**
* Return the name of the initializer method (the first one in case of multiple methods).
*/ */
@Override @Override
@Nullable @Nullable
public String getInitMethodName() { public String getInitMethodName() {
return this.initMethodName; return (!ObjectUtils.isEmpty(this.initMethodNames) ? this.initMethodNames[0] : null);
} }
/** /**
@ -958,21 +978,41 @@ public abstract class AbstractBeanDefinition extends BeanMetadataAttributeAccess
} }
/** /**
* Set the name of the destroy method. * Specify the names of multiple destroy methods.
* <p>The default is {@code null} in which case there is no destroy method. * <p>The default is {@code null} in which case there are no destroy methods.
* @since 6.0
* @see #setDestroyMethodName
*/ */
@Override public void setDestroyMethodNames(@Nullable String... destroyMethodNames) {
public void setDestroyMethodName(@Nullable String destroyMethodName) { this.destroyMethodNames = destroyMethodNames;
this.destroyMethodName = destroyMethodName;
} }
/** /**
* Return the name of the destroy method. * Return the names of the destroy methods.
* @since 6.0
*/
@Nullable
public String[] getDestroyMethodNames() {
return this.destroyMethodNames;
}
/**
* Set the name of the destroy method.
* <p>The default is {@code null} in which case there is no destroy method.
* @see #setDestroyMethodNames
*/
@Override
public void setDestroyMethodName(@Nullable String destroyMethodName) {
this.destroyMethodNames = (destroyMethodName != null ? new String[] {destroyMethodName} : null);
}
/**
* Return the name of the destroy method (the first one in case of multiple methods).
*/ */
@Override @Override
@Nullable @Nullable
public String getDestroyMethodName() { public String getDestroyMethodName() {
return this.destroyMethodName; return (!ObjectUtils.isEmpty(this.destroyMethodNames) ? this.destroyMethodNames[0] : null);
} }
/** /**
@ -1189,9 +1229,9 @@ public abstract class AbstractBeanDefinition extends BeanMetadataAttributeAccess
ObjectUtils.nullSafeEquals(this.methodOverrides, that.methodOverrides) && ObjectUtils.nullSafeEquals(this.methodOverrides, that.methodOverrides) &&
ObjectUtils.nullSafeEquals(this.factoryBeanName, that.factoryBeanName) && ObjectUtils.nullSafeEquals(this.factoryBeanName, that.factoryBeanName) &&
ObjectUtils.nullSafeEquals(this.factoryMethodName, that.factoryMethodName) && ObjectUtils.nullSafeEquals(this.factoryMethodName, that.factoryMethodName) &&
ObjectUtils.nullSafeEquals(this.initMethodName, that.initMethodName) && ObjectUtils.nullSafeEquals(this.initMethodNames, that.initMethodNames) &&
this.enforceInitMethod == that.enforceInitMethod && this.enforceInitMethod == that.enforceInitMethod &&
ObjectUtils.nullSafeEquals(this.destroyMethodName, that.destroyMethodName) && ObjectUtils.nullSafeEquals(this.destroyMethodNames, that.destroyMethodNames) &&
this.enforceDestroyMethod == that.enforceDestroyMethod && this.enforceDestroyMethod == that.enforceDestroyMethod &&
this.synthetic == that.synthetic && this.synthetic == that.synthetic &&
this.role == that.role && this.role == that.role &&
@ -1241,8 +1281,8 @@ public abstract class AbstractBeanDefinition extends BeanMetadataAttributeAccess
sb.append("; primary=").append(this.primary); sb.append("; primary=").append(this.primary);
sb.append("; factoryBeanName=").append(this.factoryBeanName); sb.append("; factoryBeanName=").append(this.factoryBeanName);
sb.append("; factoryMethodName=").append(this.factoryMethodName); sb.append("; factoryMethodName=").append(this.factoryMethodName);
sb.append("; initMethodName=").append(this.initMethodName); sb.append("; initMethodNames=").append(this.initMethodNames);
sb.append("; destroyMethodName=").append(this.destroyMethodName); sb.append("; destroyMethodNames=").append(this.destroyMethodNames);
if (this.resource != null) { if (this.resource != null) {
sb.append("; defined in ").append(this.resource.getDescription()); sb.append("; defined in ").append(this.resource.getDescription());
} }

View File

@ -32,6 +32,7 @@ import org.springframework.lang.Nullable;
import org.springframework.util.Assert; import org.springframework.util.Assert;
import org.springframework.util.ClassUtils; import org.springframework.util.ClassUtils;
import org.springframework.util.CollectionUtils; import org.springframework.util.CollectionUtils;
import org.springframework.util.ObjectUtils;
import org.springframework.util.ReflectionUtils; import org.springframework.util.ReflectionUtils;
import org.springframework.util.StringUtils; import org.springframework.util.StringUtils;
@ -51,7 +52,7 @@ import org.springframework.util.StringUtils;
* @see AbstractBeanFactory * @see AbstractBeanFactory
* @see org.springframework.beans.factory.DisposableBean * @see org.springframework.beans.factory.DisposableBean
* @see org.springframework.beans.factory.config.DestructionAwareBeanPostProcessor * @see org.springframework.beans.factory.config.DestructionAwareBeanPostProcessor
* @see AbstractBeanDefinition#getDestroyMethodName() * @see AbstractBeanDefinition#getDestroyMethodNames()
*/ */
@SuppressWarnings("serial") @SuppressWarnings("serial")
class DisposableBeanAdapter implements DisposableBean, Runnable, Serializable { class DisposableBeanAdapter implements DisposableBean, Runnable, Serializable {
@ -76,10 +77,10 @@ class DisposableBeanAdapter implements DisposableBean, Runnable, Serializable {
private boolean invokeAutoCloseable; private boolean invokeAutoCloseable;
@Nullable @Nullable
private String destroyMethodName; private String[] destroyMethodNames;
@Nullable @Nullable
private transient Method destroyMethod; private transient Method[] destroyMethods;
@Nullable @Nullable
private final List<DestructionAwareBeanPostProcessor> beanPostProcessors; private final List<DestructionAwareBeanPostProcessor> beanPostProcessors;
@ -103,14 +104,18 @@ class DisposableBeanAdapter implements DisposableBean, Runnable, Serializable {
this.invokeDisposableBean = (bean instanceof DisposableBean && this.invokeDisposableBean = (bean instanceof DisposableBean &&
!beanDefinition.isExternallyManagedDestroyMethod(DESTROY_METHOD_NAME)); !beanDefinition.isExternallyManagedDestroyMethod(DESTROY_METHOD_NAME));
String destroyMethodName = inferDestroyMethodIfNecessary(bean, beanDefinition); String[] destroyMethodNames = inferDestroyMethodsIfNecessary(bean, beanDefinition);
if (destroyMethodName != null && if (!ObjectUtils.isEmpty(destroyMethodNames) &&
!(this.invokeDisposableBean && DESTROY_METHOD_NAME.equals(destroyMethodName)) && !(this.invokeDisposableBean && DESTROY_METHOD_NAME.equals(destroyMethodNames[0])) &&
!beanDefinition.isExternallyManagedDestroyMethod(destroyMethodName)) { !beanDefinition.isExternallyManagedDestroyMethod(destroyMethodNames[0])) {
this.invokeAutoCloseable = (bean instanceof AutoCloseable && CLOSE_METHOD_NAME.equals(destroyMethodName)); this.invokeAutoCloseable =
(bean instanceof AutoCloseable && CLOSE_METHOD_NAME.equals(destroyMethodNames[0]));
if (!this.invokeAutoCloseable) { if (!this.invokeAutoCloseable) {
this.destroyMethodName = destroyMethodName; this.destroyMethodNames = destroyMethodNames;
Method[] destroyMethods = new Method[destroyMethodNames.length];
for (int i = 0; i < destroyMethodNames.length; i++) {
String destroyMethodName = destroyMethodNames[i];
Method destroyMethod = determineDestroyMethod(destroyMethodName); Method destroyMethod = determineDestroyMethod(destroyMethodName);
if (destroyMethod == null) { if (destroyMethod == null) {
if (beanDefinition.isEnforceDestroyMethod()) { if (beanDefinition.isEnforceDestroyMethod()) {
@ -132,7 +137,9 @@ class DisposableBeanAdapter implements DisposableBean, Runnable, Serializable {
} }
destroyMethod = ClassUtils.getInterfaceMethodIfPossible(destroyMethod, bean.getClass()); destroyMethod = ClassUtils.getInterfaceMethodIfPossible(destroyMethod, bean.getClass());
} }
this.destroyMethod = destroyMethod; destroyMethods[i] = destroyMethod;
}
this.destroyMethods = destroyMethods;
} }
} }
@ -158,7 +165,7 @@ class DisposableBeanAdapter implements DisposableBean, Runnable, Serializable {
* Create a new DisposableBeanAdapter for the given bean. * Create a new DisposableBeanAdapter for the given bean.
*/ */
private DisposableBeanAdapter(Object bean, String beanName, boolean nonPublicAccessAllowed, private DisposableBeanAdapter(Object bean, String beanName, boolean nonPublicAccessAllowed,
boolean invokeDisposableBean, boolean invokeAutoCloseable, @Nullable String destroyMethodName, boolean invokeDisposableBean, boolean invokeAutoCloseable, @Nullable String[] destroyMethodNames,
@Nullable List<DestructionAwareBeanPostProcessor> postProcessors) { @Nullable List<DestructionAwareBeanPostProcessor> postProcessors) {
this.bean = bean; this.bean = bean;
@ -166,7 +173,7 @@ class DisposableBeanAdapter implements DisposableBean, Runnable, Serializable {
this.nonPublicAccessAllowed = nonPublicAccessAllowed; this.nonPublicAccessAllowed = nonPublicAccessAllowed;
this.invokeDisposableBean = invokeDisposableBean; this.invokeDisposableBean = invokeDisposableBean;
this.invokeAutoCloseable = invokeAutoCloseable; this.invokeAutoCloseable = invokeAutoCloseable;
this.destroyMethodName = destroyMethodName; this.destroyMethodNames = destroyMethodNames;
this.beanPostProcessors = postProcessors; this.beanPostProcessors = postProcessors;
} }
@ -219,13 +226,18 @@ class DisposableBeanAdapter implements DisposableBean, Runnable, Serializable {
} }
} }
} }
else if (this.destroyMethod != null) { else if (this.destroyMethods != null) {
invokeCustomDestroyMethod(this.destroyMethod); for (Method destroyMethod : this.destroyMethods) {
invokeCustomDestroyMethod(destroyMethod);
} }
else if (this.destroyMethodName != null) { }
Method destroyMethod = determineDestroyMethod(this.destroyMethodName); else if (this.destroyMethodNames != null) {
for (String destroyMethodName: this.destroyMethodNames) {
Method destroyMethod = determineDestroyMethod(destroyMethodName);
if (destroyMethod != null) { if (destroyMethod != null) {
invokeCustomDestroyMethod(ClassUtils.getInterfaceMethodIfPossible(destroyMethod, this.bean.getClass())); invokeCustomDestroyMethod(
ClassUtils.getInterfaceMethodIfPossible(destroyMethod, this.bean.getClass()));
}
} }
} }
} }
@ -255,14 +267,14 @@ class DisposableBeanAdapter implements DisposableBean, Runnable, Serializable {
* for a method with a single boolean argument (passing in "true", * for a method with a single boolean argument (passing in "true",
* assuming a "force" parameter), else logging an error. * assuming a "force" parameter), else logging an error.
*/ */
private void invokeCustomDestroyMethod(final Method destroyMethod) { private void invokeCustomDestroyMethod(Method destroyMethod) {
int paramCount = destroyMethod.getParameterCount(); int paramCount = destroyMethod.getParameterCount();
final Object[] args = new Object[paramCount]; final Object[] args = new Object[paramCount];
if (paramCount == 1) { if (paramCount == 1) {
args[0] = Boolean.TRUE; args[0] = Boolean.TRUE;
} }
if (logger.isTraceEnabled()) { if (logger.isTraceEnabled()) {
logger.trace("Invoking custom destroy method '" + this.destroyMethodName + logger.trace("Invoking custom destroy method '" + destroyMethod.getName() +
"' on bean with name '" + this.beanName + "'"); "' on bean with name '" + this.beanName + "'");
} }
try { try {
@ -270,7 +282,7 @@ class DisposableBeanAdapter implements DisposableBean, Runnable, Serializable {
destroyMethod.invoke(this.bean, args); destroyMethod.invoke(this.bean, args);
} }
catch (InvocationTargetException ex) { catch (InvocationTargetException ex) {
String msg = "Custom destroy method '" + this.destroyMethodName + "' on bean with name '" + String msg = "Custom destroy method '" + destroyMethod.getName() + "' on bean with name '" +
this.beanName + "' threw an exception"; this.beanName + "' threw an exception";
if (logger.isDebugEnabled()) { if (logger.isDebugEnabled()) {
logger.warn(msg, ex.getTargetException()); logger.warn(msg, ex.getTargetException());
@ -280,7 +292,7 @@ class DisposableBeanAdapter implements DisposableBean, Runnable, Serializable {
} }
} }
catch (Throwable ex) { catch (Throwable ex) {
logger.warn("Failed to invoke custom destroy method '" + this.destroyMethodName + logger.warn("Failed to invoke custom destroy method '" + destroyMethod.getName() +
"' on bean with name '" + this.beanName + "'", ex); "' on bean with name '" + this.beanName + "'", ex);
} }
} }
@ -302,7 +314,7 @@ class DisposableBeanAdapter implements DisposableBean, Runnable, Serializable {
} }
return new DisposableBeanAdapter( return new DisposableBeanAdapter(
this.bean, this.beanName, this.nonPublicAccessAllowed, this.invokeDisposableBean, this.bean, this.beanName, this.nonPublicAccessAllowed, this.invokeDisposableBean,
this.invokeAutoCloseable, this.destroyMethodName, serializablePostProcessors); this.invokeAutoCloseable, this.destroyMethodNames, serializablePostProcessors);
} }
@ -312,7 +324,7 @@ class DisposableBeanAdapter implements DisposableBean, Runnable, Serializable {
* @param beanDefinition the corresponding bean definition * @param beanDefinition the corresponding bean definition
*/ */
public static boolean hasDestroyMethod(Object bean, RootBeanDefinition beanDefinition) { public static boolean hasDestroyMethod(Object bean, RootBeanDefinition beanDefinition) {
return (bean instanceof DisposableBean || inferDestroyMethodIfNecessary(bean, beanDefinition) != null); return (bean instanceof DisposableBean || inferDestroyMethodsIfNecessary(bean, beanDefinition) != null);
} }
@ -330,7 +342,12 @@ class DisposableBeanAdapter implements DisposableBean, Runnable, Serializable {
* interfaces, reflectively calling the "close" method on implementing beans as well. * interfaces, reflectively calling the "close" method on implementing beans as well.
*/ */
@Nullable @Nullable
private static String inferDestroyMethodIfNecessary(Object bean, RootBeanDefinition beanDefinition) { private static String[] inferDestroyMethodsIfNecessary(Object bean, RootBeanDefinition beanDefinition) {
String[] destroyMethodNames = beanDefinition.getDestroyMethodNames();
if (destroyMethodNames != null && destroyMethodNames.length > 1) {
return destroyMethodNames;
}
String destroyMethodName = beanDefinition.resolvedDestroyMethodName; String destroyMethodName = beanDefinition.resolvedDestroyMethodName;
if (destroyMethodName == null) { if (destroyMethodName == null) {
destroyMethodName = beanDefinition.getDestroyMethodName(); destroyMethodName = beanDefinition.getDestroyMethodName();
@ -361,7 +378,7 @@ class DisposableBeanAdapter implements DisposableBean, Runnable, Serializable {
} }
beanDefinition.resolvedDestroyMethodName = (destroyMethodName != null ? destroyMethodName : ""); beanDefinition.resolvedDestroyMethodName = (destroyMethodName != null ? destroyMethodName : "");
} }
return (StringUtils.hasLength(destroyMethodName) ? destroyMethodName : null); return (StringUtils.hasLength(destroyMethodName) ? new String[] {destroyMethodName} : null);
} }
/** /**

View File

@ -22,6 +22,7 @@ import java.lang.reflect.Field;
import java.net.MalformedURLException; import java.net.MalformedURLException;
import java.text.NumberFormat; import java.text.NumberFormat;
import java.text.ParseException; import java.text.ParseException;
import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.HashSet; import java.util.HashSet;
import java.util.Iterator; import java.util.Iterator;
@ -2333,6 +2334,19 @@ class DefaultListableBeanFactoryTests {
assertThat(tb2.getBeanName()).isEqualTo("myBeanName"); assertThat(tb2.getBeanName()).isEqualTo("myBeanName");
} }
@Test
void multipleInitAndDestroyMethods() {
RootBeanDefinition bd = new RootBeanDefinition(BeanWithInitAndDestroyMethods.class);
bd.setInitMethodNames("init1", "init2");
bd.setDestroyMethodNames("destroy2", "destroy1");
lbf.registerBeanDefinition("test", bd);
BeanWithInitAndDestroyMethods bean = lbf.getBean("test", BeanWithInitAndDestroyMethods.class);
assertThat(bean.initMethods).containsExactly("init", "init1", "init2");
assertThat(bean.destroyMethods).isEmpty();
lbf.destroySingletons();
assertThat(bean.destroyMethods).containsExactly("destroy", "destroy2", "destroy1");
}
@Test @Test
void beanPostProcessorWithWrappedObjectAndDisposableBean() { void beanPostProcessorWithWrappedObjectAndDisposableBean() {
RootBeanDefinition bd = new RootBeanDefinition(BeanWithDisposableBean.class); RootBeanDefinition bd = new RootBeanDefinition(BeanWithDisposableBean.class);
@ -2758,9 +2772,42 @@ class DefaultListableBeanFactoryTests {
} }
static class BeanWithInitAndDestroyMethods implements InitializingBean, DisposableBean {
final List<String> initMethods = new ArrayList<>();
final List<String> destroyMethods = new ArrayList<>();
@Override
public void afterPropertiesSet() {
initMethods.add("init");
}
void init1() {
initMethods.add("init1");
}
void init2() {
initMethods.add("init2");
}
@Override
public void destroy() {
destroyMethods.add("destroy");
}
void destroy1() {
destroyMethods.add("destroy1");
}
void destroy2() {
destroyMethods.add("destroy2");
}
}
public static class BeanWithDisposableBean implements DisposableBean { public static class BeanWithDisposableBean implements DisposableBean {
private static boolean closed; static boolean closed;
@Override @Override
public void destroy() { public void destroy() {
@ -2771,7 +2818,7 @@ class DefaultListableBeanFactoryTests {
public static class BeanWithCloseable implements Closeable { public static class BeanWithCloseable implements Closeable {
private static boolean closed; static boolean closed;
@Override @Override
public void close() { public void close() {
@ -2788,7 +2835,7 @@ class DefaultListableBeanFactoryTests {
public static class BeanWithDestroyMethod extends BaseClassWithDestroyMethod { public static class BeanWithDestroyMethod extends BaseClassWithDestroyMethod {
private static int closeCount = 0; static int closeCount = 0;
@SuppressWarnings("unused") @SuppressWarnings("unused")
private BeanWithDestroyMethod inner; private BeanWithDestroyMethod inner;

View File

@ -1,5 +1,5 @@
/* /*
* Copyright 2002-2020 the original author or authors. * Copyright 2002-2022 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -510,7 +510,7 @@ public class ScriptFactoryPostProcessor implements SmartInstantiationAwareBeanPo
Signature signature = new Signature(setterName, Type.VOID_TYPE, new Type[] {Type.getType(propertyType)}); Signature signature = new Signature(setterName, Type.VOID_TYPE, new Type[] {Type.getType(propertyType)});
maker.add(signature, new Type[0]); maker.add(signature, new Type[0]);
} }
if (bd.getInitMethodName() != null) { if (StringUtils.hasText(bd.getInitMethodName())) {
Signature signature = new Signature(bd.getInitMethodName(), Type.VOID_TYPE, new Type[0]); Signature signature = new Signature(bd.getInitMethodName(), Type.VOID_TYPE, new Type[0]);
maker.add(signature, new Type[0]); maker.add(signature, new Type[0]);
} }

View File

@ -1,5 +1,5 @@
/* /*
* Copyright 2002-2019 the original author or authors. * Copyright 2002-2022 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -22,8 +22,6 @@ import java.util.List;
import jakarta.annotation.PostConstruct; import jakarta.annotation.PostConstruct;
import jakarta.annotation.PreDestroy; import jakarta.annotation.PreDestroy;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.DisposableBean; import org.springframework.beans.factory.DisposableBean;
@ -35,15 +33,12 @@ import org.springframework.util.ObjectUtils;
import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThat;
/** /**
* <p> * Unit test which verifies expected <em>init</em> and <em>destroy</em>
* JUnit-3.8-based unit test which verifies expected <em>init</em> and * bean lifecycle behavior as requested in
* <em>destroy</em> bean lifecycle behavior as requested in <a * <a href="https://opensource.atlassian.com/projects/spring/browse/SPR-3775"
* href="https://opensource.atlassian.com/projects/spring/browse/SPR-3775"
* target="_blank">SPR-3775</a>. * target="_blank">SPR-3775</a>.
* </p> *
* <p> * <p>Specifically, combinations of the following are tested:
* Specifically, combinations of the following are tested:
* </p>
* <ul> * <ul>
* <li>{@link InitializingBean} &amp; {@link DisposableBean} interfaces</li> * <li>{@link InitializingBean} &amp; {@link DisposableBean} interfaces</li>
* <li>Custom {@link RootBeanDefinition#getInitMethodName() init} &amp; * <li>Custom {@link RootBeanDefinition#getInitMethodName() init} &amp;
@ -57,26 +52,17 @@ import static org.assertj.core.api.Assertions.assertThat;
*/ */
public class Spr3775InitDestroyLifecycleTests { public class Spr3775InitDestroyLifecycleTests {
private static final Log logger = LogFactory.getLog(Spr3775InitDestroyLifecycleTests.class);
/** LIFECYCLE_TEST_BEAN. */
private static final String LIFECYCLE_TEST_BEAN = "lifecycleTestBean"; private static final String LIFECYCLE_TEST_BEAN = "lifecycleTestBean";
private void debugMethods(Class<?> clazz, String category, List<String> methodNames) { private void assertMethodOrdering(String category, List<String> expectedMethods, List<String> actualMethods) {
if (logger.isDebugEnabled()) { assertThat(ObjectUtils.nullSafeEquals(expectedMethods, actualMethods)).
logger.debug(clazz.getSimpleName() + ": " + category + ": " + methodNames); as("Verifying " + category + ": expected<" + expectedMethods + "> but got<" + actualMethods + ">.").isTrue();
}
} }
private void assertMethodOrdering(Class<?> clazz, String category, List<String> expectedMethods, private DefaultListableBeanFactory createBeanFactoryAndRegisterBean(
List<String> actualMethods) { Class<?> beanClass, String initMethodName, String destroyMethodName) {
debugMethods(clazz, category, actualMethods);
assertThat(ObjectUtils.nullSafeEquals(expectedMethods, actualMethods)).as("Verifying " + category + ": expected<" + expectedMethods + "> but got<" + actualMethods + ">.").isTrue();
}
private DefaultListableBeanFactory createBeanFactoryAndRegisterBean(final Class<?> beanClass,
final String initMethodName, final String destroyMethodName) {
DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory(); DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();
RootBeanDefinition beanDefinition = new RootBeanDefinition(beanClass); RootBeanDefinition beanDefinition = new RootBeanDefinition(beanClass);
beanDefinition.setInitMethodName(initMethodName); beanDefinition.setInitMethodName(initMethodName);
@ -88,75 +74,77 @@ public class Spr3775InitDestroyLifecycleTests {
@Test @Test
public void testInitDestroyMethods() { public void testInitDestroyMethods() {
final Class<?> beanClass = InitDestroyBean.class; Class<?> beanClass = InitDestroyBean.class;
final DefaultListableBeanFactory beanFactory = createBeanFactoryAndRegisterBean(beanClass, DefaultListableBeanFactory beanFactory = createBeanFactoryAndRegisterBean(beanClass,
"afterPropertiesSet", "destroy"); "afterPropertiesSet", "destroy");
final InitDestroyBean bean = (InitDestroyBean) beanFactory.getBean(LIFECYCLE_TEST_BEAN); InitDestroyBean bean = (InitDestroyBean) beanFactory.getBean(LIFECYCLE_TEST_BEAN);
assertMethodOrdering(beanClass, "init-methods", Arrays.asList("afterPropertiesSet"), bean.initMethods); assertMethodOrdering("init-methods", Arrays.asList("afterPropertiesSet"), bean.initMethods);
beanFactory.destroySingletons(); beanFactory.destroySingletons();
assertMethodOrdering(beanClass, "destroy-methods", Arrays.asList("destroy"), bean.destroyMethods); assertMethodOrdering("destroy-methods", Arrays.asList("destroy"), bean.destroyMethods);
} }
@Test @Test
public void testInitializingDisposableInterfaces() { public void testInitializingDisposableInterfaces() {
final Class<?> beanClass = CustomInitializingDisposableBean.class; Class<?> beanClass = CustomInitializingDisposableBean.class;
final DefaultListableBeanFactory beanFactory = createBeanFactoryAndRegisterBean(beanClass, "customInit", DefaultListableBeanFactory beanFactory = createBeanFactoryAndRegisterBean(beanClass, "customInit",
"customDestroy"); "customDestroy");
final CustomInitializingDisposableBean bean = (CustomInitializingDisposableBean) beanFactory.getBean(LIFECYCLE_TEST_BEAN); CustomInitializingDisposableBean bean = (CustomInitializingDisposableBean) beanFactory.getBean(LIFECYCLE_TEST_BEAN);
assertMethodOrdering(beanClass, "init-methods", Arrays.asList("afterPropertiesSet", "customInit"), assertMethodOrdering("init-methods", Arrays.asList("afterPropertiesSet", "customInit"),
bean.initMethods); bean.initMethods);
beanFactory.destroySingletons(); beanFactory.destroySingletons();
assertMethodOrdering(beanClass, "destroy-methods", Arrays.asList("destroy", "customDestroy"), assertMethodOrdering("destroy-methods", Arrays.asList("destroy", "customDestroy"),
bean.destroyMethods); bean.destroyMethods);
} }
@Test @Test
public void testInitializingDisposableInterfacesWithShadowedMethods() { public void testInitializingDisposableInterfacesWithShadowedMethods() {
final Class<?> beanClass = InitializingDisposableWithShadowedMethodsBean.class; Class<?> beanClass = InitializingDisposableWithShadowedMethodsBean.class;
final DefaultListableBeanFactory beanFactory = createBeanFactoryAndRegisterBean(beanClass, DefaultListableBeanFactory beanFactory = createBeanFactoryAndRegisterBean(beanClass,
"afterPropertiesSet", "destroy"); "afterPropertiesSet", "destroy");
final InitializingDisposableWithShadowedMethodsBean bean = (InitializingDisposableWithShadowedMethodsBean) beanFactory.getBean(LIFECYCLE_TEST_BEAN); InitializingDisposableWithShadowedMethodsBean bean =
assertMethodOrdering(beanClass, "init-methods", Arrays.asList("InitializingBean.afterPropertiesSet"), (InitializingDisposableWithShadowedMethodsBean) beanFactory.getBean(LIFECYCLE_TEST_BEAN);
assertMethodOrdering("init-methods", Arrays.asList("InitializingBean.afterPropertiesSet"),
bean.initMethods); bean.initMethods);
beanFactory.destroySingletons(); beanFactory.destroySingletons();
assertMethodOrdering(beanClass, "destroy-methods", Arrays.asList("DisposableBean.destroy"), bean.destroyMethods); assertMethodOrdering("destroy-methods", Arrays.asList("DisposableBean.destroy"), bean.destroyMethods);
} }
@Test @Test
public void testJsr250Annotations() { public void testJsr250Annotations() {
final Class<?> beanClass = CustomAnnotatedInitDestroyBean.class; Class<?> beanClass = CustomAnnotatedInitDestroyBean.class;
final DefaultListableBeanFactory beanFactory = createBeanFactoryAndRegisterBean(beanClass, "customInit", DefaultListableBeanFactory beanFactory = createBeanFactoryAndRegisterBean(beanClass, "customInit",
"customDestroy"); "customDestroy");
final CustomAnnotatedInitDestroyBean bean = (CustomAnnotatedInitDestroyBean) beanFactory.getBean(LIFECYCLE_TEST_BEAN); CustomAnnotatedInitDestroyBean bean = (CustomAnnotatedInitDestroyBean) beanFactory.getBean(LIFECYCLE_TEST_BEAN);
assertMethodOrdering(beanClass, "init-methods", Arrays.asList("postConstruct", "afterPropertiesSet", assertMethodOrdering("init-methods", Arrays.asList("postConstruct", "afterPropertiesSet",
"customInit"), bean.initMethods); "customInit"), bean.initMethods);
beanFactory.destroySingletons(); beanFactory.destroySingletons();
assertMethodOrdering(beanClass, "destroy-methods", Arrays.asList("preDestroy", "destroy", "customDestroy"), assertMethodOrdering("destroy-methods", Arrays.asList("preDestroy", "destroy", "customDestroy"),
bean.destroyMethods); bean.destroyMethods);
} }
@Test @Test
public void testJsr250AnnotationsWithShadowedMethods() { public void testJsr250AnnotationsWithShadowedMethods() {
final Class<?> beanClass = CustomAnnotatedInitDestroyWithShadowedMethodsBean.class; Class<?> beanClass = CustomAnnotatedInitDestroyWithShadowedMethodsBean.class;
final DefaultListableBeanFactory beanFactory = createBeanFactoryAndRegisterBean(beanClass, "customInit", DefaultListableBeanFactory beanFactory = createBeanFactoryAndRegisterBean(beanClass, "customInit",
"customDestroy"); "customDestroy");
final CustomAnnotatedInitDestroyWithShadowedMethodsBean bean = (CustomAnnotatedInitDestroyWithShadowedMethodsBean) beanFactory.getBean(LIFECYCLE_TEST_BEAN); CustomAnnotatedInitDestroyWithShadowedMethodsBean bean =
assertMethodOrdering(beanClass, "init-methods", (CustomAnnotatedInitDestroyWithShadowedMethodsBean) beanFactory.getBean(LIFECYCLE_TEST_BEAN);
assertMethodOrdering("init-methods",
Arrays.asList("@PostConstruct.afterPropertiesSet", "customInit"), bean.initMethods); Arrays.asList("@PostConstruct.afterPropertiesSet", "customInit"), bean.initMethods);
beanFactory.destroySingletons(); beanFactory.destroySingletons();
assertMethodOrdering(beanClass, "destroy-methods", Arrays.asList("@PreDestroy.destroy", "customDestroy"), assertMethodOrdering("destroy-methods", Arrays.asList("@PreDestroy.destroy", "customDestroy"),
bean.destroyMethods); bean.destroyMethods);
} }
@Test @Test
public void testAllLifecycleMechanismsAtOnce() { public void testAllLifecycleMechanismsAtOnce() {
final Class<?> beanClass = AllInOneBean.class; Class<?> beanClass = AllInOneBean.class;
final DefaultListableBeanFactory beanFactory = createBeanFactoryAndRegisterBean(beanClass, DefaultListableBeanFactory beanFactory = createBeanFactoryAndRegisterBean(beanClass,
"afterPropertiesSet", "destroy"); "afterPropertiesSet", "destroy");
final AllInOneBean bean = (AllInOneBean) beanFactory.getBean(LIFECYCLE_TEST_BEAN); AllInOneBean bean = (AllInOneBean) beanFactory.getBean(LIFECYCLE_TEST_BEAN);
assertMethodOrdering(beanClass, "init-methods", Arrays.asList("afterPropertiesSet"), bean.initMethods); assertMethodOrdering("init-methods", Arrays.asList("afterPropertiesSet"), bean.initMethods);
beanFactory.destroySingletons(); beanFactory.destroySingletons();
assertMethodOrdering(beanClass, "destroy-methods", Arrays.asList("destroy"), bean.destroyMethods); assertMethodOrdering("destroy-methods", Arrays.asList("destroy"), bean.destroyMethods);
} }
@ -165,7 +153,6 @@ public class Spr3775InitDestroyLifecycleTests {
final List<String> initMethods = new ArrayList<>(); final List<String> initMethods = new ArrayList<>();
final List<String> destroyMethods = new ArrayList<>(); final List<String> destroyMethods = new ArrayList<>();
public void afterPropertiesSet() throws Exception { public void afterPropertiesSet() throws Exception {
this.initMethods.add("afterPropertiesSet"); this.initMethods.add("afterPropertiesSet");
} }
@ -175,8 +162,9 @@ public class Spr3775InitDestroyLifecycleTests {
} }
} }
public static class InitializingDisposableWithShadowedMethodsBean extends InitDestroyBean implements
InitializingBean, DisposableBean { public static class InitializingDisposableWithShadowedMethodsBean extends InitDestroyBean
implements InitializingBean, DisposableBean {
@Override @Override
public void afterPropertiesSet() throws Exception { public void afterPropertiesSet() throws Exception {
@ -255,14 +243,14 @@ public class Spr3775InitDestroyLifecycleTests {
final List<String> initMethods = new ArrayList<>(); final List<String> initMethods = new ArrayList<>();
final List<String> destroyMethods = new ArrayList<>(); final List<String> destroyMethods = new ArrayList<>();
@Override
@PostConstruct @PostConstruct
@Override
public void afterPropertiesSet() throws Exception { public void afterPropertiesSet() throws Exception {
this.initMethods.add("afterPropertiesSet"); this.initMethods.add("afterPropertiesSet");
} }
@Override
@PreDestroy @PreDestroy
@Override
public void destroy() throws Exception { public void destroy() throws Exception {
this.destroyMethods.add("destroy"); this.destroyMethods.add("destroy");
} }