官术网_书友最值得收藏!

6.2 IoC

IoC容器是Spring框架中非常重要的核心組件,可以說,是伴隨著Spring誕生和成長的。Spring通過IoC容器來管理所有Java對象(也被稱為Spring Bean)及其相互間的依賴關系。本節全面講解IoC容器的概念及用法。

6.2.1 依賴注入與控制反轉

很多人都會被問及“依賴注入(Dependency Injection,DI)”與“控制反轉”之間到底有哪些聯系和區別。在Java應用程序中,無論是受限的嵌入式應用程序,還是多層架構的服務端企業級應用程序,它們通常由來自應用適當的對象進行組合合作。也就是說,對象在應用程序中通過彼此依賴來實現功能。

盡管Java平臺提供了豐富的應用程序開發功能,但它缺乏組織基本構建塊成為一個完整系統的方法。那么,組織系統這個任務最后只能留給架構師和開發人員。開發者可以使用各種設計模式(如Factory、Abstract Factory、Builder、Decorator和Service Locator)來組合各種類和對象實例構成應用程序。雖然這些模式給出了能解決什么類型的問題,但使用模式的一個最大的障礙是,除非開發者有非常豐富的經驗,否則仍然無法在應用程序中正確地使用它,這就給Java開發者設定了一定的技術門檻,特別是那些普通的開發人員。

而Spring框架的IoC(Inversion of Control,控制反轉)組件能夠通過提供正規化的方法來組合不同的組件,使之成為一個完整可用的應用。Spring框架將規范化的設計模式作為一級的對象,這樣方便開發者將之集成到自己的應用程序,這也是很多組織和機構選擇使用Spring框架來開發健壯的、可維護的應用程序的原因。開發人員無須手動處理對象的依賴關系,而是交給了Spring容器去管理,這極大地提升了開發體驗。

那么依賴注入與控制反轉又是什么關系呢?

依賴注入是Martin Fowler在2004年提出的關于控制反轉的解釋(6)。Martin Fowler認為控制反轉一詞讓人產生疑惑,無法直白地理解“到底哪方面的控制被反轉了”。所以,Martin Fowler建議采用依賴注入一詞來代替控制反轉。

依賴注入和控制反轉其實就是一個事物的兩種不同的說法而已,本質上是一回事。依賴注入是一個程序設計模式和架構模型,有時候也稱為控制反轉,盡管從技術上來講,依賴注入是一個控制反轉的特殊實現。依賴注入是指一個對象應用另一個對象來提供一個特殊的能力。例如,把一個數據庫連接以參數的形式傳到一個對象的結構方法里,而不是在那個對象內部自行創建一個連接。依賴注入和控制反轉的基本思想就是把類的依賴從類內部轉化到外部以減少依賴。利用控制反轉,對象在被創建的時候會由一個調控系統統一進行對象實例的管理,將該對象所依賴的對象的引用通過調控系統傳遞給它。也可以說,依賴被注入對象中。所以,控制反轉是關于一個對象如何獲取它所依賴的對象的引用的過程,而這個過程體現為“誰來傳遞依賴的引用”這個職責的反轉。

控制反轉一般分為兩種實現類型,依賴注入和依賴查找(Dependency Lookup)。其中依賴注入應用得比較廣泛。Spring是采用依賴注入這種方式來實現控制反轉的。

6.2.2 IoC容器和Bean

Spring通過IoC容器來管理所有Java對象及其相互間的依賴關系。在軟件開發過程中,系統的各個對象之間、各個模塊之間、軟件系統與硬件系統之間或多或少都會存在耦合關系,如果一個系統的耦合度過高,就會造成難以維護的問題。但是完全沒有耦合的代碼是不能工作的,代碼需要相互協作、相互依賴來完成功能。而IoC的技術恰好解決了這類問題,各個對象之間不需要直接關聯,而是在需要用到對方的時候由IoC容器來管理對象之間的依賴關系,對于開發人員來說只需要維護相對獨立的各個對象代碼即可。

IoC是一個過程,即對象定義其依賴關系,而其他與之配合的對象只能通過構造函數參數、工廠方法的參數或者在工廠方法構造或返回后在對象實例上設置的屬性來定義其依賴關系。然后,IoC容器在創建bean時會注入這些依賴項。這個過程在職責上是反轉的,就是把原先代碼里需要實現的對象創建、依賴的代碼反轉給容器來幫忙實現和管理,所以稱為“控制反轉”。

IoC應用了以下設計模式:

  • 反射:在運行狀態中,根據提供的類的路徑或者類名,通過反射來動態地獲取該類的所有屬性和方法。
  • 工廠模式:把IoC容器當作一個工廠,在配置文件或者注解中給出定義,然后利用反射技術,根據給出的類名生成相應的對象。對象生成的代碼及對象之間的依賴關系在配置文件中定義,這樣就實現了解耦。

org.springframework.beans和org.springframework.context包是Spring IoC容器的基礎。BeanFactory接口提供了能夠管理任何類型的對象的高級配置機制。ApplicationContext是BeanFactory的子接口,它更容易與Spring的AOP功能集成,進行消息資源處理(用于國際化)、事件發布以及作為應用層特定的上下文(例如,用于Web應用程序的WebApplicationContext)。簡而言之,BeanFactory提供了基本的配置功能,而ApplicationContext在此基礎之上增加了更多的企業特定功能。

在Spring應用中,bean是由Spring IoC容器來進行實例化、組裝并受其管理的對象。bean和它們之間的依賴關系反映在容器使用的配置元數據中。

6.2.3 配置元數據

配置元數據描述了Spring容器在應用程序中是如何來實例化、配置和組裝對象的。

最初,Spring用XML文件格式來記錄配置元數據,從而很好地實現了IoC容器本身與實際寫入此配置元數據的格式完全分離。

當然,基于XML的元數據不是唯一允許的配置元數據形式。目前,比較流行的配置元數據的方式是基于注解的配置和基于Java的配置。

  • 基于注解的配置:Spring 2.5引入了支持基于注解的配置元數據。
  • 基于Java的配置:從Spring 3.0開始,Spring JavaConfig項目提供了許多功能,并成為Spring框架核心的一部分。因此,可以使用Java而不是XML文件來定義應用程序類外部的bean。這類注解比較常用的有@Configuration、@Bean、@Import和@DependsOn等。

Spring配置至少需要一個或者多個由容器管理的Bean。基于XML的配置方式,需要用<beans/>元素內的<bean/>元素來配置這些Bean;而在基于Java的配置方式中,通常在使用了@Configuration注解的類中使用@Bean注解的方法。

以下示例顯示基于XML的配置元數據的基本結構。

在上面的XML文件中,id屬性用于標識單個bean定義的字符串。class屬性定義bean的類型,并使用完全限定的類名。id屬性的值是指協作對象。

以下示例顯示基于注解的配置元數據的基本結構。

6.2.4 實例化容器

Spring IoC容器需要在應用啟動時進行實例化。在實例化過程中,IoC容器會從各種外部資源(如本地文件系統、Java類路徑等)加載配置元數據,提供給ApplicationContext構造函數。

下面是一個從類路徑中加載基于XML的配置元數據的例子。

當系統規模比較大時,通常會讓bean定義分到多個XML文件。這樣,每個單獨的XML配置文件通常就能夠表示系統結構中的邏輯層或模塊。就如上面的例子所演示的那樣,當某個構造函數需要多個資源位置時,可以使用一個或多個<import/>來從另一個文件加載bean的定義,例如,

6.2.5 使用容器

ApplicationContext是高級工廠的接口,能夠維護不同bean及其依賴項的注冊表。其提供的方法T getBean(String name, Class<T> requiredType)可以用于檢索Bean的實例。

ApplicationContext讀取bean定義并按如下方式訪問它們:

若配置方式不是XML而是Groovy,則可以將ClassPathXmlApplicationContext改為GenericGroovyApplicationContext。GenericGroovyApplicationContext是另一個Spring框架上下文的實現:

以上是使用ApplicationContext的getBean來檢索Bean的實例的方式。ApplicationContext接口還有其他一些檢索Bean的方法,但理想情況下應用程序代碼不應該使用它們。因為程序代碼根本不需要調用getBean方法的話,就可以完全不依賴于Spring API。例如,Spring與Web框架的集成為各種Web框架組件(如控制器和JSF托管的Bean)提供了依賴注入,允許通過元數據(例如自動裝配注入)聲明對特定bean的依賴關系。

6.2.6 Bean的命名

每個Bean都有一個或多個標識符。這些標識符在托管Bean的容器中必須是唯一的。一個Bean通常只有一個標識符,但是如果它需要多個標識符,那么額外的可以被認為是別名。

在基于XML的配置元數據中,使用id或者name屬性來指定bean標識符。id屬性允許指定一個id。通常,這些標識符的名稱是字母,比如myBean、userService等,但也可能包含特殊字符。如果你想向bean引入其他別名,那么可以在name屬性中指定它們,用“,”“;”或空格分隔。歷史原因,在Spring 3.1以前的版本中,id屬性被定義為一個xsd:ID類型,所以限制了可能的字符。從Spring 3.1開始,它被定義為一個xsd:string類型。注意,雖然類型更改了,但bean id的唯一性仍由容器強制執行。

用戶也可以不必為bean提供名稱或標識符。如果沒有顯式地提供名稱或標識符,容器就會為該bean自動生成一個唯一的名稱。但是,如果要通過名稱引用該bean,就必須提供一個名稱。

在命名bean時盡量遵守使用標準Java約定。也就是說,bean的名字使用以一個小寫字母開頭的駱駝法命名規則,比如accountManager、accountService、userDao、loginController等。使用這樣命名的bean會讓應用程序的配置更易于閱讀和理解。

Spring為未命名的組件生成bean名稱,同樣遵循以上規則。本質上,簡單的命名方式就是直接采用類名稱并將其初始字符變為小寫。但也有特例,當前兩個字符或多個字符是大寫時,我們不進行處理。比如,URL類的bean名稱仍然是URL。這些命名規則定義在java.beans.Introspector.decapitalize方法中。

6.2.7 實例化bean的方式

所謂bean的實例化,就是根據配置來創建對象的過程。

如果是使用基于XML的配置方式,就在<bean />元素的class屬性中指定需要實例化的對象的類型(或類)。這個class屬性在內部實現,通常是一個BeanDefinition實例的Class屬性。但也有例外情況,比如使用工廠方法或者bean定義繼承進行實例化。

使用Class屬性有兩種方式:

  • 通常,容器本身是通過反射機制來調用指定類的構造函數,從而創建bean。這與使用Java代碼的new運算符相同。
  • 通過靜態工廠方法創建,類中包含靜態方法。通過調用靜態方法返回對象的類型可能和Class一樣,也可能完全不一樣。

如果你想配置使用靜態的內部類,就必須用內部類的二進制名稱。例如,在com.waylau包下有一個User類,這個類里面有一個靜態的內部類Account,這種情況下bean定義的class屬性應該是com.waylau.User$Account。這里需要注意,使用“$”字符來分割外部類和內部類的名稱。

概括起來,bean的實例化有3種方式,分別說明如下:

1.通過構造函數實例化

Spring IoC容器可以管理幾乎所有你想讓它管理的類,不限于管理POJO。大多數Spring用戶更喜歡使用POJO(一個默認無參的構造方法和setter、getter方法)。但在容器中使用非bean形式的類也是可以的,比如遺留系統中的連接池,很顯然它與JavaBean規范不符,但Spring也能管理它。

當你使用構造方法來創建bean的時候,Spring對類來說并沒有什么特殊之處。也就是說,正在開發的類不需要實現任何特定的接口或者以特定的方式進行編碼。但是,根據你所使用的IoC類型,可能需要一個默認(無參)的構造方法。

當使用基于XML的元數據配置文件時,可以這樣來指定bean類:

2.使用靜態工廠方法實例化

當采用靜態工廠方法創建bean時,除了需要指定class屬性外,還需要通過factory-method屬性來指定創建bean實例的工廠方法,Spring將調用此方法返回實例對象。就此而言,跟通過普通構造器創建類實例沒什么兩樣。

下面的bean定義展示了如何通過工廠方法來創建bean實例。

以下是基于XML的元數據配置文件:

以下是需要創建實例的類的定義:

注 意

在此例中,createInstance()必須是一個static方法。

3.使用工廠實例方法實例化

通過調用工廠實例的非靜態方法進行實例化與通過靜態工廠方法實例化類似。使用這種方式時,class屬性置為空,而factory-bean屬性必須指定為當前(或其祖先)容器中包含工廠方法的bean的名稱,而該bean的工廠方法本身必須通過factory-method屬性來設定。

以下是基于XML的元數據配置文件:

以下是需要創建實例的類的定義:

當然,一個工廠類也可以有多個工廠方法。以下是基于XML的元數據配置文件:

以下是需要創建實例的類的定義:

6.2.8 注入方式

在Spring框架中,主要有以下兩種注入方式:

1.基于構造函數

基于構造函數的DI是通過調用具有多個參數的構造函數的容器來完成的,每個參數表示依賴關系,這個與調用具有特定參數的靜態工廠方法來構造bean幾乎是等效的。以下示例演示一個只能使用構造函數注入的依賴注入的類,該類是一個POJO,并不依賴于容器特定的接口、基類或注解。

基于構造函數的DI通常需要處理傳參。構造函數的參數解析是通過參數的類型來匹配的。如果bean的構造函數參數不存在歧義,那么構造器參數的順序就是這些參數實例化以及裝載的順序。參考如下代碼:

假設Bar和Baz在繼承層次上不相關,也沒有什么歧義,下面的配置完全可以工作正常,開發者不需要再去<constructor-arg>元素中指定構造函數參數的索引或類型信息。

當引用另一個bean的時候,如果類型確定,匹配就會工作正常(如上面的例子)。

當使用簡單的類型的時候,比如<value>true</value>,Spring IoC容器是無法判斷值的類型的,所以是無法匹配的。考慮代碼如下:

那么,在上面的代碼這種情況下,容器可以通過使用構造函數參數的type屬性來實現簡單類型的匹配,比如:

或者使用index屬性來指定構造參數的位置,比如:

這個索引同時是為了解決構造函數中有多個相同類型的參數無法精確匹配的問題。需要注意的是,索引是從0開始的。

開發者可以通過參數的名稱來去除二義性。

需要注意的是,做這項工作的代碼必須啟用了調試標記編譯,這樣Spring才可以從構造函數查找參數名稱。開發者也可以使用@ConstructorProperties注解來顯式聲明構造函數的名稱,比如如下代碼:

2.基于setter方法

基于setter方法的DI是通過在調用無參數構造函數或無參數靜態工廠方法來實例化bean之后,通過容器調用bean的setter方法完成的。

以下示例演示一個只能使用setter來將依賴進行注入的類。該類是一個POJO,并不依賴于容器特定的接口、基類或注解。

6.2.9 實戰:依賴注入的例子

我們創建一個名為dependency-injection的應用來演示依賴注入的用法。

dependency-injection應用是基于XML的配置方式。我們將會演示基于構造函數的依賴注入,同時會演示如何來解析構造函數的參數。

1.定義服務類

定義了消息服務接口MessageService,該接口的主要職責是打印消息,代碼如下:

消息服務類接口的實現是MessageServiceImpl,返回我們真實想要的業務消息,代碼如下:

其中,MessageServiceImpl是具有帶參的構造函數,username和age是構造函數的參數。這兩個參數最終會在getMessage方法中返回。

2.定義打印器

定義了打印器MessagePrinter,用于打印消息,代碼如下:

我們期望,在執行printMessage方法之后就能將消息內容打印出來。而消息內容是依賴于MessageService提供的。稍后,我們會通過XML配置的方式,來將MessageService的實現進行注入。

3.定義應用主類

Application是應用的入口類,代碼如下:

由于我們的應用是基于XML的配置,因此這里需要ClassPathXmlApplicationContext類。這個類是Spring上下文的其中一種實現,可以實現基于XML的配置加載。按照約定,Spring應用的配置文件spring.xml放置在應用的resources目錄下。

4.創建配置文件

在應用的resources目錄下創建了一個Spring應用的配置文件spring.xml:

在該spring.xml文件中,我們可以清楚地看到bean之間的依賴關系。messageServiceImpl有兩個構造函數的參數:username和age,其參數值在實例化的時候就解析了。messagePrinter引用了messageServiceImpl作為其構造函數的參數。

5.運行

運行Application類就能在控制臺看到“Hello World! Way Lau, age is 30”字樣的信息。

6.2.10 依賴注入的詳細配置

在上面的示例中,我們展示了依賴注入的大部分配置。開發者可以通過定義bean的依賴來引用其他bean或者一些值。Spring基于XML的配置元數據通過支持一些子元素<property/>以及<constructor-arg/>來達到這一目的。這些配置可以滿足應用開發的大部分場景。

下面就這些配置內容進行詳細的講解。

1.直接賦值

直接賦值支持字符串、原始類型的數據。

元素<property/>有value屬性,通過對人友好易讀的形式來配置屬性值或者構造參數。Spring的便利之處就是用來將這些字符串的值轉換成指定的類型。

下面的例子使用的p命名空間是更為簡潔的XML配置。

雖然上面的XML更為簡潔,但是因為屬性的類型是在運行時確定的,而非設計時確定的,所以可能需要IDE特定的支持才能夠自動完成屬性配置。

開發者也可以定義一個java.util.Properties實例,比如:

Spring的容器會將<value/>里面的文本通過使用JavaBean的PropertyEditor機制轉換成一個java.util.Properties實例。這是一個捷徑,也是一些Spring團隊更喜歡使用嵌套的<value/>元素而不是value屬性風格的原因。

2.引用其他bean

如果bean之間有協作的關系,就可以引用其他bean。

ref元素是<constructor-arg/>或者<property/>中的一個終極標簽。開發者可以通過這個標簽配置一個bean來引用另一個bean。當需要引用一個bean的時候,被引用的bean會先實例化,然后配置屬性,也就是引用的依賴。如果該bean是單例的話,那么該bean會由容器初始化。所有引用最終都是對另一個對象的引用。bean的范圍以及校驗取決于開發者是否通過bean、local、parent這些屬性來指定對象的id或者name屬性。

通過指定bean屬性中的<ref/>來指定依賴是常見的一種方式,可以引用容器或者父容器中的bean,無論是否在同一個XML文件定義都可以引用。其中bean屬性中的值可以和其他引用bean中的id屬性一致,或者和其中的一個name屬性一致。

<ref bean="someBean"/>

通過指定bean的parent屬性會創建一個引用到當前容器的父容器中。parent屬性的值可以跟目標bean的id屬性一致,或者和目標bean的name屬性中的一個一致,且目標bean必須是當前引用目標bean容器的父容器。開發者一般只有在存在層次化容器關系,并且希望通過代理來包裹父容器中一個存在的bean的時候才會用到這個屬性。

我們來看這個例子。這個accountService是父容器的bean:

<bean id="accountService" class="com.waylau.SimpleAccountService">
</bean>

在子容器中同樣有一個名為accountService的bean:

由于兩個容器有相同id屬性的bean,因此為了避免歧義,需要加parent屬性的值。

3.內部bean

定義在<bean/>元素的<property/>或者<constructor-arg/>元素之內的bean叫作內部bean。

內部bean的定義是不需要指定id或者名字的。如果指定了,容器就不會用之作為區分bean的標識符。容器同時也會無視內部bean的scope標簽。所以,內部bean總是匿名的,而且總是隨著外部bean同時來創建的。開發者是無法將內部bean注入外部bean以外的其他bean的。

4.集合

在<list/>、<set/>、<map/>和<props/>元素中,開發者可以配置Java集合類型List、Set、Map以及Properties的屬性和參數。示例如下:

當然,map的key、value或者集合的value都可以配置為下列元素:

    bean | ref | idref | list | set | map | props | value | null

5.Null及空字符的值

Spring會將屬性的空參數直接當成空字符串來處理。下面的基于XML的配置會將email屬性配置為String的"":

上面的例子和以下Java代碼的效果是一致的。

    exampleBean.setEmail("");

而<null/>元素則用來處理Null值,代碼如下:

上面的代碼和下面的Java代碼效果是一樣的:

    exampleBean.setEmail(null);

6.XML短域名空間

p命名空間令開發者可以使用bean的屬性,而不用使用嵌套的<property/>元素就能描述開發者想要注入的依賴。以下是使用了p命名空間的例子:

與p命名空間類似,c命名空間允許內聯的屬性來配置構造參數而不用使用constructor-arg元素。c命名空間是在Spring 3.1首次引入的。

下面是一個使用了c命名空間的例子:

7.復合屬性名稱

開發者可以在配置屬性的時候配置復合屬性名稱,只要確保除了最后一個屬性外,其余的屬性值都不能為Null即可。

考慮以下的例子:

foo有一個fred屬性,而其中fred屬性有一個bob屬性,而bob屬性中有一個sammy屬性,最后這個sammy屬性會配置為123。想要上述配置能夠生效,就需要確保foo的fred屬性和fred的bob屬性在構造bean之后不能為Null,否則拋出NullPointerException異常。

6.2.11 使用depends-on

如果一個bean是另一個bean的依賴,那么通常這個bean也是另一個bean的屬性之一。多數情況下,開發者可以在配置XML元數據的時候使用<ref/>標簽。然而,有時bean之間的依賴關系不是直接關聯的。比如需要調用類的靜態實例化器來觸發,類似數據庫驅動注冊。depends-on屬性會使明確的強迫依賴的bean在引用之前就會初始化。下面的例子使用depends-on屬性來表示單例bean上的依賴。

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

如果想要依賴多個bean,就可以提供多個名字作為depends-on的值,以逗號、空格或者分號分割,代碼如下:

6.2.12 延遲加載bean

默認情況下,ApplicationContext會在實例化的過程中創建和配置所有的單例bean。總的來說,這個預初始化是很不錯的。因為這樣能及時發現環境上的一些配置錯誤,而不是系統運行了很久之后才發現。如果這個行為不是迫切需要的,開發者就可以通過將bean標記為延遲加載阻止這個預初始化。延遲初始化的bean會通知IoC不要讓bean預初始化,而是在被引用的時候才會實例化。

在XML中,可以通過<bean/>元素的lazy-init屬性來控制這個行為,代碼如下:

當將bean配置為上面的XML的時候,ApplicationContext中的延遲加載bean是不會隨著ApplicationContext的啟動而進入預初始化狀態的,而那些非延遲加載的bean是處于預初始化狀態的。

然而,如果一個延遲加載的bean作為另一個非延遲加載的單例bean的依賴而存在,那么延遲加載的bean仍然會在ApplicationContext啟動的時候加載,因為作為單例bean的依賴會隨著單例bean的實例化而實例化。

開發者可以通過使用<beans/>的default-lazy-init屬性在容器層次控制bean是否延遲初始化,比如:

<beans default-lazy-init="true">
</beans>

6.2.13 自動裝配

Spring Boot通常使用基于Java的配置,建議主配置是單個@Configuration類。通常定義main方法的類作為主要的@Configuration類。

Spring Boot應用了很多Spring框架中的自動配置功能。自動配置會嘗試根據添加的jar依賴關系自動配置Spring應用程序。例如,如果HSQLDB或者H2在類路徑上,并且沒有手動配置任何數據庫連接bean,那么Spring Boot會自動配置為內存數據庫。

要啟用自動配置功能,需要將@EnableAutoConfiguration或@SpringBootApplication注解添加到一個@Configuration類中。

1.自動配置

在Spring應用中,可以自由使用任何標準的Spring框架技術來定義bean及其注入的依賴關系。為了簡化程序的開發,通常使用@ComponentScan來找到bean,并結合@Autowired構造函數來將bean進行自動裝配注入。這些bean涵蓋了所有應用程序組件,如@Component、@Service、@Repository、@Controller等。下面是一個實際的例子。

如果一個bean只有一個構造函數,就可以省略@Autowired。

2.使用@SpringBootApplication注解

@SpringBootApplication注解是Spring Boot中的配置類注解。由于Spring Boot開發人員總是頻繁使用@Configuration、@EnableAutoConfiguration和@ComponentScan來注解它們的主類,并且這些注解經常被一起使用,因此Spring Boot提供了一種方便的@SpringBootApplication注解來替代。

@SpringBootApplication注解相當于使用@Configuration、@EnableAutoConfiguration和@ComponentScan及其默認屬性。

6.2.14 方法注入

在大多數應用場景下,大多數的bean都是單例的。當這個單例的bean需要和非單例的bean聯合使用的時候,有可能會因為不同的bean生命周期的不同而產生問題。假設單例的bean A在每個方法調用中使用了非單例的bean B,由于容器只會創建bean A一次,而只有一個機會來配置屬性,因此容器無法給bean A每次都提供一個新的bean B的實例。

一個解決方案就是放棄一些IoC。開發者可以通過實現ApplicationContextAware接口,調用ApplicationContext的getBean("B")方法來在bean A需要新的實例的時候來獲取到新的B實例。參考下面的例子:

當然,這種方式有一些弊端,就是需要依賴于Spring的API。這在一定程度上對Spring框架存在耦合。

那么是否有其他方案來避免這些弊端呢?答案是肯定的。

Spring框架提供了<lookup-method/>和<replaced-method/>來解決上述問題。

1.lookup-method注入

lookup-method注入是Spring動態改變bean里方法的實現。其實現原理是利用CGLIB庫,將bean方法執行返回的對象,重新生成子類和重寫配置,從而達到動態改變的效果。

下面來看一個例子:

XML配置如下:

當然,如果是基于注解的配置方式,就可以添加@Lookup注解到相應的方法上:

下面的方式是等效的:

注 意

由于采用CGLIB生成之類的方式,因此需要用來動態注入的類不能是final修飾的,需要動態注入的方法也不能是final修飾的。

同時,還得注意myCommand的scope的配置,如果scope配置為singleton,那么每次調用方法createCommand返回的對象都是相同的;如果scope配置為prototype,那么每次調用返回的對象都不同。

2.replaced-method注入

replaced-method注入是Spring動態改變bean里方法的實現。需要改變的方法使用Spring內原有的其他類(需要繼承接口org.springframework.beans.factory.support.MethodReplacer)的邏輯替換這個方法。通過改變方法執行邏輯來動態改變方法。內部實現為使用CGLIB方法,重新生成子類,重寫配置的方法和返回對象,達到動態改變的效果。

下面來看一個例子:

另一個類則實現了org.springframework.beans.factory.support.MethodReplacer接口。

XML配置如下:

注 意

由于采用CGLIB生成之類的方式,因此需要用來動態注入的類不能是final修飾的,需要動態注入的方法也不能是final修飾的。

6.2.15 bean scope

默認情況下,所有Spring bean都是單例的,意味著整個Spring應用中,bean的實例只有一個。可以在bean中添加scope屬性來修改這個默認值。scope屬性可用的值如表6-1所示。

表6-1 Spring bean scope屬性值

下面詳細討論singleton bean與prototype bean在用法上的差異。

6.2.16 singleton bean與prototype bean

對于singleton bean來說,IoC容器只管理一個singleton bean的共享實例,所有對該bean的請求都會導致Spring容器返回一個特定的bean實例。

換句話說,當定義一個bean并將其定義為singleton時,Spring IoC容器將僅創建一個由該bean定義的對象實例。該單個實例存儲在緩存中,對該bean所有后續請求和引用都將返回緩存中的對象實例。

在Spring IoC容器中,singleton bean是默認的創建bean的方式,可以更好地重用對象,節省了重復創建對象的開銷。

圖6-1所示為singleton bean使用示意圖。

對于prototype bean來說,IoC容器導致在每次對該特定bean進行請求時創建一個新的bean實例。

圖6-1 singleton bean使用示意圖

從某種意義上來說,Spring容器在prototype bean上的作用等同于Java的new操作符,所有過去的生命周期管理都必須由客戶端處理。

圖6-2 prototype bean使用示意圖

使用singleton bean還是prototype bean需要注意業務場景。一般情況下,singleton bean適用于大多數場景,但某些場景(如多線程)需要每次調用都生成一個實例,此時scope就應該設為prototype。

需要注意singleton bean引用prototype bean時的陷阱(7)。你不能依賴注入一個prototype范圍的bean到你的singleton bean中,因為這個注入只發生一次,就是當Spring容器正在實例化singleton bean并解析和注入它的依賴時。如果你不止一次在運行時需要一個prototype bean的新實例,就可以采用方法注入的方式。

6.2.17 理解生命周期機制

在Spring 2.5之后,開發者有3種選擇來控制bean的生命周期行為:

  • InitializingBean和DisposableBean回調接口。
  • 自定義的init()以及destroy方法。
  • 使用@PostConstruct以及@PreDestroy注解。

開發者也可以在Bean上聯合這些機制一起使用。如果一個bean配置了多個生命周期機制,并且含有不同的方法名,執行的順序如下:

  • 包含@PostConstruct注解的方法。
  • 在InitializingBean接口中的afterPropertiesSet()方法。
  • 自定義的init()方法。

銷毀方法的執行順序和初始化的執行順序相同:

  • 包含@PreDestroy注解的方法。
  • 在DisposableBean接口中的destroy()方法。
  • 自定義的destroy()方法。

6.2.18 基于注解的配置

Spring應用支持多種配置方式。除了XML配置之外,開發人員更加流行使用基于注解的配置。基于注解的配置方式允許開發人員將配置信息移入組件類本身中,在相關的類、方法或字段上聲明使用注解。

Spring提供了非常多的注解,比如Spring 2.0引入的用@Required注解來強制所需屬性不能為空。在Spring 2.5中,可以使用相同的處理方法來驅動Spring的依賴注入。從本質上來說,@Autowired注解提供了更細粒度的控制和更廣泛的適用性。Spring 2.5添加了對JSR-250注解的支持,比如@Resource、@PostConstruct和@PreDestroy。Spring 3.0添加了對JSR-330注解的支持,包含在javax.inject包下,比如有@Inject、@Qualifier、@Named和@Provider等。使用這些注解需要在Spring容器中注冊特定的BeanPostProcessor。

注 意

基于注解的配置注入會在基于XML的配置注入之前執行,因此同時使用兩種方式,后面的配置會覆蓋前面裝配的屬性。

1.@Required

@Required注解應用于bean屬性的setter方法,就像下面這個示例:

這個注解只是表明受影響的bean的屬性必須在bean的定義中或者自動裝配中通過明確的屬性值在配置時來填充。如果受影響的bean屬性沒有被填充,那么容器就會拋出異常。這就是通過快速失敗的機制來避免NullPointerException。

2.@Autowired

可以使用@Autowired注解到“傳統的”setter方法中:

JSR-330的@Inject注解可以代替上面示例中的Spring的@Autowired注解。

也可以將注解應用于任意名稱和(或)多個參數的方法:

也可以將它用于構造方法和字段:

也可以提供ApplicationContext中特定類型的所有bean,通過添加注解到期望哪種類型的數組的字段或者方法上:

同樣,也可以用于特定類型的集合:

默認情況下,當出現零個候選bean的時候,自動裝配就會失敗。默認的行為是將被注解的方法、構造方法和字段作為需要的依賴關系。這種行為也可以通過下面這樣的做法來改變。

推薦使用@Autowired的required屬性而不是@Required注解。required屬性表示屬性對于自動裝配的目的不是必需的,如果它不能被自動裝配,那么屬性就會忽略。另一方面,@Required更健壯一些,它強制由容器支持的各種方式的屬性設置。如果沒有注入任何值,就會拋出對應的異常。

3.@Primary

因為通過類型的自動裝配可能有多個候選者,所以在選擇過程中通常需要更多控制。達成這個目的的一種做法是使用Spring的@Primary注解。當一個依賴有多個候選者bean時,@Primary指定了一個優先提供的特殊bean。當多個候選者bean中存在一個確切的指定了@Primary的bean時,它將會自動裝載這個bean。

下面來看一個例子:

對于上面的配置,下面的MovieRecommender將會使用firstMovieCatalog自動注解。

4.@Qualifier

因為通過類型的自動裝配可能有多個候選者,所以在選擇過程中通常需要更多控制。達成這個目的的一種做法是使用Spring的@Qualifier注解。你可以用特定的參數來關聯限定符的值,縮小類型的集合匹配,那么通過參數就能選擇特定的bean。用法如下:

@Qualifier注解也可以在獨立的構造方法參數或方法參數中來指定:

5.@Resource

Spring支持使用JSR-250的@Resource注解在字段或bean屬性的setter方法上的注入。這在Java EE 5和Java EE 6中是一個通用的模式,比如在JSF 1.2中管理的bean或JAX-WS 2.0端點。Spring也為其所管理的對象支持這種模式。

@Resource使用name屬性,默認情況下Spring解析這個值作為要注入的bean的名稱。換句話說,如果遵循by-name語義,就如在這個示例所展示的:

如果沒有明確地指定name值,那么默認的名稱就從字段名稱或setter方法中派生出來。如果是字段,它就會選用字段名稱;如果是setter方法,它就會選用bean的屬性名稱。所以下面的示例中名為movieFinder的bean通過setter方法來注入:

6.@PostConstruct和@PreDestroy

CommonAnnotationBeanPostProcessor不但能識別@Resource注解,而且還能識別JSR-250生命周期注解。在下面的示例中,在初始化后緩存會預先填充,并在銷毀后會清理。

6.2.19 基于注解的配置與基于XML的配置

毫無疑問,最早的Spring配置是基于XML的配置。隨著JDK 1.5發布,Java開始支持注解,同時,Spring也開始支持基于注解的配置方式。

基于注解的配置方式一定要比基于XML的配置方式更好嗎?答案是具體問題具體分析。

實際上,無論是基于注解的配置方式,還是基于XML的配置方式,每種方式都有它的利與弊,通常是讓開發人員來決定使用哪種策略更適合。由于定義它們的方式,注解在聲明中提供了大量的上下文,使得配置更加簡潔。然而,XML更擅長裝配組件,而不需要觸碰它們的源代碼或重新編譯。一些開發人員更喜歡裝配源碼,因為添加了注解的類會被有些人認為不再是POJO了,而且基于注解的配置會讓配置變得分散并且難以控制。

無論怎么選擇,Spring都可以容納兩種方式,甚至是它們的混合體。值得指出的是通過JavaConfig方式,Spring允許以非侵入式的方式來使用注解,而不需要觸碰目標組件的源代碼和工具。

主站蜘蛛池模板: 河池市| 方山县| 宣城市| 嘉义县| 漠河县| 石狮市| 抚松县| 沭阳县| 吉木萨尔县| 方正县| 肥乡县| 黔西县| 北票市| 定州市| 阳西县| 正宁县| 孝感市| 库车县| 湖北省| 同仁县| 定州市| 海盐县| 平南县| 万荣县| 永丰县| 剑川县| 壶关县| 江西省| 怀集县| 彭山县| 于都县| 栖霞市| 大竹县| 丰都县| 筠连县| 永春县| 海林市| 镇沅| 桦川县| 游戏| 西青区|