Skip to content

Commit 7104076

Browse files
committed
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
1 parent 4755467 commit 7104076

File tree

2 files changed

+84
-15
lines changed

2 files changed

+84
-15
lines changed

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

Lines changed: 8 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1062,7 +1062,7 @@ public void destroyScopedBean(String beanName) {
10621062
String scopeName = mbd.getScope();
10631063
Scope scope = this.scopes.get(scopeName);
10641064
if (scope == null) {
1065-
throw new IllegalStateException("No Scope SPI registered for scope '" + scopeName + "'");
1065+
throw new IllegalStateException("No Scope SPI registered for scope name '" + scopeName + "'");
10661066
}
10671067
Object bean = scope.remove(beanName);
10681068
if (bean != null) {
@@ -1251,15 +1251,14 @@ protected RootBeanDefinition getMergedBeanDefinition(
12511251

12521252
// Set default singleton scope, if not configured before.
12531253
if (!StringUtils.hasLength(mbd.getScope())) {
1254-
mbd.setScope(RootBeanDefinition.SCOPE_SINGLETON);
1254+
mbd.setScope(BeanDefinition.SCOPE_SINGLETON);
12551255
}
12561256

1257-
// A bean contained in a non-singleton bean cannot be a singleton itself.
1258-
// Let's correct this on the fly here, since this might be the result of
1259-
// parent-child merging for the outer bean, in which case the original inner bean
1260-
// definition will not have inherited the merged outer bean's singleton status.
1261-
if (containingBd != null && !containingBd.isSingleton() && mbd.isSingleton()) {
1262-
mbd.setScope(containingBd.getScope());
1257+
// Check for a mismatch between an inner bean's scope and its containing
1258+
// bean's scope: For example, a bean contained in a non-singleton bean
1259+
// cannot be a singleton itself. Let's correct this on the fly here.
1260+
if (containingBd != null && !mbd.isPrototype() && !mbd.getScope().equals(containingBd.getScope())) {
1261+
mbd.setScope(containingBd.isSingleton() ? BeanDefinition.SCOPE_PROTOTYPE : containingBd.getScope());
12631262
}
12641263

12651264
// Only cache the merged bean definition if we're already about to create an
@@ -1638,7 +1637,7 @@ protected void registerDisposableBeanIfNecessary(String beanName, Object bean, R
16381637
// A bean with a custom scope...
16391638
Scope scope = this.scopes.get(mbd.getScope());
16401639
if (scope == null) {
1641-
throw new IllegalStateException("No Scope registered for scope '" + mbd.getScope() + "'");
1640+
throw new IllegalStateException("No Scope registered for scope name '" + mbd.getScope() + "'");
16421641
}
16431642
scope.registerDestructionCallback(beanName,
16441643
new DisposableBeanAdapter(bean, beanName, mbd, getBeanPostProcessors(), acc));

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

Lines changed: 76 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2511,10 +2511,74 @@ public Object postProcessAfterInitialization(Object bean, String beanName) {
25112511
return bean;
25122512
}
25132513
});
2514-
BeanWithDestroyMethod.closed = false;
2514+
BeanWithDestroyMethod.closeCount = 0;
25152515
lbf.preInstantiateSingletons();
25162516
lbf.destroySingletons();
2517-
assertTrue("Destroy method invoked", BeanWithDestroyMethod.closed);
2517+
assertEquals("Destroy methods invoked", 1, BeanWithDestroyMethod.closeCount);
2518+
}
2519+
2520+
@Test
2521+
public void testDestroyMethodOnInnerBean() {
2522+
DefaultListableBeanFactory lbf = new DefaultListableBeanFactory();
2523+
RootBeanDefinition innerBd = new RootBeanDefinition(BeanWithDestroyMethod.class);
2524+
innerBd.setDestroyMethodName("close");
2525+
RootBeanDefinition bd = new RootBeanDefinition(BeanWithDestroyMethod.class);
2526+
bd.setDestroyMethodName("close");
2527+
bd.getPropertyValues().add("inner", innerBd);
2528+
lbf.registerBeanDefinition("test", bd);
2529+
BeanWithDestroyMethod.closeCount = 0;
2530+
lbf.preInstantiateSingletons();
2531+
lbf.destroySingletons();
2532+
assertEquals("Destroy methods invoked", 2, BeanWithDestroyMethod.closeCount);
2533+
}
2534+
2535+
@Test
2536+
public void testDestroyMethodOnInnerBeanAsPrototype() {
2537+
DefaultListableBeanFactory lbf = new DefaultListableBeanFactory();
2538+
RootBeanDefinition innerBd = new RootBeanDefinition(BeanWithDestroyMethod.class);
2539+
innerBd.setScope(RootBeanDefinition.SCOPE_PROTOTYPE);
2540+
innerBd.setDestroyMethodName("close");
2541+
RootBeanDefinition bd = new RootBeanDefinition(BeanWithDestroyMethod.class);
2542+
bd.setDestroyMethodName("close");
2543+
bd.getPropertyValues().add("inner", innerBd);
2544+
lbf.registerBeanDefinition("test", bd);
2545+
BeanWithDestroyMethod.closeCount = 0;
2546+
lbf.preInstantiateSingletons();
2547+
lbf.destroySingletons();
2548+
assertEquals("Destroy methods invoked", 1, BeanWithDestroyMethod.closeCount);
2549+
}
2550+
2551+
@Test
2552+
public void testDestroyMethodOnInnerBeanAsCustomScope() {
2553+
DefaultListableBeanFactory lbf = new DefaultListableBeanFactory();
2554+
RootBeanDefinition innerBd = new RootBeanDefinition(BeanWithDestroyMethod.class);
2555+
innerBd.setScope("custom");
2556+
innerBd.setDestroyMethodName("close");
2557+
RootBeanDefinition bd = new RootBeanDefinition(BeanWithDestroyMethod.class);
2558+
bd.setDestroyMethodName("close");
2559+
bd.getPropertyValues().add("inner", innerBd);
2560+
lbf.registerBeanDefinition("test", bd);
2561+
BeanWithDestroyMethod.closeCount = 0;
2562+
lbf.preInstantiateSingletons();
2563+
lbf.destroySingletons();
2564+
assertEquals("Destroy methods not invoked", 1, BeanWithDestroyMethod.closeCount);
2565+
}
2566+
2567+
@Test
2568+
public void testDestroyMethodOnInnerBeanAsCustomScopeWithinPrototype() {
2569+
DefaultListableBeanFactory lbf = new DefaultListableBeanFactory();
2570+
RootBeanDefinition innerBd = new RootBeanDefinition(BeanWithDestroyMethod.class);
2571+
innerBd.setScope("custom");
2572+
innerBd.setDestroyMethodName("close");
2573+
RootBeanDefinition bd = new RootBeanDefinition(BeanWithDestroyMethod.class);
2574+
bd.setScope(RootBeanDefinition.SCOPE_PROTOTYPE);
2575+
bd.setDestroyMethodName("close");
2576+
bd.getPropertyValues().add("inner", innerBd);
2577+
lbf.registerBeanDefinition("test", bd);
2578+
BeanWithDestroyMethod.closeCount = 0;
2579+
Object prototypeInstance = lbf.getBean("test");
2580+
lbf.destroyBean("test", prototypeInstance);
2581+
assertEquals("Destroy methods not invoked", 1, BeanWithDestroyMethod.closeCount);
25182582
}
25192583

25202584
@Test
@@ -2780,7 +2844,7 @@ public void testByTypeLookupIsFastEnough() {
27802844

27812845
@Test(timeout = 1000)
27822846
public void testRegistrationOfManyBeanDefinitionsIsFastEnough() {
2783-
// Assume.group(TestGroup.PERFORMANCE);
2847+
Assume.group(TestGroup.PERFORMANCE);
27842848
DefaultListableBeanFactory bf = new DefaultListableBeanFactory();
27852849
bf.registerBeanDefinition("b", new RootBeanDefinition(B.class));
27862850
// bf.getBean("b");
@@ -2792,7 +2856,7 @@ public void testRegistrationOfManyBeanDefinitionsIsFastEnough() {
27922856

27932857
@Test(timeout = 1000)
27942858
public void testRegistrationOfManySingletonsIsFastEnough() {
2795-
// Assume.group(TestGroup.PERFORMANCE);
2859+
Assume.group(TestGroup.PERFORMANCE);
27962860
DefaultListableBeanFactory bf = new DefaultListableBeanFactory();
27972861
bf.registerBeanDefinition("b", new RootBeanDefinition(B.class));
27982862
// bf.getBean("b");
@@ -2913,10 +2977,16 @@ public void close() {
29132977

29142978
public static class BeanWithDestroyMethod {
29152979

2916-
private static boolean closed;
2980+
private static int closeCount = 0;
2981+
2982+
private BeanWithDestroyMethod inner;
2983+
2984+
public void setInner(BeanWithDestroyMethod inner) {
2985+
this.inner = inner;
2986+
}
29172987

29182988
public void close() {
2919-
closed = true;
2989+
closeCount++;
29202990
}
29212991
}
29222992

0 commit comments

Comments
 (0)