提交 38e90105 编写于 作者: C Chris Beams

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
上级 8cafb7ee
......@@ -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,
......
<?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>
/*
* 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;
}
}
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册