Browse Source

Document destruction callback behavior for inner beans in case of scope mismatch

Issue: SPR-13739
(cherry picked from commit 998da2f)
pull/931/head
Juergen Hoeller 10 years ago
parent
commit
c56d1a6677
  1. 4
      spring-beans/src/main/java/org/springframework/beans/factory/support/AbstractBeanFactory.java
  2. 44
      spring-beans/src/main/resources/org/springframework/beans/factory/xml/spring-beans-4.1.xsd
  3. 116
      spring-beans/src/test/java/org/springframework/beans/factory/DefaultListableBeanFactoryTests.java
  4. 15
      src/asciidoc/index.adoc

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

@ -1055,7 +1055,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) {
@ -1578,7 +1578,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));

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

@ -31,8 +31,8 @@
(or an ancestor factory). (or an ancestor factory).
As alternative to bean references, "inner bean definitions" can be used. As alternative to bean references, "inner bean definitions" can be used.
Singleton flags of such inner bean definitions are effectively ignored: Such inner beans do not have an independent lifecycle; they are typically
inner beans are typically anonymous prototypes. anonymous nested objects that share the scope of their containing bean.
There is also support for lists, sets, maps, and java.util.Properties There is also support for lists, sets, maps, and java.util.Properties
as bean property types or constructor argument types. as bean property types or constructor argument types.
@ -113,16 +113,19 @@
<xsd:annotation> <xsd:annotation>
<xsd:documentation><![CDATA[ <xsd:documentation><![CDATA[
The default 'lazy-init' value; see the documentation for the The default 'lazy-init' value; see the documentation for the
'lazy-init' attribute of the 'bean' element. 'lazy-init' attribute of the 'bean' element. The default is "default",
indicating inheritance from outer 'beans' sections in case of nesting,
otherwise falling back to "false".
]]></xsd:documentation> ]]></xsd:documentation>
</xsd:annotation> </xsd:annotation>
</xsd:attribute> </xsd:attribute>
<xsd:attribute name="default-merge" default="default" type="defaultable-boolean"> <xsd:attribute name="default-merge" default="default" type="defaultable-boolean">
<xsd:annotation> <xsd:annotation>
<xsd:documentation><![CDATA[ <xsd:documentation><![CDATA[
The default 'merge' value; see the documentation for the The default 'merge' value; see the documentation for the 'merge'
'merge' attribute of the various collection elements. The default attribute of the various collection elements. The default is "default",
is 'false'. indicating inheritance from outer 'beans' sections in case of nesting,
otherwise falling back to "false".
]]></xsd:documentation> ]]></xsd:documentation>
</xsd:annotation> </xsd:annotation>
</xsd:attribute> </xsd:attribute>
@ -130,7 +133,9 @@
<xsd:annotation> <xsd:annotation>
<xsd:documentation><![CDATA[ <xsd:documentation><![CDATA[
The default 'autowire' value; see the documentation for the The default 'autowire' value; see the documentation for the
'autowire' attribute of the 'bean' element. The default is 'default'. 'autowire' attribute of the 'bean' element. The default is "default",
indicating inheritance from outer 'beans' sections in case of nesting,
otherwise falling back to "no" (i.e. no externally driven autowiring).
]]></xsd:documentation> ]]></xsd:documentation>
</xsd:annotation> </xsd:annotation>
<xsd:simpleType> <xsd:simpleType>
@ -305,10 +310,10 @@
service objects. Further scopes, such as "request" or "session", might service objects. Further scopes, such as "request" or "session", might
be supported by extended bean factories (e.g. in a web environment). be supported by extended bean factories (e.g. in a web environment).
Inner bean definitions inherit the singleton status of their containing Inner bean definitions inherit the scope of their containing bean
bean definition, unless explicitly specified: The inner bean will be a definition, unless explicitly specified: The inner bean will be a
singleton if the containing bean is a singleton, and a prototype if singleton if the containing bean is a singleton, and a prototype if
the containing bean has any other scope. the containing bean is a prototype, etc.
]]></xsd:documentation> ]]></xsd:documentation>
</xsd:annotation> </xsd:annotation>
</xsd:attribute> </xsd:attribute>
@ -328,13 +333,15 @@
<xsd:attribute name="lazy-init" default="default" type="defaultable-boolean"> <xsd:attribute name="lazy-init" default="default" type="defaultable-boolean">
<xsd:annotation> <xsd:annotation>
<xsd:documentation><![CDATA[ <xsd:documentation><![CDATA[
Indicates whether or not this bean is to be lazily initialized. Indicates whether this bean is to be lazily initialized. If "false",
If false, it will be instantiated on startup by bean factories it will be instantiated on startup by bean factories that perform eager
that perform eager initialization of singletons. The default is initialization of singletons. The effective default is "false".
"false".
Note: This attribute will not be inherited by child bean definitions. Note: This attribute will not be inherited by child bean definitions.
Hence, it needs to be specified per concrete bean definition. Hence, it needs to be specified per concrete bean definition. It can be
shared through the 'default-lazy-init' attribute at the 'beans' level
and potentially inherited from outer 'beans' defaults in case of nested
'beans' sections (e.g. with different profiles).
]]></xsd:documentation> ]]></xsd:documentation>
</xsd:annotation> </xsd:annotation>
</xsd:attribute> </xsd:attribute>
@ -344,7 +351,7 @@
Controls whether bean properties are "autowired". Controls whether bean properties are "autowired".
This is an automagical process in which bean references don't need This is an automagical process in which bean references don't need
to be coded explicitly in the XML bean definition file, but rather the to be coded explicitly in the XML bean definition file, but rather the
Spring container works out dependencies. Spring container works out dependencies. The effective default is "no".
There are 4 modes: There are 4 modes:
@ -379,7 +386,10 @@
elements, always override autowiring. elements, always override autowiring.
Note: This attribute will not be inherited by child bean definitions. Note: This attribute will not be inherited by child bean definitions.
Hence, it needs to be specified per concrete bean definition. Hence, it needs to be specified per concrete bean definition. It can be
shared through the 'default-autowire' attribute at the 'beans' level
and potentially inherited from outer 'beans' defaults in case of nested
'beans' sections (e.g. with different profiles).
]]></xsd:documentation> ]]></xsd:documentation>
</xsd:annotation> </xsd:annotation>
<xsd:simpleType> <xsd:simpleType>

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

@ -713,7 +713,7 @@ public class DefaultListableBeanFactoryTests {
RootBeanDefinition parentDefinition = new RootBeanDefinition(TestBean.class); RootBeanDefinition parentDefinition = new RootBeanDefinition(TestBean.class);
parentDefinition.setAbstract(true); parentDefinition.setAbstract(true);
parentDefinition.getPropertyValues().add("name", EXPECTED_NAME); parentDefinition.getPropertyValues().add("name", EXPECTED_NAME);
parentDefinition.getPropertyValues().add("age", new Integer(EXPECTED_AGE)); parentDefinition.getPropertyValues().add("age", EXPECTED_AGE);
ChildBeanDefinition childDefinition = new ChildBeanDefinition("alias"); ChildBeanDefinition childDefinition = new ChildBeanDefinition("alias");
@ -1201,7 +1201,7 @@ public class DefaultListableBeanFactoryTests {
RootBeanDefinition rbd = new RootBeanDefinition(PropertiesFactoryBean.class); RootBeanDefinition rbd = new RootBeanDefinition(PropertiesFactoryBean.class);
MutablePropertyValues pvs = new MutablePropertyValues(); MutablePropertyValues pvs = new MutablePropertyValues();
pvs.add("locations", new String[] {"#{foo}"}); pvs.add("locations", new String[]{"#{foo}"});
rbd.setPropertyValues(pvs); rbd.setPropertyValues(pvs);
bf.registerBeanDefinition("myProperties", rbd); bf.registerBeanDefinition("myProperties", rbd);
Properties properties = (Properties) bf.getBean("myProperties"); Properties properties = (Properties) bf.getBean("myProperties");
@ -2264,32 +2264,6 @@ public class DefaultListableBeanFactoryTests {
assertTrue("Prototype creation took too long: " + sw.getTotalTimeMillis(), sw.getTotalTimeMillis() < 3000); assertTrue("Prototype creation took too long: " + sw.getTotalTimeMillis(), sw.getTotalTimeMillis() < 3000);
} }
/**
* @Test
* public void testPrototypeCreationWithConstructorArgumentsIsFastEnough2() throws Exception {
* if (factoryLog.isTraceEnabled() || factoryLog.isDebugEnabled()) {
* // Skip this test: Trace logging blows the time limit.
* return;
* }
* DefaultListableBeanFactory lbf = new DefaultListableBeanFactory();
* Constructor<TestBean> ctor = TestBean.class.getConstructor(String.class, int.class);
* Method setBeanNameMethod = TestBean.class.getMethod("setBeanName", String.class);
* Method setBeanFactoryMethod = TestBean.class.getMethod("setBeanFactory", BeanFactory.class);
* StopWatch sw = new StopWatch();
* sw.start("prototype");
* for (int i = 0; i < 100000; i++) {
* TestBean tb = ctor.newInstance("juergen", 99);
* setBeanNameMethod.invoke(tb, "test");
* setBeanFactoryMethod.invoke(tb, lbf);
* assertEquals("juergen", tb.getName());
* assertEquals(99, tb.getAge());
* }
* sw.stop();
* // System.out.println(sw.getTotalTimeMillis());
* assertTrue("Prototype creation took too long: " + sw.getTotalTimeMillis(), sw.getTotalTimeMillis() < 1500);
* }
*/
@Test @Test
public void testPrototypeCreationWithResolvedConstructorArgumentsIsFastEnough() { public void testPrototypeCreationWithResolvedConstructorArgumentsIsFastEnough() {
Assume.group(TestGroup.PERFORMANCE); Assume.group(TestGroup.PERFORMANCE);
@ -2334,31 +2308,6 @@ public class DefaultListableBeanFactoryTests {
assertTrue("Prototype creation took too long: " + sw.getTotalTimeMillis(), sw.getTotalTimeMillis() < 3000); assertTrue("Prototype creation took too long: " + sw.getTotalTimeMillis(), sw.getTotalTimeMillis() < 3000);
} }
/**
* public void testPrototypeCreationWithPropertiesIsFastEnough2() throws Exception {
* if (factoryLog.isTraceEnabled() || factoryLog.isDebugEnabled()) {
* // Skip this test: Trace logging blows the time limit.
* return;
* }
* DefaultListableBeanFactory lbf = new DefaultListableBeanFactory();
* StopWatch sw = new StopWatch();
* Method setBeanNameMethod = TestBean.class.getMethod("setBeanName", String.class);
* Method setBeanFactoryMethod = TestBean.class.getMethod("setBeanFactory", BeanFactory.class);
* Method setNameMethod = TestBean.class.getMethod("setName", String.class);
* Method setAgeMethod = TestBean.class.getMethod("setAge", int.class);
* sw.start("prototype");
* for (int i = 0; i < 100000; i++) {
* TestBean tb = TestBean.class.newInstance();
* setBeanNameMethod.invoke(tb, "test");
* setBeanFactoryMethod.invoke(tb, lbf);
* setNameMethod.invoke(tb, "juergen");
* setAgeMethod.invoke(tb, 99);
* }
* sw.stop();
* // System.out.println(sw.getTotalTimeMillis());
* assertTrue("Prototype creation took too long: " + sw.getTotalTimeMillis(), sw.getTotalTimeMillis() < 750);
* }
*/
@Test @Test
public void testPrototypeCreationWithResolvedPropertiesIsFastEnough() { public void testPrototypeCreationWithResolvedPropertiesIsFastEnough() {
Assume.group(TestGroup.PERFORMANCE); Assume.group(TestGroup.PERFORMANCE);
@ -2439,10 +2388,41 @@ public class DefaultListableBeanFactoryTests {
return bean; return bean;
} }
}); });
BeanWithDestroyMethod.closed = false; BeanWithDestroyMethod.closeCount = 0;
lbf.preInstantiateSingletons();
lbf.destroySingletons();
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.preInstantiateSingletons();
lbf.destroySingletons(); lbf.destroySingletons();
assertTrue("Destroy method invoked", BeanWithDestroyMethod.closed); assertEquals("Destroy methods invoked", 1, BeanWithDestroyMethod.closeCount);
} }
@Test @Test
@ -2679,10 +2659,6 @@ public class DefaultListableBeanFactoryTests {
verify(r3, never()).resolveStringValue(isNull(String.class)); verify(r3, never()).resolveStringValue(isNull(String.class));
} }
static class A { }
static class B { }
/** /**
* Test that by-type bean lookup caching is working effectively by searching for a * Test that by-type bean lookup caching is working effectively by searching for a
* bean of type B 10K times within a container having 1K additional beans of type A. * bean of type B 10K times within a container having 1K additional beans of type A.
@ -2693,24 +2669,29 @@ public class DefaultListableBeanFactoryTests {
* under the 1000 ms timeout, usually ~= 300ms. With caching removed and on the same * under the 1000 ms timeout, usually ~= 300ms. With caching removed and on the same
* hardware the method will take ~13000 ms. See SPR-6870. * hardware the method will take ~13000 ms. See SPR-6870.
*/ */
@Test(timeout=1000) @Test(timeout = 1000)
public void testByTypeLookupIsFastEnough() { public void testByTypeLookupIsFastEnough() {
Assume.group(TestGroup.PERFORMANCE); Assume.group(TestGroup.PERFORMANCE);
DefaultListableBeanFactory bf = new DefaultListableBeanFactory(); DefaultListableBeanFactory bf = new DefaultListableBeanFactory();
for (int i = 0; i < 1000; i++) { for (int i = 0; i < 1000; i++) {
bf.registerBeanDefinition("a"+i, new RootBeanDefinition(A.class)); bf.registerBeanDefinition("a" + i, new RootBeanDefinition(A.class));
} }
bf.registerBeanDefinition("b", new RootBeanDefinition(B.class)); bf.registerBeanDefinition("b", new RootBeanDefinition(B.class));
bf.freezeConfiguration(); bf.freezeConfiguration();
for (int i=0; i<10000; i++) { for (int i = 0; i < 10000; i++) {
bf.getBean(B.class); bf.getBean(B.class);
} }
} }
static class A { }
static class B { }
public static class NoDependencies { public static class NoDependencies {
private NoDependencies() { private NoDependencies() {
@ -2816,10 +2797,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++;
} }
} }
@ -2981,7 +2968,6 @@ public class DefaultListableBeanFactoryTests {
private FactoryBean<?> factoryBean; private FactoryBean<?> factoryBean;
public final FactoryBean<?> getFactoryBean() { public final FactoryBean<?> getFactoryBean() {
return this.factoryBean; return this.factoryBean;
} }

15
src/asciidoc/index.adoc

@ -2663,10 +2663,17 @@ so-called __inner bean__.
</bean> </bean>
---- ----
An inner bean definition does not require a defined id or name; the container ignores An inner bean definition does not require a defined id or name; if specified, the container
these values. It also ignores the `scope` flag. Inner beans are __always__ anonymous and does not use such a value as an identifier. The container also ignores the `scope` flag on
they are __always__ created with the outer bean. It is __not__ possible to inject inner creation: Inner beans are __always__ anonymous and they are __always__ created with the outer
beans into collaborating beans other than into the enclosing bean. bean. It is __not__ possible to inject inner beans into collaborating beans other than into
the enclosing bean or to access them independently.
As a corner case, it is possible to receive destruction callbacks from a custom scope, e.g.
for a request-scoped inner bean contained within a singleton bean: The creation of the inner
bean instance will be tied to its containing bean, but destruction callbacks allow it to
participate in the request scope's lifecycle. This is not a common scenario; inner beans
typically simply share their containing bean's scope.
[[beans-collection-elements]] [[beans-collection-elements]]

Loading…
Cancel
Save