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;

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