ITEEDU

Spring Gossip: Type 2 IoC、Type 3 IoC

第一个 Spring 程序 中利用Bean的Setter完成依赖注入,Spring鼓励的是Setter injection,也就是Type 2,但也允许您使用Type 3的Constructor injection,使用Setter或Constructor来注入依赖关系视您的需求而定,这边先来看看如何使用Constructor injection,首先看看HelloBean:
HelloBean.java
package onlyfun.caterpillar; 

public class HelloBean {
private String name;
private String helloWord;

public HelloBean() {
}

public HelloBean(String name, String helloWord) {
this.name = name;
this.helloWord = helloWord;
}

public void setName(String name) {
this.name = name;
}
public String getName() {
return name;
}

public void setHelloWord(String helloWord) {
this.helloWord = helloWord;
}
public String getHelloWord() {
return helloWord;
}
}

注意建构函式的两个参数顺序,在Bean定义文件中设定时必须指定参数的顺序,如下所示:
beans-config.xml
<?xml version="1.0" encoding="UTF-8"?> 
<!DOCTYPE beans PUBLIC "-//SPRING/DTD BEAN/EN"
"http://www.springframework.org/dtd/spring-beans.dtd">
<beans>
<bean id="helloBean"
class="onlyfun.caterpillar.HelloBean">
<constructor-arg index="0">
<value>Justin</value>
</constructor-arg>
<constructor-arg index="1">
<value>caterpillar</value>
</constructor-arg>
</bean>
</beans>

在Bean的定义档案中,使用<constructor-arg>来表示将使用Constructor injection,由于使用Constructor injection时并不如Setter injection时拥有setXXX()这样易懂的名称,所以必须指定参数的位置索引,index属性就是用于指定对象将注入至建构函式中的哪一个参 数,参数的顺序指定中,第一个参数的索引值是0,第二个是1,依此类推。

来看看测试程序:
SpringDemo.java
package onlyfun.caterpillar; 

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.FileSystemXmlApplicationContext;

public class SpringDemo {
public static void main(String[] args) {
ApplicationContext context =
new FileSystemXmlApplicationContext("beans-config.xml");

HelloBean hello =
(HelloBean) context.getBean("helloBean");
System.out.print("name: ");
System.out.println(hello.getName());
System.out.print("word: ");
System.out.println(hello.getHelloWord());
}
}

实际的执行结果如下:
资 讯: Unable to locate ApplicationEventMulticaster with name 'applicationEventMulticaster': using default [org.springframework.context.event.SimpleApplicationEventMulticaster@12b6651]
2005/10/17 下午 09:08:50 org.springframework.beans.factory.support.DefaultListableBeanFactory preInstantiateSingletons
信息: Pre-instantiating singletons in factory [org.springframework.beans.factory.support.DefaultListableBeanFactory defining beans [helloBean]; root of BeanFactory hierarchy]
name: Justin
word: caterpillar


这边的例子在Bean上使用具有两个参数的建构函式作范例,如果建构函式上只有一个参数,则不必指定index属性,例如建构函式上若只有一个name参数,则可以在Bean定义档中如下设定:

...
<bean ...>
	<constructor-arg>
		<value>Justin</value>
	</constructor-arg>
</bean>
...
另一个例子是若有两个以上的参数,而参数型态各不相同的话,例如若HelloBean是这么定义的:
HelloBean.java
package onlyfun.caterpillar; 

public class HelloBean {
private String name;
private Integer age;

public HelloBean() {
}

public HelloBean(String name, Integer age) {
this.name = name;
this.age = age;
}

public void setName(String name) {
this.name = name;
}
public String getName() {
return name;
}

public void setAge(Integer age) {
this.age = age;
}
public Integer getAge() {
return age;
}
}

这次在Bean定义档的<constructor-arg>上,可以使用type来指定建构函式上的参数型态,例如:
beans-config.xml
<?xml version="1.0" encoding="UTF-8"?> 
<!DOCTYPE beans PUBLIC "-//SPRING/DTD BEAN/EN"
"http://www.springframework.org/dtd/spring-beans.dtd">
<beans>
<bean id="helloBean"
class="onlyfun.caterpillar.HelloBean">
<constructor-arg type="java.lang.String">
<value>Justin</value>
</constructor-arg>
<constructor-arg type="java.lang.Integer">
<value>20</value>
</constructor-arg>
</bean>
</beans>

简单的将SpringDemo类别改为以下:
SpringDemo.java
package onlyfun.caterpillar; 

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.FileSystemXmlApplicationContext;

public class SpringDemo {
public static void main(String[] args) {
ApplicationContext context =
new FileSystemXmlApplicationContext("beans-config.xml");

HelloBean hello =
(HelloBean) context.getBean("helloBean");
System.out.print("name: ");
System.out.println(hello.getName());
System.out.print("word: ");
System.out.println(hello.getAge());
}
}

执行结果如下所示:
...
name: Justin
word: 20
...
至于要使用Constructor或Setter来完成依赖注入这个问题,其实就等于在讨论一个古老的问题,要在对象建立时就准备好所有的资源,或是在对象建立好后,使用Setter来进行设定。

使用Constructor的好处之一是,您可以在建构对象的同时一并完成依赖关系的建立,对象一建立则所有的一切也就准备好了,但如果要建立的对象关系很多,使用Constructor injection会在建构函式上留下一长串的参数,且不易记忆,这时使用Setter会是个不错的选择,另一方面,Setter可以有明确的名称可以了解注入的对象 会是什么,像是setXXX()这样的名称会比记忆Constructor上某个参数位置代表某个对象来得好。

然而使用Setter由于提供了setXXX()方法,所以不能保证相关的数据成员或资源在执行时期不会被更改设定,所以如果您想要让一些数据成员或资源变为只读或是私有,使用Constructor injection会是个简单的选择。