Browse Source

Avoid Scope.registerDestructionCallback for inner beans in case of scope mismatch

This commit refines getMergedBeanDefinition's scope adaptation algorithm for inner beans, never leaving a custom scope within a containing bean of a different scope. The inner bean's scope will either be aligned with the containing bean's scope (matching the effective state) or be switched to prototype in case of an outer singleton (indicating that no singleton state management is desired).

Issue: SPR-13739
pull/928/merge
Juergen Hoeller 10 years ago
parent
commit
7104076e19
  1. 17
      spring-beans/src/main/java/org/springframework/beans/factory/support/AbstractBeanFactory.java
  2. 82
      spring-beans/src/test/java/org/springframework/beans/factory/DefaultListableBeanFactoryTests.java

17
spring-beans/src/main/java/org/springframework/beans/factory/support/AbstractBeanFactory.java

@ -1062,7 +1062,7 @@ public abstract class AbstractBeanFactory extends FactoryBeanRegistrySupport imp
String scopeName = mbd.getScope(); String scopeName = mbd.getScope();
Scope scope = this.scopes.get(scopeName); Scope scope = this.scopes.get(scopeName);
if (scope == null) { if (scope == null) {
throw new IllegalStateException("No Scope SPI registered for scope '" + scopeName + "'"); throw new IllegalStateException("No Scope SPI registered for scope name '" + scopeName + "'");
} }
Object bean = scope.remove(beanName); Object bean = scope.remove(beanName);
if (bean != null) { if (bean != null) {
@ -1251,15 +1251,14 @@ public abstract class AbstractBeanFactory extends FactoryBeanRegistrySupport imp
// Set default singleton scope, if not configured before. // Set default singleton scope, if not configured before.
if (!StringUtils.hasLength(mbd.getScope())) { if (!StringUtils.hasLength(mbd.getScope())) {
mbd.setScope(RootBeanDefinition.SCOPE_SINGLETON); mbd.setScope(BeanDefinition.SCOPE_SINGLETON);
} }
// A bean contained in a non-singleton bean cannot be a singleton itself. // Check for a mismatch between an inner bean's scope and its containing
// Let's correct this on the fly here, since this might be the result of // bean's scope: For example, a bean contained in a non-singleton bean
// parent-child merging for the outer bean, in which case the original inner bean // cannot be a singleton itself. Let's correct this on the fly here.
// definition will not have inherited the merged outer bean's singleton status. if (containingBd != null && !mbd.isPrototype() && !mbd.getScope().equals(containingBd.getScope())) {
if (containingBd != null && !containingBd.isSingleton() && mbd.isSingleton()) { mbd.setScope(containingBd.isSingleton() ? BeanDefinition.SCOPE_PROTOTYPE : containingBd.getScope());
mbd.setScope(containingBd.getScope());
} }
// Only cache the merged bean definition if we're already about to create an // Only cache the merged bean definition if we're already about to create an
@ -1638,7 +1637,7 @@ public abstract class AbstractBeanFactory extends FactoryBeanRegistrySupport imp
// A bean with a custom scope... // A bean with a custom scope...
Scope scope = this.scopes.get(mbd.getScope()); Scope scope = this.scopes.get(mbd.getScope());
if (scope == null) { if (scope == null) {
throw new IllegalStateException("No Scope registered for scope '" + mbd.getScope() + "'"); throw new IllegalStateException("No Scope registered for scope name '" + mbd.getScope() + "'");
} }
scope.registerDestructionCallback(beanName, scope.registerDestructionCallback(beanName,
new DisposableBeanAdapter(bean, beanName, mbd, getBeanPostProcessors(), acc)); new DisposableBeanAdapter(bean, beanName, mbd, getBeanPostProcessors(), acc));

82
spring-beans/src/test/java/org/springframework/beans/factory/DefaultListableBeanFactoryTests.java

@ -2511,10 +2511,74 @@ public class DefaultListableBeanFactoryTests {
return bean; return bean;
} }
}); });
BeanWithDestroyMethod.closed = false; BeanWithDestroyMethod.closeCount = 0;
lbf.preInstantiateSingletons(); lbf.preInstantiateSingletons();
lbf.destroySingletons(); lbf.destroySingletons();
assertTrue("Destroy method invoked", BeanWithDestroyMethod.closed); assertEquals("Destroy methods invoked", 1, BeanWithDestroyMethod.closeCount);
}
@Test
public void testDestroyMethodOnInnerBean() {
DefaultListableBeanFactory lbf = new DefaultListableBeanFactory();
RootBeanDefinition innerBd = new RootBeanDefinition(BeanWithDestroyMethod.class);
innerBd.setDestroyMethodName("close");
RootBeanDefinition bd = new RootBeanDefinition(BeanWithDestroyMethod.class);
bd.setDestroyMethodName("close");
bd.getPropertyValues().add("inner", innerBd);
lbf.registerBeanDefinition("test", bd);
BeanWithDestroyMethod.closeCount = 0;
lbf.preInstantiateSingletons();
lbf.destroySingletons();
assertEquals("Destroy methods invoked", 2, BeanWithDestroyMethod.closeCount);
}
@Test
public void testDestroyMethodOnInnerBeanAsPrototype() {
DefaultListableBeanFactory lbf = new DefaultListableBeanFactory();
RootBeanDefinition innerBd = new RootBeanDefinition(BeanWithDestroyMethod.class);
innerBd.setScope(RootBeanDefinition.SCOPE_PROTOTYPE);
innerBd.setDestroyMethodName("close");
RootBeanDefinition bd = new RootBeanDefinition(BeanWithDestroyMethod.class);
bd.setDestroyMethodName("close");
bd.getPropertyValues().add("inner", innerBd);
lbf.registerBeanDefinition("test", bd);
BeanWithDestroyMethod.closeCount = 0;
lbf.preInstantiateSingletons();
lbf.destroySingletons();
assertEquals("Destroy methods invoked", 1, BeanWithDestroyMethod.closeCount);
}
@Test
public void testDestroyMethodOnInnerBeanAsCustomScope() {
DefaultListableBeanFactory lbf = new DefaultListableBeanFactory();
RootBeanDefinition innerBd = new RootBeanDefinition(BeanWithDestroyMethod.class);
innerBd.setScope("custom");
innerBd.setDestroyMethodName("close");
RootBeanDefinition bd = new RootBeanDefinition(BeanWithDestroyMethod.class);
bd.setDestroyMethodName("close");
bd.getPropertyValues().add("inner", innerBd);
lbf.registerBeanDefinition("test", bd);
BeanWithDestroyMethod.closeCount = 0;
lbf.preInstantiateSingletons();
lbf.destroySingletons();
assertEquals("Destroy methods not invoked", 1, BeanWithDestroyMethod.closeCount);
}
@Test
public void testDestroyMethodOnInnerBeanAsCustomScopeWithinPrototype() {
DefaultListableBeanFactory lbf = new DefaultListableBeanFactory();
RootBeanDefinition innerBd = new RootBeanDefinition(BeanWithDestroyMethod.class);
innerBd.setScope("custom");
innerBd.setDestroyMethodName("close");
RootBeanDefinition bd = new RootBeanDefinition(BeanWithDestroyMethod.class);
bd.setScope(RootBeanDefinition.SCOPE_PROTOTYPE);
bd.setDestroyMethodName("close");
bd.getPropertyValues().add("inner", innerBd);
lbf.registerBeanDefinition("test", bd);
BeanWithDestroyMethod.closeCount = 0;
Object prototypeInstance = lbf.getBean("test");
lbf.destroyBean("test", prototypeInstance);
assertEquals("Destroy methods not invoked", 1, BeanWithDestroyMethod.closeCount);
} }
@Test @Test
@ -2780,7 +2844,7 @@ public class DefaultListableBeanFactoryTests {
@Test(timeout = 1000) @Test(timeout = 1000)
public void testRegistrationOfManyBeanDefinitionsIsFastEnough() { public void testRegistrationOfManyBeanDefinitionsIsFastEnough() {
// Assume.group(TestGroup.PERFORMANCE); Assume.group(TestGroup.PERFORMANCE);
DefaultListableBeanFactory bf = new DefaultListableBeanFactory(); DefaultListableBeanFactory bf = new DefaultListableBeanFactory();
bf.registerBeanDefinition("b", new RootBeanDefinition(B.class)); bf.registerBeanDefinition("b", new RootBeanDefinition(B.class));
// bf.getBean("b"); // bf.getBean("b");
@ -2792,7 +2856,7 @@ public class DefaultListableBeanFactoryTests {
@Test(timeout = 1000) @Test(timeout = 1000)
public void testRegistrationOfManySingletonsIsFastEnough() { public void testRegistrationOfManySingletonsIsFastEnough() {
// Assume.group(TestGroup.PERFORMANCE); Assume.group(TestGroup.PERFORMANCE);
DefaultListableBeanFactory bf = new DefaultListableBeanFactory(); DefaultListableBeanFactory bf = new DefaultListableBeanFactory();
bf.registerBeanDefinition("b", new RootBeanDefinition(B.class)); bf.registerBeanDefinition("b", new RootBeanDefinition(B.class));
// bf.getBean("b"); // bf.getBean("b");
@ -2913,10 +2977,16 @@ public class DefaultListableBeanFactoryTests {
public static class BeanWithDestroyMethod { public static class BeanWithDestroyMethod {
private static boolean closed; private static int closeCount = 0;
private BeanWithDestroyMethod inner;
public void setInner(BeanWithDestroyMethod inner) {
this.inner = inner;
}
public void close() { public void close() {
closed = true; closeCount++;
} }
} }

Loading…
Cancel
Save