Reputation: 2803
I have a simple example program here:
package com.test;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.context.support.FileSystemXmlApplicationContext;
public class Main {
public static void main(String[] args) throws InterruptedException {
try {
FileSystemXmlApplicationContext context = new FileSystemXmlApplicationContext("TestBeanFile.xml");
Object o = context.getBean("AInstance");
}
catch (Throwable e) {
e.printStackTrace();
}
Thread.sleep(Long.MAX_VALUE);
}
private static class A implements InitializingBean {
private B myB;
public A() {
System.out.println("A constructor");
}
public void setB(B aB) {
myB = aB;
}
@Override
public void afterPropertiesSet() throws Exception {
System.out.println("A aps");
}
}
private static class B implements Runnable, InitializingBean {
private C myC;
private volatile boolean exit = false;
public B() {
System.out.println("B constructor");
}
public void setC(C aC) {
myC = aC;
}
@Override
public void afterPropertiesSet() throws Exception {
System.out.println("B aps");
if (myC == null) throw new IllegalArgumentException("C cannot be null");
new Thread(this).start();
}
public void exit() {
exit = true;
}
@Override
public void run() {
while (!exit) {
System.out.println(myC.getValue());
try {
Thread.sleep(1000);
}
catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
private static class C implements InitializingBean {
private String value = "C's value";
public C() {
System.out.println("C constructor");
}
public String getValue() {
return value;
}
@Override
public void afterPropertiesSet() throws Exception {
System.out.println("C aps");
}
}
}
And here is a simple XML to bean load them which intentionally has an incorrect fully qualified name to the A class. This should mean that C B and A dont get constructed.
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:int="http://www.springframework.org/schema/integration"
xmlns:int-stream="http://www.springframework.org/schema/integration/stream"
xmlns:int-ip="http://www.springframework.org/schema/integration/ip"
xsi:schemaLocation="
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/integration http://www.springframework.org/schema/integration/spring-integration-2.1.xsd
http://www.springframework.org/schema/integration/stream http://www.springframework.org/schema/integration/stream/spring-integration-stream-2.1.xsd
http://www.springframework.org/schema/integration/ip http://www.springframework.org/schema/integration/ip/spring-integration-ip-2.1.xsd">
<bean id="CInstance" class = "com.test.Main.C" />
<bean id="BInstance" class = "com.test.Main.B">
<property name="C" ref="CInstance" />
</bean>
<bean id="AInstance" class = "com.test.Main.A1">
<property name="B" ref="BInstance"/>
</bean>
</beans>
When you run this application using the above XML C and B DO get constructed but A doesnt. It will produce the following output (omitting the stack trace when the exception is thrown, but I assure you A does not get constructed):
C constructor
C aps
B constructor
B aps
C's value
C's value
C's value
....... // one a second
If you modify the XML to look like this:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:int="http://www.springframework.org/schema/integration"
xmlns:int-stream="http://www.springframework.org/schema/integration/stream"
xmlns:int-ip="http://www.springframework.org/schema/integration/ip"
xsi:schemaLocation="
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/integration http://www.springframework.org/schema/integration/spring-integration-2.1.xsd
http://www.springframework.org/schema/integration/stream http://www.springframework.org/schema/integration/stream/spring-integration-stream-2.1.xsd
http://www.springframework.org/schema/integration/ip http://www.springframework.org/schema/integration/ip/spring-integration-ip-2.1.xsd">
<bean id="AInstance" class = "com.test.Main.A1">
<property name="B" ref="BInstance"/>
</bean>
<bean id="BInstance" class = "com.test.Main.B">
<property name="C" ref="CInstance" />
</bean>
<bean id="CInstance" class = "com.test.Main.C" />
</beans>
The only output you get is the stack trace, and the B and C objects dont get constructed at all.
This seems like its a bug in spring. I dont understand why the marshaling of the objects, and the constructors / afterpropertiesSet methods which are run depend on the order of their appearance in the XML. What this means for me, is that when I have classes which construct threads in their constructors / after properties set methods, if I'm not careful about how I order the elements in the XML I can end up leaking resources if my only reference to them is, in this example, the A class. Because it fails construction in both cases.
So, is this a bug, or is this some feature that I dont understand? If this is a feature, what reason would they have to make the order of marshaling dependent on the XML file, instead of the object graph of the bean definitions?
Upvotes: 1
Views: 517
Reputation: 279940
Spring first reads the <bean>
and other elements in the XML configuration and creates appropriate BeanDefinition
objects defining them.
It then has to initialize them. To do this, it has to decide on an order. The order of initialization is undefined, unless a bean depends on another or there is a mix of @Order
, implementations of Ordered
and a few others (depends on the case).
In your first example, CInstance
is initialized first. Then BInstance
is initialized. Part of its initialization involves invoking its afterPropertiesSet()
method which launches a non-daemon thread. Then Spring attempts to initialize AInstance
and fails.
In your second example, Spring attempts to initialize AInstance
first. It fails immediately, with no change to start a second thread.
Note that in a declaration like
<bean id="AInstance" class="com.example.Spring.A1">
<property name="B" ref="BInstance" />
</bean>
although AInstance
depends on BInstance
, the actual instance needs to be initialized before any property setters can be invoked to assign BInstance
to B
. So Spring begins the initialization of AInstance
by instantiating the class. If it fails, the entirety of context refresh fails. If it passes, it will then initialize the BInstance
bean and use it to set the B
property.
Upvotes: 2