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會是個簡單的選擇。