Skip to content

Commit 9c6aa3e

Browse files
jhoellerunknown
authored and
unknown
committed
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
1 parent 5cb1930 commit 9c6aa3e

File tree

5 files changed

+111
-43
lines changed

5 files changed

+111
-43
lines changed

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

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,6 @@
5252
import org.springframework.beans.factory.BeanIsNotAFactoryException;
5353
import org.springframework.beans.factory.BeanNotOfRequiredTypeException;
5454
import org.springframework.beans.factory.CannotLoadBeanClassException;
55-
import org.springframework.beans.factory.DisposableBean;
5655
import org.springframework.beans.factory.FactoryBean;
5756
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
5857
import org.springframework.beans.factory.ObjectFactory;
@@ -1466,8 +1465,7 @@ public boolean isBeanNameInUse(String beanName) {
14661465
*/
14671466
protected boolean requiresDestruction(Object bean, RootBeanDefinition mbd) {
14681467
return (bean != null &&
1469-
(bean instanceof DisposableBean || mbd.getDestroyMethodName() != null ||
1470-
hasDestructionAwareBeanPostProcessors()));
1468+
(DisposableBeanAdapter.hasDestroyMethod(bean, mbd) || hasDestructionAwareBeanPostProcessors()));
14711469
}
14721470

14731471
/**

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

Lines changed: 66 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2010 the original author or authors.
2+
* Copyright 2002-2012 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -16,10 +16,10 @@
1616

1717
package org.springframework.beans.factory.support;
1818

19+
import java.io.Closeable;
1920
import java.io.Serializable;
2021
import java.lang.reflect.InvocationTargetException;
2122
import java.lang.reflect.Method;
22-
import java.lang.reflect.Modifier;
2323
import java.security.AccessControlContext;
2424
import java.security.AccessController;
2525
import java.security.PrivilegedAction;
@@ -36,6 +36,7 @@
3636
import org.springframework.beans.factory.config.BeanPostProcessor;
3737
import org.springframework.beans.factory.config.DestructionAwareBeanPostProcessor;
3838
import org.springframework.util.Assert;
39+
import org.springframework.util.ClassUtils;
3940
import org.springframework.util.ReflectionUtils;
4041

4142
/**
@@ -58,8 +59,22 @@
5859
@SuppressWarnings("serial")
5960
class DisposableBeanAdapter implements DisposableBean, Runnable, Serializable {
6061

62+
private static final String CLOSE_METHOD_NAME = "close";
63+
6164
private static final Log logger = LogFactory.getLog(DisposableBeanAdapter.class);
6265

66+
private static Class closeableInterface;
67+
68+
static {
69+
try {
70+
closeableInterface = DisposableBeanAdapter.class.getClassLoader().loadClass("java.lang.AutoCloseable");
71+
}
72+
catch (ClassNotFoundException ex) {
73+
closeableInterface = Closeable.class;
74+
}
75+
}
76+
77+
6378
private final Object bean;
6479

6580
private final String beanName;
@@ -95,8 +110,7 @@ public DisposableBeanAdapter(Object bean, String beanName, RootBeanDefinition be
95110
(this.bean instanceof DisposableBean && !beanDefinition.isExternallyManagedDestroyMethod("destroy"));
96111
this.nonPublicAccessAllowed = beanDefinition.isNonPublicAccessAllowed();
97112
this.acc = acc;
98-
inferDestroyMethodIfNecessary(beanDefinition);
99-
final String destroyMethodName = beanDefinition.getDestroyMethodName();
113+
String destroyMethodName = inferDestroyMethodIfNecessary(bean, beanDefinition);
100114
if (destroyMethodName != null && !(this.invokeDisposableBean && "destroy".equals(destroyMethodName)) &&
101115
!beanDefinition.isExternallyManagedDestroyMethod(destroyMethodName)) {
102116
this.destroyMethodName = destroyMethodName;
@@ -122,31 +136,6 @@ else if (paramTypes.length == 1 && !paramTypes[0].equals(boolean.class)) {
122136
this.beanPostProcessors = filterPostProcessors(postProcessors);
123137
}
124138

125-
/**
126-
* If the current value of the given beanDefinition's destroyMethodName property is
127-
* {@link AbstractBeanDefinition#INFER_METHOD}, then attempt to infer a destroy method.
128-
* Candidate methods are currently limited to public, no-arg methods named 'close'
129-
* (whether declared locally or inherited). The given beanDefinition's
130-
* destroyMethodName is updated to be null if no such method is found, otherwise set
131-
* to the name of the inferred method. This constant serves as the default for the
132-
* {@code @Bean#destroyMethod} attribute and the value of the constant may also be
133-
* used in XML within the {@code <bean destroy-method="">} or {@code
134-
* <beans default-destroy-method="">} attributes.
135-
*/
136-
private void inferDestroyMethodIfNecessary(RootBeanDefinition beanDefinition) {
137-
if ("(inferred)".equals(beanDefinition.getDestroyMethodName())) {
138-
try {
139-
Method candidate = bean.getClass().getMethod("close");
140-
if (Modifier.isPublic(candidate.getModifiers())) {
141-
beanDefinition.setDestroyMethodName(candidate.getName());
142-
}
143-
} catch (NoSuchMethodException ex) {
144-
// no candidate destroy method found
145-
beanDefinition.setDestroyMethodName(null);
146-
}
147-
}
148-
}
149-
150139
/**
151140
* Create a new DisposableBeanAdapter for the given bean.
152141
*/
@@ -164,6 +153,37 @@ private DisposableBeanAdapter(Object bean, String beanName, boolean invokeDispos
164153
}
165154

166155

156+
/**
157+
* If the current value of the given beanDefinition's "destroyMethodName" property is
158+
* {@link AbstractBeanDefinition#INFER_METHOD}, then attempt to infer a destroy method.
159+
* Candidate methods are currently limited to public, no-arg methods named "close"
160+
* (whether declared locally or inherited). The given BeanDefinition's
161+
* "destroyMethodName" is updated to be null if no such method is found, otherwise set
162+
* to the name of the inferred method. This constant serves as the default for the
163+
* {@code @Bean#destroyMethod} attribute and the value of the constant may also be
164+
* used in XML within the {@code <bean destroy-method="">} or {@code
165+
* <beans default-destroy-method="">} attributes.
166+
* <p>Also processes the {@link java.io.Closeable} and {@link java.lang.AutoCloseable}
167+
* interfaces, reflectively calling the "close" method on implementing beans as well.
168+
*/
169+
private String inferDestroyMethodIfNecessary(Object bean, RootBeanDefinition beanDefinition) {
170+
if (AbstractBeanDefinition.INFER_METHOD.equals(beanDefinition.getDestroyMethodName()) ||
171+
(beanDefinition.getDestroyMethodName() == null && closeableInterface.isInstance(bean))) {
172+
// Only perform destroy method inference or Closeable detection
173+
// in case of the bean not explicitly implementing DisposableBean
174+
if (!(bean instanceof DisposableBean)) {
175+
try {
176+
return bean.getClass().getMethod(CLOSE_METHOD_NAME).getName();
177+
}
178+
catch (NoSuchMethodException ex) {
179+
// no candidate destroy method found
180+
}
181+
}
182+
return null;
183+
}
184+
return beanDefinition.getDestroyMethodName();
185+
}
186+
167187
/**
168188
* Search for all DestructionAwareBeanPostProcessors in the List.
169189
* @param postProcessors the List to search
@@ -335,4 +355,21 @@ protected Object writeReplace() {
335355
this.nonPublicAccessAllowed, this.destroyMethodName, serializablePostProcessors);
336356
}
337357

358+
359+
/**
360+
* Check whether the given bean has any kind of destroy method to call.
361+
* @param bean the bean instance
362+
* @param beanDefinition the corresponding bean definition
363+
*/
364+
public static boolean hasDestroyMethod(Object bean, RootBeanDefinition beanDefinition) {
365+
if (bean instanceof DisposableBean || closeableInterface.isInstance(bean)) {
366+
return true;
367+
}
368+
String destroyMethodName = beanDefinition.getDestroyMethodName();
369+
if (AbstractBeanDefinition.INFER_METHOD.equals(destroyMethodName)) {
370+
return ClassUtils.hasMethod(bean.getClass(), CLOSE_METHOD_NAME);
371+
}
372+
return (destroyMethodName != null);
373+
}
374+
338375
}

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

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -435,15 +435,21 @@
435435
The name of the custom initialization method to invoke after setting
436436
bean properties. The method must have no arguments, but may throw any
437437
exception.
438+
439+
This is an alternative to implementing Spring's InitializingBean
440+
interface or marking a method with the PostConstruct annotation.
438441
]]></xsd:documentation>
439442
</xsd:annotation>
440443
</xsd:attribute>
441444
<xsd:attribute name="destroy-method" type="xsd:string">
442445
<xsd:annotation>
443446
<xsd:documentation><![CDATA[
444-
The name of the custom destroy method to invoke on bean factory
445-
shutdown. The method must have no arguments, but may throw any
446-
exception.
447+
The name of the custom destroy method to invoke on bean factory shutdown.
448+
The method must have no arguments, but may throw any exception.
449+
450+
This is an alternative to implementing Spring's DisposableBean
451+
interface or the standard Java Closeable/AutoCloseable interface,
452+
or marking a method with the PreDestroy annotation.
447453
448454
Note: Only invoked on beans whose lifecycle is under the full
449455
control of the factory - which is always the case for singletons,

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

Lines changed: 30 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616

1717
package org.springframework.beans.factory;
1818

19+
import java.io.Closeable;
1920
import java.lang.reflect.Field;
2021
import java.net.MalformedURLException;
2122
import java.security.AccessControlContext;
@@ -1909,7 +1910,6 @@ public void testBeanPostProcessorWithWrappedObjectAndDisposableBean() {
19091910
public Object postProcessBeforeInitialization(Object bean, String beanName) {
19101911
return new TestBean();
19111912
}
1912-
19131913
public Object postProcessAfterInitialization(Object bean, String beanName) {
19141914
return bean;
19151915
}
@@ -1920,6 +1920,25 @@ public Object postProcessAfterInitialization(Object bean, String beanName) {
19201920
assertTrue("Destroy method invoked", BeanWithDisposableBean.closed);
19211921
}
19221922

1923+
@Test
1924+
public void testBeanPostProcessorWithWrappedObjectAndCloseable() {
1925+
DefaultListableBeanFactory lbf = new DefaultListableBeanFactory();
1926+
RootBeanDefinition bd = new RootBeanDefinition(BeanWithCloseable.class);
1927+
lbf.registerBeanDefinition("test", bd);
1928+
lbf.addBeanPostProcessor(new BeanPostProcessor() {
1929+
public Object postProcessBeforeInitialization(Object bean, String beanName) {
1930+
return new TestBean();
1931+
}
1932+
public Object postProcessAfterInitialization(Object bean, String beanName) {
1933+
return bean;
1934+
}
1935+
});
1936+
BeanWithDisposableBean.closed = false;
1937+
lbf.preInstantiateSingletons();
1938+
lbf.destroySingletons();
1939+
assertTrue("Destroy method invoked", BeanWithCloseable.closed);
1940+
}
1941+
19231942
@Test
19241943
public void testBeanPostProcessorWithWrappedObjectAndDestroyMethod() {
19251944
DefaultListableBeanFactory lbf = new DefaultListableBeanFactory();
@@ -1930,7 +1949,6 @@ public void testBeanPostProcessorWithWrappedObjectAndDestroyMethod() {
19301949
public Object postProcessBeforeInitialization(Object bean, String beanName) {
19311950
return new TestBean();
19321951
}
1933-
19341952
public Object postProcessAfterInitialization(Object bean, String beanName) {
19351953
return bean;
19361954
}
@@ -2250,6 +2268,16 @@ public void destroy() {
22502268
}
22512269

22522270

2271+
public static class BeanWithCloseable implements Closeable {
2272+
2273+
private static boolean closed;
2274+
2275+
public void close() {
2276+
closed = true;
2277+
}
2278+
}
2279+
2280+
22532281
public static class BeanWithDestroyMethod {
22542282

22552283
private static boolean closed;

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

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

232-
}
231+
}

0 commit comments

Comments
 (0)