Skip to content

Commit c56d1a6

Browse files
committed
Document destruction callback behavior for inner beans in case of scope mismatch
Issue: SPR-13739 (cherry picked from commit 998da2f)
1 parent e6d3c28 commit c56d1a6

File tree

4 files changed

+91
-88
lines changed

4 files changed

+91
-88
lines changed

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1055,7 +1055,7 @@ public void destroyScopedBean(String beanName) {
10551055
String scopeName = mbd.getScope();
10561056
Scope scope = this.scopes.get(scopeName);
10571057
if (scope == null) {
1058-
throw new IllegalStateException("No Scope SPI registered for scope '" + scopeName + "'");
1058+
throw new IllegalStateException("No Scope SPI registered for scope name '" + scopeName + "'");
10591059
}
10601060
Object bean = scope.remove(beanName);
10611061
if (bean != null) {
@@ -1578,7 +1578,7 @@ protected void registerDisposableBeanIfNecessary(String beanName, Object bean, R
15781578
// A bean with a custom scope...
15791579
Scope scope = this.scopes.get(mbd.getScope());
15801580
if (scope == null) {
1581-
throw new IllegalStateException("No Scope registered for scope '" + mbd.getScope() + "'");
1581+
throw new IllegalStateException("No Scope registered for scope name '" + mbd.getScope() + "'");
15821582
}
15831583
scope.registerDestructionCallback(beanName,
15841584
new DisposableBeanAdapter(bean, beanName, mbd, getBeanPostProcessors(), acc));

spring-beans/src/main/resources/org/springframework/beans/factory/xml/spring-beans-4.1.xsd

Lines changed: 27 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -31,8 +31,8 @@
3131
(or an ancestor factory).
3232
3333
As alternative to bean references, "inner bean definitions" can be used.
34-
Singleton flags of such inner bean definitions are effectively ignored:
35-
inner beans are typically anonymous prototypes.
34+
Such inner beans do not have an independent lifecycle; they are typically
35+
anonymous nested objects that share the scope of their containing bean.
3636
3737
There is also support for lists, sets, maps, and java.util.Properties
3838
as bean property types or constructor argument types.
@@ -113,24 +113,29 @@
113113
<xsd:annotation>
114114
<xsd:documentation><![CDATA[
115115
The default 'lazy-init' value; see the documentation for the
116-
'lazy-init' attribute of the 'bean' element.
116+
'lazy-init' attribute of the 'bean' element. The default is "default",
117+
indicating inheritance from outer 'beans' sections in case of nesting,
118+
otherwise falling back to "false".
117119
]]></xsd:documentation>
118120
</xsd:annotation>
119121
</xsd:attribute>
120122
<xsd:attribute name="default-merge" default="default" type="defaultable-boolean">
121123
<xsd:annotation>
122124
<xsd:documentation><![CDATA[
123-
The default 'merge' value; see the documentation for the
124-
'merge' attribute of the various collection elements. The default
125-
is 'false'.
125+
The default 'merge' value; see the documentation for the 'merge'
126+
attribute of the various collection elements. The default is "default",
127+
indicating inheritance from outer 'beans' sections in case of nesting,
128+
otherwise falling back to "false".
126129
]]></xsd:documentation>
127130
</xsd:annotation>
128131
</xsd:attribute>
129132
<xsd:attribute name="default-autowire" default="default">
130133
<xsd:annotation>
131134
<xsd:documentation><![CDATA[
132135
The default 'autowire' value; see the documentation for the
133-
'autowire' attribute of the 'bean' element. The default is 'default'.
136+
'autowire' attribute of the 'bean' element. The default is "default",
137+
indicating inheritance from outer 'beans' sections in case of nesting,
138+
otherwise falling back to "no" (i.e. no externally driven autowiring).
134139
]]></xsd:documentation>
135140
</xsd:annotation>
136141
<xsd:simpleType>
@@ -305,10 +310,10 @@
305310
service objects. Further scopes, such as "request" or "session", might
306311
be supported by extended bean factories (e.g. in a web environment).
307312
308-
Inner bean definitions inherit the singleton status of their containing
309-
bean definition, unless explicitly specified: The inner bean will be a
313+
Inner bean definitions inherit the scope of their containing bean
314+
definition, unless explicitly specified: The inner bean will be a
310315
singleton if the containing bean is a singleton, and a prototype if
311-
the containing bean has any other scope.
316+
the containing bean is a prototype, etc.
312317
]]></xsd:documentation>
313318
</xsd:annotation>
314319
</xsd:attribute>
@@ -328,13 +333,15 @@
328333
<xsd:attribute name="lazy-init" default="default" type="defaultable-boolean">
329334
<xsd:annotation>
330335
<xsd:documentation><![CDATA[
331-
Indicates whether or not this bean is to be lazily initialized.
332-
If false, it will be instantiated on startup by bean factories
333-
that perform eager initialization of singletons. The default is
334-
"false".
336+
Indicates whether this bean is to be lazily initialized. If "false",
337+
it will be instantiated on startup by bean factories that perform eager
338+
initialization of singletons. The effective default is "false".
335339
336340
Note: This attribute will not be inherited by child bean definitions.
337-
Hence, it needs to be specified per concrete bean definition.
341+
Hence, it needs to be specified per concrete bean definition. It can be
342+
shared through the 'default-lazy-init' attribute at the 'beans' level
343+
and potentially inherited from outer 'beans' defaults in case of nested
344+
'beans' sections (e.g. with different profiles).
338345
]]></xsd:documentation>
339346
</xsd:annotation>
340347
</xsd:attribute>
@@ -344,7 +351,7 @@
344351
Controls whether bean properties are "autowired".
345352
This is an automagical process in which bean references don't need
346353
to be coded explicitly in the XML bean definition file, but rather the
347-
Spring container works out dependencies.
354+
Spring container works out dependencies. The effective default is "no".
348355
349356
There are 4 modes:
350357
@@ -379,7 +386,10 @@
379386
elements, always override autowiring.
380387
381388
Note: This attribute will not be inherited by child bean definitions.
382-
Hence, it needs to be specified per concrete bean definition.
389+
Hence, it needs to be specified per concrete bean definition. It can be
390+
shared through the 'default-autowire' attribute at the 'beans' level
391+
and potentially inherited from outer 'beans' defaults in case of nested
392+
'beans' sections (e.g. with different profiles).
383393
]]></xsd:documentation>
384394
</xsd:annotation>
385395
<xsd:simpleType>

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

Lines changed: 51 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -713,7 +713,7 @@ public void testCanReferenceParentBeanFromChildViaAlias() {
713713
RootBeanDefinition parentDefinition = new RootBeanDefinition(TestBean.class);
714714
parentDefinition.setAbstract(true);
715715
parentDefinition.getPropertyValues().add("name", EXPECTED_NAME);
716-
parentDefinition.getPropertyValues().add("age", new Integer(EXPECTED_AGE));
716+
parentDefinition.getPropertyValues().add("age", EXPECTED_AGE);
717717

718718
ChildBeanDefinition childDefinition = new ChildBeanDefinition("alias");
719719

@@ -1201,7 +1201,7 @@ public void testExpressionInStringArray() {
12011201

12021202
RootBeanDefinition rbd = new RootBeanDefinition(PropertiesFactoryBean.class);
12031203
MutablePropertyValues pvs = new MutablePropertyValues();
1204-
pvs.add("locations", new String[] {"#{foo}"});
1204+
pvs.add("locations", new String[]{"#{foo}"});
12051205
rbd.setPropertyValues(pvs);
12061206
bf.registerBeanDefinition("myProperties", rbd);
12071207
Properties properties = (Properties) bf.getBean("myProperties");
@@ -2264,32 +2264,6 @@ public void testPrototypeCreationWithConstructorArgumentsIsFastEnough() {
22642264
assertTrue("Prototype creation took too long: " + sw.getTotalTimeMillis(), sw.getTotalTimeMillis() < 3000);
22652265
}
22662266

2267-
/**
2268-
* @Test
2269-
* public void testPrototypeCreationWithConstructorArgumentsIsFastEnough2() throws Exception {
2270-
* if (factoryLog.isTraceEnabled() || factoryLog.isDebugEnabled()) {
2271-
* // Skip this test: Trace logging blows the time limit.
2272-
* return;
2273-
* }
2274-
* DefaultListableBeanFactory lbf = new DefaultListableBeanFactory();
2275-
* Constructor<TestBean> ctor = TestBean.class.getConstructor(String.class, int.class);
2276-
* Method setBeanNameMethod = TestBean.class.getMethod("setBeanName", String.class);
2277-
* Method setBeanFactoryMethod = TestBean.class.getMethod("setBeanFactory", BeanFactory.class);
2278-
* StopWatch sw = new StopWatch();
2279-
* sw.start("prototype");
2280-
* for (int i = 0; i < 100000; i++) {
2281-
* TestBean tb = ctor.newInstance("juergen", 99);
2282-
* setBeanNameMethod.invoke(tb, "test");
2283-
* setBeanFactoryMethod.invoke(tb, lbf);
2284-
* assertEquals("juergen", tb.getName());
2285-
* assertEquals(99, tb.getAge());
2286-
* }
2287-
* sw.stop();
2288-
* // System.out.println(sw.getTotalTimeMillis());
2289-
* assertTrue("Prototype creation took too long: " + sw.getTotalTimeMillis(), sw.getTotalTimeMillis() < 1500);
2290-
* }
2291-
*/
2292-
22932267
@Test
22942268
public void testPrototypeCreationWithResolvedConstructorArgumentsIsFastEnough() {
22952269
Assume.group(TestGroup.PERFORMANCE);
@@ -2334,31 +2308,6 @@ public void testPrototypeCreationWithPropertiesIsFastEnough() {
23342308
assertTrue("Prototype creation took too long: " + sw.getTotalTimeMillis(), sw.getTotalTimeMillis() < 3000);
23352309
}
23362310

2337-
/**
2338-
* public void testPrototypeCreationWithPropertiesIsFastEnough2() throws Exception {
2339-
* if (factoryLog.isTraceEnabled() || factoryLog.isDebugEnabled()) {
2340-
* // Skip this test: Trace logging blows the time limit.
2341-
* return;
2342-
* }
2343-
* DefaultListableBeanFactory lbf = new DefaultListableBeanFactory();
2344-
* StopWatch sw = new StopWatch();
2345-
* Method setBeanNameMethod = TestBean.class.getMethod("setBeanName", String.class);
2346-
* Method setBeanFactoryMethod = TestBean.class.getMethod("setBeanFactory", BeanFactory.class);
2347-
* Method setNameMethod = TestBean.class.getMethod("setName", String.class);
2348-
* Method setAgeMethod = TestBean.class.getMethod("setAge", int.class);
2349-
* sw.start("prototype");
2350-
* for (int i = 0; i < 100000; i++) {
2351-
* TestBean tb = TestBean.class.newInstance();
2352-
* setBeanNameMethod.invoke(tb, "test");
2353-
* setBeanFactoryMethod.invoke(tb, lbf);
2354-
* setNameMethod.invoke(tb, "juergen");
2355-
* setAgeMethod.invoke(tb, 99);
2356-
* }
2357-
* sw.stop();
2358-
* // System.out.println(sw.getTotalTimeMillis());
2359-
* assertTrue("Prototype creation took too long: " + sw.getTotalTimeMillis(), sw.getTotalTimeMillis() < 750);
2360-
* }
2361-
*/
23622311
@Test
23632312
public void testPrototypeCreationWithResolvedPropertiesIsFastEnough() {
23642313
Assume.group(TestGroup.PERFORMANCE);
@@ -2439,10 +2388,41 @@ public Object postProcessAfterInitialization(Object bean, String beanName) {
24392388
return bean;
24402389
}
24412390
});
2442-
BeanWithDestroyMethod.closed = false;
2391+
BeanWithDestroyMethod.closeCount = 0;
2392+
lbf.preInstantiateSingletons();
2393+
lbf.destroySingletons();
2394+
assertEquals("Destroy methods invoked", 1, BeanWithDestroyMethod.closeCount);
2395+
}
2396+
2397+
@Test
2398+
public void testDestroyMethodOnInnerBean() {
2399+
DefaultListableBeanFactory lbf = new DefaultListableBeanFactory();
2400+
RootBeanDefinition innerBd = new RootBeanDefinition(BeanWithDestroyMethod.class);
2401+
innerBd.setDestroyMethodName("close");
2402+
RootBeanDefinition bd = new RootBeanDefinition(BeanWithDestroyMethod.class);
2403+
bd.setDestroyMethodName("close");
2404+
bd.getPropertyValues().add("inner", innerBd);
2405+
lbf.registerBeanDefinition("test", bd);
2406+
BeanWithDestroyMethod.closeCount = 0;
2407+
lbf.preInstantiateSingletons();
2408+
lbf.destroySingletons();
2409+
assertEquals("Destroy methods invoked", 2, BeanWithDestroyMethod.closeCount);
2410+
}
2411+
2412+
@Test
2413+
public void testDestroyMethodOnInnerBeanAsPrototype() {
2414+
DefaultListableBeanFactory lbf = new DefaultListableBeanFactory();
2415+
RootBeanDefinition innerBd = new RootBeanDefinition(BeanWithDestroyMethod.class);
2416+
innerBd.setScope(RootBeanDefinition.SCOPE_PROTOTYPE);
2417+
innerBd.setDestroyMethodName("close");
2418+
RootBeanDefinition bd = new RootBeanDefinition(BeanWithDestroyMethod.class);
2419+
bd.setDestroyMethodName("close");
2420+
bd.getPropertyValues().add("inner", innerBd);
2421+
lbf.registerBeanDefinition("test", bd);
2422+
BeanWithDestroyMethod.closeCount = 0;
24432423
lbf.preInstantiateSingletons();
24442424
lbf.destroySingletons();
2445-
assertTrue("Destroy method invoked", BeanWithDestroyMethod.closed);
2425+
assertEquals("Destroy methods invoked", 1, BeanWithDestroyMethod.closeCount);
24462426
}
24472427

24482428
@Test
@@ -2679,10 +2659,6 @@ public void resolveEmbeddedValue() throws Exception {
26792659
verify(r3, never()).resolveStringValue(isNull(String.class));
26802660
}
26812661

2682-
2683-
static class A { }
2684-
static class B { }
2685-
26862662
/**
26872663
* Test that by-type bean lookup caching is working effectively by searching for a
26882664
* bean of type B 10K times within a container having 1K additional beans of type A.
@@ -2693,24 +2669,29 @@ static class B { }
26932669
* under the 1000 ms timeout, usually ~= 300ms. With caching removed and on the same
26942670
* hardware the method will take ~13000 ms. See SPR-6870.
26952671
*/
2696-
@Test(timeout=1000)
2672+
@Test(timeout = 1000)
26972673
public void testByTypeLookupIsFastEnough() {
26982674
Assume.group(TestGroup.PERFORMANCE);
26992675
DefaultListableBeanFactory bf = new DefaultListableBeanFactory();
27002676

27012677
for (int i = 0; i < 1000; i++) {
2702-
bf.registerBeanDefinition("a"+i, new RootBeanDefinition(A.class));
2678+
bf.registerBeanDefinition("a" + i, new RootBeanDefinition(A.class));
27032679
}
27042680
bf.registerBeanDefinition("b", new RootBeanDefinition(B.class));
27052681

27062682
bf.freezeConfiguration();
27072683

2708-
for (int i=0; i<10000; i++) {
2684+
for (int i = 0; i < 10000; i++) {
27092685
bf.getBean(B.class);
27102686
}
27112687
}
27122688

27132689

2690+
static class A { }
2691+
2692+
static class B { }
2693+
2694+
27142695
public static class NoDependencies {
27152696

27162697
private NoDependencies() {
@@ -2816,10 +2797,16 @@ public void close() {
28162797

28172798
public static class BeanWithDestroyMethod {
28182799

2819-
private static boolean closed;
2800+
private static int closeCount = 0;
2801+
2802+
private BeanWithDestroyMethod inner;
2803+
2804+
public void setInner(BeanWithDestroyMethod inner) {
2805+
this.inner = inner;
2806+
}
28202807

28212808
public void close() {
2822-
closed = true;
2809+
closeCount++;
28232810
}
28242811
}
28252812

@@ -2981,7 +2968,6 @@ private static class FactoryBeanDependentBean {
29812968

29822969
private FactoryBean<?> factoryBean;
29832970

2984-
29852971
public final FactoryBean<?> getFactoryBean() {
29862972
return this.factoryBean;
29872973
}

src/asciidoc/index.adoc

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2663,10 +2663,17 @@ so-called __inner bean__.
26632663
</bean>
26642664
----
26652665

2666-
An inner bean definition does not require a defined id or name; the container ignores
2667-
these values. It also ignores the `scope` flag. Inner beans are __always__ anonymous and
2668-
they are __always__ created with the outer bean. It is __not__ possible to inject inner
2669-
beans into collaborating beans other than into the enclosing bean.
2666+
An inner bean definition does not require a defined id or name; if specified, the container
2667+
does not use such a value as an identifier. The container also ignores the `scope` flag on
2668+
creation: Inner beans are __always__ anonymous and they are __always__ created with the outer
2669+
bean. It is __not__ possible to inject inner beans into collaborating beans other than into
2670+
the enclosing bean or to access them independently.
2671+
2672+
As a corner case, it is possible to receive destruction callbacks from a custom scope, e.g.
2673+
for a request-scoped inner bean contained within a singleton bean: The creation of the inner
2674+
bean instance will be tied to its containing bean, but destruction callbacks allow it to
2675+
participate in the request scope's lifecycle. This is not a common scenario; inner beans
2676+
typically simply share their containing bean's scope.
26702677

26712678

26722679
[[beans-collection-elements]]

0 commit comments

Comments
 (0)