Wednesday, October 24, 2007

Bean Definition Tip1: Avoid Circular Reference

Spring is a really powerful framework, which is not only providing the JEE similar or much better and lighter container services, but also providing a straightforward declarative and configuration driven bean definition model. With the flexibility and capability of bean definition mechanism, especially when you unleashing the power of XML schema based context file introduced since Spring 2.0, you can almost do any sorts of bean wiring, parameter configuration, application assembling tasks. After you use it, I bet you will forget all the unpleasant and cumbersome JEE configuration files and the coarse grained Enterprise Beans. However, when dealing with bean definitions, it's still very tricky and the similar problems probably happen as in your traditional programmatic approach.

One typical problem is that the bean definition circular referencing. This is the circumstance under which there are beans relying on each others instance before they could be instantiated by Spring container.

For below example:

public class BeanA {
BeanB b;
public BeanA(BeanB beanB){
b = beanB;
}
public void print(String s) {
System.out.print(s);
}
public void foo(){
b.foo();
}
}

public class BeanB {
private BeanA a;
public BeanB(BeanA beanA){
a = beanA;
}

public void print(String s){
a.print(s);
}

public void foo() {
System.out.print("foo!");
}
}

public class TestStub {
public static void main(String[] args){
AbstractApplicationContext ctxt= new ClassPathXmlApplicationContext("beandef.xml");
}
}

...bean definitions in beandef.xml ...

<bean id="beanA" class="BeanA">
<constructor-arg ref="beanB" />
</bean>
<bean id="beanB" class="BeanB">
<constructor-arg ref="beanA"/>
</bean>

When you run TestStub, it will show following exception (pay attention to the underline):

Exception in thread "main" org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'beanA' defined in class path resource [beandef.xml]: Cannot resolve reference to bean 'beanB' while setting constructor argument; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'beanB' defined in class path resource [beandef.xml]: Cannot resolve reference to bean 'beanA' while setting constructor argument; nested exception is org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name 'beanA': Requested bean is currently in creation: Is there an unresolvable circular reference?
Caused by: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'beanB' defined in class path resource [beandef.xml]: Cannot resolve reference to bean 'beanA' while setting constructor argument; nested exception is org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name 'beanA': Requested bean is currently in creation: Is there an unresolvable circular reference?
Caused by: org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name 'beanA': Requested bean is currently in creation: Is there an unresolvable circular reference?

It's a simple sample of bean circular reference. Specifically, the container tried to instantiate "beanA" first, then it found "beanA" need a reference of "beanB" as constructor argument; subsequently, it tried to create an instance of "BeanB" as "beanB", but this time the "beanB" need a "beanA" instance as a constructor reference argument that is still creation pending waiting for "beanB". This situation confused the container and it can not resolve all the bean references. Then it came the complains of exception. This circular reference problem was caused by circular references of bean by means of Constructor Injection.

To solve this issue, one option is that replace constructor reference to "beanA" of "beanB" with a setter injection. Then the container will not try to create the "beanB" with the "beanB" reference at same time. Instead, it will defer this by injecting the "beanA" reference latter on via "setter" method of "BeanB" after the "beanA" has been created. The same changes happen to "BeanA". It needs some tweaks to the code and bean definitions as well.

public class BeanA {
BeanB b;
public BeanA(){
}
public void setBeanB(BeanB b){
this.b = b;
}
public void print(String s) {
System.out.print(s);
}
public void foo(){
b.foo();
}
}


public class BeanB {
private BeanA a;
public BeanB(){
}

public void setBeanA(BeanA a){
this.a = a;
}
public void print(String s){
a.print(s);
}

public void foo() {
System.out.print("foo!");
}
}


...

<bean id="beanA" class="BeanA">
<property name="beanB" ref="beanB"/>
</bean>
<bean id="beanB" class="BeanB">
<property name="beanA" ref="beanA"/>
</bean>

...

This time, the Sping was very happy to create all the beans.

An alternative option is still workable for this case but a lit bit tricky related with bean instantiating order. We can ask "beanA" injected "beanB" by setter, whereas "beanB" can still use the constructor injection. But wait, the order of bean definition is very important. You can not let constructor injected "beanB" defined before setter injected "beanA".

public class BeanA {
BeanB b;
public BeanA(){
}
public void setBeanB(BeanB b){
this.b = b;
}
public void print(String s) {
System.out.print(s);
}
public void foo(){
b.foo();
}
}

public class BeanB {
private BeanA a;
public BeanB(BeanA a){
this.a = a;
}

public void print(String s){
a.print(s);
}

public void foo() {
System.out.print("foo!");
}
}

...

<bean id="beanA" class="BeanA">
<property name="beanB" ref="beanB"/>
</bean>
<bean id="beanB" class="BeanB">
<constructor-arg ref="beanA"/>
</bean>

...

This works very well. If you move the "beanB" definition before "beanA", it will not work because the "beanA" will need a creating "beanB".

In a nutshell, the circular reference problem is very typical for a sophisticated "Spring" application if you did not cook it very well. It will cost you time to debug and fix it. But not just that. Because the nature of configuration driven of "Spring", the same bean definition file itself could have huge chances to be manipulated by different personnel, thus, more chances of introducing new errors regarding the "Spring" competency level. I will talk about in future sessions how to address this issue by establishing your project or organization level bean definition schema extended from "Spring" base schema. In this way, it mandates some of important project wise constraints for bean definition, such as available tags, bean types, data types etc. Thus, it could reduce some errors caused by arbitrary string parameters or typos in bean definition files for your project.

It's strongly recommended to use setter injection for bean wiring and bean reference injection. If it's very necessary to do constructor based reference injection, and unfortunately involved with circular reference, please well comment or document the bean definition and put notices for future maintaining people. You don't just want someone else happened to ruin your whole fragile bean constructor injection hierarchy someday, do you?

No comments: