spring-framework-中文文檔三:依賴注入DI

5.4依賴性

典型的企業應用程序不包含單個對象(或Spring的說法中的bean)。即便最簡單的應用程序也有幾個對象一塊兒工做來展現最終用戶將其視爲一個連貫的應用程序。下一節將介紹如何從定義許多獨立的bean定義到徹底實現的應用程序,在這些應用程序中對象協做實現目標。php

5.4.1依賴注入

依賴注入(DI)是一個過程,經過這種過程,對象能夠經過構造函數參數,工廠方法參數或者在構造或返回對象實例後設置的屬性來定義它們的依賴關係,也就是說,它們使用的其餘對象從工廠方法。容器 在建立bean時會注入這些依賴關係。這個過程從根本上說是相反的,所以名爲 控制反轉(IoC),它自己經過使用類的直接構造或服務定位器模式來控制它本身的依賴關係的實例化或位置html

代碼與DI原理相比更加清晰,而且在對象提供依賴關係時解耦更有效。該對象不查找其依賴項,而且不知道依賴項的位置或類。所以,您的類變得更容易測試,特別是當依賴關係位於接口或抽象基類上時,它們容許在單元測試中使用存根或模擬實現。java

DI存在兩種主要的變體,基於構造器的依賴注入基於Setter的依賴注入mysql

基於構造函數的依賴注入

基於構造器的 DI經過容器調用具備多個參數的構造函數完成,每一個參數表示一個依賴項。調用static具備特定參數工廠方法來構造bean幾乎是等價的,本討論static相似地將參數視爲構造函數和工廠方法。如下示例顯示了只能經過構造函數注入進行依賴注入的類。請注意,這個類沒有什麼 特別之處,它是一個POJO,它不依賴於容器特定的接口,基類或註釋。spring

public class SimpleMovieLister {

  // the SimpleMovieLister has a dependency on a 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...
}
構造函數參數解析

使用參數的類型發生構造函數參數解析匹配。若是bean定義的構造函數參數中沒有可能存在的歧義,那麼在bean定義中定義構造函數參數的順序是當實例化bean時將這些參數提供給相應構造函數的順序。考慮如下課程:sql

package x.y;

public class Foo {

  public Foo(Bar bar, Baz baz) {
      // ...
  }
}

沒有潛在的歧義存在,假設 BarBaz類不經過繼承相關。所以,如下配置能夠正常工做,而且不須要在<constructor-arg/>元素中明確指定構造函數參數索引和/或類型 數據庫

<beans>
  <bean id="foo" class="x.y.Foo">
      <constructor-arg ref="bar"/>
      <constructor-arg ref="baz"/>
  </bean>

  <bean id="bar" class="x.y.Bar"/>
  <bean id="baz" class="x.y.Baz"/>

</beans>

當引用另外一個bean時,類型是已知的,而且能夠發生匹配(就像前面的例子那樣)。當使用簡單類型時,例如 <value>true<value>Spring沒法肯定值的類型,所以沒法在沒有幫助的狀況下按類型進行匹配。考慮如下課程:apache

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;
  }
}

構造函數參數類型匹配

在前面的場景中,若是使用屬性明確指定構造函數參數的類型,容器 能夠使用簡單類型的類型匹配 type例如:編程

<bean  id = 「exampleBean」  class = 「examples.ExampleBean」 > 
<constructor-arg  type = 「int」  value = 「7500000」 /> 
<constructor-arg  type = 「java.lang.String」  value = 「42」 / > 
</ bean>

構造函數參數索引

使用該index屬性來明確指定構造函數參數的索引。例如:api

<bean  id = 「exampleBean」  class = 「examples.ExampleBean」 > 
<constructor-arg  index = 「0」  value = 「7500000」 /> 
<constructor-arg  index = 「1」  value = 「42」 /> 
</ bean >

除了解決多個簡單值的歧義以外,指定索引還解決了構造函數具備兩個相同類型參數的含糊問題。請注意,該索引是基於0的

構造函數參數名稱

從Spring 3.0開始,您還可使用構造函數參數名稱進行值消歧:

<bean  id = 「exampleBean」  class = 「examples.ExampleBean」 > 
<constructor-arg  name = 「years」  value = 「7500000」 /> 
<constructor-arg  name = 「ultimateanswer」  value = 「42」 /> 
</ bean >

請記住,要使這項工做脫離框架,您的代碼必須在啓用了調試標誌的狀況下編譯,以便Spring能夠從構造函數中查找參數名稱。若是你不能用調試標誌編譯你的代碼(或者不想),你可使用 @ConstructorProperties JDK標註明確你的構造函數參數。示例類將不得不以下所示:

package examples;

public class ExampleBean {

  // Fields omitted

  @ConstructorProperties({"years", "ultimateAnswer"})
  public ExampleBean(int years, String ultimateAnswer) {
      this.years = years;
      this.ultimateAnswer = ultimateAnswer;
  }
}

基於Setter的依賴注入

在調用無參數構造函數或無參數static工廠方法來實例化bean以後,基於Setter的 DI經過調用bean上的容器調用setter方法來完成

如下示例顯示了一個只能使用純setter注入進行依賴注入的類。這個類是傳統的Java。這是一個POJO,它不依賴於容器特定的接口,基類或註釋。

public class SimpleMovieLister {

  // SimpleMovieLister對MovieFinder有依賴性
  private MovieFinder movieFinder;

  //一個setter方法,以便Spring容器能夠'注入'一個MovieFinder
  public void setMovieFinder(MovieFinder movieFinder) {
      this.movieFinder = movieFinder;
  }

  //實際「使用」注入的MovieFinder的業務邏輯被省略。
}

ApplicationContext支持它所管理的bean的基於構造函數和基於setter的DI。它也支持基於setter的DI以後,經過構造函數方法已經注入了一些依賴項。你在的形式配置的依賴關係BeanDefinition,這與你使用的PropertyEditor狀況下,將屬性從一種格式轉換爲另外一種。然而,大多數Spring用戶不直接使用這些類(以編程方式),而是使用XML定義文件,而後將其內部轉換爲這些類的實例,並用於加載整個Spring IoC容器實例。

基於構造函數或基於setter的DI?

既然你能夠混合使用基於構造函數和基於Setter的DI,那麼使用強制依賴性的構造函數參數和可選依賴性的設置符是一個很好的經驗法則。請注意,在 setter上使用@Required註釋能夠用來使setter須要依賴關係。

Spring團隊一般主張setter注入,由於大量的構造函數參數可能會變得笨拙,特別是當屬性是可選的時候。Setter方法也使該類的對象能夠從新配置或稍後從新注入。經過JMX MBeans進行管理是一個引人注目的用例。

一些純粹主義者喜歡基於構造函數的注入。提供全部對象依賴性意味着對象老是以徹底初始化的狀態返回給客戶端(調用)代碼。缺點是物體變得不適合從新配置和從新注入。

使用對某個班級最有意義的DI。有時,在處理您沒有來源的第三方課程時,您能夠選擇。遺留類可能不會公開任何setter方法,所以構造函數注入是惟一可用的DI。

依賴性解決過程

該容器執行bean依賴性解析以下:

  1. 使用ApplicationContext描述全部bean的配置元數據建立和初始化。配置元數據能夠經過XML,Java代碼或註釋來指定。

  2. 對於每一個bean,若是使用該屬性而不是普通構造函數,則它的依賴關係以屬性,構造函數參數或靜態工廠方法的參數的形式表示。當bean被實際建立時,這些依賴被提供給bean 

  3. 每一個屬性或構造函數參數都是要設置的值的實際定義,或者是對容器中另外一個bean的引用。

  4. 做爲值的每一個屬性或構造函數參數都從其指定的格式轉換爲該屬性或構造函數參數的實際類型。默認狀況下,Spring可以轉換成字符串格式提供給全部的內置類型,好比數值 intlong, Stringboolean,等。

Spring容器在建立容器時驗證每一個bean的配置,包括驗證bean引用屬性是否引用有效的bean。可是,在實際建立 bean以前,bean屬性自己不會被設置Beans是單身做用域而且被設置爲預先實例化的(默認的)是在建立容器時建立的。範圍在第5.5節「Bean範圍」中定義。不然,只有在請求時才建立bean。建立一個bean可能會致使建立一個bean圖,由於bean的依賴關係及其依賴關係的依賴關係(等等)被建立和分配。

一般你能夠相信春天作正確的事情。它在容器加載時檢測配置問題,好比引用不存在的bean和循環依賴關係。當bean實際建立時,Spring會盡量晚地設置屬性並解決依賴關係。這意味着,若是在建立該對象或其某個依賴關係時遇到問題,那麼請求對象時,正確加載的Spring容器可能會稍後生成異常。例如,因爲缺乏或無效的屬性,bean拋出異常。某些配置問題的可能延遲可見性是緣由ApplicationContext 實現默認預先實例化單例bean。在實際須要這些bean以前,爲了建立這些bean須要必定的時間和內存,您ApplicationContext會在建立時發現配置問題 ,而不是稍後。您仍然能夠重寫此默認行爲,以便單例bean將會進行延遲初始化,而不是預先實例化。

若是不存在循環依賴關係,則當一個或多個協做bean被注入到一個依賴bean中時,每一個協做bean都被注入到依賴bean 以前徹底配置。這意味着若是bean A對bean B有依賴性,Spring IoC容器在調用bean A上的setter方法以前徹底配置bean B.換句話說,bean被實例化(若是不是預先實例化的單例),它的將設置依賴關係,並調用相關的生命週期方法(例如配置的init方法InitializingBean回調方法)。

依賴注入的例子

如下示例使用基於設置者的DI的基於XML的配置元數據。Spring XML配置文件的一小部分指定了一些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;
  }
}

在前面的例子中,setters被聲明爲與XML文件中指定的屬性相匹配。如下示例使用基於構造函數的DI:

<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調用一個static工廠方法來返回對象的一個​​實例:

<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;
  }
}

 

static工廠方法的參數是經過<constructor-arg/>元素提供的,就像實際使用構造函數同樣。工廠方法返回的類的類型沒必要與包含static 工廠方法的類屬於同一類型,儘管在本例中是這樣。實例(非靜態)工廠方法將以基本相同的方式使用(除了使用factory-bean屬性而不是class屬性),所以在此不討論細節。

5.4.2詳細的依賴和配置

如前一節所述,您能夠將bean屬性和構造函數參數定義爲對其餘託管bean(協做者)的引用,或者將它們定義爲內聯定義的值。Spring的基於XML的配置元數據爲此支持其元素<property/>和 <constructor-arg/>元素中的子元素類型 

直線值(基元,Strings等等)

value所述的屬性 <property/>元素指定屬性或構造器參數的人類可讀的字符串表示。如前所述,JavaBeans PropertyEditors用於將這些字符串值從a轉換String爲屬性或參數的實際類型。

<bean id="myDataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">

<!-- results in a setDriverClassName(String) call -->
<property name="driverClassName" value="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/mydb"/>
<property name="username" value="root"/>
<property name="password" value="masterkaoli"/>
</bean>

如下示例使用p-namespace進行更簡潔的XML配置。

<beans xmlns="http://www.springframework.org/schema/beans"
     xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
     xmlns:p="http://www.springframework.org/schema/p"
     xsi:schemaLocation="http://www.springframework.org/schema/beans
     http://www.springframework.org/schema/beans/spring-beans.xsd">

<bean id="myDataSource" class="org.apache.commons.dbcp.BasicDataSource"
      destroy-method="close"
      p:driverClassName="com.mysql.jdbc.Driver"
      p:url="jdbc:mysql://localhost:3306/mydb"
      p:username="root"
      p:password="masterkaoli"/>

</beans>

前面的XML更簡潔;然而,錯字是在運行時而不是設計時發現的,除非您在建立bean定義時使用支持自動屬性完成的IDE(例如IntelliJ IDEA或SpringSource Tool Suite(STS))。強烈建議這種IDE幫助。 您還能夠將java.util.Properties實例配置爲:

<bean id="mappings"
    class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">

 <!-- typed as a java.util.Properties -->
 <property name="properties">
    <value>
       jdbc.driver.className=com.mysql.jdbc.Driver
       jdbc.url=jdbc:mysql://localhost:3306/mydb
    </value>
 </property>
</bean>

Spring容器經過使用JavaBeans 機制<value/>元素內部的文本 轉換爲 java.util.Properties實例PropertyEditor這是一個很好的捷徑,它是Spring團隊同意<value/>value屬性樣式上使用嵌套元素的少數幾個地方之一

idref元素

idref元素只是一種防錯的方式,將容器中另外一個bean id(字符串值 - 不是引用)傳遞給 <constructor-arg/>or <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」 /> 
</ bean>

 

第一種形式比第二種形式更可取,由於使用 idref標籤容許容器在部署時驗證 所引用的命名bean實際存在。在第二種變體中,不會對傳遞給bean targetName屬性 的值執行驗證 clientclientbean實際實例化時,只會發現錯誤(最有可能致命的結果)若是這個clientbean是一個 原型 bean,那麼這個錯字和產生的異常可能只會在容器被部署後很長時間才被發現。

此外,若是被引用的bean位於同一個XML單元中,而且bean名稱是bean id,則可使用該 local屬性,該屬性容許XML解析器自己在XML文檔分析時更早地驗證bean id。

 
<property  name = 「targetName」 > 
 <! - 一個ID爲'theTargetBean'的bean必須存在; 不然會拋出異常 - >
  <idref  local = 「theTargetBean」 /> 
</ property>

其中<IDREF />元素帶來值A共同的地方(至少在早期比Spring 2.0版本)是在配置AOP攔截在 ProxyFactoryBeanbean定義。指定攔截器名稱時使用<idref />元素能夠防止拼寫錯誤攔截器ID。

參考其餘豆類(協做者)

ref元素是一個<constructor-arg/>或 <property/>定義元素中的最後一個 元素。在這裏,您將bean的指定屬性的值設置爲對由容器管理的另外一個bean(協做者)的引用。被引用的bean是其屬性將被設置的bean的依賴項,而且在屬性設置以前根據須要初始化它。(若是協做者是單身bean,它可能已被容器初始化。)全部引用最終都是對另外一個對象的引用。劃定範圍和有效性取決因而否經過指定其餘對象的ID /名稱 beanlocal,或 parent屬性。

經過標記bean 屬性指定目標bean <ref/>是最通用的形式,而且容許建立對同一個容器或父容器中的任何bean的引用,而無論它是否位於同一個XML文件中。bean屬性的值可能id與目標bean 屬性相同,或者與目標bean屬性中的值之一相同name

<ref  bean = 「someBean」 />

經過local 屬性指定目標bean 利用XML解析器驗證同一文件中的XML id引用的能力。local屬性的值 必須id與目標bean 屬性相同 若是在同一文件中找不到匹配的元素,則XML解析器會發出錯誤。所以,若是目標bean位於同一個XML文件中,則使用本地變體是最佳選擇(以便儘量早地瞭解錯誤)。

<ref  local = 「someBean」 />

經過parent 屬性指定目標Bean 將建立對當前容器的父容器中的bean的引用。parent 屬性的值可能id與目標bean 屬性或目標bean 屬性中的一個值相同name,而且目標bean必須位於當前bean的父容器中。您主要在具備容器層次結構時使用此bean參考變體,而且想要使用與父bean名稱相同的代理將父容器中的現有bean包裝在父容器中。

<! - 在父上下文中 - >
 <bean  id = 「accountService」  class = 「com.foo.SimpleAccountService」 > 
  <! - 根據須要插入依賴關係 - >
 </ bean>



<! - 在子(後代)上下文中 - >
 <bean  id = 「accountService」   < -  bean名稱與父bean相同 - >
    類= 「的org.springframework.aop.framework.ProxyFactoryBean」>
    <property  name = 「target」 > 
        <ref  parent = 「accountService」 />   <! - 注意咱們如何引用父bean  - >
     </ property> 
  <! - 根據須要插入其餘配置和依賴關係 - >
 </ bean>

內置bean

<bean/>內部的元件 <property/>或 <constructor-arg/>元件定義了一個所謂的 內部bean

<bean  id = 「outer」  class = 「...」 > 
<! - 而不是使用對目標bean的引用,只需定義目標bean inline  - >
 <property  name = 「target」 > 
  <bean  class = 「com.example.Person」 >  <! - 這是內部bean  - >
     <property  name = 「name」  value = 「Fiona Apple」 /> 
    <property  name = 「age」  value = 「25」 /> 
  < / bean> 
</ property> 
</ bean>

內部bean定義不須要定義的id或名稱; 該容器忽略這些值。它也忽略了 scope國旗。內部bean 始終是匿名的,而且它們 始終使用外部bean建立。這是 不是能夠內部bean注入到協做不是進入封閉豆等豆類。

集合

<list/>, <set/><map/>,和 <props/>元素,你將Java的性能和參數Collection類型 ListSet, Map,和 Properties分別。

<bean  id = 「moreComplexObject」  class = 「example.ComplexObject」 > 
<! - 致使setAdminEmails(java.util.Properties)調用 - >
 <property  name = 「adminEmails」 > 
  <props> 
      <prop  key = 「管理員「 > administrator@example.org </ prop> 
      <prop  key = 」support「 > support@example.org </ prop> 
      <prop  key = 」development「 > development@example.org </ prop>
  </ props> 
</ property>
<! - 產生一個setSomeList(java.util.List)調用 - >
 <property  name = 「someList」 > 
  <list> 
      <value>一個列表元素,後跟一個引用 </ value> 
      <ref  bean = 「myDataSource /> 
  </ list> 
</ property> 
<! - 致使setSomeMap(java.util.Map)調用 - >
 <property  name = 」someMap「 > 
  <map> 
      <entry  key = 」entry「  value = 「只是一些字符串」 /> 
      <entry  key = 「a ref」  value-ref = 「myDataSource」/> 
  </ map> 
</ property> 
<! - 致使setSomeSet(java.util.Set)調用 - >
 <property  name = 「someSet」 > 
  <set> 
      <value>只是一些字符串</ value> 
      <ref  bean = 「myDataSource」 /> 
  </ set> 
</ property> 
</ bean>

映射鍵或值或設置值的值也能夠是如下任何元素:

bean | ref | idref | 列表| set | 地圖| 道具| 值| 空值
集合合併

從Spring 2.0開始,容器支持 合併集合。應用程序開發人員能夠定義一個父風格<list/>, <map/><set/>或 <props/>元素,並有孩子式的 <list/><map/><set/><props/> 元素繼承和父集合覆蓋值。也就是說,子集合的值是合併父集合和子集合元素的結果,子集合元素覆蓋父集合中指定的值。

這部分關於合併討論了父子bean機制。不熟悉父代和子代bean定義的讀者可能但願在繼續以前閱讀相關章節

如下示例演示了集合合併:

<beans>
<bean id="parent" abstract="true" class="example.ComplexObject">
  <property name="adminEmails">
      <props>
          <prop key="administrator">administrator@example.com</prop>
          <prop key="support">support@example.com</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">sales@example.com</prop>
          <prop key="support">support@example.co.uk</prop>
      </props>
  </property>
</bean>
<beans>

注意在bean定義merge=true屬性的<props/>元素上使用 adminEmails屬性 child當 childbean被容器解析並實例化時,生成的實例具備一個adminEmailsProperties 集合,該 集合包含合併子集合 adminEmails與父 adminEmails集合的結果。

administrator=administrator@example.com 
sales=sales@example.com 
support = support@example.co.uk

孩子Properties集合的值設置繼承父全部屬性元素 <props/>,和孩子的爲值 support值將覆蓋父集合的價值。

這一合併行爲一樣適用於 <list/><map/>和 <set/>集合類型。<list/>元素的特定狀況下,List集合類型相關聯的語義(即ordered值集合的概念)被保留; 父項的值在全部子項列表的值以前。在的狀況下Map, Set和 Properties集合類型,沒有順序存在。所以,沒有排序的語義在背後的關聯的集合類型的效果 MapSet以及 Properties該容器內部使用實現類型。

收集合並的限制

您不能合併不一樣的集合類型(如a Map和a List),而且若是您確實嘗試這樣作,Exception則會引起適當的集合類型 該 merge屬性必須在較低的繼承的子定義上指定; merge 在父集合定義上指定屬性是多餘的,而且不會致使所需的合併。合併功能僅在Spring 2.0及更高版本中可用。

強類型集合(僅限Java 5+)

在Java 5及更高版本中,您可使用強類型集合(使用泛型類型)。也就是說,能夠聲明一個 Collection只能包含String元素的類型(例如)。若是您使用Spring將強類型依賴注入 Collection到bean中,則能夠利用Spring的類型轉換支持,以便強類型Collection 實例的元素在添加到類型以前轉換爲適當的類型Collection

 

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>

bean accounts屬性 foo準備注入時,有關強類型的元素類型的泛型信息 Map<String, Float>可經過反射得到。所以,Spring的類型轉換基礎結構將各類值元素識別爲類型 Float和字符串值9.99, 2.75,並將3.99其轉換爲實際Float類型。

空和空字符串值

春天將屬性等的空論據視爲空白 Strings如下基於XML的配置元數據片斷將email屬性設置爲空 String值(「」)

<bean  class = 「ExampleBean」 > 
<property  name = 「email」  value = 「」 /> 
</ bean>

前面的示例等同於如下Java代碼: exampleBean.setEmail("")該 <null/>元素處理null 值。例如

<bean  class = 「ExampleBean」 > 
<property  name = 「email」 > <null /> </ property> 
</ bean>

以上配置至關於如下Java代碼: exampleBean.setEmail(null)

帶有p命名空間的XML快捷方式

p-名稱空間使您可使用bean 元素的屬性(而不是嵌套 <property/>元素)來描述屬性值和/或合做bean。

Spring 2.0和更高版本支持帶有命名空間的可擴展配置格式,這些命名空間基於XML模式定義。beans本章討論配置格式在XML Schema文檔中定義。可是,p-namespace並未在XSD文件中定義,而只存在於Spring的核心中。

如下示例顯示瞭解析爲相同結果的兩個XML片斷:第一個使用標準XML格式,第二個使用p命名空間。

<beans  xmlns = 「http://www.springframework.org/schema/beans」 
  xmlns:xsi = 「http://www.w3.org/2001/XMLSchema-instance」 
  xmlns:p = 「http:// www .springframework.org / schema / p「 
  xsi:schemaLocation = 」http://www.springframework.org/schema/beans 
      http://www.springframework.org/schema/beans/spring-beans.xsd「 > 

  <bean  name = 「classic」  class = 「com.example.ExampleBean」 > 
      <property  name = 「email」  value = 「 foo@bar.com 」/> 
  </ bean> 

  <bean name = 「p-namespace」  class = 「com.example.ExampleBean」 
        p:email = 「 foo@bar.com 」 /> 
</ beans>

該示例在bean定義中顯示了名爲email的p名稱空間中的一個屬性。這告訴Spring包含一個屬性聲明。如前所述,p-名稱空間沒有模式定義,所以您能夠將該屬性的名稱設置爲屬性名稱。

下一個示例包含兩個更多的bean定義,這兩個定義均可以引用另外一個bean:

<beans xmlns="http://www.springframework.org/schema/beans"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xmlns:p="http://www.springframework.org/schema/p"
  xsi:schemaLocation="http://www.springframework.org/schema/beans
      http://www.springframework.org/schema/beans/spring-beans.xsd">

  <bean name="john-classic" class="com.example.Person">
      <property name="name" value="John Doe"/>
      <property name="spouse" ref="jane"/>
  </bean>

  <bean name="john-modern"
      class="com.example.Person"
      p:name="John Doe"
      p:spouse-ref="jane"/>

  <bean name="jane" class="com.example.Person">
      <property name="name" value="Jane Doe"/>
  </bean>
</beans>

 

正如你所看到的,這個例子不只包含使用p-命名空間的屬性值,還使用特殊格式來聲明屬性引用。第一個bean定義用於 <property name="spouse" ref="jane"/>建立從bean john到bean 的引用jane,而第二個bean定義 p:spouse-ref="jane"用做屬性來完成一樣的事情。在這種狀況下spouse是屬性名稱,而該-ref部分代表這不是一個正值,而是對另外一個bean的引用。

[注意]

p-名稱空間不如標準XML格式那麼靈活。例如,聲明屬性引用的格式與結尾的屬性發生衝突Ref,而標準的XML格式則不會。咱們建議您謹慎選擇您的方法,並將其傳達給您的團隊成員,以免生成同時使用全部三種方法的XML文檔。

帶有c-namespace的XML快捷方式

相似於「帶有p-namespace的XML快捷方式」一節,Spring 3.1中新引入c-namespace容許使用內聯屬性來配置構造函數參數,而不是嵌套constructor-arg 元素。

讓咱們回顧一下從例子中稱爲「基於構造函數的依賴注入」一節c命名空間:

 

<beans xmlns = 「http://www.springframework.org/schema/beans」 
  xmlns:xsi = 「http://www.w3.org/2001/XMLSchema-instance」 
  xmlns:c = 「http:// www .springframework.org /模式/ C」 
  的xsi:的schemaLocation = 「http://www.springframework.org/schema/beans
       HTTP://www.springframework.org/schema/beans/spring-beans.xsd」>
 
  <bean id = 「bar」  class = 「xyBar」 /> 
  <bean id = 「baz」  class = 「xyBaz」 /> 

  < - 'traditional'declaration - > 
  <bean id = 「foo」 class = 「xyFoo」 >
      <constructor-arg ref = 「bar」 /> 
      <constructor-arg ref = 「baz」 /> 
      <constructor-arg value = 「 foo@bar.com 」 /> 
  </ bean> 

  < - 'c-namespace'聲明- > 
  <bean id = 「foo」  class = 「xyFoo」 c:bar-ref = 「bar」 c:baz-ref = 「baz」 c:email = 「 foo@bar.com 」 > 

</ beans>

c:命名空間使用相同的約定做爲p:一個(後-ref爲bean引用),供他們的名字設置構造函數的參數。一樣,即便它沒有在XSD模式中定義(但它存在於Spring內核中),也須要聲明它。

對於構造函數參數名稱不可用的罕見狀況(一般若是字節碼是在沒有調試信息的狀況下編譯的),可使用回退參數索引:

< - 'c-namespace'索引聲明 - > 
<bean id = 「foo」  class = 「xyFoo」 c:_ 0 -ref = 「bar」 c:_ 1 -ref = 「baz」 >
[注意]

因爲XML語法,索引表示法要求存在前導_,由於XML屬性名稱不能以數字開頭(即便某些IDE容許)。

實際上,構造器解析機制在匹配參數方面很是有效,因此除非真的須要,不然咱們建議在整個配置中使用名稱符號。

複合屬性名稱

在設置bean屬性時,只要最終屬性名稱之外的路徑的全部組件都不是,就可使用複合或嵌套屬性名稱null考慮下面的bean定義。

<bean  id = 「foo」  class = 「foo.Bar」 > 
<property  name = 「fred.bob.sammy」  value = 「123」 /> 
</ bean>

foobean有一個fred 屬性,該屬性具備一個屬性,該bob屬性具備 sammy屬性,而且該最終 sammy屬性被設置爲該值 123爲了使這一工做, fred財產foobob財產fred毫不能 null豆後構造,或 NullPointerException拋出。

5.4.3使用 depends-on

若是一個bean是另外一個bean的依賴,那一般意味着一個bean被設置爲另外一個bean的屬性。一般,您可使用基於XML的配置元數據中<ref/> 元素完成此操做 可是,有時豆類之間的依賴性不那麼直接; 例如,類中的靜態初始化程序須要被觸發,例如數據庫驅動程序註冊。depends-on使用此元素的bean被初始化以前,屬性能夠明確地強制一個或多個bean被初始化。如下示例使用該 depends-on屬性來表示對單個bean的依賴關係:

<bean  id = 「beanOne」  class = 「ExampleBean」  depends-on = 「 manager 」/> <bean id = 「 manager 」class =「ManagerBean」/>

要表示對多個bean的依賴關係,請提供一個bean名稱列表做爲depends-on屬性的值,並使用逗號,空格和分號做爲有效分隔符

<bean  id = 「beanOne」  class = 「ExampleBean」  depends-on = 「manager,accountDao」 > 
<property  name = 「manager」  ref = 「manager」 /> 
</ bean> 

<bean  id = 「manager」  class = 「 ManagerBean「 /> 
<bean  id = 」accountDao「  class = 」xyjdbc.JdbcAccountDao「 />

 

[注意]

depends-onbean定義中屬性能夠指定一個初始化時間依賴關係,而且在只有singleton bean 的狀況下能夠指定 一個相應的銷燬時間依賴關係。定義depends-on與給定bean 關係的依賴bean在銷燬給定bean以前首先被銷燬。所以depends-on也能夠控制關​​機順序。

5.4.4懶惰初始化的bean

默認狀況下, ApplicationContext 實現急切地建立和配置全部的singleton bean做爲初始化過程的一部分。一般,這種預先實例化是可取的,由於配置或周圍環境中的錯誤是當即發現的,而不是幾小時甚至幾天後。當發生這種狀況是理想的,你能夠經過標記bean定義爲延遲初始化,防止單豆的預實例化。一個惰性初始化bean告訴IoC容器在第一次請求時建立一個bean實例,而不是在啓動時。

在XML中,此行爲由元素lazy-init上的屬性 控制 <bean/>例如:

 

<bean  id = 「lazy」  class = 「com.foo.ExpensiveToCreateBean」  lazy-init =「true」 /> <bean name = 「not.lazy」 class = 「com.foo.AnotherBean」 />

當前面的配置被an消費時 ApplicationContext,名爲bean的bean lazyApplicationContext啓動時並無被急切地預先實例化 ,而not.lazybean被急切地預先實例化。

然而,當一個懶惰初始化bean是一個未通過延遲初始化的單例bean的依賴時,它 ApplicationContext會在啓動時建立延遲初始化的bean,由於它必須知足單例的依賴關係。懶惰初始化的bean被注入一個單獨的bean中,而且沒有被初始化。

您還能夠經過使用元素default-lazy-init上的屬性 來控制容器級別的延遲初始化<beans/>例如:

<beans  default-lazy-init = 「true」 > 
  <! - 沒有bean將被預先實例化 - >
 </ beans>

5.4.5自動裝配合做者

Spring容器能夠自動鏈接合做bean之間的關係。您能夠容許Spring經過檢查Bean的內容來自動爲bean解析協做者(其餘bean)ApplicationContext自動裝配具備如下優勢:

  • 自動裝配能夠顯着減小指定屬性或構造函數參數的須要。(其餘機制,例如本章其餘地方討論的bean模板 在這方面也頗有價值。)

  • 隨着對象的發展,自動裝配能夠更新配置。例如,若是須要向類中添加依賴項,則能夠自動知足該依賴項,而無需修改配置。所以,自動裝配在開發過程當中可能特別有用,並且不會影響代碼庫變得更加穩定時切換到顯式佈線的選項。

當使用基於XML的配置元數據[2]時,可使用元素autowire屬性爲 bean定義指定autowire模式 <bean/>自動裝配功能有五種模式。您能夠指定每一個 bean的自動裝配,所以能夠選擇自動裝配的自動裝配。

表5.2。自動裝配模式

模式 說明
沒有

(默認)無自動裝配。Bean引用必須經過一個ref元素定義建議不要將更改默認設置用於較大的部署,由於指定的協做者明確提供了更好的控制和清晰度。它在必定程度上記錄了系統的結構。

暱稱

按物業名稱自動裝配。Spring會查找與須要自動裝配的屬性同名的bean。例如,若是一個bean定義被設置爲autowire的名稱,而且它包含一個屬性(也就是說,它有一個 setMaster(..)方法),Spring會查找名爲的bean定義master,並使用它來設置屬性。

byType的

若是屬性類型中只有一個bean存在於容器中,則容許屬性爲自動裝配。若是存在多於一個,則會引起致命異常,這代表您可能不會爲該bean 使用byType自動裝配。若是沒有匹配的bean,則什麼都不會發生; 該物業未設置。

constructor

相似於byType,但適用於構造函數參數。若是容器中不存在惟一的構造函數參數類型的bean,則會引起致命錯誤。

 

使用byType構造函數 自動裝配模式,您能夠連線陣列和類型集合。在這種狀況下 全部被提供在所述容器內自動裝配候選匹配所指望的類型,爲了知足的依賴性。若是預期的密鑰類型是,則能夠自動裝入強類型的地圖 String自動裝配的Maps值將由全部與預期類型匹配的bean實例組成,Maps鍵將包含相應的bean名稱。

您能夠將autowire行爲與依賴性檢查結合起來,這是在自動裝配完成後執行的。

自動裝配的侷限和缺點

自動裝配在項目中一導致用時效果最佳。若是一般不使用自動裝配,開發人員可能會使用它來僅鏈接一個或兩個bean定義。

考慮自動裝配的侷限性和缺點:

  • 顯式依賴關係property和 constructor-arg設置老是覆蓋自動裝配。您不能自動調用所謂的 簡單屬性,如基元 Strings,和Classes (以及這種簡單屬性的數組)。這個限制是經過設計。

  • 自動裝配不如準確佈線。雖然,如上表所述,Spring在注意避免猜想可能會有意想不到的結果的狀況下進行猜想,但您的Spring管理的對象之間的關係再也不明確記錄。

  • 佈線信息可能沒法用於可能從Spring容器生成文檔的工具。

  • 容器中的多個bean定義能夠匹配由setter方法或構造函數參數指定的類型以進行自動裝配。對於數組,集合或地圖,這不必定是個問題。然而,對於指望單一值的依賴關係,這種不明確性不是任意解決的。若是沒有惟一的bean定義可用,則拋出異常。

在後一種狀況下,您有幾種選擇:

  • 放棄自動佈線以支持顯式佈線。

  • 經過設置其autowire-candidate屬性, 避免爲bean定義自動裝配, false以下一節所述。

  • 經過將元素 屬性 設置爲,將單個bean定義指定爲 主要候選者 primary<bean/>true

  • 若是您使用的是Java 5或更高版本,請參見第5.9節「基於註釋的容器配置」中所述實現基於註釋的配置可用的更細粒度的控制

從自動裝配中排除bean

在每一個bean的基礎上,您能夠從自動裝配中排除一個bean。在Spring的XML格式中,設置元素autowire-candidate 屬性<bean/>爲 false容器使該特定的bean定義對自動裝配基礎結構不可用(包括註釋樣式配置等@Autowired)。

您還能夠根據與bean名稱的模式匹配來限制自動導向候選項。頂級<beans/> 元素在其default-autowire-candidates屬性中接受一個或多個模式 例如,要將autowire候選者狀態限制爲名稱以存儲庫結尾的任何bean ,請提供值* Repository。要提供多種模式,請在逗號分隔列表中定義它們。 bean定義屬性的顯式值true或者false對於bean定義autowire-candidate屬性的顯式值老是優先的,對於這樣的bean,模式匹配規則不適用。

這些技術對於不想經過自動裝配注入其餘bean的bean很是有用。這並不意味着排除的bean自己不能使用自動裝配進行配置。相反,該bean自己不是自動裝配其餘bean的候選者。

5.4.6方法注入

在大多數應用場景中,容器中的大部分bean都是單例當單例bean須要與另外一個單例bean協做,或者非單例bean須要與另外一個非單例bean協做時,一般經過將一個bean定義爲另外一個bean的屬性來處理依賴。當bean生命週期不一樣時會出現問題。假設單例bean A須要使用非單例(原型)bean B,可能在A上的每一個方法調用上。容器只建立一次單例bean A,所以只有一次機會來設置屬性。每次須要時,容器都不能向bean A提供bean B的新實例。

解決方案是放棄一些控制反轉。您能夠經過實現 接口讓bean A知道容器ApplicationContextAware,而且每當bean A須要時,經過對容器的getBean(「B」)調用請求(一般是新的)bean B實例。如下是這種方法的一個例子:

// a class that uses a stateful Command-style class to perform some processing
package fiona.apple;

// Spring-API imports
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;

public class CommandManager implements ApplicationContextAware {

 private ApplicationContext applicationContext;

 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();
 }

 protected Command createCommand() {
    // notice the Spring API dependency!
    return this.applicationContext.getBean("command", Command.class);
 }

 public void setApplicationContext(ApplicationContext applicationContext)
                                                                  throws BeansException {
    this.applicationContext = applicationContext;
 }
}

前面的內容是不可取的,由於業務代碼知道而且耦合到Spring框架。方法注入是Spring IoC容器的一個高級特性,它容許以乾淨的方式處理這個用例。

查找方法注入

查找方法注入是容器覆蓋容器管理的bean上方法的能力,以返回容器中另外一個命名bean的查找結果。查找一般包含一個原型bean,如前一節所述。Spring Framework經過使用CGLIB庫中的字節碼生成來動態生成覆蓋該方法的子類,從而實現了此方法注入。

[注意]

爲了使這個動態子類工做,Spring容器將繼承的類不能成爲final,而且被覆蓋的方法也不能final此外,測試具備abstract方法的類須要您本身對該類進行子類化並提供方法的存根實現abstract最後,已經成爲方法注入目標的對象不能被序列化。從Spring 3.2開始,再也不須要將CGLIB添加到類路徑中,由於CGLIB類在org.springframework下從新打包並在Spring-Core JAR中分發。這是爲了方便起見以及避免與使用不一樣版本的CGLIB的其餘項目的潛在衝突。

查看CommandManager前面的代碼片斷中的類,你會發現Spring容器會動態地覆蓋該createCommand()方法的實現 您的 CommandManager類將不會有任何Spring依賴關係,如在從新構造的示例中所示:

<!-- 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>

在包含要注入的方法的客戶端類(在這種狀況下爲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>

在包含要注入的方法的客戶類( CommandManager在這種狀況下)中,要注入的方法須要如下形式的簽名:

<public | protected> [abstract] <return-type> theMethodName(no-arguments);

若是該方法是abstract,則動態生成的子類將實現該方法。不然,動態生成的子類會覆蓋原始類中定義的具體方法。例如

<! - 做爲原型部署的有狀態bean(非單
實例) - > <bean  id = 「command」  class = 「fiona.apple.AsyncCommand」  scope = 「prototype」 > 
<! - 根據須要在這裏注入依賴關係- >
 </ bean>

<! -  commandProcessor使用statefulCommandHelper  - >
 <bean  id = 「commandManager」  class = 「fiona.apple.CommandManager」 > 
<lookup-method  name = 「createCommand」  bean = 「command」 /> 
</ bean>

標識爲commandManager的bean createCommand()在須要命令 bean 的新實例時調用其本身的方法你必須當心地將commandbean 部署爲原型,若是這其實是須要的話。若是它做爲單例部署command則每次都返回同一個bean 實例

[小費]

感興趣的讀者也能夠找到 ServiceLocatorFactoryBean(在 org.springframework.beans.factory.config包中)使用。ServiceLocatorFactoryBean中使用的方法與另外一個實用程序類類似 ObjectFactoryCreatingFactoryBean,但它容許您指定本身的查找界面,而不是Spring特定的查找界面。有關這些類的JavaDocs以及此博客條目,請參閱ServiceLocatorFactoryBean的其餘信息。

任意方法替換

一種比查找方法更少用的方法注入形式注入可以用另外一種方法實現來替換託管bean中的任意方法。在實際須要功能以前,用戶能夠安全地跳過本節的其他部分。

使用基於XML的配置元數據,您可使用該 replaced-method元素將現有的方法實現替換爲已部署的bean。考慮下面的類,咱們想要覆蓋一個方法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(String)
  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"/>

 

您可使用<arg-type/>元素中的一個或多個包含 元素 <replaced-method/>來指示被覆蓋的方法的方法簽名。只有當方法過載而且類中存在多個變體時,參數的簽名纔是必需的。爲了方便,參數的類型字符串多是徹底限定類型名稱的子字符串。例如,如下所有匹配 java.lang.String

 java.lang.String  String  Str

因爲參數的數量一般足以區分每種可能的選擇,所以只需鍵入與參數類型匹配的最短字符串,此快捷鍵就能夠節省大量輸入。

相關文章
相關標籤/搜索