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.ResolvableType;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
import org.springframework.util.ObjectUtils;
import org.springframework.util.ReflectionUtils;
@@ -1789,11 +1788,15 @@ public abstract class AbstractAutowireCapableBeanFactory extends AbstractBeanFac
}
if (mbd != null && bean.getClass() != NullBean.class) {
String initMethodName = mbd.getInitMethodName();
if (StringUtils.hasLength(initMethodName) &&
!(isInitializingBean && "afterPropertiesSet".equals(initMethodName)) &&
!mbd.isExternallyManagedInitMethod(initMethodName)) {
invokeCustomInitMethod(beanName, bean, mbd);
String[] initMethodNames = mbd.getInitMethodNames();
if (initMethodNames != null) {
for (String initMethodName : initMethodNames) {
if (StringUtils.hasLength(initMethodName) &&
!(isInitializingBean && "afterPropertiesSet".equals(initMethodName)) &&
!mbd.isExternallyManagedInitMethod(initMethodName)) {
invokeCustomInitMethod(beanName, bean, mbd, initMethodName);
}
}
}
}
}
@@ -1805,11 +1808,9 @@ public abstract class AbstractAutowireCapableBeanFactory extends AbstractBeanFac
* methods with arguments.
* @see #invokeInitMethods
*/
protected void invokeCustomInitMethod(String beanName, Object bean, RootBeanDefinition mbd)
protected void invokeCustomInitMethod(String beanName, Object bean, RootBeanDefinition mbd, String initMethodName)
throws Throwable {
String initMethodName = mbd.getInitMethodName();
Assert.state(initMethodName != null, "No init method set");
Method initMethod = (mbd.isNonPublicAccessAllowed() ?
BeanUtils.findMethod(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");
* 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();
@Nullable
private String initMethodName;
private String[] initMethodNames;
@Nullable
private String destroyMethodName;
private String[] destroyMethodNames;
private boolean enforceInitMethod = true;
@@ -262,9 +262,9 @@ public abstract class AbstractBeanDefinition extends BeanMetadataAttributeAccess
setInstanceSupplier(originalAbd.getInstanceSupplier());
setNonPublicAccessAllowed(originalAbd.isNonPublicAccessAllowed());
setLenientConstructorResolution(originalAbd.isLenientConstructorResolution());
setInitMethodName(originalAbd.getInitMethodName());
setInitMethodNames(originalAbd.getInitMethodNames());
setEnforceInitMethod(originalAbd.isEnforceInitMethod());
setDestroyMethodName(originalAbd.getDestroyMethodName());
setDestroyMethodNames(originalAbd.getDestroyMethodNames());
setEnforceDestroyMethod(originalAbd.isEnforceDestroyMethod());
setSynthetic(originalAbd.isSynthetic());
setResource(originalAbd.getResource());
@@ -338,12 +338,12 @@ public abstract class AbstractBeanDefinition extends BeanMetadataAttributeAccess
setInstanceSupplier(otherAbd.getInstanceSupplier());
setNonPublicAccessAllowed(otherAbd.isNonPublicAccessAllowed());
setLenientConstructorResolution(otherAbd.isLenientConstructorResolution());
if (otherAbd.getInitMethodName() != null) {
setInitMethodName(otherAbd.getInitMethodName());
if (otherAbd.getInitMethodNames() != null) {
setInitMethodNames(otherAbd.getInitMethodNames());
setEnforceInitMethod(otherAbd.isEnforceInitMethod());
}
if (otherAbd.getDestroyMethodName() != null) {
setDestroyMethodName(otherAbd.getDestroyMethodName());
if (otherAbd.getDestroyMethodNames() != null) {
setDestroyMethodNames(otherAbd.getDestroyMethodNames());
setEnforceDestroyMethod(otherAbd.isEnforceDestroyMethod());
}
setSynthetic(otherAbd.isSynthetic());
@@ -919,21 +919,41 @@ public abstract class AbstractBeanDefinition extends BeanMetadataAttributeAccess
}
/**
* Set the name of the initializer method.
* <p>The default is {@code null} in which case there is no initializer method.
* Specify the names of multiple initializer methods.
* <p>The default is {@code null} in which case there are no initializer methods.
* @since 6.0
* @see #setInitMethodName
*/
@Override
public void setInitMethodName(@Nullable String initMethodName) {
this.initMethodName = initMethodName;
public void setInitMethodNames(@Nullable String... initMethodNames) {
this.initMethodNames = initMethodNames;
}
/**
* 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
@Nullable
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.
* <p>The default is {@code null} in which case there is no destroy method.
* Specify the names of multiple destroy methods.
* <p>The default is {@code null} in which case there are no destroy methods.
* @since 6.0
* @see #setDestroyMethodName
*/
@Override
public void setDestroyMethodName(@Nullable String destroyMethodName) {
this.destroyMethodName = destroyMethodName;
public void setDestroyMethodNames(@Nullable String... destroyMethodNames) {
this.destroyMethodNames = destroyMethodNames;
}
/**
* 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
@Nullable
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.factoryBeanName, that.factoryBeanName) &&
ObjectUtils.nullSafeEquals(this.factoryMethodName, that.factoryMethodName) &&
ObjectUtils.nullSafeEquals(this.initMethodName, that.initMethodName) &&
ObjectUtils.nullSafeEquals(this.initMethodNames, that.initMethodNames) &&
this.enforceInitMethod == that.enforceInitMethod &&
ObjectUtils.nullSafeEquals(this.destroyMethodName, that.destroyMethodName) &&
ObjectUtils.nullSafeEquals(this.destroyMethodNames, that.destroyMethodNames) &&
this.enforceDestroyMethod == that.enforceDestroyMethod &&
this.synthetic == that.synthetic &&
this.role == that.role &&
@@ -1241,8 +1281,8 @@ public abstract class AbstractBeanDefinition extends BeanMetadataAttributeAccess
sb.append("; primary=").append(this.primary);
sb.append("; factoryBeanName=").append(this.factoryBeanName);
sb.append("; factoryMethodName=").append(this.factoryMethodName);
sb.append("; initMethodName=").append(this.initMethodName);
sb.append("; destroyMethodName=").append(this.destroyMethodName);
sb.append("; initMethodNames=").append(this.initMethodNames);
sb.append("; destroyMethodNames=").append(this.destroyMethodNames);
if (this.resource != null) {
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.ClassUtils;
import org.springframework.util.CollectionUtils;
import org.springframework.util.ObjectUtils;
import org.springframework.util.ReflectionUtils;
import org.springframework.util.StringUtils;
@@ -51,7 +52,7 @@ import org.springframework.util.StringUtils;
* @see AbstractBeanFactory
* @see org.springframework.beans.factory.DisposableBean
* @see org.springframework.beans.factory.config.DestructionAwareBeanPostProcessor
* @see AbstractBeanDefinition#getDestroyMethodName()
* @see AbstractBeanDefinition#getDestroyMethodNames()
*/
@SuppressWarnings("serial")
class DisposableBeanAdapter implements DisposableBean, Runnable, Serializable {
@@ -76,10 +77,10 @@ class DisposableBeanAdapter implements DisposableBean, Runnable, Serializable {
private boolean invokeAutoCloseable;
@Nullable
private String destroyMethodName;
private String[] destroyMethodNames;
@Nullable
private transient Method destroyMethod;
private transient Method[] destroyMethods;
@Nullable
private final List<DestructionAwareBeanPostProcessor> beanPostProcessors;
@@ -103,36 +104,42 @@ class DisposableBeanAdapter implements DisposableBean, Runnable, Serializable {
this.invokeDisposableBean = (bean instanceof DisposableBean &&
!beanDefinition.isExternallyManagedDestroyMethod(DESTROY_METHOD_NAME));
String destroyMethodName = inferDestroyMethodIfNecessary(bean, beanDefinition);
if (destroyMethodName != null &&
!(this.invokeDisposableBean && DESTROY_METHOD_NAME.equals(destroyMethodName)) &&
!beanDefinition.isExternallyManagedDestroyMethod(destroyMethodName)) {
String[] destroyMethodNames = inferDestroyMethodsIfNecessary(bean, beanDefinition);
if (!ObjectUtils.isEmpty(destroyMethodNames) &&
!(this.invokeDisposableBean && DESTROY_METHOD_NAME.equals(destroyMethodNames[0])) &&
!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) {
this.destroyMethodName = destroyMethodName;
Method destroyMethod = determineDestroyMethod(destroyMethodName);
if (destroyMethod == null) {
if (beanDefinition.isEnforceDestroyMethod()) {
throw new BeanDefinitionValidationException("Could not find a destroy method named '" +
destroyMethodName + "' on bean with name '" + beanName + "'");
}
}
else {
if (destroyMethod.getParameterCount() > 0) {
Class<?>[] paramTypes = destroyMethod.getParameterTypes();
if (paramTypes.length > 1) {
throw new BeanDefinitionValidationException("Method '" + destroyMethodName + "' of bean '" +
beanName + "' has more than one parameter - not supported as destroy method");
}
else if (paramTypes.length == 1 && boolean.class != paramTypes[0]) {
throw new BeanDefinitionValidationException("Method '" + destroyMethodName + "' of bean '" +
beanName + "' has a non-boolean parameter - not supported as destroy method");
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);
if (destroyMethod == null) {
if (beanDefinition.isEnforceDestroyMethod()) {
throw new BeanDefinitionValidationException("Could not find a destroy method named '" +
destroyMethodName + "' on bean with name '" + beanName + "'");
}
}
destroyMethod = ClassUtils.getInterfaceMethodIfPossible(destroyMethod, bean.getClass());
else {
if (destroyMethod.getParameterCount() > 0) {
Class<?>[] paramTypes = destroyMethod.getParameterTypes();
if (paramTypes.length > 1) {
throw new BeanDefinitionValidationException("Method '" + destroyMethodName + "' of bean '" +
beanName + "' has more than one parameter - not supported as destroy method");
}
else if (paramTypes.length == 1 && boolean.class != paramTypes[0]) {
throw new BeanDefinitionValidationException("Method '" + destroyMethodName + "' of bean '" +
beanName + "' has a non-boolean parameter - not supported as destroy method");
}
}
destroyMethod = ClassUtils.getInterfaceMethodIfPossible(destroyMethod, bean.getClass());
}
destroyMethods[i] = destroyMethod;
}
this.destroyMethod = destroyMethod;
this.destroyMethods = destroyMethods;
}
}
@@ -158,7 +165,7 @@ class DisposableBeanAdapter implements DisposableBean, Runnable, Serializable {
* Create a new DisposableBeanAdapter for the given bean.
*/
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) {
this.bean = bean;
@@ -166,7 +173,7 @@ class DisposableBeanAdapter implements DisposableBean, Runnable, Serializable {
this.nonPublicAccessAllowed = nonPublicAccessAllowed;
this.invokeDisposableBean = invokeDisposableBean;
this.invokeAutoCloseable = invokeAutoCloseable;
this.destroyMethodName = destroyMethodName;
this.destroyMethodNames = destroyMethodNames;
this.beanPostProcessors = postProcessors;
}
@@ -219,13 +226,18 @@ class DisposableBeanAdapter implements DisposableBean, Runnable, Serializable {
}
}
}
else if (this.destroyMethod != null) {
invokeCustomDestroyMethod(this.destroyMethod);
else if (this.destroyMethods != null) {
for (Method destroyMethod : this.destroyMethods) {
invokeCustomDestroyMethod(destroyMethod);
}
}
else if (this.destroyMethodName != null) {
Method destroyMethod = determineDestroyMethod(this.destroyMethodName);
if (destroyMethod != null) {
invokeCustomDestroyMethod(ClassUtils.getInterfaceMethodIfPossible(destroyMethod, this.bean.getClass()));
else if (this.destroyMethodNames != null) {
for (String destroyMethodName: this.destroyMethodNames) {
Method destroyMethod = determineDestroyMethod(destroyMethodName);
if (destroyMethod != null) {
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",
* assuming a "force" parameter), else logging an error.
*/
private void invokeCustomDestroyMethod(final Method destroyMethod) {
private void invokeCustomDestroyMethod(Method destroyMethod) {
int paramCount = destroyMethod.getParameterCount();
final Object[] args = new Object[paramCount];
if (paramCount == 1) {
args[0] = Boolean.TRUE;
}
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 + "'");
}
try {
@@ -270,7 +282,7 @@ class DisposableBeanAdapter implements DisposableBean, Runnable, Serializable {
destroyMethod.invoke(this.bean, args);
}
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";
if (logger.isDebugEnabled()) {
logger.warn(msg, ex.getTargetException());
@@ -280,7 +292,7 @@ class DisposableBeanAdapter implements DisposableBean, Runnable, Serializable {
}
}
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);
}
}
@@ -302,7 +314,7 @@ class DisposableBeanAdapter implements DisposableBean, Runnable, Serializable {
}
return new DisposableBeanAdapter(
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
*/
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.
*/
@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;
if (destroyMethodName == null) {
destroyMethodName = beanDefinition.getDestroyMethodName();
@@ -361,7 +378,7 @@ class DisposableBeanAdapter implements DisposableBean, Runnable, Serializable {
}
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.text.NumberFormat;
import java.text.ParseException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Iterator;
@@ -2333,6 +2334,19 @@ class DefaultListableBeanFactoryTests {
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
void beanPostProcessorWithWrappedObjectAndDisposableBean() {
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 {
private static boolean closed;
static boolean closed;
@Override
public void destroy() {
@@ -2771,7 +2818,7 @@ class DefaultListableBeanFactoryTests {
public static class BeanWithCloseable implements Closeable {
private static boolean closed;
static boolean closed;
@Override
public void close() {
@@ -2788,7 +2835,7 @@ class DefaultListableBeanFactoryTests {
public static class BeanWithDestroyMethod extends BaseClassWithDestroyMethod {
private static int closeCount = 0;
static int closeCount = 0;
@SuppressWarnings("unused")
private BeanWithDestroyMethod inner;