Browse Source

Java 5 Closeable and Java 7 AutoCloseable automatically detected as destroy methods

Also, @Bean destroy method inference not applying for DisposableBean implementers anymore (avoiding double destruction).

Issue: SPR-10034
pull/179/merge
Juergen Hoeller 13 years ago committed by unknown
parent
commit
9c6aa3e43b
  1. 4
      spring-beans/src/main/java/org/springframework/beans/factory/support/AbstractBeanFactory.java
  2. 95
      spring-beans/src/main/java/org/springframework/beans/factory/support/DisposableBeanAdapter.java
  3. 12
      spring-beans/src/main/resources/org/springframework/beans/factory/xml/spring-beans-3.2.xsd
  4. 32
      spring-beans/src/test/java/org/springframework/beans/factory/DefaultListableBeanFactoryTests.java
  5. 11
      spring-context/src/main/java/org/springframework/context/annotation/Bean.java

4
spring-beans/src/main/java/org/springframework/beans/factory/support/AbstractBeanFactory.java

@ -52,7 +52,6 @@ import org.springframework.beans.factory.BeanIsAbstractException;
import org.springframework.beans.factory.BeanIsNotAFactoryException; import org.springframework.beans.factory.BeanIsNotAFactoryException;
import org.springframework.beans.factory.BeanNotOfRequiredTypeException; import org.springframework.beans.factory.BeanNotOfRequiredTypeException;
import org.springframework.beans.factory.CannotLoadBeanClassException; import org.springframework.beans.factory.CannotLoadBeanClassException;
import org.springframework.beans.factory.DisposableBean;
import org.springframework.beans.factory.FactoryBean; import org.springframework.beans.factory.FactoryBean;
import org.springframework.beans.factory.NoSuchBeanDefinitionException; import org.springframework.beans.factory.NoSuchBeanDefinitionException;
import org.springframework.beans.factory.ObjectFactory; import org.springframework.beans.factory.ObjectFactory;
@ -1466,8 +1465,7 @@ public abstract class AbstractBeanFactory extends FactoryBeanRegistrySupport imp
*/ */
protected boolean requiresDestruction(Object bean, RootBeanDefinition mbd) { protected boolean requiresDestruction(Object bean, RootBeanDefinition mbd) {
return (bean != null && return (bean != null &&
(bean instanceof DisposableBean || mbd.getDestroyMethodName() != null || (DisposableBeanAdapter.hasDestroyMethod(bean, mbd) || hasDestructionAwareBeanPostProcessors()));
hasDestructionAwareBeanPostProcessors()));
} }
/** /**

95
spring-beans/src/main/java/org/springframework/beans/factory/support/DisposableBeanAdapter.java

@ -1,5 +1,5 @@
/* /*
* Copyright 2002-2010 the original author or authors. * Copyright 2002-2012 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.
@ -16,10 +16,10 @@
package org.springframework.beans.factory.support; package org.springframework.beans.factory.support;
import java.io.Closeable;
import java.io.Serializable; import java.io.Serializable;
import java.lang.reflect.InvocationTargetException; import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method; import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.security.AccessControlContext; import java.security.AccessControlContext;
import java.security.AccessController; import java.security.AccessController;
import java.security.PrivilegedAction; import java.security.PrivilegedAction;
@ -36,6 +36,7 @@ import org.springframework.beans.factory.DisposableBean;
import org.springframework.beans.factory.config.BeanPostProcessor; import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.beans.factory.config.DestructionAwareBeanPostProcessor; import org.springframework.beans.factory.config.DestructionAwareBeanPostProcessor;
import org.springframework.util.Assert; import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
import org.springframework.util.ReflectionUtils; import org.springframework.util.ReflectionUtils;
/** /**
@ -58,8 +59,22 @@ import org.springframework.util.ReflectionUtils;
@SuppressWarnings("serial") @SuppressWarnings("serial")
class DisposableBeanAdapter implements DisposableBean, Runnable, Serializable { class DisposableBeanAdapter implements DisposableBean, Runnable, Serializable {
private static final String CLOSE_METHOD_NAME = "close";
private static final Log logger = LogFactory.getLog(DisposableBeanAdapter.class); private static final Log logger = LogFactory.getLog(DisposableBeanAdapter.class);
private static Class closeableInterface;
static {
try {
closeableInterface = DisposableBeanAdapter.class.getClassLoader().loadClass("java.lang.AutoCloseable");
}
catch (ClassNotFoundException ex) {
closeableInterface = Closeable.class;
}
}
private final Object bean; private final Object bean;
private final String beanName; private final String beanName;
@ -95,8 +110,7 @@ class DisposableBeanAdapter implements DisposableBean, Runnable, Serializable {
(this.bean instanceof DisposableBean && !beanDefinition.isExternallyManagedDestroyMethod("destroy")); (this.bean instanceof DisposableBean && !beanDefinition.isExternallyManagedDestroyMethod("destroy"));
this.nonPublicAccessAllowed = beanDefinition.isNonPublicAccessAllowed(); this.nonPublicAccessAllowed = beanDefinition.isNonPublicAccessAllowed();
this.acc = acc; this.acc = acc;
inferDestroyMethodIfNecessary(beanDefinition); String destroyMethodName = inferDestroyMethodIfNecessary(bean, beanDefinition);
final String destroyMethodName = beanDefinition.getDestroyMethodName();
if (destroyMethodName != null && !(this.invokeDisposableBean && "destroy".equals(destroyMethodName)) && if (destroyMethodName != null && !(this.invokeDisposableBean && "destroy".equals(destroyMethodName)) &&
!beanDefinition.isExternallyManagedDestroyMethod(destroyMethodName)) { !beanDefinition.isExternallyManagedDestroyMethod(destroyMethodName)) {
this.destroyMethodName = destroyMethodName; this.destroyMethodName = destroyMethodName;
@ -122,31 +136,6 @@ class DisposableBeanAdapter implements DisposableBean, Runnable, Serializable {
this.beanPostProcessors = filterPostProcessors(postProcessors); this.beanPostProcessors = filterPostProcessors(postProcessors);
} }
/**
* If the current value of the given beanDefinition's destroyMethodName property is
* {@link AbstractBeanDefinition#INFER_METHOD}, then attempt to infer a destroy method.
* Candidate methods are currently limited to public, no-arg methods named 'close'
* (whether declared locally or inherited). The given beanDefinition's
* destroyMethodName is updated to be null if no such method is found, otherwise set
* to the name of the inferred method. This constant serves as the default for the
* {@code @Bean#destroyMethod} attribute and the value of the constant may also be
* used in XML within the {@code <bean destroy-method="">} or {@code
* <beans default-destroy-method="">} attributes.
*/
private void inferDestroyMethodIfNecessary(RootBeanDefinition beanDefinition) {
if ("(inferred)".equals(beanDefinition.getDestroyMethodName())) {
try {
Method candidate = bean.getClass().getMethod("close");
if (Modifier.isPublic(candidate.getModifiers())) {
beanDefinition.setDestroyMethodName(candidate.getName());
}
} catch (NoSuchMethodException ex) {
// no candidate destroy method found
beanDefinition.setDestroyMethodName(null);
}
}
}
/** /**
* Create a new DisposableBeanAdapter for the given bean. * Create a new DisposableBeanAdapter for the given bean.
*/ */
@ -164,6 +153,37 @@ class DisposableBeanAdapter implements DisposableBean, Runnable, Serializable {
} }
/**
* If the current value of the given beanDefinition's "destroyMethodName" property is
* {@link AbstractBeanDefinition#INFER_METHOD}, then attempt to infer a destroy method.
* Candidate methods are currently limited to public, no-arg methods named "close"
* (whether declared locally or inherited). The given BeanDefinition's
* "destroyMethodName" is updated to be null if no such method is found, otherwise set
* to the name of the inferred method. This constant serves as the default for the
* {@code @Bean#destroyMethod} attribute and the value of the constant may also be
* used in XML within the {@code <bean destroy-method="">} or {@code
* <beans default-destroy-method="">} attributes.
* <p>Also processes the {@link java.io.Closeable} and {@link java.lang.AutoCloseable}
* interfaces, reflectively calling the "close" method on implementing beans as well.
*/
private String inferDestroyMethodIfNecessary(Object bean, RootBeanDefinition beanDefinition) {
if (AbstractBeanDefinition.INFER_METHOD.equals(beanDefinition.getDestroyMethodName()) ||
(beanDefinition.getDestroyMethodName() == null && closeableInterface.isInstance(bean))) {
// Only perform destroy method inference or Closeable detection
// in case of the bean not explicitly implementing DisposableBean
if (!(bean instanceof DisposableBean)) {
try {
return bean.getClass().getMethod(CLOSE_METHOD_NAME).getName();
}
catch (NoSuchMethodException ex) {
// no candidate destroy method found
}
}
return null;
}
return beanDefinition.getDestroyMethodName();
}
/** /**
* Search for all DestructionAwareBeanPostProcessors in the List. * Search for all DestructionAwareBeanPostProcessors in the List.
* @param postProcessors the List to search * @param postProcessors the List to search
@ -335,4 +355,21 @@ class DisposableBeanAdapter implements DisposableBean, Runnable, Serializable {
this.nonPublicAccessAllowed, this.destroyMethodName, serializablePostProcessors); this.nonPublicAccessAllowed, this.destroyMethodName, serializablePostProcessors);
} }
/**
* Check whether the given bean has any kind of destroy method to call.
* @param bean the bean instance
* @param beanDefinition the corresponding bean definition
*/
public static boolean hasDestroyMethod(Object bean, RootBeanDefinition beanDefinition) {
if (bean instanceof DisposableBean || closeableInterface.isInstance(bean)) {
return true;
}
String destroyMethodName = beanDefinition.getDestroyMethodName();
if (AbstractBeanDefinition.INFER_METHOD.equals(destroyMethodName)) {
return ClassUtils.hasMethod(bean.getClass(), CLOSE_METHOD_NAME);
}
return (destroyMethodName != null);
}
} }

12
spring-beans/src/main/resources/org/springframework/beans/factory/xml/spring-beans-3.2.xsd

@ -435,15 +435,21 @@
The name of the custom initialization method to invoke after setting The name of the custom initialization method to invoke after setting
bean properties. The method must have no arguments, but may throw any bean properties. The method must have no arguments, but may throw any
exception. exception.
This is an alternative to implementing Spring's InitializingBean
interface or marking a method with the PostConstruct annotation.
]]></xsd:documentation> ]]></xsd:documentation>
</xsd:annotation> </xsd:annotation>
</xsd:attribute> </xsd:attribute>
<xsd:attribute name="destroy-method" type="xsd:string"> <xsd:attribute name="destroy-method" type="xsd:string">
<xsd:annotation> <xsd:annotation>
<xsd:documentation><![CDATA[ <xsd:documentation><![CDATA[
The name of the custom destroy method to invoke on bean factory The name of the custom destroy method to invoke on bean factory shutdown.
shutdown. The method must have no arguments, but may throw any The method must have no arguments, but may throw any exception.
exception.
This is an alternative to implementing Spring's DisposableBean
interface or the standard Java Closeable/AutoCloseable interface,
or marking a method with the PreDestroy annotation.
Note: Only invoked on beans whose lifecycle is under the full Note: Only invoked on beans whose lifecycle is under the full
control of the factory - which is always the case for singletons, control of the factory - which is always the case for singletons,

32
spring-beans/src/test/java/org/springframework/beans/factory/DefaultListableBeanFactoryTests.java

@ -16,6 +16,7 @@
package org.springframework.beans.factory; package org.springframework.beans.factory;
import java.io.Closeable;
import java.lang.reflect.Field; import java.lang.reflect.Field;
import java.net.MalformedURLException; import java.net.MalformedURLException;
import java.security.AccessControlContext; import java.security.AccessControlContext;
@ -1909,7 +1910,6 @@ public class DefaultListableBeanFactoryTests {
public Object postProcessBeforeInitialization(Object bean, String beanName) { public Object postProcessBeforeInitialization(Object bean, String beanName) {
return new TestBean(); return new TestBean();
} }
public Object postProcessAfterInitialization(Object bean, String beanName) { public Object postProcessAfterInitialization(Object bean, String beanName) {
return bean; return bean;
} }
@ -1920,6 +1920,25 @@ public class DefaultListableBeanFactoryTests {
assertTrue("Destroy method invoked", BeanWithDisposableBean.closed); assertTrue("Destroy method invoked", BeanWithDisposableBean.closed);
} }
@Test
public void testBeanPostProcessorWithWrappedObjectAndCloseable() {
DefaultListableBeanFactory lbf = new DefaultListableBeanFactory();
RootBeanDefinition bd = new RootBeanDefinition(BeanWithCloseable.class);
lbf.registerBeanDefinition("test", bd);
lbf.addBeanPostProcessor(new BeanPostProcessor() {
public Object postProcessBeforeInitialization(Object bean, String beanName) {
return new TestBean();
}
public Object postProcessAfterInitialization(Object bean, String beanName) {
return bean;
}
});
BeanWithDisposableBean.closed = false;
lbf.preInstantiateSingletons();
lbf.destroySingletons();
assertTrue("Destroy method invoked", BeanWithCloseable.closed);
}
@Test @Test
public void testBeanPostProcessorWithWrappedObjectAndDestroyMethod() { public void testBeanPostProcessorWithWrappedObjectAndDestroyMethod() {
DefaultListableBeanFactory lbf = new DefaultListableBeanFactory(); DefaultListableBeanFactory lbf = new DefaultListableBeanFactory();
@ -1930,7 +1949,6 @@ public class DefaultListableBeanFactoryTests {
public Object postProcessBeforeInitialization(Object bean, String beanName) { public Object postProcessBeforeInitialization(Object bean, String beanName) {
return new TestBean(); return new TestBean();
} }
public Object postProcessAfterInitialization(Object bean, String beanName) { public Object postProcessAfterInitialization(Object bean, String beanName) {
return bean; return bean;
} }
@ -2250,6 +2268,16 @@ public class DefaultListableBeanFactoryTests {
} }
public static class BeanWithCloseable implements Closeable {
private static boolean closed;
public void close() {
closed = true;
}
}
public static class BeanWithDestroyMethod { public static class BeanWithDestroyMethod {
private static boolean closed; private static boolean closed;

11
spring-context/src/main/java/org/springframework/context/annotation/Bean.java

@ -207,7 +207,6 @@ public @interface Bean {
* application context, for example a {@code close()} method on a JDBC {@code * application context, for example a {@code close()} method on a JDBC {@code
* DataSource} implementation, or a Hibernate {@code SessionFactory} object. * DataSource} implementation, or a Hibernate {@code SessionFactory} object.
* The method must have no arguments but may throw any exception. * The method must have no arguments but may throw any exception.
*
* <p>As a convenience to the user, the container will attempt to infer a destroy * <p>As a convenience to the user, the container will attempt to infer a destroy
* method against an object returned from the {@code @Bean} method. For example, given a * method against an object returned from the {@code @Bean} method. For example, given a
* {@code @Bean} method returning an Apache Commons DBCP {@code BasicDataSource}, the * {@code @Bean} method returning an Apache Commons DBCP {@code BasicDataSource}, the
@ -217,16 +216,16 @@ public @interface Bean {
* 'close'. The method may be declared at any level of the inheritance hierarchy and * 'close'. The method may be declared at any level of the inheritance hierarchy and
* will be detected regardless of the return type of the {@code @Bean} method (i.e., * will be detected regardless of the return type of the {@code @Bean} method (i.e.,
* detection occurs reflectively against the bean instance itself at creation time). * detection occurs reflectively against the bean instance itself at creation time).
*
* <p>To disable destroy method inference for a particular {@code @Bean}, specify an * <p>To disable destroy method inference for a particular {@code @Bean}, specify an
* empty string as the value, e.g. {@code @Bean(destroyMethod="")}. * empty string as the value, e.g. {@code @Bean(destroyMethod="")}. Note that the
* * {@link org.springframework.beans.factory.DisposableBean} and the
* {@link java.io.Closeable}/{@link java.lang.AutoCloseable} interfaces will
* nevertheless get detected and the corresponding destroy/close method invoked.
* <p>Note: Only invoked on beans whose lifecycle is under the full control of the * <p>Note: Only invoked on beans whose lifecycle is under the full control of the
* factory, which is always the case for singletons but not guaranteed for any * factory, which is always the case for singletons but not guaranteed for any
* other scope. * other scope.
*
* @see org.springframework.context.ConfigurableApplicationContext#close() * @see org.springframework.context.ConfigurableApplicationContext#close()
*/ */
String destroyMethod() default AbstractBeanDefinition.INFER_METHOD; String destroyMethod() default AbstractBeanDefinition.INFER_METHOD;
} }

Loading…
Cancel
Save