Spring參考手冊-第三章 IoC容器-3.3 依賴

 
3. 3 依賴
你的典型的企業應用不會只有單一的對象(spring中叫做bean)組成。即使是最簡單的應用也至少會有一系列的對象組成,它們共同協作、裝配成面向用戶的統一應用。
下面的部分將會介紹你如何組裝單獨的定義的bean來形成一個完整的應用(應用通常都是爲了實現最終用戶的某個特定目標)。
3.3.1依賴注射
依賴注射(DI)的基本原則是:對象在從構造器或者工廠方法返回時,通過構造器參數、工廠方法參數或者對象實例的屬性設置來定義它們的依賴性(如那些它協同的對象)。然後,在創建bean的時候,容器完成依賴注射工作。這就是所謂的控制反轉(Inversion of Control-IoC):bean自身控制初始化,或使用它自身的構造器來定位自身的依賴,這有點類似於Service Locator模式。
什麼明顯,當DI原則被充分應用的時候,代碼將會變得更乾淨(cleaner),同時應用實現了高層次的分離也會變得更容易,此時bean並不能感知它的依賴,但這些依賴卻能夠被提供給它們(bean甚至並不知道依賴的對象在哪兒,或者依賴的對象是什麼類)。
正如前面所述,DI有兩種主要的實現方式,分別叫做:Setter InjectionConstructor Injection
3.3.1.1 Setter Injection(注射)
在調用一個無參數的構造器或者靜態工廠方法創建bean之後,通過調用beansetter方法來初始化bean的方法就叫做setter injection
下面的例子中,該類只能用setter方法實現依賴注射。注意,該類只是一個平常的Java對象。
public class SimpleMovieLister {
 
    // the SimpleMovieLister has a dependency on the MovieFinder
    private MovieFinder movieFinder;
 
    // a setter method so that the Spring container can 'inject' a MovieFinder
    public void setMoveFinder(MovieFinder movieFinder) {
        this.movieFinder = movieFinder;
    }
    
    // business logic that actually 'uses' the injected MovieFinder is omitted...
}
3.3.1.2 Constructor Injection(構造器注射)
構造器注射方式是通過調用一個採用參數(參數表示協作或者依賴的事物)的構造器來實現的。此外,通過調用帶有特定參數的靜態工廠方法來創建bean的方式也可以看作是這種方法的延伸,下面的章節也把這兩種情況類似看待。
下面的例子中,該類只能用構造器注射方法實現依賴注射。再次注意,該類也是一個普通類。
public class SimpleMovieLister {
 
    // the SimpleMovieLister has a dependency on the MovieFinder
    private MovieFinder movieFinder;
 
    // a constructor so that the Spring container can 'inject' a MovieFinder
    public SimpleMovieLister(MovieFinder movieFinder) {
        this.movieFinder = movieFinder;
    }
    
    // business logic that actually 'uses' the injected MovieFinder is omitted...
}
 

選擇constructor-還是setter
Spring團隊推薦使用setter方法,因爲如果constructor的參數很多的話,就會變得很難處理,特別是當一些參數是可選的時候。Setter方法的出現使你能夠在bean生成以後可以重新配置(重新注射)依賴。(JMX MBeans的管理就是一個典型的例子)。
經過如此,constructor方法還是會被一些純化論者所推薦。他們的理由是:在bean初始化的時候就將bean的所有依賴注射完成意味着該對象可以儘量少的在客戶端調用代碼中出現。問題是:不能重新配置(注射)這個bean。
這兒並不存在硬性規定。無論哪種DI方式要依賴於具體的類;有時候,當你使用第三方的類的時候,你並沒有類的具體實現細節,也並未開放任何的setter方法,那麼此時的選擇已經確定,那就是constructor方式。

BeanFactory支持上面兩種方式的注射方法。(實際上,如果部分依賴已經通過構造器方法注射後,它然後支持通過setter方法注射)依賴的配置在容器中表現爲帶有PropertyEditor實例的BeanDefinition,它知道如何變換屬性的格式。然而,Spring的大多數用戶一般不會直接處理這些類(例如通過程序方式),他們寧願去處理將被轉換成類實例的XML定義文件,然後加載整個Spring IoC容器。
Bean依賴的處理方式通常有以下幾種:
l         BeanFactory通常在初始化的時候,會讀取所有bean的配置文件。(大多數Spring用戶使用BeanFactory或者ApplicationContext
l         Bean使用屬性、構造器參數或者靜態工廠方法參數來表示依賴。在創建的時候,這些依賴會傳遞給bean
l         每個屬性或者構造器參數要麼是要設置的值,要麼是對於容器中另外的bean的引用。
l         每個屬性或者構造器參數能夠被轉換成實際要求的類型。默認的,Spring可以將String類型的值轉換成內建類型,如intlongStringboolean等。
認識到Spring實際上是在容器創建的時候來驗證每個Bean的配置信息這點是十分重要的,包括Bean屬性引用是引用合法的bean的驗證。(如,被引用的bean要求是定義在同一個容器中。然而,Bean屬性的設置是直到bean創建完成的時候才完成的。對於那些單例模式且被設置成預初始化的bean,是在容器初始化的時候就創建的,而其他的bean實際上是在被請求的時候才創建的。當bean被創建的時候,這會引起具有依賴的bean被創建,包括依賴的、依賴的依賴的等也被一起創建)。

循環依賴:
如果使用典型的構造器注射方式,有可能會形成循環依賴的情況。
考慮如下情況:類A在構造器注射時,需要類B實例,而類B同樣需要A的實例。如果你配置A和B互相注射,那麼Spring IoC容器會在運行時檢測到循環引用,然後拋出BeanCurrentlyInCreationException異常。
解決此問題的一個方法是用setter方法代替構造器方法。另外一個解決方法,是不要使用構造器方法,而是使用setter。

你一般情況下可以相信Spring會做正確的事情。Spring會在容器加載的時候,監測不匹配的配置項,例如對於不存在bean的引用或者循環依賴。容器將儘可能晚的設置屬性和解析依賴(如僅在請求發生的時候纔會去創建依賴)。這意味着Spring在你請求一個bean(如果該bean創建錯誤或者創建依賴的時候錯誤)的時候,會比較晚的拋出異常。當一個bean在發現非法屬性的時候就會拋出異常,這也引起容器拋出異常。某些配置項的晚邦定正是ApplicationContext接口默認預實例化單例bean的原因。ApplicationContext創建的同時,相關的配置項也一起創建,而這是以創建那些暫時不需要的bean所耗費的時間和內存爲代價的。當然,你可以去重載這種默認行爲,如將單例bean設置成“懶初始化(lazy-initialize)”(如:並不預初始化)。
最後,要說一下的是,在一個或者多個bean被注射到一個依賴的bean的時候,每個被注射的bean在注射之前就是配置完成的(通過DI方式)。也就是說,如果bean A對於bean B依賴,那麼Spring IoC容器在調用ASetter方法注射B之前,就完整配置好B;‘完整配置(totally configure)’的意思是bean被初始化(如果不是預實例化的單例bean的話),bean的所有依賴被設置並且相關的生命週期方法會被調用。(如配置初始化方法-configured init method或者初始化bean回調方法-initializingbean callback method)。
3.3.1.3 例子
首先,第一個例子採用XML配置方式的DI。參看下面的部分的bean定義文件。
<bean id="exampleBean" class="examples.ExampleBean">
 <!-- setter injection using the nested <ref/> element -->
 <property name="beanOne"><ref bean="anotherExampleBean"/></property>
 <!-- setter injection using the neater 'ref' attribute -->
 <property name="beanTwo" ref="yetAnotherBean"/>
 <property name="integerProperty" value="1"/>
</bean>
<bean id="anotherExampleBean" class="examples.AnotherBean"/>
<bean id="yetAnotherBean" class="examples.YetAnotherBean"/>
 
public class ExampleBean {
 
    private AnotherBean beanOne;
    private YetAnotherBean beanTwo;
   private int i;
 
    public void setBeanOne(AnotherBean beanOne) {
        this.beanOne = beanOne;
    }
 
    public void setBeanTwo(YetAnotherBean beanTwo) {
        this.beanTwo = beanTwo;
    }
 
    public void setIntegerProperty(int i) {
        this.i = i;
    }    
}
可以看出,針對XML配置文件中的屬性,已經聲明瞭相應的setters方法。
下面,另外一個例子是使用構造器注射方式。參看下面的配置片斷和相應的Java類代碼。
<bean id="exampleBean" class="examples.ExampleBean">
 
 <!-- constructor injection using the nested <ref/> element -->
 <constructor-arg><ref bean="anotherExampleBean"/></constructor-arg>
 
  <!-- constructor injection using the neater 'ref' attribute -->
 <constructor-arg ref="yetAnotherBean"/>
 
  <constructor-arg type="int" value="1"/>
</bean>
 
<bean id="anotherExampleBean" class="examples.AnotherBean"/>
<bean id="yetAnotherBean" class="examples.YetAnotherBean"/>
 
 
public class ExampleBean {
 
    private AnotherBean beanOne;
    private YetAnotherBean beanTwo;
    private int i;
    
    public ExampleBean(
        AnotherBean anotherBean, YetAnotherBean yetAnotherBean, int i) {
        this.beanOne = anotherBean;
        this.beanTwo = yetAnotherBean;
        this.i = i;
    }
}
可以看出,bean定義中的構造器參數指定了將會傳遞給ExampleBean構造器的參數值。
下面,考慮構造器方法的一個變形的情況,Spring通過調用靜態工廠方法來返回對象實例。
<bean id="exampleBean" class="examples.ExampleBean"
      factory-method="createInstance">
 <constructor-arg ref="anotherExampleBean"/>
 <constructor-arg ref="yetAnotherBean"/>
 <constructor-arg value="1"/> 
</bean>
 
<bean id="anotherExampleBean" class="examples.AnotherBean"/>
<bean id="yetAnotherBean" class="examples.YetAnotherBean"/>
 
public class ExampleBean {
 
    // a private constructor
    private ExampleBean(...) {
      ...
    }
    
    // a static factory method; the arguments to this method can be
    // considered the dependencies of the bean that is returned,
    // regardless of how those arguments are actually used.
    public static ExampleBean createInstance (
            AnotherBean anotherBean, YetAnotherBean yetAnotherBean, int i) {
        ExampleBean eb = new ExampleBean (...);
        // some other operations
        ...
        return eb;
    }
}
注意,傳遞給靜態工廠方法的參數是用constructor-arg元素實現的,這點和構造器方法是一樣的。另外重要的一點是,通過工廠方法返回的類並不一定就是包含了工廠方法的類,儘管本例子是這樣。實例工廠方法和這類似(不同的是用factory-bean屬性代替class屬性),細節這裏不再贅述。
3.3.2構造器參數解析
當使用參數類型時,構造器參數解析匹配將會發生。如果在bean定義中,不存在參數歧義的情況,那麼bean定義文件的構造器參數順序就是實際傳遞給bean構造器的參數順序。參看下面的類:
package x.y;
 
public class Foo {
 
    public Foo(Bar bar, Baz baz) {
        // ...
    }
}
此時,不存在潛在的歧義情況(假定類Bar和類Baz無繼承層次關聯)。這樣,下面的配置將會正常工作,並且也不需要指定構造器參數的索引,也不需要顯式指定參數類型,它會按照你期望的那樣正常工作。
<beans>
    <bean name="foo" class="x.y.Foo">
        <constructor-arg>
           <bean class="x.y.Bar"/>
        </constructor-arg>
        <constructor-arg>
            <bean class="x.y.Baz"/>
        </constructor-arg>
    </bean>
</beans>
bean被引用的時候,可以知道類型,並且發生匹配(正如前面的例子那樣)。然而,在使用簡單值類型,如<value>true<value>的時候,Spring無法確定值的類型,此時如果沒有其他配置的話,將無法正確匹配。參考下面的類,後面將會使用。
package examples;
 
public class ExampleBean {
 
    // No. of years to the calculate the Ultimate Answer
    private int years;
    
    // The Answer to Life, the Universe, and Everything
    private String ultimateAnswer;
 
    public ExampleBean(int years, String ultimateAnswer) {
        this.years = years;
        this.ultimateAnswer = ultimateAnswer;
    }
}
 
3.3.2.1 構造器參數類型匹配
對於上面的情況可以通過使用'type'屬性指定構造器參數類型,從而實現簡單類型匹配。如:
<bean id="exampleBean" class="examples.ExampleBean">
 <constructor-arg type="int" value="7500000"/>
 <constructor-arg type="java.lang.String" value="42"/>
</bean>
3.3.2.2 構造器參數索引
可以通過使用index屬性來顯式的制定構造器參數的索引。例如:
 
<bean id="exampleBean" class="examples.ExampleBean">
 <constructor-arg index="0" value="7500000"/>
 <constructor-arg index="1" value="42"/>
</bean>
在一個構造器有多個同類型參數的情況下,通過指定參數索引,同樣可以解決多個簡單值類型參數的歧義問題。注意,索引從0開始。
提示:
指定構造器參數索引是IoC容器的首選方式。
3.3.3 Bean屬性和構造器參數細節
正如前面所述,bean屬性和構造器參數可以引用其他受管理的bean(協作者),或者是內部定義的值。SpringXML配置中的<property/><constructor-arg/>屬性有很多子元素就可以實現這點。
3.3.3.1 直接賦值(原始類型,串型等)
<value/>屬性以易於閱讀的方式來制定屬性或者構造器參數的值。正如前面所述,JavaBeanPropertyEditors將這些值從java.lang.String類型轉換成相應的類型。
<bean id="myDataSource" destroy-method="close"
    class="org.apache.commons.dbcp.BasicDataSource">
 <!-- results in a setDriverClassName(String) call -->
 <property name="driverClassName">
    <value>com.mysql.jdbc.Driver</value>
 </property>
 <property name="url">
    <value>jdbc:mysql://localhost:3306/mydb</value>
 </property>
 <property name="username">
    <value>root</value>
 </property>
</bean>
3.3.3.1.1 idref元素
Idref元素是一種簡單的傳遞容器中的其他的beanid的“錯誤驗證”(error-proof)方式。(<constructor-arg/>或者<property/>元素的子元素)
<bean id="theTargetBean" class="..."/>
 
<bean id="theClientBean" class="...">
    <property name="targetName">
        <idref bean="theTargetBean" />
    </property>
</bean>
上面的bean定義文件等同於下面的定義:
<bean id="theTargetBean" class="..."/>
 
<bean id="client" class="...">
    <property name="targetName">
        <value>theTargetBean</value>
    </property>
</bean>
第一種方式比第二種更好,原因是idref標誌讓容器在分發時驗證被引用的bean是否實際存在。第二種情況中,並沒用驗證'targetName'屬性引用的bean是否存在。
對於第二種方式,在被引用bean被實例化之前,例外將會隨時可能拋出(往往會導致系統崩潰)。如果被引用的是原型bean,那麼,那麼異常可能會在容器分發完畢之後很長時間纔會拋出。
此外,如果被引用的bean是在同樣的XML配置單元,且beanname就是beanid,可以使用'local'屬性來指定,這樣,在XML文檔解析的時候,就可以驗證bean的合法性。
<property name="targetName">
   <!-- a bean with an id of 'theTargetBean' must exist, else an XML exception will be thrown -->
   <idref local="theTargetBean"/>
</property>
3.3.3.2 對於其他bean的引用(協作者)
Ref是最後一個可以包含在<constructor-arg/>或者<property/>中的元素。它被用來指定引用其它的bean的屬性值(協助者)。正如前面所述,被引用的bean被認爲是引用它的bean的依賴,會在屬性設置之前被實例化(對於單例bean可能已經完成了初始化)。
所有的引用最終只是對於另外一個對象的引用,但是有三種方法來指定被引用對象的id或者name,不同的方法決定對象的範圍和驗證方式的不同。
通過使用<ref/>標籤的bean屬性來制定目標bean是最常用的方式,這將會創建一個對於同容器或者父容器的bean的引用(無論在不在同樣的XML文件中)。'bean'屬性的值可以是目標bean'id'屬性,或者是'name'屬性。
<ref bean="someBean"/>
通過local屬性來制定目標bean的方式,可以利用XML解析器驗證同一文件中的XMLID引用的功能。Local屬性必須和目標beanid屬性值相同。如果沒有匹配到對應的元素,那麼XML解析器將會拋出異常。因此,如果目標bean在同一個定義文件中,那麼採用local屬性是最好的方式(可以儘可能早的發現錯誤)。
<ref local="someBean"/>
通過'parent'屬性來制定目標bean,可以引用當前容器的父容器中的bean'parent'的值可以是目標beanid屬性,或者'name'屬性值,且目標bean必須在當前容器的父容器中。當存在容器繼承情況,而且你需要封裝一個父容器中的bean,且需要實現和父bean的同名代理時(例如,子bean的定義將會重載父bean的定義)。
<!-- in the parent context -->
<bean id="accountService" class="com.foo.SimpleAccountService">
    <!-- insert dependencies as required as here -->
</bean>
<!-- in the child (descendant) context -->
<bean id="accountService" <-- notice that the name of this bean is the same as the name of the 'parent' bean
      class="org.springframework.aop.framework.ProxyFactoryBean">
      <property name="target">
          <ref parent="accountService"/> <-- notice how we refer to the parent bean
      </property>
    <!-- insert other configuration and dependencies as required as here -->
</bean>
(坦率的說,'parent'屬性較少使用。
3.3.3.3 內部bean
<property/>或者<constructor-arg>屬性的<bean/>元素用來定義叫做內部beanbeaninner bean)。內部bean的定義不需要id或者name,由於idname將會被容器忽略,所以不指定任何id或者name就是最好的方式。
參看下面的一個內部bean的例子:
<bean id="outer" class="...">
 <!-- instead of using a reference to a target bean, simply define the target inline -->
 <property name="target">
    <bean class="com.mycompany.Person"> <!-- this is the inner bean -->
      <property name="name" value="Fiona Apple"/>
      <property name="age" value="25"/>
    </bean>
  </property>
</bean>
注意,在內部bean的情況下,'singleton''id'或者'name'屬性將會被容器完全忽略。內部bean總是以匿名方式存在,且它們總是原型bean。請記住,試圖將內部bean注射到依賴的bean中是不可能的。
3.3.3.4 集合
<list/><set/><map/><props/>元素可以定義和設置Java集合類型的屬性和構造器參數,如:ListSetMapProperties
<bean id="moreComplexObject" class="example.ComplexObject">
 <!-- results in a setAdminEmails(java.util.Properties) call -->
 <property name="adminEmails">
    <props>
        <prop key="administrator">[email protected]</prop>
        <prop key="support">[email protected]</prop>
        <prop key="development">[email protected]</prop>
    </props>
 </property>
 <!-- results in a setSomeList(java.util.List) call -->
 <property name="someList">
    <list>
        <value>a list element followed by a reference</value>
        <ref bean="myDataSource" />
    </list>
 </property>
 <!-- results in a setSomeMap(java.util.Map) call -->
 <property name="someMap">
    <map>
        <entry>
            <key>
                <value>yup an entry</value>
            </key>
            <value>just some string</value>
        </entry>
        <entry>
            <key>
                <value>yup a ref</value>
            </key>
            <ref bean="myDataSource" />
        </entry>
    </map>
 </property>
 <!-- results in a setSomeSet(java.util.Set) call -->
 <property name="someSet">
    <set>
        <value>just some string</value>
        <ref bean="myDataSource" />
    </set>
 </property>
</bean>
注意,mapkey或者value的值,可以使下面的元素中的一種:
bean | ref | idref | list | set | map | props | value | null
3.3.3.4.1 集合合併
Spring2.0開始,容器也支持集合的合併。應用開發者可以定義一個父類型的<list/>, <map/>, <set/>或者<props/>元素,然後定義子類型的<list/>, <map/>, <set/>或者<props/>元素,以實現對於父集合值的繼承和重載;子集合的值是父、子集合的值合併的結果,子集合元素重載對應的父集合的值。
請注意,合併的定義使用的是父子bean機制(parent-child)。該概念還仍然沒介紹,所以不熟悉的讀者在繼續之前,需要閱讀一下相關的章節。(參看第3.6章,“bean定義繼承”)。
下面的例子能夠很好的演示這個特性:
<beans>
<bean id="parent" abstract="true" class="example.ComplexObject">
    <property name="adminEmails">
        <props>
            <prop key="administrator">[email protected]</prop>
            <prop key="support">[email protected]</prop>
        </props>
    </property>
</bean>
<bean id="child" parent="parent">
    <property name="adminEmails">
        <!-- the merge is specified on the *child* collection definition -->
        <props merge="true">
            <prop key="sales">[email protected]</prop>
            <prop key="support">[email protected]</prop>
        </props>
    </property>
</bean>
<beans>
請注意child bean的定義中,adminEmails屬性的<props/>元素中,merge屬性設置爲true。當child bean實際在容器中實例化的後,實例將會擁有父、子bean合併後的adminEmails屬性集合。
注意,子屬性集合的值繼承了所有父屬性集合的元素值,同時,子屬性集合中的support值重載了父集合中的值。
合併行爲可以類似的應用與<list/>, <map/>, <set/>集合類型。對於<list/>元素,與List集合類型(例如一個有序集合值)關聯的語義會被維護,父值始終前導子值。對於MapSetProperties集合類型,並不是有序的,因此,對於MapSetProperties集合的實現類型,容器並不維護有序的語義。
最後,再說明幾個關於合併的問題;不可以合併不同類型集合(如不能合併MapList),因此,如果你嘗試那麼做,那麼一個異常將被拋出;需要明確的一點是,'merge'屬性必須在“低層次”(lower level)設置,並且是繼承型且子定義型的;如果在父集合定義中指定'merge'屬性,那麼並不會產生期望的合併;最後,請注意合併特性只有在Spring2.0中可以使用。(或者以後的版本)
3.3.3.4.2 強類型集合(僅對於Java5+
如果你有幸成爲Java5Tiger)用戶之一,你將會瞭解到它是支持強類型集合的(我推薦的)。也就是說,可以聲明一個只包含String元素的Collection類型。
如果你利用Spring將強類型的Collection注射到bean,那麼你可以利用Spring的類型轉化(type-conversion)支持,它在元素被添加到Collection之前,將強類型Collection實例的元素轉化爲合適的類型。
下面的例子可以清晰地說明這點;參看下面的類定義和它的XML配置內容...
public class Foo {
                
    private Map<String, Float> accounts;
    
    public void setAccounts(Map<String, Float> accounts) {
        this.accounts = accounts;
    }
 
}
<beans>
    <bean id="foo" class="x.y.Foo">
        <property name="accounts">
            <map>
                <entry key="one" value="9.99"/>
                <entry key="two" value="2.75"/>
                <entry key="six" value="3.99"/>
            </map>
        </property>
    </bean>
</beans>
當注射'foo'bean'accounts'屬性的時候,可以通過反射機制來獲取強類型集合Map<String,Float>的元素類型信息,所以Spring的類型轉換機制將會把值看作是Float類型,因此'9.99', '2.75', '3.99'將被轉換成實際的Float類型。
3.3.3.5 空類型
<null/>元素被用來處理空值。Spring把賦予屬性等的空參數視爲空串。下面的XML配置段演示瞭如何將email屬性置爲空的串值(””)。
<bean class="ExampleBean">
 <property name="email"><value></value></property>
</bean>
上面的配置等同於下面的Java代碼:exampleBean.setEmail(“”)。指定的<null>元素也可以被用來指定空值。例如:
<bean class="ExampleBean">
 <property name="email"><null/></property>
</bean>
上面的配置等同於下面的Java代碼:exampleBean.setEmail(null)
3.3.3.6 XML配置方式的捷徑
需要配置一個值或者bean引用的情況很多見,Spring中有一些比使用<value/>或者<ref/>元素更便捷的方式。<property/>, <constructor-arg/>, <entry/>元素均支持’value’屬性,這可以用來替代內嵌的<value/>元素。參看下面的例子:
<property name="myProperty">
 <value>hello</value>
</property>
<constructor-arg>
 <value>hello</value>
</constructor-arg>
<entry key="myKey">
 <value>hello</value>
</entry>
等同於:
<property name="myProperty" value="hello"/>
<constructor-arg value="hello"/>
<entry key="myKey" value="hello"/>
一般來說,在手工寫配置的時候,建議你使用便捷的方式(Spring團隊也是這麼做的)。<property/><constructor-arg/>元素支持'ref'屬性,可以用來代替內嵌的<ref/>元素。參看下面的例子:
<property name="myProperty">
 <ref bean="myBean">
</property>
<constructor-arg>
 <ref bean="myBean">
</constructor-arg>
等同於:
<property name="myProperty" ref="myBean"/>
<constructor-arg ref="myBean"/>
注意,這種方式是等同於<ref bean=”xxx”>配置的;對於<ref local=”xxx”>並沒有類似的便捷方式。爲了實現強制的本地引用,你必須使用原來的配置方式。
最後,entry元素也支持制定key值或者map值得便捷方式,它使用’key’’key-ref’’value’’value-ref’屬性來實現。參看下面的例子:
<entry>
 <key>
    <ref bean="myKeyBean" />
 </key>
 <ref bean="myValueBean" />
</entry>
等同於:
<entry key-ref="myKeyBean" value-ref="myValueBean"/>
同樣,這種方式是等同於<ref bean=”xxx”>配置的;對於<ref local=”xxx”>並沒有類似的便捷方式。
3.3.3.7 複合屬性的命名
在設置bean屬性的時候,複合或者內嵌的屬性命名是合法的,只要所有在路徑上除了最後屬性都是非空。例如:
<bean id="foo" class="foo.Bar">
 <property name="fred.bob.sammy" value="123" />
</bean>
Foo這個bean有一個叫做fred的屬性,而fred則有一個叫做bob的屬性,同時bob則有一個叫做sammy的屬性,最後的這個屬性sammy被設置爲123Foofred屬性,fredbob屬性必須在bean被構造後保持非空,否則NullPointerException異常將被拋出。
3.3.4使用depends-on
大多數情況下,bean的依賴可以簡單的通過設置屬性來實現,通常利用XML配置的<ref/>元素來實現。另外一種方式是,bean會被賦予依賴的對象ID(使用串值或者<idref/>元素)。第一方式,bean以程序方式向容器請求它的依賴對象。而第二種,在依賴產生之前,被依賴的對象實際上已經被實例化。
有些情況下,bean之間的依賴相對不是很直接(例如,數據庫驅動註冊,此時,一個靜態的類初始化方法需要被觸發),那麼可以使用'depends-on'屬性來在bean被使用之前,顯式的觸發它的實例化。參看下面的例子:
<bean id="beanOne" class="ExampleBean" depends-on="manager"/>
 
<bean id="manager" class="ManagerBean" />
如果需要表示多個bean的依賴,可以用逗號,空格或者分號等所有合法的分隔符將'depends-on'屬性的值隔開,參看下面的例子,它演示瞭如何配置對於多個bean的依賴。
<bean id="beanOne" class="ExampleBean" depends-on="manager,accountDao">
 <property name="manager" ref="manager" />
</bean>
 
<bean id="manager" class="ManagerBean" />
<bean id="accountDao" class="x.y.jdbc.JdbcAccountDao" />
3.3.5懶實例化bean
ApplicationContext的默認行爲是在啓動的時候,儘可能早的預實例化所有的單例bean。所謂“預實例化”是指ApplicationContext實例會在自身的初始化過程中,儘可能的創建和配置所有的單例bean。通常這是正確的,因爲這意味着在配置中,或者在相關輔助環境中的錯誤會被立刻發現(相比可能會在數小時甚至數天才被發現而言)。
然後,有時這種默認的行爲卻不是我們所需要的。當你不需要ApplicationContext預實例化單例bean時,你可以(在bean定義文件的基礎上)有選擇地控制這種默認行爲,將bean設置爲懶實例化(lazy-initialized)。懶實例化bean告訴容器實在啓動時創建,還是當被請求的時候才創建。
通過XML配置bean時,是通過'lazy-init'屬性來控制“懶加載”(lazy-loading)的。參看下面的例子:
<bean id="lazy" class="com.foo.ExpensiveToCreateBean" lazy-init="true">
    <!-- various properties here... -->
</bean>
 
<bean name="not.lazy" class="com.foo.AnotherBean">
    <!-- various properties here... -->
</bean>
對於上面的配置,叫做’lazy’beanApplicationContext初始化時不會被預實例化,而叫做’not.lazy’bean則會被儘早的預實例化。
關於懶實例化,需要說明的一點是,即使bean被配置成懶實例化,假如該bean是一個非懶實例化的bean的依賴,那麼在ApplicationContext預實例化bean的時候,會創建它的所有依賴,即使它們中存在懶實例化的bean!所以,當容器把你配置成懶實例化的bean實例化的時候,不必疑惑;那是因爲,這個懶實例化的bean是被依賴注射到了你的配置中其他某個地方的非懶實例化的單例bean
在容器層次,還可以通過使用<beans/>元素的'default-lazy-init'屬性實現懶實例化;參看下面的例子:
<beans default-lazy-init="true">
    <!-- no beans will be eagerly pre-instantiated... -->
</beans>
3.3.6自裝配的協作者
SpringIoC容器可以自裝配Bean之間的協作關係。這意味着,可以讓Spring通過檢查BeanFactory的內容,來解析bean的協作關係。自裝配有5種模式。自裝配是針對bean定義的,因此,可以指定一些bean自裝配,而另外一些則不需要。使用自裝配模式,完全可能不需要指定屬性或者構造器參數,這樣可以少一些手工配置。在XML配置中,自裝配模式通過使用<bean/>autowire屬性來實現。參看,autowire允許的值列表:
 
 
 
 
 
 
 3.2. 自裝配模式
模式
解釋
no
不使用自裝配模式。Bean的引用使用ref元素定義。這是默認方式,對於大多數開發者來說,建議不要修改這個默認配置,因爲,顯式的指定協作者的方式有更多的可控性和明確性。從某種意義上說,這是系統構造的標準方式。
byName
屬性名方式自裝配。容器會尋找和屬性同名的bean來進行裝配。例如,假設有一個bean被設置成這種方式,它包含一個master屬性(也就是說,它有一個說它Master(...)方法),Spring會查詢命名爲masterbean,然後用它來設置屬性。
byType
屬性類型方式自裝配。這種方式假定在容器中存在一個和屬性類型相同的bean。如果在容器中有兩個這樣的bean存在,那麼將會拋出致命異常,這種情況下是不允許使用這種方式的。如果,沒有這樣的bean,那麼沒有任何問題;該屬性不被設置。如果需要有提示,那麼將dependency-check屬性設置爲”objects”,那麼這種情況下,將會拋出一個錯誤。
constructor
類似於byType方式,但是針對的是構造器參數。如果在容器中,不存在與構造器參數類型相同的bean,致命異常被拋出。
autodetect
通過bean類自己來選擇constructor還是byType方式。如果bean採用的是默認的構造器,那麼將會使用byType方式。
注意,對於propertyconstructor-arg的顯式依賴設置會覆蓋自裝配模式。也要注意一點的是,自裝配模式不適用與簡單類型屬性,如原始類型、StringsClasses類型(包括這些簡單類型組成的數組類型)。(儘管這是應該被設計和考慮實現的一個特性)自裝配模式應該和依賴檢查結合起來,這樣,在裝配完成後,可以進行進一步的檢查。
瞭解自裝配模式的優劣也是很重要的。
下面是優點:
l         自裝配模式可以大大減少手工配置內容。然而,在這方面,諸如bean模版等機制也可以做到(在後面討論)。
l         自裝配模式在對象發生更新的時候,可以自動的更新配置情況。例如,當需要爲一個類增加額外的依賴時,那麼通過自裝配可以在不修改配置信息的情況下實現。因此,在開發期間,自裝配模式什麼有用,當代碼基線逐步穩定以下,可以在轉換成顯式配置方式。
下面是缺點:
l         自裝配模式相比而言更加具有不可確定性。儘管,在上面的表格中提到,Spring儘可能的避免因爲猜測而導致不可預知的結果,但是Spring管理的那些對象之間的關係變得不再明確。
l         對於那些需要從Spring容器獲取信息,然後產生描述信息的工具來說,裝配過程信息不可利用;
l         當容器中只存在唯一一個與setter方法或者構造器參數類型匹配的bean定義的時候,自裝配模式才能正常工作。如果存在任何潛在的不確定性,那麼你最好還是進行顯式裝配。
具體使用哪種方式都沒有“對”或者“錯”。但在一個項目中,保持一致性還是最好的方式;例如,如果在項目中基本沒有使用自裝配模式,當你只是在一兩個bean定義中採用這種方式的時候,很可能讓人產生迷惑。
3.3.6.1 禁止Bean的自裝配
你也可以禁止bean的自裝配模式。當使用SpringXML配置bean時,<bean/>
'autowire-candidate'屬性可以設置成’false’;這樣,容器就會將該bean從自裝配體系中排除出去。
當你需要指定某個bean禁止以自裝配方式注射到其他bean的時候,這個屬性就變得
很有用。當然,這並不是說,被排除的bean自身不能使用自裝配方式...,而是說它自身不可以作爲自裝配的候選對象。
3.3.7依賴檢查
 SpringIoC容器可以檢查當前容器中的bean的未解析依賴的存在性。檢查的內容包括那些沒有在bean定義中設置值的bean屬性,或者那些應該通過自裝配模式設置的屬性。
 當你需要確認bean的所有屬性(或某種類型的所有屬性)是否被設置時,該特性就顯得很有用了。當然,大多數情況下,bean會有屬性的默認值,或者有些屬性在有些場景下不需要賦值,因此,該特性最好有節制的使用。依賴檢查同樣可以對於單獨的bean進行配置。依賴檢查包括幾種不同的模式。使用XML配置時,通過指定'dependency-check'屬性來實現。該屬性可以取下面的值。
 
 
 3.3. 依賴檢查模式
模式
解釋
none
不執行依賴檢查。即使bean屬性沒有賦值也沒有關係。
simple
對於原始類型和集合類型屬性的依賴檢查。(不檢查協作者類型依賴,如bean的引用)
object
對於協作者的依賴檢查。
all
對於協作者、原始類型和集合類型的依賴檢查。
如果是Java5Tiger)用戶,可以參考源碼級的註釋,參考第25.3.1章,“@Required”。
3.3.8方法注射
 在大多數應用場合,容器中的bean主要以單例方式存在。如果某個單例bean需要和另外一個單例bean合作,或者某個非單例bean需要和另外一個非單例bean合作,通常的處理方式是將一個定義爲另外一個bean的屬性。然後,對於bean生命週期不同的時,存在一個問題。考慮下面這種情況:單例bean A需要使用非單例bean B(原型),可能A的每個方法均需要調用B。容器僅創建A的一個實例,因此只可以設置A的屬性一次。不可能每次當B被請求的時候,都用新的B實例來設置。
 對於這種情況的一個處理方式是一定程度上放棄控制反轉機制。A可以實現BeanFactoryAware接口,這樣可以被容器感知,然後使用程序方式讓容器在需要的時候重新請求B的實例,如調用getBean(“B”)方法。參看下面的例子:
// a class that uses a stateful Command-style class to perform some processing
package fiona.apple;
 
// lots of Spring-API imports
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.BeanFactoryAware;
 
public class CommandManager implements BeanFactoryAware {
 
   private BeanFactory beanFactory;
 
   public Object process(Map commandState) {
      // grab a new instance of the appropriate Command
      Command command = createCommand();
      // set the state on the (hopefully brand new) Command instance
      command.setState(commandState);
      return command.execute();
   }
 
   // the Command returned here could be an implementation that executes asynchronously, or whatever
   protected Command createCommand() {
      return (Command) this.beanFactory.getBean("command"); // notice the Spring API dependency
   }
 
   public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
      this.beanFactory = beanFactory;
   }
}
上面的例子不是一個合適的解決方法,因爲,業務代碼和Spring框架出現耦合。方法注射-Spring IoC容器的高級特性,可以用清晰的方式處理這種情況。
3.3.8.1 “查找方式”(lookup)的方法注射

方法注射。。。:
…,有點類似於Tapestry 4.0的情況,開發者寫好抽象的屬性,然後Tapestry將會在運行時重載並實現它。
可以參考下面的關於方法注射介紹的blog

 查找方式的方法注射基於容器對於它管理的bean的方法重載能力實現,這樣,容器可以查找需要的bean,然後返回。上面提到的場合中,需要查找的bean是原型的(當然,也可以查找單例,但對於單例可以直接注射實例)。Spring框架使用基於CGLIB庫的字節碼創建方式,動態的創建重載的方法子集,並以此實現方法注射。
 看了前面的代碼片斷(CommandManager類),Spring容器會動態實現對於createCommand()方法的重載。這樣,CommandManager類不會產生對於Spring框架的依賴,可以參看下面改寫後的例子:
package fiona.apple;
 
// no more Spring imports! 
 
public class CommandManager {
 
   public Object process(Object command) {
      // grab a new instance of the appropriate Command interface
      Command command = createCommand();
      // set the state on the (hopefully brand new) Command instance
      command.setState(commandState);
      return command.execute();
   }
 
    // mmm, but where is the implementation of this method?
   protected abstract CommandHelper createHelper();
 
}
 該類(CommandManager)包含了要被注射的方法,該方法必須符合下面的形式:
<public|protected> [abstract] <return-type> theMethodName(no-arguments);
 如果方法是抽象的,動態創建的子類將會實現它。如果不是,那麼將會重載它。參看下面的例子:
<!-- a stateful bean deployed as a prototype (non-singleton) -->
<bean id="command" class="fiona.apple.AsyncCommand" scope="prototype">
 <!-- inject dependencies here as required -->
</bean>
 
<!-- commandProcessor uses statefulCommandHelper -->
<bean id="commandManager" class="fiona.apple.CommandManager">
 <lookup-method name="createCommand" bean="command"/>
</bean>
 Bean commandManager會在每次需要bean command的時候調用它自己的createCommand()方法。
注意,構造器和setter注射都可以使用查找方法注射的方式。
要讓動態生成的子類正常運行,那麼必須把CGLIB這個jar放在類路徑上(classpath)。此外,將要被繼承的類不可以是final的,並且需要被重載的方法也不可以是final的。而且,測試類的抽象方法也有其必要性,因此,你可以自己實現該類的子類,並提供抽象方法的一個實現。最後,要進行方法注射的bean不可以被序列化。
提示:
感興趣的讀者可能發現ServiceLocatorFactoryBean(位於org.springframework.beans.factory.config包)可以利用...使用方法類似於ObjectFactoryCreatingFactoryBean,但允許你使用你自己的lookup接口,而不是必須使用Spring指定的接口,如ObjectFactory。可以參考java文檔(很多)進一步瞭解ServiceLocatorFactoryBean的使用方法(這種方式有利於進一步減少對於Spring框架的依賴)。
3.3.8.2 任意的方法替換
 相比lookup方法注射方式而言,還有一種不太用的方法注射方法,可以用方法替代另外其他方法。用戶可以忽略本節的剩餘部分(描述了Spring的高級特性),等到使用的時候再看。
 當使用XML配置方式時,replaced-method元素用來指定方法的另外一個替代方法。參看下面的類,包括了將要被替換的方法computeValue
public class MyValueCalculator {
 
 public String computeValue(String input) {
    // some real code...
 }
 
 // some other methods...
 
}
 另外一個實現了org.springframework.beans.factory.support.MethodReplacer接口的類提供了另外一個方法。
/** meant to be used to override the existing computeValue
    implementation in MyValueCalculator */
public class ReplacementComputeValue implements MethodReplacer {
 
    public Object reimplement(Object o, Method m, Object[] args) throws Throwable {
        // get the input value, work with it, and return a computed result
        String input = (String) args[0];
        ... 
        return ...;
}
 下面的bean定義將完成方法替代:
<bean id="myValueCalculator class="x.y.z.MyValueCalculator">
 <!-- arbitrary method replacement -->
 <replaced-method name="computeValue" replacer="replacementComputeValue">
    <arg-type>String</arg-type>
 </replaced-method>
</bean>
 
<bean id="replacementComputeValue" class="a.b.c.ReplacementComputeValue"/>
 可以使用<replaced-method>元素中的一個或者多個<arg-type>元素來說明將要被替代的方法的參數特點。注意,對於參數的說明只在有方法需要被替代,而且類中存在多個變量的時候才需要。爲了方便,參數類型的描述可以採用簡寫方式。例如,下面的都是匹配java.lang.String類型的。
    java.lang.String
    String
   Str
 由於參數的數量大多數情況下足以區分每個可能的選擇,這種方式可以節省許多的手工輸入工作,你只需要手工輸入足以表達參數類型的最短的字符串就可以了。
發佈了28 篇原創文章 · 獲贊 6 · 訪問量 13萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章