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 @@ -1055,7 +1055,7 @@ public abstract class AbstractBeanFactory extends FactoryBeanRegistrySupport imp
String scopeName = mbd.getScope();
Scope scope = this.scopes.get(scopeName);
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);
if (bean != null) {
@ -1578,7 +1578,7 @@ public abstract class AbstractBeanFactory extends FactoryBeanRegistrySupport imp @@ -1578,7 +1578,7 @@ public abstract class AbstractBeanFactory extends FactoryBeanRegistrySupport imp
// A bean with a custom scope...
Scope scope = this.scopes.get(mbd.getScope());
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,
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 @@ @@ -31,8 +31,8 @@
(or an ancestor factory).
As alternative to bean references, "inner bean definitions" can be used.
Singleton flags of such inner bean definitions are effectively ignored:
inner beans are typically anonymous prototypes.
Such inner beans do not have an independent lifecycle; they are typically
anonymous nested objects that share the scope of their containing bean.
There is also support for lists, sets, maps, and java.util.Properties
as bean property types or constructor argument types.
@ -113,16 +113,19 @@ @@ -113,16 +113,19 @@
<xsd:annotation>
<xsd:documentation><![CDATA[
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:annotation>
</xsd:attribute>
<xsd:attribute name="default-merge" default="default" type="defaultable-boolean">
<xsd:annotation>
<xsd:documentation><![CDATA[
The default 'merge' value; see the documentation for the
'merge' attribute of the various collection elements. The default
is 'false'.
The default 'merge' value; see the documentation for the 'merge'
attribute of the various collection elements. The default is "default",
indicating inheritance from outer 'beans' sections in case of nesting,
otherwise falling back to "false".
]]></xsd:documentation>
</xsd:annotation>
</xsd:attribute>
@ -130,7 +133,9 @@ @@ -130,7 +133,9 @@
<xsd:annotation>
<xsd:documentation><![CDATA[
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:annotation>
<xsd:simpleType>
@ -305,10 +310,10 @@ @@ -305,10 +310,10 @@
service objects. Further scopes, such as "request" or "session", might
be supported by extended bean factories (e.g. in a web environment).
Inner bean definitions inherit the singleton status of their containing
bean definition, unless explicitly specified: The inner bean will be a
Inner bean definitions inherit the scope of their containing bean
definition, unless explicitly specified: The inner bean will be a
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:annotation>
</xsd:attribute>
@ -328,13 +333,15 @@ @@ -328,13 +333,15 @@
<xsd:attribute name="lazy-init" default="default" type="defaultable-boolean">
<xsd:annotation>
<xsd:documentation><![CDATA[
Indicates whether or not this bean is to be lazily initialized.
If false, it will be instantiated on startup by bean factories
that perform eager initialization of singletons. The default is
"false".
Indicates whether this bean is to be lazily initialized. If "false",
it will be instantiated on startup by bean factories that perform eager
initialization of singletons. The effective default is "false".
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:annotation>
</xsd:attribute>
@ -344,7 +351,7 @@ @@ -344,7 +351,7 @@
Controls whether bean properties are "autowired".
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
Spring container works out dependencies.
Spring container works out dependencies. The effective default is "no".
There are 4 modes:
@ -379,7 +386,10 @@ @@ -379,7 +386,10 @@
elements, always override autowiring.
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:annotation>
<xsd:simpleType>

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

@ -713,7 +713,7 @@ public class DefaultListableBeanFactoryTests { @@ -713,7 +713,7 @@ public class DefaultListableBeanFactoryTests {
RootBeanDefinition parentDefinition = new RootBeanDefinition(TestBean.class);
parentDefinition.setAbstract(true);
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");
@ -1201,7 +1201,7 @@ public class DefaultListableBeanFactoryTests { @@ -1201,7 +1201,7 @@ public class DefaultListableBeanFactoryTests {
RootBeanDefinition rbd = new RootBeanDefinition(PropertiesFactoryBean.class);
MutablePropertyValues pvs = new MutablePropertyValues();
pvs.add("locations", new String[] {"#{foo}"});
pvs.add("locations", new String[]{"#{foo}"});
rbd.setPropertyValues(pvs);
bf.registerBeanDefinition("myProperties", rbd);
Properties properties = (Properties) bf.getBean("myProperties");
@ -2264,32 +2264,6 @@ public class DefaultListableBeanFactoryTests { @@ -2264,32 +2264,6 @@ public class DefaultListableBeanFactoryTests {
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
public void testPrototypeCreationWithResolvedConstructorArgumentsIsFastEnough() {
Assume.group(TestGroup.PERFORMANCE);
@ -2334,31 +2308,6 @@ public class DefaultListableBeanFactoryTests { @@ -2334,31 +2308,6 @@ public class DefaultListableBeanFactoryTests {
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
public void testPrototypeCreationWithResolvedPropertiesIsFastEnough() {
Assume.group(TestGroup.PERFORMANCE);
@ -2439,10 +2388,41 @@ public class DefaultListableBeanFactoryTests { @@ -2439,10 +2388,41 @@ public class DefaultListableBeanFactoryTests {
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.destroySingletons();
assertTrue("Destroy method invoked", BeanWithDestroyMethod.closed);
assertEquals("Destroy methods invoked", 1, BeanWithDestroyMethod.closeCount);
}
@Test
@ -2679,10 +2659,6 @@ public class DefaultListableBeanFactoryTests { @@ -2679,10 +2659,6 @@ public class DefaultListableBeanFactoryTests {
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
* bean of type B 10K times within a container having 1K additional beans of type A.
@ -2693,24 +2669,29 @@ public class DefaultListableBeanFactoryTests { @@ -2693,24 +2669,29 @@ public class DefaultListableBeanFactoryTests {
* under the 1000 ms timeout, usually ~= 300ms. With caching removed and on the same
* hardware the method will take ~13000 ms. See SPR-6870.
*/
@Test(timeout=1000)
@Test(timeout = 1000)
public void testByTypeLookupIsFastEnough() {
Assume.group(TestGroup.PERFORMANCE);
DefaultListableBeanFactory bf = new DefaultListableBeanFactory();
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.freezeConfiguration();
for (int i=0; i<10000; i++) {
for (int i = 0; i < 10000; i++) {
bf.getBean(B.class);
}
}
static class A { }
static class B { }
public static class NoDependencies {
private NoDependencies() {
@ -2816,10 +2797,16 @@ public class DefaultListableBeanFactoryTests { @@ -2816,10 +2797,16 @@ public class DefaultListableBeanFactoryTests {
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() {
closed = true;
closeCount++;
}
}
@ -2981,7 +2968,6 @@ public class DefaultListableBeanFactoryTests { @@ -2981,7 +2968,6 @@ public class DefaultListableBeanFactoryTests {
private FactoryBean<?> factoryBean;
public final FactoryBean<?> getFactoryBean() {
return this.factoryBean;
}

15
src/asciidoc/index.adoc

@ -2663,10 +2663,17 @@ so-called __inner bean__. @@ -2663,10 +2663,17 @@ so-called __inner bean__.
</bean>
----
An inner bean definition does not require a defined id or name; the container ignores
these values. It also ignores the `scope` flag. Inner beans are __always__ anonymous and
they are __always__ created with the outer bean. It is __not__ possible to inject inner
beans into collaborating beans other than into the enclosing bean.
An inner bean definition does not require a defined id or name; if specified, the container
does not use such a value as an identifier. The container also ignores the `scope` flag on
creation: Inner beans are __always__ anonymous and they are __always__ created with the outer
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]]

Loading…
Cancel
Save