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

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

@ -1,5 +1,5 @@ @@ -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");
* you may not use this file except in compliance with the License.
@ -16,10 +16,10 @@ @@ -16,10 +16,10 @@
package org.springframework.beans.factory.support;
import java.io.Closeable;
import java.io.Serializable;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.security.AccessControlContext;
import java.security.AccessController;
import java.security.PrivilegedAction;
@ -36,6 +36,7 @@ import org.springframework.beans.factory.DisposableBean; @@ -36,6 +36,7 @@ import org.springframework.beans.factory.DisposableBean;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.beans.factory.config.DestructionAwareBeanPostProcessor;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
import org.springframework.util.ReflectionUtils;
/**
@ -58,8 +59,22 @@ import org.springframework.util.ReflectionUtils; @@ -58,8 +59,22 @@ import org.springframework.util.ReflectionUtils;
@SuppressWarnings("serial")
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 Class closeableInterface;
static {
try {
closeableInterface = DisposableBeanAdapter.class.getClassLoader().loadClass("java.lang.AutoCloseable");
}
catch (ClassNotFoundException ex) {
closeableInterface = Closeable.class;
}
}
private final Object bean;
private final String beanName;
@ -95,8 +110,7 @@ class DisposableBeanAdapter implements DisposableBean, Runnable, Serializable { @@ -95,8 +110,7 @@ class DisposableBeanAdapter implements DisposableBean, Runnable, Serializable {
(this.bean instanceof DisposableBean && !beanDefinition.isExternallyManagedDestroyMethod("destroy"));
this.nonPublicAccessAllowed = beanDefinition.isNonPublicAccessAllowed();
this.acc = acc;
inferDestroyMethodIfNecessary(beanDefinition);
final String destroyMethodName = beanDefinition.getDestroyMethodName();
String destroyMethodName = inferDestroyMethodIfNecessary(bean, beanDefinition);
if (destroyMethodName != null && !(this.invokeDisposableBean && "destroy".equals(destroyMethodName)) &&
!beanDefinition.isExternallyManagedDestroyMethod(destroyMethodName)) {
this.destroyMethodName = destroyMethodName;
@ -122,31 +136,6 @@ class DisposableBeanAdapter implements DisposableBean, Runnable, Serializable { @@ -122,31 +136,6 @@ class DisposableBeanAdapter implements DisposableBean, Runnable, Serializable {
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.
*/
@ -164,6 +153,37 @@ class DisposableBeanAdapter implements DisposableBean, Runnable, Serializable { @@ -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.
* @param postProcessors the List to search
@ -335,4 +355,21 @@ class DisposableBeanAdapter implements DisposableBean, Runnable, Serializable { @@ -335,4 +355,21 @@ class DisposableBeanAdapter implements DisposableBean, Runnable, Serializable {
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 @@ @@ -435,15 +435,21 @@
The name of the custom initialization method to invoke after setting
bean properties. The method must have no arguments, but may throw any
exception.
This is an alternative to implementing Spring's InitializingBean
interface or marking a method with the PostConstruct annotation.
]]></xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="destroy-method" type="xsd:string">
<xsd:annotation>
<xsd:documentation><![CDATA[
The name of the custom destroy method to invoke on bean factory
shutdown. The method must have no arguments, but may throw any
exception.
The name of the custom destroy method to invoke on bean factory shutdown.
The method must have no arguments, but may throw any 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
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 @@ @@ -16,6 +16,7 @@
package org.springframework.beans.factory;
import java.io.Closeable;
import java.lang.reflect.Field;
import java.net.MalformedURLException;
import java.security.AccessControlContext;
@ -1909,7 +1910,6 @@ public class DefaultListableBeanFactoryTests { @@ -1909,7 +1910,6 @@ public class DefaultListableBeanFactoryTests {
public Object postProcessBeforeInitialization(Object bean, String beanName) {
return new TestBean();
}
public Object postProcessAfterInitialization(Object bean, String beanName) {
return bean;
}
@ -1920,6 +1920,25 @@ public class DefaultListableBeanFactoryTests { @@ -1920,6 +1920,25 @@ public class DefaultListableBeanFactoryTests {
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
public void testBeanPostProcessorWithWrappedObjectAndDestroyMethod() {
DefaultListableBeanFactory lbf = new DefaultListableBeanFactory();
@ -1930,7 +1949,6 @@ public class DefaultListableBeanFactoryTests { @@ -1930,7 +1949,6 @@ public class DefaultListableBeanFactoryTests {
public Object postProcessBeforeInitialization(Object bean, String beanName) {
return new TestBean();
}
public Object postProcessAfterInitialization(Object bean, String beanName) {
return bean;
}
@ -2250,6 +2268,16 @@ public class DefaultListableBeanFactoryTests { @@ -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 {
private static boolean closed;

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

@ -207,7 +207,6 @@ public @interface Bean { @@ -207,7 +207,6 @@ public @interface Bean {
* application context, for example a {@code close()} method on a JDBC {@code
* DataSource} implementation, or a Hibernate {@code SessionFactory} object.
* 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
* 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
@ -217,16 +216,16 @@ public @interface Bean { @@ -217,16 +216,16 @@ public @interface Bean {
* '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.,
* detection occurs reflectively against the bean instance itself at creation time).
*
* <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
* factory, which is always the case for singletons but not guaranteed for any
* other scope.
*
* @see org.springframework.context.ConfigurableApplicationContext#close()
*/
String destroyMethod() default AbstractBeanDefinition.INFER_METHOD;
}
}

Loading…
Cancel
Save