Support destroy method inference
Anywhere the value of a destroy method may be expressed, specifying the value "(inferred)" now indicates that the container should attempt to automatically discover a destroy method. This functionality is currently limited to detecting public, no-arg methods named 'close'; this is particularly useful for commonly used types such as Hibernate SessionFactory most JDBC DataSource implementations, JMS connection factories, and so forth. This special value is captured as the constant AbstractBeanDefinition#INFER_METHOD, which in turn serves as the default value of the @Bean#destroyMethod attribute. For example in the following case @Bean public BasicDataSource dataSource() { ... } the container will automatically detect BasicDataSource#close and invoke it when the enclosing ApplicationContext is closed. This is exactly equivalent to @Bean(destroyMethod="(inferred)") public BasicDataSource dataSource() { ... } A user may override this inference-by-default convention simply by specifying a different method @Bean(destroyMethod="myClose") public MyBasicDataSource dataSource() { ... } or, in the case of a bean that has an otherwise inferrable 'close' method, but the user wishes to disable handling it entirely, an empty string may be specified @Bean(destroyMethod="") public MyBasicDataSource dataSource() { ... } The special destroy method name "(inferred)" may also be specified in an XML context, e.g. <bean destroy-method="(inferred)"> or <beans default-destroy-method="(inferred)"> Note that "(inferred)" is the default value for @Bean#destroyMethod, but NOT for the destroy-method and default-destroy-method attributes in the spring-beans XML schema. The principal reason for introducing this feature is to avoid forcing @Configuration class users to type destroyMethod="close" every time a closeable bean is configured. This kind of boilerplate is easily forgotten, and this simple convention means the right thing is done by default, while allowing the user full control over customization or disablement in special cases. Issue: SPR-8751
This commit is contained in:
parent
8cafb7ee13
commit
38e90105a0
|
@ -123,6 +123,15 @@ public abstract class AbstractBeanDefinition extends BeanMetadataAttributeAccess
|
|||
*/
|
||||
public static final int DEPENDENCY_CHECK_ALL = 3;
|
||||
|
||||
/**
|
||||
* Constant that indicates the container should attempt to infer the {@link
|
||||
* #setDestroyMethodName destroy method name} for a bean as opposed to explicit
|
||||
* specification of a method name. The value {@value} is specifically designed to
|
||||
* include characters otherwise illegal in a method name, ensuring no possibility of
|
||||
* collisions with a legitimately named methods having the same name.
|
||||
*/
|
||||
public static final String INFER_METHOD = "(inferred)";
|
||||
|
||||
|
||||
private volatile Object beanClass;
|
||||
|
||||
|
|
|
@ -19,6 +19,7 @@ package org.springframework.beans.factory.support;
|
|||
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;
|
||||
|
@ -94,7 +95,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();
|
||||
if (destroyMethodName != null && !(this.invokeDisposableBean && "destroy".equals(destroyMethodName)) &&
|
||||
!beanDefinition.isExternallyManagedDestroyMethod(destroyMethodName)) {
|
||||
|
@ -121,6 +122,31 @@ 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.
|
||||
*/
|
||||
|
|
|
@ -23,6 +23,7 @@ import java.lang.annotation.RetentionPolicy;
|
|||
import java.lang.annotation.Target;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowire;
|
||||
import org.springframework.beans.factory.support.AbstractBeanDefinition;
|
||||
|
||||
/**
|
||||
* Indicates that a method produces a bean to be managed by the Spring container. The
|
||||
|
@ -166,16 +167,18 @@ public @interface Bean {
|
|||
|
||||
/**
|
||||
* The optional name of a method to call on the bean instance upon closing the
|
||||
* application context, for example a {@code close()} method on a {@code DataSource}.
|
||||
* 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 based on the return type of the {@code @Bean} method. For example, given a
|
||||
* method against object returned from the {@code @Bean} method. For example, given a
|
||||
* {@code @Bean} method returning an Apache Commons DBCP {@code BasicDataSource}, the
|
||||
* container will notice the {@code close()} method available on that type and
|
||||
* automatically register it as the {@code destroyMethod}. By contrast, for a return
|
||||
* type of JDBC {@code DataSource} interface (which does not declare a {@code close()}
|
||||
* method, no inference is possible and the user must fall back to manually declaring
|
||||
* {@code @Bean(destroyMethod="close")}.
|
||||
* container will notice the {@code close()} method available on that object and
|
||||
* automatically register it as the {@code destroyMethod}. This 'destroy method
|
||||
* inference' is currently limited to detecting only public, no-arg methods named
|
||||
* '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="")}.
|
||||
* <p>Note: Only invoked on beans whose lifecycle is under the full control of the
|
||||
|
@ -183,6 +186,6 @@ public @interface Bean {
|
|||
* for any other scope.
|
||||
* @see org.springframework.context.ConfigurableApplicationContext#close()
|
||||
*/
|
||||
String destroyMethod() default ConfigurationClassUtils.INFER_METHOD;
|
||||
String destroyMethod() default AbstractBeanDefinition.INFER_METHOD;
|
||||
|
||||
}
|
||||
|
|
|
@ -46,8 +46,6 @@ abstract class ConfigurationClassUtils {
|
|||
private static final String CONFIGURATION_CLASS_ATTRIBUTE =
|
||||
Conventions.getQualifiedAttributeName(ConfigurationClassPostProcessor.class, "configurationClass");
|
||||
|
||||
static final String INFER_METHOD = ""; // TODO SPR-8751 update to '-' or some such
|
||||
|
||||
|
||||
/**
|
||||
* Check whether the given bean definition is a candidate for a configuration class,
|
||||
|
|
|
@ -0,0 +1,25 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<beans xmlns="http://www.springframework.org/schema/beans"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns:context="http://www.springframework.org/schema/context"
|
||||
xsi:schemaLocation="http://www.springframework.org/schema/beans
|
||||
http://www.springframework.org/schema/beans/spring-beans-3.1.xsd
|
||||
http://www.springframework.org/schema/context
|
||||
http://www.springframework.org/schema/context/spring-context-3.1.xsd">
|
||||
|
||||
<bean id="x1"
|
||||
class="org.springframework.context.annotation.DestroyMethodInferenceTests$WithLocalCloseMethod"/>
|
||||
|
||||
<bean id="x2"
|
||||
class="org.springframework.context.annotation.DestroyMethodInferenceTests$WithLocalCloseMethod"
|
||||
destroy-method="(inferred)"/>
|
||||
|
||||
<beans default-destroy-method="(inferred)">
|
||||
<bean id="x3"
|
||||
class="org.springframework.context.annotation.DestroyMethodInferenceTests$WithLocalCloseMethod"/>
|
||||
|
||||
<bean id="x4"
|
||||
class="org.springframework.context.annotation.DestroyMethodInferenceTests$WithNoCloseMethod"/>
|
||||
</beans>
|
||||
|
||||
</beans>
|
|
@ -0,0 +1,151 @@
|
|||
/*
|
||||
* Copyright 2002-2011 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.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.context.annotation;
|
||||
|
||||
import static org.hamcrest.CoreMatchers.is;
|
||||
import static org.junit.Assert.assertThat;
|
||||
|
||||
import java.io.Closeable;
|
||||
import java.io.IOException;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.springframework.context.ConfigurableApplicationContext;
|
||||
import org.springframework.context.support.GenericXmlApplicationContext;
|
||||
|
||||
public class DestroyMethodInferenceTests {
|
||||
|
||||
@Test
|
||||
public void beanMethods() {
|
||||
ConfigurableApplicationContext ctx =
|
||||
new AnnotationConfigApplicationContext(Config.class);
|
||||
WithExplicitDestroyMethod c0 = ctx.getBean(WithExplicitDestroyMethod.class);
|
||||
WithLocalCloseMethod c1 = ctx.getBean("c1", WithLocalCloseMethod.class);
|
||||
WithLocalCloseMethod c2 = ctx.getBean("c2", WithLocalCloseMethod.class);
|
||||
WithInheritedCloseMethod c3 = ctx.getBean("c3", WithInheritedCloseMethod.class);
|
||||
WithInheritedCloseMethod c4 = ctx.getBean("c4", WithInheritedCloseMethod.class);
|
||||
WithInheritedCloseMethod c5 = ctx.getBean("c5", WithInheritedCloseMethod.class);
|
||||
WithNoCloseMethod c6 = ctx.getBean("c6", WithNoCloseMethod.class);
|
||||
|
||||
assertThat(c0.closed, is(false));
|
||||
assertThat(c1.closed, is(false));
|
||||
assertThat(c2.closed, is(false));
|
||||
assertThat(c3.closed, is(false));
|
||||
assertThat(c4.closed, is(false));
|
||||
assertThat(c5.closed, is(false));
|
||||
assertThat(c6.closed, is(false));
|
||||
ctx.close();
|
||||
assertThat("c0", c0.closed, is(true));
|
||||
assertThat("c1", c1.closed, is(true));
|
||||
assertThat("c2", c2.closed, is(true));
|
||||
assertThat("c3", c3.closed, is(true));
|
||||
assertThat("c4", c4.closed, is(true));
|
||||
assertThat("c5", c5.closed, is(true));
|
||||
assertThat("c6", c6.closed, is(false));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void xml() {
|
||||
ConfigurableApplicationContext ctx = new GenericXmlApplicationContext(
|
||||
getClass(), "DestroyMethodInferenceTests-context.xml");
|
||||
WithLocalCloseMethod x1 = ctx.getBean("x1", WithLocalCloseMethod.class);
|
||||
WithLocalCloseMethod x2 = ctx.getBean("x2", WithLocalCloseMethod.class);
|
||||
WithLocalCloseMethod x3 = ctx.getBean("x3", WithLocalCloseMethod.class);
|
||||
WithNoCloseMethod x4 = ctx.getBean("x4", WithNoCloseMethod.class);
|
||||
assertThat(x1.closed, is(false));
|
||||
assertThat(x2.closed, is(false));
|
||||
assertThat(x3.closed, is(false));
|
||||
assertThat(x4.closed, is(false));
|
||||
ctx.close();
|
||||
assertThat(x1.closed, is(false));
|
||||
assertThat(x2.closed, is(true));
|
||||
assertThat(x3.closed, is(true));
|
||||
assertThat(x4.closed, is(false));
|
||||
}
|
||||
|
||||
@Configuration
|
||||
static class Config {
|
||||
@Bean(destroyMethod="explicitClose")
|
||||
public WithExplicitDestroyMethod c0() {
|
||||
return new WithExplicitDestroyMethod();
|
||||
}
|
||||
|
||||
@Bean
|
||||
public WithLocalCloseMethod c1() {
|
||||
return new WithLocalCloseMethod();
|
||||
}
|
||||
|
||||
@Bean
|
||||
public Object c2() {
|
||||
return new WithLocalCloseMethod();
|
||||
}
|
||||
|
||||
@Bean
|
||||
public WithInheritedCloseMethod c3() {
|
||||
return new WithInheritedCloseMethod();
|
||||
}
|
||||
|
||||
@Bean
|
||||
public Closeable c4() {
|
||||
return new WithInheritedCloseMethod();
|
||||
}
|
||||
|
||||
@Bean(destroyMethod="other")
|
||||
public WithInheritedCloseMethod c5() {
|
||||
return new WithInheritedCloseMethod() {
|
||||
@Override
|
||||
public void close() throws IOException {
|
||||
throw new RuntimeException("close() should not be called");
|
||||
}
|
||||
@SuppressWarnings("unused")
|
||||
public void other() {
|
||||
this.closed = true;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@Bean
|
||||
public WithNoCloseMethod c6() {
|
||||
return new WithNoCloseMethod();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static class WithExplicitDestroyMethod {
|
||||
boolean closed = false;
|
||||
public void explicitClose() {
|
||||
closed = true;
|
||||
}
|
||||
}
|
||||
|
||||
static class WithLocalCloseMethod {
|
||||
boolean closed = false;
|
||||
public void close() {
|
||||
closed = true;
|
||||
}
|
||||
}
|
||||
|
||||
static class WithInheritedCloseMethod implements Closeable {
|
||||
boolean closed = false;
|
||||
public void close() throws IOException {
|
||||
closed = true;
|
||||
}
|
||||
}
|
||||
|
||||
static class WithNoCloseMethod {
|
||||
boolean closed = false;
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue