提交 bcf6f232 编写于 作者: J Juergen Hoeller

declarative destroy-method="..." specifications get validated at bean creation time (SPR-5602)

上级 d14cc0d7
...@@ -512,7 +512,12 @@ public abstract class AbstractAutowireCapableBeanFactory extends AbstractBeanFac ...@@ -512,7 +512,12 @@ public abstract class AbstractAutowireCapableBeanFactory extends AbstractBeanFac
} }
// Register bean as disposable. // Register bean as disposable.
registerDisposableBeanIfNecessary(beanName, bean, mbd); try {
registerDisposableBeanIfNecessary(beanName, bean, mbd);
}
catch (BeanDefinitionValidationException ex) {
throw new BeanCreationException(mbd.getResourceDescription(), beanName, "Invalid destruction signature", ex);
}
return exposedObject; return exposedObject;
} }
...@@ -1387,8 +1392,8 @@ public abstract class AbstractAutowireCapableBeanFactory extends AbstractBeanFac ...@@ -1387,8 +1392,8 @@ public abstract class AbstractAutowireCapableBeanFactory extends AbstractBeanFac
Method initMethod = BeanUtils.findMethod(bean.getClass(), initMethodName, null); Method initMethod = BeanUtils.findMethod(bean.getClass(), initMethodName, null);
if (initMethod == null) { if (initMethod == null) {
if (enforceInitMethod) { if (enforceInitMethod) {
throw new NoSuchMethodException("Couldn't find an init method named '" + initMethodName + throw new BeanDefinitionValidationException("Couldn't find an init method named '" +
"' on bean with name '" + beanName + "'"); initMethodName + "' on bean with name '" + beanName + "'");
} }
else { else {
if (logger.isDebugEnabled()) { if (logger.isDebugEnabled()) {
......
/* /*
* Copyright 2002-2008 the original author or authors. * Copyright 2002-2009 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
...@@ -58,9 +58,9 @@ class DisposableBeanAdapter implements DisposableBean, Runnable, Serializable { ...@@ -58,9 +58,9 @@ class DisposableBeanAdapter implements DisposableBean, Runnable, Serializable {
private final boolean invokeDisposableBean; private final boolean invokeDisposableBean;
private final String destroyMethodName; private String destroyMethodName;
private final boolean enforceDestroyMethod; private transient Method destroyMethod;
private List<DestructionAwareBeanPostProcessor> beanPostProcessors; private List<DestructionAwareBeanPostProcessor> beanPostProcessors;
...@@ -79,11 +79,37 @@ class DisposableBeanAdapter implements DisposableBean, Runnable, Serializable { ...@@ -79,11 +79,37 @@ class DisposableBeanAdapter implements DisposableBean, Runnable, Serializable {
Assert.notNull(bean, "Bean must not be null"); Assert.notNull(bean, "Bean must not be null");
this.bean = bean; this.bean = bean;
this.beanName = beanName; this.beanName = beanName;
this.invokeDisposableBean = !beanDefinition.isExternallyManagedDestroyMethod("destroy"); this.invokeDisposableBean =
this.destroyMethodName = (this.bean instanceof DisposableBean && !beanDefinition.isExternallyManagedDestroyMethod("destroy"));
(!beanDefinition.isExternallyManagedDestroyMethod(beanDefinition.getDestroyMethodName()) ? String destroyMethodName = beanDefinition.getDestroyMethodName();
beanDefinition.getDestroyMethodName() : null); if (destroyMethodName != null && !(this.invokeDisposableBean && "destroy".equals(destroyMethodName)) &&
this.enforceDestroyMethod = beanDefinition.isEnforceDestroyMethod(); !beanDefinition.isExternallyManagedDestroyMethod(destroyMethodName)) {
this.destroyMethodName = destroyMethodName;
try {
this.destroyMethod = BeanUtils.findMethodWithMinimalParameters(bean.getClass(), destroyMethodName);
}
catch (IllegalArgumentException ex) {
throw new BeanDefinitionValidationException("Couldn't find a unique destroy method on bean with name '" +
this.beanName + ": " + ex.getMessage());
}
if (this.destroyMethod == null) {
if (beanDefinition.isEnforceDestroyMethod()) {
throw new BeanDefinitionValidationException("Couldn't find a destroy method named '" +
destroyMethodName + "' on bean with name '" + beanName + "'");
}
}
else {
Class[] paramTypes = this.destroyMethod.getParameterTypes();
if (paramTypes.length > 1) {
throw new BeanDefinitionValidationException("Method '" + destroyMethodName + "' of bean '" +
beanName + "' has more than one parameter - not supported as destroy method");
}
else if (paramTypes.length == 1 && !paramTypes[0].equals(boolean.class)) {
throw new BeanDefinitionValidationException("Method '" + destroyMethodName + "' of bean '" +
beanName + "' has a non-boolean parameter - not supported as destroy method");
}
}
}
this.beanPostProcessors = filterPostProcessors(postProcessors); this.beanPostProcessors = filterPostProcessors(postProcessors);
} }
...@@ -91,23 +117,17 @@ class DisposableBeanAdapter implements DisposableBean, Runnable, Serializable { ...@@ -91,23 +117,17 @@ class DisposableBeanAdapter implements DisposableBean, Runnable, Serializable {
* Create a new DisposableBeanAdapter for the given bean. * Create a new DisposableBeanAdapter for the given bean.
* @param bean the bean instance (never <code>null</code>) * @param bean the bean instance (never <code>null</code>)
* @param beanName the name of the bean * @param beanName the name of the bean
* @param invokeDisposableBean whether to actually invoke * @param invokeDisposableBean whether to actually invoke DisposableBean's destroy method here
* DisposableBean's destroy method here * @param destroyMethodName the name of the custom destroy method (<code>null</code> if there is none)
* @param destroyMethodName the name of the custom destroy method
* (<code>null</code> if there is none)
* @param enforceDestroyMethod whether to the specified custom
* destroy method (if any) has to be present on the bean object
* @param postProcessors the List of DestructionAwareBeanPostProcessors, if any * @param postProcessors the List of DestructionAwareBeanPostProcessors, if any
*/ */
private DisposableBeanAdapter(Object bean, String beanName, boolean invokeDisposableBean, private DisposableBeanAdapter(Object bean, String beanName, boolean invokeDisposableBean,
String destroyMethodName, boolean enforceDestroyMethod, String destroyMethodName, List<DestructionAwareBeanPostProcessor> postProcessors) {
List<DestructionAwareBeanPostProcessor> postProcessors) {
this.bean = bean; this.bean = bean;
this.beanName = beanName; this.beanName = beanName;
this.invokeDisposableBean = invokeDisposableBean; this.invokeDisposableBean = invokeDisposableBean;
this.destroyMethodName = destroyMethodName; this.destroyMethodName = destroyMethodName;
this.enforceDestroyMethod = enforceDestroyMethod;
this.beanPostProcessors = postProcessors; this.beanPostProcessors = postProcessors;
} }
...@@ -142,8 +162,7 @@ class DisposableBeanAdapter implements DisposableBean, Runnable, Serializable { ...@@ -142,8 +162,7 @@ class DisposableBeanAdapter implements DisposableBean, Runnable, Serializable {
} }
} }
boolean isDisposableBean = (this.bean instanceof DisposableBean); if (this.invokeDisposableBean) {
if (isDisposableBean && this.invokeDisposableBean) {
if (logger.isDebugEnabled()) { if (logger.isDebugEnabled()) {
logger.debug("Invoking destroy() on bean with name '" + this.beanName + "'"); logger.debug("Invoking destroy() on bean with name '" + this.beanName + "'");
} }
...@@ -161,8 +180,13 @@ class DisposableBeanAdapter implements DisposableBean, Runnable, Serializable { ...@@ -161,8 +180,13 @@ class DisposableBeanAdapter implements DisposableBean, Runnable, Serializable {
} }
} }
if (this.destroyMethodName != null && !(isDisposableBean && "destroy".equals(this.destroyMethodName))) { if (this.destroyMethod != null) {
invokeCustomDestroyMethod(); invokeCustomDestroyMethod(this.destroyMethod);
}
else if (this.destroyMethodName != null) {
Method destroyMethod =
BeanUtils.findMethodWithMinimalParameters(this.bean.getClass(), this.destroyMethodName);
invokeCustomDestroyMethod(destroyMethod);
} }
} }
...@@ -172,62 +196,33 @@ class DisposableBeanAdapter implements DisposableBean, Runnable, Serializable { ...@@ -172,62 +196,33 @@ class DisposableBeanAdapter implements DisposableBean, Runnable, Serializable {
* for a method with a single boolean argument (passing in "true", * for a method with a single boolean argument (passing in "true",
* assuming a "force" parameter), else logging an error. * assuming a "force" parameter), else logging an error.
*/ */
private void invokeCustomDestroyMethod() { private void invokeCustomDestroyMethod(Method destroyMethod) {
Class[] paramTypes = destroyMethod.getParameterTypes();
Object[] args = new Object[paramTypes.length];
if (paramTypes.length == 1) {
args[0] = Boolean.TRUE;
}
if (logger.isDebugEnabled()) {
logger.debug("Invoking destroy method '" + this.destroyMethodName +
"' on bean with name '" + this.beanName + "'");
}
ReflectionUtils.makeAccessible(destroyMethod);
try { try {
Method destroyMethod = destroyMethod.invoke(this.bean, args);
BeanUtils.findMethodWithMinimalParameters(this.bean.getClass(), this.destroyMethodName); }
if (destroyMethod == null) { catch (InvocationTargetException ex) {
if (this.enforceDestroyMethod) { String msg = "Invocation of destroy method '" + this.destroyMethodName +
logger.warn("Couldn't find a destroy method named '" + this.destroyMethodName + "' failed on bean with name '" + this.beanName + "'";
"' on bean with name '" + this.beanName + "'"); if (logger.isDebugEnabled()) {
} logger.warn(msg, ex.getTargetException());
} }
else { else {
Class[] paramTypes = destroyMethod.getParameterTypes(); logger.warn(msg + ": " + ex.getTargetException());
if (paramTypes.length > 1) {
logger.error("Method '" + this.destroyMethodName + "' of bean '" + this.beanName +
"' has more than one parameter - not supported as destroy method");
}
else if (paramTypes.length == 1 && !paramTypes[0].equals(boolean.class)) {
logger.error("Method '" + this.destroyMethodName + "' of bean '" + this.beanName +
"' has a non-boolean parameter - not supported as destroy method");
}
else {
Object[] args = new Object[paramTypes.length];
if (paramTypes.length == 1) {
args[0] = Boolean.TRUE;
}
if (logger.isDebugEnabled()) {
logger.debug("Invoking destroy method '" + this.destroyMethodName +
"' on bean with name '" + this.beanName + "'");
}
ReflectionUtils.makeAccessible(destroyMethod);
try {
destroyMethod.invoke(this.bean, args);
}
catch (InvocationTargetException ex) {
String msg = "Invocation of destroy method '" + this.destroyMethodName +
"' failed on bean with name '" + this.beanName + "'";
if (logger.isDebugEnabled()) {
logger.warn(msg, ex.getTargetException());
}
else {
logger.warn(msg + ": " + ex.getTargetException());
}
}
catch (Throwable ex) {
logger.error("Couldn't invoke destroy method '" + this.destroyMethodName +
"' on bean with name '" + this.beanName + "'", ex);
}
}
} }
} }
catch (IllegalArgumentException ex) { catch (Throwable ex) {
// thrown from findMethodWithMinimalParameters logger.error("Couldn't invoke destroy method '" + this.destroyMethodName +
logger.error("Couldn't find a unique destroy method on bean with name '" + "' on bean with name '" + this.beanName + "'", ex);
this.beanName + ": " + ex.getMessage());
} }
} }
...@@ -247,7 +242,7 @@ class DisposableBeanAdapter implements DisposableBean, Runnable, Serializable { ...@@ -247,7 +242,7 @@ class DisposableBeanAdapter implements DisposableBean, Runnable, Serializable {
} }
} }
return new DisposableBeanAdapter(this.bean, this.beanName, this.invokeDisposableBean, return new DisposableBeanAdapter(this.bean, this.beanName, this.invokeDisposableBean,
this.destroyMethodName, this.enforceDestroyMethod, serializablePostProcessors); this.destroyMethodName, serializablePostProcessors);
} }
} }
/* /*
* Copyright 2002-2008 the original author or authors. * Copyright 2002-2009 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
...@@ -16,21 +16,20 @@ ...@@ -16,21 +16,20 @@
package org.springframework.beans.factory.xml; package org.springframework.beans.factory.xml;
import static org.junit.Assert.*;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.Properties; import java.util.Properties;
import static org.junit.Assert.*;
import org.junit.Test; import org.junit.Test;
import test.beans.TestBean;
import org.springframework.beans.factory.BeanCreationException; import org.springframework.beans.factory.BeanCreationException;
import org.springframework.beans.factory.BeanDefinitionStoreException; import org.springframework.beans.factory.BeanDefinitionStoreException;
import org.springframework.beans.factory.support.DefaultListableBeanFactory; import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.core.io.ClassPathResource; import org.springframework.core.io.ClassPathResource;
import test.beans.TestBean;
/** /**
* @author Juergen Hoeller * @author Juergen Hoeller
* @author Chris Beams * @author Chris Beams
...@@ -71,6 +70,21 @@ public class FactoryMethodTests { ...@@ -71,6 +70,21 @@ public class FactoryMethodTests {
assertTrue(tb.wasDestroyed()); assertTrue(tb.wasDestroyed());
} }
@Test
public void testFactoryMethodsWithInvalidDestroyMethod() {
DefaultListableBeanFactory xbf = new DefaultListableBeanFactory();
XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(xbf);
reader.loadBeanDefinitions(new ClassPathResource("factory-methods.xml", getClass()));
try {
xbf.getBean("defaultTestBeanWithInvalidDestroyMethod");
fail("Should have thrown BeanCreationException");
}
catch (BeanCreationException ex) {
// expected
}
}
@Test @Test
public void testFactoryMethodsWithNullInstance() { public void testFactoryMethodsWithNullInstance() {
DefaultListableBeanFactory xbf = new DefaultListableBeanFactory(); DefaultListableBeanFactory xbf = new DefaultListableBeanFactory();
......
...@@ -12,6 +12,9 @@ ...@@ -12,6 +12,9 @@
<bean id="defaultTestBean" factory-bean="default" factory-method="getTestBean" <bean id="defaultTestBean" factory-bean="default" factory-method="getTestBean"
init-method="haveBirthday" destroy-method="destroy"/> init-method="haveBirthday" destroy-method="destroy"/>
<bean id="defaultTestBeanWithInvalidDestroyMethod" factory-bean="default" factory-method="getTestBean"
init-method="haveBirthday" destroy-method="xxx" lazy-init="true"/>
<bean id="defaultTestBean.protected" factory-bean="default" factory-method="protectedGetTestBean" <bean id="defaultTestBean.protected" factory-bean="default" factory-method="protectedGetTestBean"
init-method="haveBirthday" destroy-method="destroy"/> init-method="haveBirthday" destroy-method="destroy"/>
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册