Mark W
Mark W

Reputation: 2803

Spring Bean object marshaling depends on the order given in the XML?

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

Answers (1)

Sotirios Delimanolis
Sotirios Delimanolis

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

Related Questions