diff --git a/org.springframework.beans/src/main/java/org/springframework/beans/factory/support/AbstractBeanDefinition.java b/org.springframework.beans/src/main/java/org/springframework/beans/factory/support/AbstractBeanDefinition.java index f5852c44bde..80300dd6670 100644 --- a/org.springframework.beans/src/main/java/org/springframework/beans/factory/support/AbstractBeanDefinition.java +++ b/org.springframework.beans/src/main/java/org/springframework/beans/factory/support/AbstractBeanDefinition.java @@ -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; diff --git a/org.springframework.beans/src/main/java/org/springframework/beans/factory/support/DisposableBeanAdapter.java b/org.springframework.beans/src/main/java/org/springframework/beans/factory/support/DisposableBeanAdapter.java index 6ffea9d0d70..42a00a03495 100644 --- a/org.springframework.beans/src/main/java/org/springframework/beans/factory/support/DisposableBeanAdapter.java +++ b/org.springframework.beans/src/main/java/org/springframework/beans/factory/support/DisposableBeanAdapter.java @@ -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 } or {@code + * } 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. */ diff --git a/org.springframework.context/src/main/java/org/springframework/context/annotation/Bean.java b/org.springframework.context/src/main/java/org/springframework/context/annotation/Bean.java index 1f944245d57..eb29e72eaac 100644 --- a/org.springframework.context/src/main/java/org/springframework/context/annotation/Bean.java +++ b/org.springframework.context/src/main/java/org/springframework/context/annotation/Bean.java @@ -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. *

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. *

To disable destroy method inference for a particular {@code @Bean}, specify an * empty string as the value, e.g. {@code @Bean(destroyMethod="")}. *

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; } diff --git a/org.springframework.context/src/main/java/org/springframework/context/annotation/ConfigurationClassUtils.java b/org.springframework.context/src/main/java/org/springframework/context/annotation/ConfigurationClassUtils.java index 99ebab95af3..2d192a809c9 100644 --- a/org.springframework.context/src/main/java/org/springframework/context/annotation/ConfigurationClassUtils.java +++ b/org.springframework.context/src/main/java/org/springframework/context/annotation/ConfigurationClassUtils.java @@ -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, diff --git a/org.springframework.context/src/test/java/org/springframework/context/annotation/DestroyMethodInferenceTests-context.xml b/org.springframework.context/src/test/java/org/springframework/context/annotation/DestroyMethodInferenceTests-context.xml new file mode 100644 index 00000000000..7033e5517d6 --- /dev/null +++ b/org.springframework.context/src/test/java/org/springframework/context/annotation/DestroyMethodInferenceTests-context.xml @@ -0,0 +1,25 @@ + + + + + + + + + + + + + + diff --git a/org.springframework.context/src/test/java/org/springframework/context/annotation/DestroyMethodInferenceTests.java b/org.springframework.context/src/test/java/org/springframework/context/annotation/DestroyMethodInferenceTests.java new file mode 100644 index 00000000000..d89deb4415c --- /dev/null +++ b/org.springframework.context/src/test/java/org/springframework/context/annotation/DestroyMethodInferenceTests.java @@ -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; + } +}