Type 2 IoC and 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会是个简单的选择。