From 9c6aa3e43bee04bf713cb0077e6de009bd642abb Mon Sep 17 00:00:00 2001 From: Juergen Hoeller Date: Mon, 26 Nov 2012 22:05:29 +0100 Subject: [PATCH] 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 --- .../factory/support/AbstractBeanFactory.java | 4 +- .../support/DisposableBeanAdapter.java | 95 +++++++++++++------ .../beans/factory/xml/spring-beans-3.2.xsd | 12 ++- .../DefaultListableBeanFactoryTests.java | 32 ++++++- .../context/annotation/Bean.java | 11 +-- 5 files changed, 111 insertions(+), 43 deletions(-) diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/support/AbstractBeanFactory.java b/spring-beans/src/main/java/org/springframework/beans/factory/support/AbstractBeanFactory.java index eb48008ef13..2919622977f 100644 --- a/spring-beans/src/main/java/org/springframework/beans/factory/support/AbstractBeanFactory.java +++ b/spring-beans/src/main/java/org/springframework/beans/factory/support/AbstractBeanFactory.java @@ -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 */ protected boolean requiresDestruction(Object bean, RootBeanDefinition mbd) { return (bean != null && - (bean instanceof DisposableBean || mbd.getDestroyMethodName() != null || - hasDestructionAwareBeanPostProcessors())); + (DisposableBeanAdapter.hasDestroyMethod(bean, mbd) || hasDestructionAwareBeanPostProcessors())); } /** diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/support/DisposableBeanAdapter.java b/spring-beans/src/main/java/org/springframework/beans/factory/support/DisposableBeanAdapter.java index 42a00a03495..d493bc80d03 100644 --- a/spring-beans/src/main/java/org/springframework/beans/factory/support/DisposableBeanAdapter.java +++ b/spring-beans/src/main/java/org/springframework/beans/factory/support/DisposableBeanAdapter.java @@ -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 @@ 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; 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; @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 { (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 { 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. */ @@ -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 } or {@code + * } attributes. + *

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 { 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); + } + } diff --git a/spring-beans/src/main/resources/org/springframework/beans/factory/xml/spring-beans-3.2.xsd b/spring-beans/src/main/resources/org/springframework/beans/factory/xml/spring-beans-3.2.xsd index ba053e86576..fe427c2f556 100644 --- a/spring-beans/src/main/resources/org/springframework/beans/factory/xml/spring-beans-3.2.xsd +++ b/spring-beans/src/main/resources/org/springframework/beans/factory/xml/spring-beans-3.2.xsd @@ -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. ]]> 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 { * '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="")}. - * + * 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. *

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; -} \ No newline at end of file +}