menu

秋梦无痕

一场秋雨无梦痕,春夜清风冻煞人。冬来冷水寒似铁,夏至京北蟑满城。

Avatar

Setter Injection和Constructor Injection有别

from: http://www.blogbus.com/blogbus/blog/diary.php?diaryid=141395

作者:[url=mailto:dreamhead.cn@gmail.com]dreamhead.cn@gmail.com[/url]
发表时间: 2004-04-15

Martin Flower在他的那篇《Inversion of Control Containers and the Dependency Injection pattern》中,提到了三种Injection的形式,它们分别是Interface Injection、Setter Injection和Constructor Injection,对应着IoC中谈到的type 1、type 2、type 3。除了Interface Injection由于较强的侵略性让我有些反感,其它的两种形式,我并没有看出什么不同。Spring和Pico都实现了这两种形式的Injection,只是Spring推崇Setter Injection,而Pico倾向于Constructor Injection。

一个有趣的小问题向我展示了二者之间一些差别。
问题是这样的,两个组件,如果A需要B,B也需要A。那么Setter Injection和Constructor Injection是否都能很好的解决问题呢?
如果你明白了我要说什么,就不必再浪费时间了。如果没有,继续往下看。

先来看看Setter Injection的代码。

public class A {
private B b;

public void setB(B b) {
this.b = b;
}
}

public class B {
private A a;

public void setA(A a) {
this.a = a;
}
}

接下来是配置文件。

<bean id="a" class="A">
<property name="b">
<ref bean="b"/>
</property>
</bean>

<bean id="b" class="B">
<property name="a">
<ref bean="a"/>
</property>
</bean>

如果用Constructor Injection来完成,代码就变成了这样。

public class A {
private B b;

public A(B b) {
this.b = b;
}
}

public class B {
private A a;

public B(A a) {
this.a = a;
}
}

配置文件也随之发生了变化。

<bean id="a" class="A">
<constructor-arg>
<ref bean="b"/>
</constructor-arg>
</bean>

<bean id="b" class="B">
<constructor-arg>
<ref bean="a"/>
</constructor-arg>
</bean>

显然,我们无法从字面上看出问题来,事实上,即便编译器也无法发现问题所在。那就让我们把代码运行起来。我们的测试代码也不用很复杂,读文件之后,取个bean就可以了。

InputStream is = new FileInputStream("config.xml");
BeanFactory factory = new XmlBeanFactory(is);
factory.getBean("a");

看到什么了?
Setter一切正常,而Constructor疯狂的滚屏,直至堆栈溢出。

解释这个现象很容易。想想如果我们是Spring的作者,我们如何来完成这两个组件间的组合。
如果是Setter,遇到A,我们用缺省的构造函数将它构造出来,发现它需要B作为它的一个属性,因为B不存在,我们构造B。B的构造过程用的也是缺省的构造函数。发现B需要A作为它的一个属性,因为A已经存在了,就把A加进来。B完成了,回头用它把它加入到A中。
而Constructor则有所不同。遇到A,我们构造A,但A的构造参数需要B,因为B不存在,我们构造B,结果B又需要A,我们只好再构造A,而A需要B,如此反复,直至堆栈溢出。

由此可见,如果需要两个组件互相知晓,通过上面的这种Constructor Injection方式显然行不通。当然,我们可以找到其它的变通手法。

或许你会问,为什么这两个组件要互相知晓?这是一个很正常的需求,比如在MVC框架中,用户在View上的操作显然要View知道Controller在哪里才能传递过去,而Controller也需要知道View在哪才能将一些处理结果让View反映给用户。

总结一下。当两个组件需要相互知晓时,Setter Injection表现得明显要比Constructor Injection好,属于心理素质好,能够正常发挥的那种,这种精神值得中国足球队的小伙子们好好学习一下。

评论已关闭