Customize destruction callback for AutoCloseable beans

Previously, a Bean implementing `AutoCloseable` (or `Closeable`) was
always destroyed regardless of its bean definition. In particular, the
documented way of disabling the destruction callback via an empty String
did not work.

AutoCloseable beans are now treated pretty much as any other bean: we
still use the presence of the interface to optimize the check of a
destroy method and we only auto-discover the method name to invoke if
the inferred mode is enabled.

Issue: SPR-13022
This commit is contained in:
Stephane Nicoll 2015-05-15 11:22:29 +02:00
parent 271804f105
commit 0ed9ca097b
8 changed files with 32 additions and 15 deletions

View File

@ -1,5 +1,5 @@
/* /*
* Copyright 2002-2014 the original author or authors. * Copyright 2002-2015 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.
@ -303,7 +303,7 @@ public abstract class AbstractBeanDefinition extends BeanMetadataAttributeAccess
setInitMethodName(otherAbd.getInitMethodName()); setInitMethodName(otherAbd.getInitMethodName());
setEnforceInitMethod(otherAbd.isEnforceInitMethod()); setEnforceInitMethod(otherAbd.isEnforceInitMethod());
} }
if (StringUtils.hasLength(otherAbd.getDestroyMethodName())) { if (otherAbd.getDestroyMethodName() != null) {
setDestroyMethodName(otherAbd.getDestroyMethodName()); setDestroyMethodName(otherAbd.getDestroyMethodName());
setEnforceDestroyMethod(otherAbd.isEnforceDestroyMethod()); setEnforceDestroyMethod(otherAbd.isEnforceDestroyMethod());
} }

View File

@ -1,5 +1,5 @@
/* /*
* Copyright 2002-2014 the original author or authors. * Copyright 2002-2015 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.
@ -38,6 +38,7 @@ import org.springframework.beans.factory.config.DestructionAwareBeanPostProcesso
import org.springframework.util.Assert; import org.springframework.util.Assert;
import org.springframework.util.ClassUtils; import org.springframework.util.ClassUtils;
import org.springframework.util.ReflectionUtils; import org.springframework.util.ReflectionUtils;
import org.springframework.util.StringUtils;
/** /**
* Adapter that implements the {@link DisposableBean} and {@link Runnable} interfaces * Adapter that implements the {@link DisposableBean} and {@link Runnable} interfaces
@ -50,6 +51,7 @@ import org.springframework.util.ReflectionUtils;
* *
* @author Juergen Hoeller * @author Juergen Hoeller
* @author Costin Leau * @author Costin Leau
* @author Stephane Nicoll
* @since 2.0 * @since 2.0
* @see AbstractBeanFactory * @see AbstractBeanFactory
* @see org.springframework.beans.factory.DisposableBean * @see org.springframework.beans.factory.DisposableBean
@ -186,8 +188,9 @@ 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.
*/ */
private String inferDestroyMethodIfNecessary(Object bean, RootBeanDefinition beanDefinition) { private String inferDestroyMethodIfNecessary(Object bean, RootBeanDefinition beanDefinition) {
if (AbstractBeanDefinition.INFER_METHOD.equals(beanDefinition.getDestroyMethodName()) || String destroyMethodName = beanDefinition.getDestroyMethodName();
(beanDefinition.getDestroyMethodName() == null && closeableInterface.isInstance(bean))) { if (AbstractBeanDefinition.INFER_METHOD.equals(destroyMethodName) ||
(destroyMethodName == null && closeableInterface.isInstance(bean))) {
// Only perform destroy method inference or Closeable detection // Only perform destroy method inference or Closeable detection
// in case of the bean not explicitly implementing DisposableBean // in case of the bean not explicitly implementing DisposableBean
if (!(bean instanceof DisposableBean)) { if (!(bean instanceof DisposableBean)) {
@ -205,7 +208,7 @@ class DisposableBeanAdapter implements DisposableBean, Runnable, Serializable {
} }
return null; return null;
} }
return beanDefinition.getDestroyMethodName(); return (StringUtils.hasLength(destroyMethodName) ? destroyMethodName : null);
} }
/** /**
@ -399,7 +402,7 @@ class DisposableBeanAdapter implements DisposableBean, Runnable, Serializable {
if (AbstractBeanDefinition.INFER_METHOD.equals(destroyMethodName)) { if (AbstractBeanDefinition.INFER_METHOD.equals(destroyMethodName)) {
return ClassUtils.hasMethod(bean.getClass(), CLOSE_METHOD_NAME); return ClassUtils.hasMethod(bean.getClass(), CLOSE_METHOD_NAME);
} }
return (destroyMethodName != null); return StringUtils.hasLength(destroyMethodName);
} }
} }

View File

@ -641,10 +641,8 @@ public class BeanDefinitionParserDelegate {
if (ele.hasAttribute(DESTROY_METHOD_ATTRIBUTE)) { if (ele.hasAttribute(DESTROY_METHOD_ATTRIBUTE)) {
String destroyMethodName = ele.getAttribute(DESTROY_METHOD_ATTRIBUTE); String destroyMethodName = ele.getAttribute(DESTROY_METHOD_ATTRIBUTE);
if (!"".equals(destroyMethodName)) {
bd.setDestroyMethodName(destroyMethodName); bd.setDestroyMethodName(destroyMethodName);
} }
}
else { else {
if (this.defaults.getDestroyMethod() != null) { if (this.defaults.getDestroyMethod() != null) {
bd.setDestroyMethodName(this.defaults.getDestroyMethod()); bd.setDestroyMethodName(this.defaults.getDestroyMethod());

View File

@ -236,7 +236,7 @@ class ConfigurationClassBeanDefinitionReader {
} }
String destroyMethodName = bean.getString("destroyMethod"); String destroyMethodName = bean.getString("destroyMethod");
if (StringUtils.hasText(destroyMethodName)) { if (destroyMethodName != null) {
beanDef.setDestroyMethodName(destroyMethodName); beanDef.setDestroyMethodName(destroyMethodName);
} }

View File

@ -165,8 +165,8 @@ class ScriptBeanDefinitionParser extends AbstractBeanDefinitionParser {
bd.setInitMethodName(beanDefinitionDefaults.getInitMethodName()); bd.setInitMethodName(beanDefinitionDefaults.getInitMethodName());
} }
if (element.hasAttribute(DESTROY_METHOD_ATTRIBUTE)) {
String destroyMethod = element.getAttribute(DESTROY_METHOD_ATTRIBUTE); String destroyMethod = element.getAttribute(DESTROY_METHOD_ATTRIBUTE);
if (StringUtils.hasLength(destroyMethod)) {
bd.setDestroyMethodName(destroyMethod); bd.setDestroyMethodName(destroyMethod);
} }
else if (beanDefinitionDefaults.getDestroyMethodName() != null) { else if (beanDefinitionDefaults.getDestroyMethodName() != null) {

View File

@ -505,7 +505,7 @@ public class ScriptFactoryPostProcessor extends InstantiationAwareBeanPostProces
Signature signature = new Signature(abd.getInitMethodName(), Type.VOID_TYPE, new Type[0]); Signature signature = new Signature(abd.getInitMethodName(), Type.VOID_TYPE, new Type[0]);
maker.add(signature, new Type[0]); maker.add(signature, new Type[0]);
} }
if (abd.getDestroyMethodName() != null) { if (StringUtils.hasText(abd.getDestroyMethodName())) {
Signature signature = new Signature(abd.getDestroyMethodName(), Type.VOID_TYPE, new Type[0]); Signature signature = new Signature(abd.getDestroyMethodName(), 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-2013 the original author or authors. * Copyright 2002-2015 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.
@ -30,6 +30,7 @@ import static org.junit.Assert.*;
/** /**
* @author Chris Beams * @author Chris Beams
* @author Juergen Hoeller * @author Juergen Hoeller
* @author Stephane Nicoll
*/ */
public class DestroyMethodInferenceTests { public class DestroyMethodInferenceTests {
@ -45,6 +46,7 @@ public class DestroyMethodInferenceTests {
WithInheritedCloseMethod c5 = ctx.getBean("c5", WithInheritedCloseMethod.class); WithInheritedCloseMethod c5 = ctx.getBean("c5", WithInheritedCloseMethod.class);
WithNoCloseMethod c6 = ctx.getBean("c6", WithNoCloseMethod.class); WithNoCloseMethod c6 = ctx.getBean("c6", WithNoCloseMethod.class);
WithLocalShutdownMethod c7 = ctx.getBean("c7", WithLocalShutdownMethod.class); WithLocalShutdownMethod c7 = ctx.getBean("c7", WithLocalShutdownMethod.class);
WithInheritedCloseMethod c8 = ctx.getBean("c8", WithInheritedCloseMethod.class);
assertThat(c0.closed, is(false)); assertThat(c0.closed, is(false));
assertThat(c1.closed, is(false)); assertThat(c1.closed, is(false));
@ -54,6 +56,7 @@ public class DestroyMethodInferenceTests {
assertThat(c5.closed, is(false)); assertThat(c5.closed, is(false));
assertThat(c6.closed, is(false)); assertThat(c6.closed, is(false));
assertThat(c7.closed, is(false)); assertThat(c7.closed, is(false));
assertThat(c8.closed, is(false));
ctx.close(); ctx.close();
assertThat("c0", c0.closed, is(true)); assertThat("c0", c0.closed, is(true));
assertThat("c1", c1.closed, is(true)); assertThat("c1", c1.closed, is(true));
@ -63,6 +66,7 @@ public class DestroyMethodInferenceTests {
assertThat("c5", c5.closed, is(true)); assertThat("c5", c5.closed, is(true));
assertThat("c6", c6.closed, is(false)); assertThat("c6", c6.closed, is(false));
assertThat("c7", c7.closed, is(true)); assertThat("c7", c7.closed, is(true));
assertThat("c8", c8.closed, is(false));
} }
@Test @Test
@ -73,6 +77,8 @@ public class DestroyMethodInferenceTests {
WithLocalCloseMethod x2 = ctx.getBean("x2", WithLocalCloseMethod.class); WithLocalCloseMethod x2 = ctx.getBean("x2", WithLocalCloseMethod.class);
WithLocalCloseMethod x3 = ctx.getBean("x3", WithLocalCloseMethod.class); WithLocalCloseMethod x3 = ctx.getBean("x3", WithLocalCloseMethod.class);
WithNoCloseMethod x4 = ctx.getBean("x4", WithNoCloseMethod.class); WithNoCloseMethod x4 = ctx.getBean("x4", WithNoCloseMethod.class);
WithInheritedCloseMethod x8 = ctx.getBean("x8", WithInheritedCloseMethod.class);
assertThat(x1.closed, is(false)); assertThat(x1.closed, is(false));
assertThat(x2.closed, is(false)); assertThat(x2.closed, is(false));
assertThat(x3.closed, is(false)); assertThat(x3.closed, is(false));
@ -82,6 +88,7 @@ public class DestroyMethodInferenceTests {
assertThat(x2.closed, is(true)); assertThat(x2.closed, is(true));
assertThat(x3.closed, is(true)); assertThat(x3.closed, is(true));
assertThat(x4.closed, is(false)); assertThat(x4.closed, is(false));
assertThat(x8.closed, is(false));
} }
@Configuration @Configuration
@ -134,6 +141,11 @@ public class DestroyMethodInferenceTests {
public WithLocalShutdownMethod c7() { public WithLocalShutdownMethod c7() {
return new WithLocalShutdownMethod(); return new WithLocalShutdownMethod();
} }
@Bean(destroyMethod = "")
public WithInheritedCloseMethod c8() {
return new WithInheritedCloseMethod();
}
} }

View File

@ -11,6 +11,10 @@
class="org.springframework.context.annotation.DestroyMethodInferenceTests$WithLocalCloseMethod" class="org.springframework.context.annotation.DestroyMethodInferenceTests$WithLocalCloseMethod"
destroy-method="(inferred)"/> destroy-method="(inferred)"/>
<bean id="x8"
class="org.springframework.context.annotation.DestroyMethodInferenceTests.WithInheritedCloseMethod"
destroy-method=""/>
<beans default-destroy-method="(inferred)"> <beans default-destroy-method="(inferred)">
<bean id="x3" <bean id="x3"
class="org.springframework.context.annotation.DestroyMethodInferenceTests$WithLocalCloseMethod"/> class="org.springframework.context.annotation.DestroyMethodInferenceTests$WithLocalCloseMethod"/>