spring-framework-reference(5.1.1.RELEASE)中文版——Core部分
前言
最近在學習Spring框架,在學習的同時,藉助有道翻譯,整理翻譯了部分文件,由於尚在學習當中,所以該篇文章將會定時更新,目標在一個月左右時間翻譯完全部版本。
雖然大部分內容為翻譯,但是其中可能會新增一些我對Spring的理解,最近也在計劃建立一個wiki版本,希望同樣在學習的你,可以參與進來,我們共同完成該版本文件的翻譯與評註。感興趣的,可以私信聯絡,方便共同學習。
以下是正式內容,標題未翻譯,為了方便與官方文件相對應。
為了防止篇幅過大,該篇主要翻譯Core核心部分.
Introduction to the Spring IoC Container and Beans
本章介紹了基於控制反轉(IoC)原理的Spring框架實現。IoC也稱為依賴注入(dependency injection, DI)。在這個過程中,物件只通過建構函式引數、工廠方法的引數或物件例項在構造或從工廠方法返回後設置的屬性來定義它們的依賴關係(也就是說,它們使用的其他物件)。然後容器在建立bean時注入這些依賴項。這個過程從根本上說是bean本身的翻轉(因此稱為控制反轉),它通過使用類或服務定位器模式等機制的直接構造來控制依賴項的例項化或位置。
這裡補充IoC的理解框圖
從這裡我們就可以看出,為什麼叫做翻轉。因為本來客戶端負責建立使用者類,而現在只需要獲取使用者類。
org.spring.framework.beans
和org.springframework.context
是Spring Framework IoC容器的基礎。BeanFactory
介面提供了一種高階配置機制,能夠管理任何型別的物件。ApplicationContext
是BeanFactory
的子介面。它增加了:
- 更容易與Spring的AOP特性整合
- 訊息資源處理
- 事件釋出
- 應用程式層特定的context,例如用於web應用程式的WebApplicationContext。
簡而言之,BeanFactory提供了配置框架和基本功能,而ApplicationContext添加了更多企業特定的功能。ApplicationContext是BeanFactory的一個完整超集,在本章中專門用於描述Spring的IoC容器。有關使用BeanFactory而不是ApplicationContext的更多資訊,請參閱BeanFactory。
在Spring中,構成應用程式主幹並由Spring IoC容器管理的物件稱為bean。bean是由Spring IoC容器例項化、組裝和以其他方式管理的物件。另外,bean只是應用程式中的眾多物件之一。bean及其之間的依賴關係反映在容器使用的配置元資料中。
Container Overview
org.springframework.context.ApplicationContext
介面代表Spring IoC容器,並負責例項化、配置和組裝bean。容器通過讀取配置元資料獲得關於例項化、配置和組裝物件的指令。配置元資料用XML、Java註釋或Java程式碼表示。它允許您表達組成應用程式的物件以及這些物件之間的豐富相互依賴關係。
Spring提供了ApplicationContext介面的幾個實現。在獨立應用程式中,通常建立ClassPathXmlApplicationContext
或FileSystemXmlApplicationContext
例項。雖然XML一直是定義配置元資料的傳統格式,但您可以通過提供少量XML配置來宣告支援這些額外的元資料格式,指示容器使用Java註釋或程式碼作為元資料格式。
在大多數應用程式場景中,不需要顯式使用者程式碼來例項化Spring IoC容器的一個或多個例項。例如,在web應用程式場景中,應用程式的web.xml檔案中的8行(或大約8行)樣板web描述符XML通常就足夠了.
如果您使用Spring工具套件(一個eclipse驅動的開發環境),您可以很容易地通過單擊滑鼠或擊鍵建立這個樣板配置。
下圖顯示了Spring工作原理的高階檢視。您的應用程式類與配置元資料相結合,這樣在建立和初始化ApplicationContext之後,您就有了一個完整配置的可執行系統或應用程式。
Configuration Metadata
如上面的圖表所示,Spring IoC容器使用了一種配置元資料的形式。這個配置元資料表示您作為應用程式開發人員如何告訴Spring容器例項化、配置和組裝應用程式中的物件。
配置元資料傳統上以簡單直觀的XML格式提供,這是本章的主要內容,用於傳遞Spring IoC容器的關鍵概念和特性。
基於xml的元資料不是配置元資料的唯一允許形式。Spring IoC容器本身與實際編寫配置元資料的格式完全分離。現在,許多開發人員為他們的Spring應用程式選擇基於java的配置。
有關使用Spring容器的其他元資料形式的資訊,請參閱:
- Annotation-based configuration: Spring 2.5引入了對基於註釋的配置元資料的支援。
- Java-based configuration: 從Spring 3.0開始,Spring JavaConfig專案提供的許多特性成為核心Spring框架的一部分。因此,您可以通過使用Java而不是XML檔案來定義應用程式類外部的bean。要使用這些新特性,請參閱@Configuration、@Bean、@Import和@DependsOn註釋。
Spring配置包含至少一個bean定義,通常是容器必須管理的多個bean定義。基於xml的配置元資料將這些bean配置為頂層元素中的元素。基於xml的配置元資料將這些bean配置為頂層
這些bean定義對應於組成應用程式的實際物件。通常,您可以定義服務層物件、資料訪問物件(DAOs)、表示物件(如Struts動作例項)、基礎設施物件(如Hibernate SessionFactories)、JMS佇列等。通常,在容器中不配置細粒度的域物件,因為建立和載入域物件通常是dao和業務邏輯的責任。但是,您可以使用Spring與AspectJ的整合來配置在IoC容器控制之外建立的物件。
下面的例子展示了基於xml的配置元資料的基本結構:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="..." class="...">
<!-- collaborators and configuration for this bean go here -->
</bean>
<bean id="..." class="...">
<!-- collaborators and configuration for this bean go here -->
</bean>
<!-- more bean definitions go here -->
</beans>
其中:
- id:i是標識單個bean定義的字串。
- class:定義bean的型別並使用完全限定的classname。
id屬性的值引用協作物件。本例中沒有顯示引用協作物件的XML。有關更多資訊,請參見依賴關係。
Instantiating a Container
提供給ApplicationContext建構函式的位置路徑或路徑是資源字串,允許容器裝載來自各種外部資源(如本地檔案系統、Java類路徑等)的配置元資料。
ApplicationContext context = new ClassPathXmlApplicationContext("services.xml", "daos.xml");
注意:在瞭解了Spring的IoC容器之後,您可能想了解更多關於Spring的資源抽象(如參考資料中所述)的內容,它提供了從URI語法中定義的位置讀取InputStream的方便機制。特別是,資源路徑用於構造應用程式上下文,正如在應用程式上下文和資源路徑中描述的那樣。
下面的示例顯示了服務層物件(service .xml)配置檔案:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
<!-- services -->
<bean id="petStore" class="org.springframework.samples.jpetstore.services.PetStoreServiceImpl">
<property name="accountDao" ref="accountDao"/>
<property name="itemDao" ref="itemDao"/>
<!-- additional collaborators and configuration for this bean go here -->
</bean>
<!-- more bean definitions for services go here -->
</beans>
下面的示例顯示了資料訪問物件daos.xml檔案:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="accountDao"
class="org.springframework.samples.jpetstore.dao.jpa.JpaAccountDao">
<!-- additional collaborators and configuration for this bean go here -->
</bean>
<bean id="itemDao" class="org.springframework.samples.jpetstore.dao.jpa.JpaItemDao">
<!-- additional collaborators and configuration for this bean go here -->
</bean>
<!-- more bean definitions for data access objects go here -->
</beans>
在前面的示例中,服務層包含PetStoreServiceImpl類和JpaAccountDao和JpaItemDao型別的兩個資料訪問物件(基於JPA物件關係對映標準)。
屬性名稱元素引用JavaBean屬性的名稱,ref元素引用另一個bean定義的名稱。id和ref元素之間的連結表示協作物件之間的依賴關係。有關配置物件依賴項的詳細資訊,請參閱依賴項。
Composing XML-based Configuration Metadata
讓bean定義跨越多個XML檔案可能很有用。通常,每個XML配置檔案都代表體系結構中的邏輯層或模組。
您可以使用Application context建構函式從所有這些XML片段載入bean定義。此建構函式接受多個資源位置,如前一節所示。或者,使用一個或多個元素來從另一個或多個檔案載入bean定義。下面的例子展示瞭如何做到這一點:
<beans>
<import resource="services.xml"/>
<import resource="resources/messageSource.xml"/>
<import resource="/resources/themeSource.xml"/>
<bean id="bean1" class="..."/>
<bean id="bean2" class="..."/>
</beans>
在前面的示例中,外部bean定義從三個檔案載入:service.xml,messageSource.xml和themeSource.xml。
所有位置路徑都相對於執行匯入操作的定義檔案,因此是service.xml必須與執行匯入的檔案位於相同的目錄或classpath位置,而messageSource.xml和themeSource.xml必須位於匯入檔案位置下方的resources位置。如您所見,前導斜槓被忽略。但是,考慮到這些路徑是相對的,最好不要使用斜槓。根據Spring模式,匯入的檔案的內容,包括頂層元素,必須是有效的XML bean定義。
可以,但不推薦,引用檔案在父目錄使用一個相對"../”路徑。這樣做會對當前應用程式之外的檔案建立依賴關係。特別是,不建議將此引用用於classpath: URLs(例如,classpath:../services.xml),其中執行時解析過程選擇“最近的”classpath根目錄,然後檢視它的父目錄。類路徑配置更改可能導致選擇不同的、不正確的目錄。
您總是可以使用完全限定的資源位置而不是相對路徑:例如,檔案:C:/config/services。xml或者類路徑:/配置/ services . xml。但是,請注意,您正在將應用程式的配置耦合到特定的絕對位置。對於這樣的絕對位置,通常最好保持間接——例如,通過在執行時針對JVM系統屬性解析的“${…}”佔位符。
名稱空間本身支援import directive特性。在Spring提供的XML名稱空間(例如context和util名稱空間)中,除了普通bean定義之外,還有其他配置特性。
The Groovy Bean Definition DSL
作為外部化配置元資料的進一步示例,bean定義也可以在Spring的Groovy bean定義DSL中表示,從Grails框架中可以知道這一點。通常,這樣的配置位於".groovy"中。結構如下例所示:
beans {
dataSource(BasicDataSource) {
driverClassName = "org.hsqldb.jdbcDriver"
url = "jdbc:hsqldb:mem:grailsDB"
username = "sa"
password = ""
settings = [mynew:"setting"]
}
sessionFactory(SessionFactory) {
dataSource = dataSource
}
myService(MyService) {
nestedBean = { AnotherBean bean ->
dataSource = dataSource
}
}
}
這種配置樣式基本上與XML bean定義等價,甚至支援Spring的XML配置名稱空間。它還允許通過importBeans指令匯入XML bean定義檔案。
Using the Container
ApplicationContext是一個高階工廠的介面,能夠維護不同bean及其依賴項的登錄檔。通過使用方法T getBean(String name, Class
ApplicationContext允許您讀取bean定義並訪問它們,如下例所示:
// create and configure beans
ApplicationContext context = new ClassPathXmlApplicationContext("services.xml", "daos.xml");
// retrieve configured instance
PetStoreService service = context.getBean("petStore", PetStoreService.class);
// use configured instance
List<String> userList = service.getUsernameList();
使用Groovy配置,bootstrapping看起來非常相似。它有一個不同的context實現類,它支援groovy-aware(但也理解XML bean定義)。下面的示例展示了Groovy配置:
ApplicationContext context = new GenericGroovyApplicationContext("services.groovy", "daos.groovy");
最靈活的變體是GenericApplicationContext和reader delegate相結合——例如,XmlBeanDefinitionReader用於XML檔案,如下示例所示:
GenericApplicationContext context = new GenericApplicationContext();
new XmlBeanDefinitionReader(context).loadBeanDefinitions("services.xml", "daos.xml");
context.refresh();
您還可以為Groovy檔案使用GroovyBeanDefinitionReader,如下例所示:
GenericApplicationContext context = new GenericApplicationContext();
new GroovyBeanDefinitionReader(context).loadBeanDefinitions("services.groovy", "daos.groovy");
context.refresh();
您可以在相同的ApplicationContext上混合並匹配這樣的讀取器委託,從不同的配置源讀取bean定義。
然後,可以使用getBean檢索bean的例項。ApplicationContext介面有一些用於檢索bean的其他方法,但理想情況下,應用程式程式碼不應該使用它們。實際上,您的應用程式程式碼根本不應該呼叫getBean()方法,因此根本不需要依賴Spring api。例如,Spring與web框架的整合為各種web框架元件(如控制器和jsf管理的bean)提供了依賴注入,允許您通過元資料(如自動連線註釋)宣告對特定bean的依賴。
Bean Overview
Spring IoC容器管理一個或多個bean。這些bean是使用提供給容器的配置元資料建立的(例如,XML 定義的形式)。
在容器本身中,這些bean定義被表示為BeanDefinition
物件,其中包含以下元資料(以及其他資訊):
- 包限定類名:通常是定義的bean的實際實現類。
- Bean行為配置元素,它說明Bean在容器中的行為(範圍、生命週期回撥等等)。
- 對bean執行其工作所需的其他bean的引用。這些引用也稱為協作者或依賴關係。
- 在新建立的物件中要設定的其他配置設定——例如,池的大小限制或管理連線池的bean中要使用的連線數。
此元資料轉換為組成每個bean定義的一組屬性。下表描述了這些屬性:
Property | Explained in... |
---|---|
Class | 例項化Bean |
Name | 命名Beans |
Scope | Bean生成期 |
Contructor arguments | 依賴注入 |
Properties | 依賴注入 |
Autowiring mode | 自動裝配合作者 |
Lazy initialization mdoe | 初始化回撥 |
Destruction method | 毀壞回撥 |
除了包含關於如何建立特定bean的資訊的bean定義之外,ApplicationContext實現還允許註冊容器之外建立的現有物件(由使用者建立)。 通過getBeanFactory()方法訪問ApplicationContext的BeanFactory,該方法返回BeanFactory DefaultListableBeanFactory實現。DefaultListableBeanFactory通過registerSingleton(..)和registerBeanDefinition(..)方法支援此註冊。但是,典型的應用程式只使用通過元資料bean定義定義的bean。
Bean元資料和手動提供的單例例項需要儘早註冊,以便容器在自動連線和其他自省步驟期間正確地推斷它們。雖然在一定程度上支援覆蓋現有的元資料和現有的單例例項,但是在執行時註冊新bean(與對工廠的實時訪問同時進行)並沒有得到正式支援,並且可能導致併發訪問異常、bean容器中的不一致狀態,或者兩者都有。
Naming Beans
每個bean都有一個或多個識別符號。這些識別符號在承載bean的容器中必須是唯一的。bean通常只有一個識別符號。但是,如果需要一個以上的別名,則可以將額外的別名看作別名。
在基於xml的配置元資料中,可以使用id屬性、name屬性或兩者來指定bean識別符號。id屬性允許您指定一個id。按照慣例,這些名稱是字母數字(“myBean”、“someService”等),但它們也可以包含特殊字元。如果希望為bean引入其他別名,還可以在name屬性中指定它們,用逗號(、)、分號(;)或空格分隔。作為歷史記錄,在Spring 3.1之前的版本中,id屬性被定義為xsd: id型別,這限制了可能的字元。在3.1中,它被定義為xsd:string型別。請注意,bean id惟一性仍然由容器強制執行,但不再由XML解析器強制執行。
您不需要為bean提供名稱或id。如果沒有顯式地提供名稱或id,容器將為該bean生成唯一的名稱。但是,如果希望通過引用ref元素或服務定位器樣式查詢來引用bean的名稱,則必須提供名稱。不提供名稱的動機與使用內部bean和自動連接合作者有關。
Bean 命名約定
約定是在命名bean時使用標準Java約定作為例項欄位名。也就是說,bean名稱以小寫字母開頭,然後以駝色大小寫字母開頭。這些名字的例子包括accountManager,accountService, userDao, loginController, 等等。
一致地命名bean使您的配置更容易閱讀和理解。另外,如果您使用Spring AOP,那麼在向一組名稱相關的bean應用建議時,它會有很大幫助。
注:通過類路徑中的元件掃描,Spring為未命名的元件生成bean名稱,遵循前面描述的規則:本質上,使用簡單的類名並將其初始字元轉換為小寫。然而,在(不尋常的)特殊情況下,當有一個以上的字元,並且第一和第二字元都是大寫字母時,原始的大小寫保留。這些規則與
java.beans.Introspector.decapitalize
(此處使用Spring)定義的規則相同。
Aliasing a Bean outside the Bean Definition
在bean定義本身中,通過使用id屬性指定的最多一個名稱和name屬性中任意數量的其他名稱的組合,可以為bean提供多個名稱。這些名稱可以等效於同一bean的別名,並且在某些情況下非常有用,例如讓應用程式中的每個元件通過使用特定於該元件本身的bean名稱引用公共依賴項。
然而,指定bean實際定義的所有別名並不總是足夠的。 有時需要為其他地方定義的bean引入別名。在大型系統中,配置通常在每個子系統之間進行分配,每個子系統都有自己的一組物件定義。在基於xml的配置元資料中,可以使用元素來完成此任務。下面的例子展示瞭如何做到這一點:
<alias name="fromName" alias="toName"/>
在這種情況下,命名為fromName的bean(在同一個容器中)也可以在使用這個別名定義之後稱為toName。 例如,子系統A的配置元資料可以通過subsystemA-dataSource的名稱引用資料來源。子系統B的配置元資料可以通過subsystemB-dataSource的名稱引用資料來源。在組合使用這兩個子系統的主應用程式時,主應用程式以myApp-dataSource的名稱引用資料來源。要使所有三個名稱都指向同一個物件,可以向配置元資料新增以下別名定義:
<alias name="subsystemA-dataSource" alias="subsystemB-dataSource"/>
<alias name="subsystemA-dataSource" alias="myApp-dataSource" />
現在,每個元件和主應用程式都可以通過唯一的名稱引用資料來源,並且保證不會與任何其他定義衝突(有效地建立名稱空間),但是它們引用的是同一個bean。
Java-Configuration
如果使用Javaconfiguration,那麼可以使用@Bean註釋來提供別名。有關詳細資訊,請參閱使用@Bean註釋。
Instantiating Beans
bean定義本質上是建立一個或多個物件的配方。容器在請求時檢視命名bean的配方,並使用該bean定義封裝的配置元資料來建立(或獲取)實際物件。
如果使用基於xml的配置元資料,則指定要在元素
- 通常,在容器本身通過反射性地呼叫其建構函式直接建立bean的情況下,指定要構造的bean類,這在某種程度上類似於使用new操作符的Java程式碼。
- 在不太常見的情況下,在容器呼叫類上的靜態工廠方法來建立bean時,指定包含用於建立物件的靜態工廠方法的實際類。從靜態工廠方法呼叫返回的物件型別可能完全是同一個類或另一個類。
Inner class names 如果要為靜態巢狀類配置bean定義,則必須使用巢狀類的二進位制名稱。
例如,如果在com.example中有一個名為SomeThing的類。這個SomeThing類有一個名為OtherThing的靜態巢狀類,bean定義上的class屬性的值應該是com.example.SomeThing$OtherThing。
注意,在名稱中使用$字元將巢狀類名稱與外部類名稱分隔開來。
Instantiation with a Constructor
當您使用建構函式方法建立bean時,所有普通類都可以使用,並且與Spring相容。也就是說,正在開發的類不需要實現任何特定的介面或以特定的方式進行編碼。簡單地指定bean類就足夠了。然而,根據對特定bean使用的IoC型別的不同,您可能需要一個預設(空)建構函式。
Spring IoC容器實際上可以管理您希望它管理的任何類。它不限於管理真正的JavaBeans。大多數Spring使用者更喜歡實際的javaBeans,只有一個預設的(無引數的)建構函式,以及根據容器中的屬性建模的適當的setter和getter。您還可以在容器中擁有更多具有外來的非bean風格的類。例如,如果您需要使用一個完全不遵守JavaBean規範的遺留連線池,Spring也可以對其進行管理。
使用基於xml的配置元資料,您可以如下所示指定bean類:
<bean id="exampleBean" class="examples.ExampleBean"/>
<bean name="anotherExample" class="examples.ExampleBeanTwo"/>
有關向建構函式提供引數(如果需要)和在構造物件之後設定物件例項屬性的機制的詳細資訊,請參見注入依賴項。
Instantiation with a Static Factory Method
在定義使用靜態工廠方法建立的bean時,使用class屬性指定包含靜態工廠方法的類和名為factory-method的屬性來指定工廠方法本身的名稱。您應該能夠呼叫這個方法(使用可選引數,稍後將進行描述),並返回一個活動物件,該物件隨後將被視為通過建構函式建立的物件。這種bean定義的一種用法是在遺留程式碼中呼叫靜態工廠。
下面的bean定義指定通過呼叫工廠方法建立bean。定義沒有指定返回物件的型別(類),只指定包含工廠方法的類。在本例中,createInstance()方法必須是一個靜態方法。下面的例子展示瞭如何指定工廠方法:
<bean id="clientService"
class="examples.ClientService"
factory-method="createInstance"/>
下面的示例顯示了一個將與前面的bean定義一起工作的類:
public class ClientService {
private static ClientService clientService = new ClientService();
private ClientService() {}
public static ClientService createInstance() {
return clientService;
}
}
有關向工廠方法提供(可選的)引數以及在從工廠返回物件後設置物件例項屬性的機制的詳細資訊,請參閱依賴項和配置的詳細資訊。
Instantiation by Using an Instance Factory Method
與通過靜態工廠方法例項化類似,使用例項工廠方法例項化從容器呼叫現有bean的非靜態方法來建立新的bean。要使用這種機制,請保持類屬性為空,並在factory-bean屬性中指定當前(或父或祖先)容器中的bean的名稱,該容器包含要呼叫來建立物件的例項方法。使用工廠方法屬性設定工廠方法本身的名稱。下面的示例展示瞭如何配置這樣的bean:
<!-- the factory bean, which contains a method called createInstance() -->
<bean id="serviceLocator" class="examples.DefaultServiceLocator">
<!-- inject any dependencies required by this locator bean -->
</bean>
<!-- the bean to be created via the factory bean -->
<bean id="clientService"
factory-bean="serviceLocator"
factory-method="createClientServiceInstance"/>
下面的例子顯示了相應的Java類:
public class DefaultServiceLocator {
private static ClientService clientService = new ClientServiceImpl();
public ClientService createClientServiceInstance() {
return clientService;
}
}
一個工廠類也可以容納多個工廠方法,如下例所示:
<bean id="serviceLocator" class="examples.DefaultServiceLocator">
<!-- inject any dependencies required by this locator bean -->
</bean>
<bean id="clientService"
factory-bean="serviceLocator"
factory-method="createClientServiceInstance"/>
<bean id="accountService"
factory-bean="serviceLocator"
factory-method="createAccountServiceInstance"/>
下面的例子顯示了相應的Java類:
public class DefaultServiceLocator {
private static ClientService clientService = new ClientServiceImpl();
private static AccountService accountService = new AccountServiceImpl();
public ClientService createClientServiceInstance() {
return clientService;
}
public AccountService createAccountServiceInstance() {
return accountService;
}
}
這種方法表明,可以通過依賴注入(dependency injection)對工廠bean本身進行管理和配置。請參閱詳細的依賴關係和配置。
在Spring文件中,"factory bean"指的是在Spring容器中配置並通過例項或靜態工廠方法建立物件的bean。相比之下,FactoryBean(注意大寫)指特定於spring的FactoryBean。
Dependencies
一個典型的企業應用程式不包含單個物件(或Spring術語中的bean)。即使是最簡單的應用程式,也有一些物件一起工作,以顯示終端使用者所看到的一致的應用程式。 下一節將解釋如何從定義獨立的許多bean定義過渡到一個完全實現的應用程式,在這個應用程式中,物件協作以實現目標。
Dependency Injection
依賴注入(Dependency injection)是一個過程,在這個過程中,物件只通過建構函式引數、工廠方法的引數或從工廠方法構造或返回的物件例項上設定的屬性來定義它們的依賴關係(也就是說,它們工作的其他物件)。然後容器在建立bean時注入這些依賴項。這個過程本質上是bean本身的翻轉(因此稱為控制翻轉),它通過使用類或服務定位器模式的直接構造來控制其依賴項的例項化或位置。
使用DI原則,程式碼更簡潔,當物件具有依賴關係時,解耦更有效。物件不查詢依賴項,也不知道依賴項的位置或類。因此,您的類變得更容易測試,特別是當依賴關係是在介面或抽象基類上時,這允許存根或模擬實現在單元測試中使用。
DI存在於兩種主要變體中:基於建構函式的依賴項注入和基於Setter的依賴項注入。
Constructor-based Dependency Injection
基於建構函式的DI由容器呼叫建構函式來完成,該建構函式有許多引數,每個引數都表示依賴關係。呼叫帶有特定引數的靜態工廠方法來構造bean幾乎是等價的,本文將把引數類似地處理給建構函式和靜態工廠方法。下面的例子展示了一個只能通過建構函式注入依賴注入的類:
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...
}
注意,這個類沒有什麼特別之處。它是一個POJO,不依賴於容器特定的介面、基類或註釋。
Constructor Argument Resolution
建構函式引數解析匹配通過使用引數的型別來實現。如果bean定義的建構函式引數中不存在潛在的歧義,那麼在bean定義中定義建構函式引數的順序就是在例項化bean時將這些引數提供給適當的建構函式的順序。考慮以下類:
package x.y;
public class ThingOne {
public ThingOne(ThingTwo thingTwo, ThingThree thingThree) {
// ...
}
}
假設ThingTwo和ThingThree類不通過繼承關聯,則不存在潛在的歧義。因此,下面的配置可以很好地工作,您不需要在元素<constructor-arg/>
中顯式指定建構函式引數索引或型別。
<beans>
<bean id="thingOne" class="x.y.ThingOne">
<constructor-arg ref="thingTwo"/>
<constructor-arg ref="thingThree"/>
</bean>
<bean id="thingTwo" class="x.y.ThingTwo"/>
<bean id="thingThree" class="x.y.ThingThree"/>
</beans>
當引用另一個bean時,型別是已知的,並且可以進行匹配(就像前面的例子那樣)。當使用簡單型別(如)時,Spring無法確定值的型別,因此在沒有幫助的情況下無法按型別匹配。考慮以下類:
package examples;
public class ExampleBean {
// Number of years to 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;
}
}
Constructor argument type matching
在前面的場景中,如果使用type屬性顯式地指定建構函式引數的型別,容器可以使用與簡單型別匹配的型別。如下例所示:
<bean id="exampleBean" class="examples.ExampleBean">
<constructor-arg type="int" value="7500000"/>
<constructor-arg type="java.lang.String" value="42"/>
</bean>
Constructor argument index
可以使用index屬性顯式地指定建構函式引數的索引,如下例所示:
<bean id="exampleBean" class="examples.ExampleBean">
<constructor-arg index="0" value="7500000"/>
<constructor-arg index="1" value="42"/>
</bean>
除了解決多個簡單值的模糊性之外,指定索引還可以解決建構函式具有相同型別的兩個引數的模糊性問題。
基於0的指數
Constructor argument name
您還可以使用建構函式引數名來消除值歧義,如下例所示:
<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-based Dependency Injection
在呼叫無引數建構函式或無引數靜態工廠方法例項化bean之後,容器呼叫bean上的setter方法就可以實現基於setter的DI。
下面的示例顯示了一個只能通過使用純setter注入進行依賴注入的類。這個類是常規Java。它是一個POJO,不依賴於容器特定的介面、基類或註釋。
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 setMovieFinder(MovieFinder movieFinder) {
this.movieFinder = movieFinder;
}
// business logic that actually uses the injected MovieFinder is omitted...
}
ApplicationContext為其管理的bean支援基於建構函式和基於setter的DI。在通過建構函式方法注入一些依賴項之後,它還支援基於setter的DI。 您可以以BeanDefinition的形式配置依賴關係,並將其與PropertyEditor例項一起使用,以將屬性從一種格式轉換為另一種格式。然而,大多數Spring使用者並不直接使用這些類(即通過程式設計方式),而是使用XML bean定義、帶註釋的元件(即用@Component、@Controller等註釋的類)或基於java的@Configuration類中的@Bean方法。然後,這些源在內部轉換為BeanDefinition例項,並用於載入整個Spring IoC容器例項。
Constructor-based or setter-based DI?
由於可以混合使用基於建構函式和基於setter的DI,因此使用建構函式處理強制依賴關係和setter方法或配置方法處理可選依賴關係是一個很好的經驗法則。注意,在setter方法上使用@Required註釋可以使屬性成為必需的依賴項。
Spring團隊通常提倡建構函式注入,因為它允許您將應用程式元件實現為不可變物件,並確保所需的依賴項不為空。此外,構造注入的元件總是以完全初始化的狀態返回給客戶端(呼叫)程式碼。作為補充說明,大量的建構函式引數是一種糟糕的程式碼味道,這意味著類可能有太多的職責,應該重構,以便更好地處理關注點的適當分離。
Setter注入主要應該只用於可選的依賴項,這些依賴項可以在類中分配合理的預設值。否則,在程式碼使用依賴項的任何地方都必須執行非空檢查。setter注入的一個好處是,setter方法使該類的物件可以稍後重新配置或重新注入。因此,通過JMX MBean進行管理是setter注入的一個引人注目的用例。
用對特定類最有意義的DI樣式。有時候,在處理沒有原始碼的第三方類時,會為您做出選擇。例如,如果第三方類不公開任何setter方法,那麼建構函式注入可能是惟一可用的DI形式。
Dependency Resolution Process
容器執行以下bean依賴項解析:
- 使用描述所有bean的配置元資料建立和初始化ApplicationContext。配置元資料可以由XML、Java程式碼或註釋指定。
- 對於每個bean,其依賴關係以屬性、建構函式引數或靜態工廠方法引數的形式表示(如果使用靜態工廠方法而不是普通建構函式)。這些依賴項在bean實際建立時提供給bean。
- 每個屬性或建構函式引數都是要設定的值的實際定義,或者是對容器中另一個bean的引用。
- 值的每個屬性或建構函式引數都從其指定格式轉換為該屬性或建構函式引數的實際型別。預設情況下,Spring可以將字串格式提供的值轉換為所有內建型別,如int、long、string、boolean等。
在建立容器時,Spring容器驗證每個bean的配置。然而,直到真正建立bean時,才會設定bean屬性本身。當建立容器時,將建立單例項作用域並設定為預例項化的bean(預設)。作用域在Bean作用域中定義。否則,只有在請求bean時才會建立它。建立bean可能會導致建立bean的圖,因為建立和分配了bean的依賴項及其依賴項(等等)。請注意,這些依賴項之間的解析不匹配可能出現得較晚——即在第一次建立受影響的bean時。
如果使用主建構函式注入,則可以建立不可解析的迴圈依賴場景。
Circular dependencies
例如:類A需要通過建構函式注入的類B例項,類B需要通過建構函式注入的類A例項。如果將類A和類B配置為相互注入的bean,那麼Spring IoC容器將在執行時檢測到此迴圈引用,並丟擲BeanCurrentlyInCreationException。
一種可能的解決方案是編輯由setter而不是構造器配置的一些類的原始碼。或者,避免建構函式注入,只使用setter注入。換句話說,儘管不建議使用setter注入配置迴圈依賴項。
與典型的情況(沒有迴圈依賴項)不同的是,Bean A和bean B之間的迴圈依賴項強制在完全初始化自身之前將一個bean注入另一個bean(典型的雞和蛋的場景)。
您通常可以相信Spring會做正確的事情。它在容器裝載時檢測配置問題,例如對不存在的bean和迴圈依賴項的引用。在實際建立bean時,Spring儘可能晚地設定屬性並解析依賴關係。這意味著,如果在建立該物件或其依賴項時出現問題,那麼在以後請求物件時,正確載入的Spring容器可以生成異常——例如,bean由於丟失或無效屬性而丟擲異常。某些配置問題可能會延遲可見性,這就是為什麼ApplicationContext實現在預設情況下預例項化單例bean。 在實際需要這些bean之前先花一些時間和記憶體來建立它們,在建立ApplicationContext時(而不是稍後),您會發現配置問題。您仍然可以覆蓋這個預設行為,以便單例bean能夠惰性地初始化,而不是被預先例項化。問題
如果不存在迴圈依賴項,當一個或多個協作bean被注入到依賴bean中時,每個協作bean在被注入到依賴bean之前都被完全配置好了。這意味著,如果bean A依賴於Bean B,那麼在呼叫bean A上的setter方法之前,Spring IoC容器已經完全配置了Bean B。換句話說,bean被例項化(如果它不是一個預先例項化的單例物件),它的依賴關係被設定,相關的生命週期方法(例如配置的init方法或InitializingBean回撥方法)被呼叫。
Examples of Dependency Injection
下面的示例將基於xml的配置元資料用於基於setter的DI。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"/>
下面的例子顯示了相應的ExampleBean類:
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;
}
}
在前面的示例中,宣告setter以匹配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"/>
下面的例子顯示了相應的ExampleBean類:
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"/>
下面的例子顯示了相應的ExampleBean類:
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;
}
}
靜態工廠方法的引數由元素提供,與實際使用的建構函式完全相同。工廠方法返回的類的型別不必與包含靜態工廠方法的類的型別相同(儘管在本例中是這樣)。例項(非靜態)工廠方法可以以一種基本相同的方式使用(除了使用factory-bean屬性而不是類屬性之外),因此我們在這裡不討論這些細節。
在靜態工廠類中的靜態方法返回的類可以時任意型別的。
Dependencies and Configuration in Detail
如前一節所述,可以將bean屬性和建構函式引數定義為對其他託管bean(協作者)的引用或內聯定義的值。為此,Spring基於xml的配置元資料支援其
Straight Values (Primitives, Strings, and so on)
元素的value屬性將屬性或建構函式引數指定為人類可讀的字串表示形式。Spring的轉換服務用於將這些值從字串轉換為屬性或引數的實際型別。下面的例子顯示了正在設定的各種值:
<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或Spring工具套件)。強烈建議提供這種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容器通過使用JavaBeansPropertyEditor
機制將
The idref element
idref元素只是將容器中另一個bean的id(字串值,而不是引用)傳遞給<建構函式-arg/>或
<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屬性的值不執行驗證。只有當客戶機bean實際例項化時,才會發現輸入錯誤(最有可能導致致命的結果)。如果客戶機bean是多例,那麼這種型別和由此產生的異常可能在部署容器很久之後才會被發現。
4.0 bean XSD不再支援idref元素上的local屬性,因為它不再為常規bean引用提供值。升級到4.0架構時,將現有的idref local引用更改為idref bean。
元素帶來價值的一個常見地方(至少在Spring 2.0之前的版本中)是ProxyFactoryBean定義中的AOP攔截器配置。在指定攔截器名稱時使用元素可以防止對攔截器ID的拼寫錯誤。
References to Other Beans (Collaborators)
ref元素是<construct -arg/>或
通過
<ref bean="someBean"/>
通過父屬性指定目標bean將建立對當前容器的父容器中的bean的引用。父屬性的值可能與目標bean的id屬性或目標bean的name屬性中的一個值相同。目標bean必須位於當前bean的父容器中。當您有一個容器層次結構,並且希望使用與父bean同名的代理將現有bean包裝在父容器中時,您應該主要使用這個bean引用變體。下面展示如何使用parent
屬性
<!-- in the parent context -->
<bean id="accountService" class="com.something.SimpleAccountService">
<!-- insert dependencies as required as here -->
</bean>
<!-- in the child (descendant) context -->
<bean id="accountService" <!-- bean name is the same as 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 here -->
</bean>
我們要注意如何引用的parent。
Inner Beans
<bean id="outer" class="...">
<!-- instead of using a reference to a target bean, simply define the target bean inline -->
<property name="target">
<bean class="com.example.Person"> <!-- this is the inner bean -->
<property name="name" value="Fiona Apple"/>
<property name="age" value="25"/>
</bean>
</property>
</bean>
內部bean定義不需要定義ID或名稱。如果指定,容器不會使用這樣的值作為識別符號。容器在建立時也會忽略範圍標誌,因為內部bean總是匿名的,並且總是與外部bean一起建立的。不可能獨立訪問內部bean,也不可能將它們注入協作bean(而不是封閉bean)中。
一種特例,可以從自定義作用域接收銷燬回撥——例如,對於單例bean中包含的請求作用域的內部bean。內部bean例項的建立與其包含的bean繫結在一起,但是銷燬回撥允許它參與請求範圍的生命週期。這不是一個常見的場景。內部bean通常只是共享其包含bean的作用域。
Collections
<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="an entry" value="just some string"/>
<entry key ="a ref" value-ref="myDataSource"/>
</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>
對映鍵或值或集值的值也可以是以下任何元素:
bean | ref | idref | list | set | map | props | value | null
Collection Merging
Spring容器還支援合併集合。應用程式開發人員可以定義父元素
Spring容器還支援合併集合。應用程式開發人員可以定義父元素
下面的例子演示了集合合併:
<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>
注意,在子bean定義的adminemail屬性的元素上使用merge=true屬性。當容器解析並例項化子bean時,生成的例項具有adminEmail屬性集合,其中包含將子bean的adminemail集合與父元件的adminemail集合合併的結果。下面的清單顯示了結果:
[email protected]
[email protected]
[email protected]
子屬性集合的值集繼承了來自父元素
Limitations of Collection Merging
不能合併不同的集合型別(例如對映和列表)。如果您確實試圖這樣做,則會丟擲一個適當的異常。merge屬性必須在較低的繼承子定義上指定。在父集合定義上指定merge屬性是多餘的,不會導致所需的合併。
Strongly-typed collection
隨著Java 5中泛型型別的引入,您可以使用強型別集合。也就是說,可以宣告一個集合型別,使其只能包含(例如)字串元素。如果使用Spring依賴於將強型別集合注入bean,則可以利用Spring的型別轉換支援,以便在新增到集合之前將強型別集合例項的元素轉換為適當的型別。下面的Java類和bean定義說明了如何做到這一點:
public class SomeClass {
private Map<String, Float> accounts;
public void setAccounts(Map<String, Float> accounts) {
this.accounts = accounts;
}
}
<beans>
<bean id="something" class="x.y.SomeClass">
<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屬性時,強型別對映的元素型別的泛型資訊可以通過反射得到。因此,Spring的型別轉換基礎結構將各種值元素識別為浮點型別,並將字串值(9.99、2.75和3.99)轉換為實際的浮點型別。
Null and Empty String Values
Spring將屬性等的空引數視為空字串。以下基於xml的配置元資料片段將email屬性設定為空字串值("")。
<bean class="ExampleBean">
<property name="email" value=""/>
</bean>
上述示例相當於以下Java程式碼:
exampleBean.setEmail("");
元素
<bean class="ExampleBean">
<property name="email">
<null/>
</property>
</bean>
上述示例相當於以下Java程式碼:
exampleBean.setEmail(null);