在 第一個 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
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>
...
<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
...
name: Justin
word: 20
...
至於要使用Constructor或Setter來完成依賴注入這個問題,其實就等於在討論一個古老的問題,要在物件建立時就準備好所有的資源,或是在物件建立好後,使用Setter來進行設定。
使用Constructor的好處之一是,您可以在建構物件的同時一併完成依賴關係的建立,物件一建立則所有的一切也就準備好了,但如果要建立的物件關係很多,使用Constructor injection會在建構函式上留下一長串的參數,且不易記憶,這時使用Setter會是個不錯的選擇,另一方面,Setter可以有明確的名稱可以瞭解注入的物件 會是什麼,像是setXXX()這樣的名稱會比記憶Constructor上某個參數位置代表某個物件來得好。
然而使用Setter由於提供了setXXX()方法,所以不能保證相關的資料成員或資源在執行時期不會被更改設定,所以如果您想要讓一些資料成員或資源變為唯讀或是私有,使用Constructor injection會是個簡單的選擇。