Spring framework核心
- 2020 年 12 月 9 日
- 筆記
- springframework5.0
這一部分涵蓋了Spring框架絕對不可或缺的所有技術。
1、IOC容器
1.1Spring IoC容器和beans介紹
org.springframework.beans和org.springframework.context包是springframework的IoC容器的基礎。
BeanFactory介面提供了一種高級配置機制,能夠管理任何類型的對象。
ApplicationContext是BeanFactory的一個子介面。它增加了與Spring的AOP特性更容易的集成;消息資源處理(用於國際化)、事件發布;和應用程式層特定的上下文,例如用於網路應用程式的WebApplicationContext 。
簡而言之,BeanFactory提供了配置框架和基本功能,而ApplicationContext添加了更多特定於企業的功能。ApplicationContext是BeanFactory的一個完整超集,在本章中專門用於描述Spring的IoC容器。
在Spring中,構成應用程式主幹並由Spring IoC容器管理的對象稱為beans。bean是由Spring IoC容器實例化、組裝和管理的對象。否則,bean只是應用程式中許多對象中的一個。Beans以及它們之間的依賴關係反映在容器使用的配置元數據中。
1.2.容器概述
介面org . Spring framework . context . application context代表Spring IoC容器,負責實例化、配置和組裝上述beans。容器通過讀取配置元數據來獲得關於實例化、配置和組裝什麼對象的指令。配置元數據用XML、Java注釋或Java程式碼表示。它允許您表達組成應用程式的對象以及這些對象之間豐富的相互依賴關係。
Spring提供了幾個現成的應用程式上下文介面實現。在獨立的應用程式中,通常創建ClassPathXmlApplicationContext 或FileSystemXmlApplicationContext的實例。雖然XML是定義配置元數據的傳統格式,但是您可以通過提供少量的XML配置來聲明性地支援這些附加的元數據格式,從而指示容器使用Java注釋或程式碼作為元數據格式。
在大多數應用程式場景中,不需要顯式用戶程式碼來實例化Spring IoC容器的一個或多個實例。例如,在一個web應用程式場景中,應用程式的web.xml文件中簡單的八行:
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/daoContext.xml /WEB-INF/applicationContext.xml</param-value>
</context-param>
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
您的應用程式類與配置元數據相結合,這樣在創建和初始化應用程式上下文之後,您就擁有了一個完全配置和可執行的系統或應用程式。

1.2.1配置元數據
Spring IoC容器消耗了一種形式的配置元數據;這種配置元數據代表了作為應用程式開發人員,您如何告訴Spring容器在您的應用程式中實例化、配置和組裝對象。
配置元數據傳統上以簡單直觀的XML格式提供,這是本章的大部分內容用來傳達Spring IoC容器的關鍵概念和功能。
基於XML的元數據不是唯一允許的配置元數據形式。Spring IoC容器本身完全脫離了實際編寫配置元數據的格式。如今,許多開發人員為他們的Spring應用程式選擇基於Java的配置。
Spring 2.5引入了對基於注釋的配置元數據的支援。
從Spring 3.0開始,Spring JavaConfig項目提供的許多特性成為了核心Spring框架的一部分。因此,您可以通過使用Java而不是XML文件來定義應用程式類外部的beans。要使用這些新功能,請參見@Configuration, @Bean, @Import and @DependsOn 注釋。
Spring配置由容器必須管理的至少一個(通常不止一個)bean定義組成。基於XML的配置元數據將這些bean配置為頂層< beans/>元素中的< bean/>元素。Java配置通常在@Configuration類中使用@Bean注釋方法。
這些bean定義對應於組成應用程式的實際對象。通常,您定義服務層對象、數據訪問對象(DAOs)、表示對象(如Struts操作實例)、基礎結構對象(如Hibernate會話工廠、JMS隊列等)。通常不在容器中配置細粒度的域對象,因為創建和載入域對象通常是Dao和業務邏輯的責任。但是,您可以使用Spring與AspectJ的集成來配置在IoC容器控制之外創建的對象。
以下示例顯示了基於XML的配置元數據的基本結構:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="//www.springframework.org/schema/beans"
xmlns:xsi="//www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="//www.springframework.org/schema/beans
//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屬性是一個字元串,用於標識單個bean定義。class屬性定義bean的類型,並使用完全限定的類名。id屬性的值指的是協作對象。
1.2.2.實例化容器
實例化Spring IoC容器很簡單。提供給ApplicationContext構造函數的一個或多個位置路徑實際上是資源字元串,它們允許容器從各種外部資源載入配置元數據,如本地文件系統、Java類路徑等。
ApplicationContext context = new ClassPathXmlApplicationContext("services.xml", "daos.xml");
以下示例顯示了服務層對象(services.xml)配置文件:
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="//www.springframework.org/schema/beans" xmlns:xsi="//www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="//www.springframework.org/schema/beans //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="//www.springframework.org/schema/beans" xmlns:xsi="//www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="//www.springframework.org/schema/beans //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元素之間的這種聯繫表達了協作對象之間的依賴關係。
編寫基於XML的配置元數據
讓bean定義跨越多個XML文件可能很有用。通常,每個單獨的XML配置文件代表您的體系結構中的一個邏輯層或模組。
您可以使用應用程式上下文構造函數從所有這些XML片段中載入bean定義。這個構造函數採用多個資源位置,如前一節所示。或者,使用一個或多個< import/>元素從另一個或多個文件載入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定義是從三個文件載入的:services.xml、messageSource.xml和themeSource.xml。所有位置路徑都是相對於執行導入的定義文件的,因此services.xml必須與執行導入的文件位於同一目錄或類路徑位置,而messageSource.xml和themeSource.xml必須位於導入文件位置下方的資源位置。如您所見,前導斜杠被忽略,但鑒於這些路徑是相對的,最好不要使用斜杠。根據Spring模式,被導入文件的內容,包括頂層的< bean/>元素,必須是有效的XML bean定義。
可以使用相對引用父目錄中的文件,但不建議這樣做”../”路徑。這樣做會創建對當前應用程式之外的文件的依賴。特別是,對於「類classpath:」網址(例如,「類classpath:../services.xml),運行時解析過程選擇「最近的」類路徑根,然後查看其父目錄。類路徑配置更改可能會導致選擇不同的不正確目錄。
您可以始終使用完全限定的資源位置,而不是相對路徑:例如,「文件:C:/config/services.xml」或「classpath:/config/services.xml」。但是,請注意,您正在將應用程式的配置耦合到特定的絕對位置。對於這種絕對位置,通常最好保持間接性,例如,通過運行時根據JVM系統屬性解析的「$ {……}」佔位符。
1.2.3.使用容器
ApplicationContext是高級工廠的介面,能夠維護不同beans及其依賴項的註冊表。使用 getBean(String name, Class<T> requiredType) 方法可以檢索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();
最靈活的變體是與讀取器委託相結合的GenericApplicationContext,例如,對於XML文件,使用XmlBeanDefinitionReader:
GenericApplicationContext context = new GenericApplicationContext(); new XmlBeanDefinitionReader(context).loadBeanDefinitions("services.xml", "daos.xml"); context.refresh();
如果需要,這樣的讀取器委託可以在同一個應用程式上下文中混合和匹配,從不同的配置源中讀取bean定義。
然後,您可以使用getBean來檢索Bean的實例。ApplicationContext介面有一些其他的方法來檢索beans,但是理想情況下,您的應用程式程式碼不應該使用它們。事實上,您的應用程式程式碼應該根本沒有對getBean()方法的調用,因此根本不依賴於Spring APIs。例如,Spring與網路框架的集成為各種網路框架組件(如控制器和JSF管理的bean)提供了依賴注入,允許您通過元數據(如自動連接注釋)聲明對特定bean的依賴。
1.3.Bean概述
Spring IoC容器管理一個或多個beans。這些beans是用您提供給容器的配置元數據創建的,例如,以XML <bean/>定義的形式。
在容器本身中,這些bean定義被表示為BeanDefinition對象,這些對象包含(除其他資訊外)以下元數據:
包限定的類名:通常是被定義的bean的實際實現類。
Bean行為配置元素,聲明bean在容器中的行為(範圍、生命周期回調等等)。
對bean完成工作所需的其他bean的引用;這些引用也被稱為協作者或依賴者。
在新創建的對象中設置的其他配置設置,例如,在管理連接池的bean中使用的連接數,或者連接池的大小限制。
這些元數據轉化為一組屬性,組成了每個bean定義。
除了包含如何創建特定bean的資訊的bean定義之外,ApplicationContext實現還允許註冊用戶在容器外創建的現有對象。這是通過方法getBeanFactory()訪問應用程式上下文的BeanFactory來完成的,該方法返回BeanFactory實現DefaultListableBeanFactory。DefaultListableBeanFactory通過方法registerSingleton(..)和registerBeanDefinition(..).然而,典型的應用程式只使用通過元數據bean定義定義的bean。
Bean元數據和手動提供的單例實例需要儘早註冊,以便容器在自動連接和其他自省步驟中能夠正確地推理它們。雖然在某種程度上支援覆蓋現有的元數據和現有的單例實例,但是官方不支援在運行時註冊新的bean(同時對工廠進行實時訪問),這可能會導致並發訪問異常和/或bean容器中的狀態不一致.
1.3.1.命名Bean
每個bean都有一個或多個標識符。這些標識符在承載bean的容器中必須是唯一的。一個bean通常只有一個標識符,但是如果它需要多個標識符,多餘的標識符可以被認為是別名。
在基於XML的配置元數據中,您使用id和/name屬性來指定bean標識符。id屬性允許您只指定一個id。傳統上,這些名稱是字母數字(『MyBean』、『FooServiCe』等)。),但也可能包含特殊字元。如果您想為bean引入其他別名,也可以在name屬性中指定它們,用逗號(,,分號(;),或者空白。作為一個歷史記錄,在Spring 3.1之前的版本中,id屬性被定義為xsd:ID類型,它約束可能的字元。從3.1開始,它被定義為xsd:string類型。請注意,bean id的唯一性仍然由容器強制執行,儘管不再由XML解析器強制執行。
您不需要為bean提供名稱或id。如果沒有顯式提供名稱或id,容器將為該bean生成一個唯一的名稱。但是,如果您想通過名稱引用該bean,通過使用引用元素或服務定位器樣式查找,您必須提供一個名稱。不提供名稱的動機與使用內部beans和自動連接協作者有關。
Bean命名約定
約定是在命名beans時使用標準的Java約定作為實例欄位名稱。也就是說,bean名稱以小寫字母開頭,並且從那時起是駱駝大小寫。這類名稱的例子有(不帶引號)’accountManager’, ‘accountService’, ‘userDao’, ‘loginController’等等
在Bean定義之外別名Bean
在bean定義本身中,您可以為bean提供多個名稱,方法是使用由id屬性指定的最多一個名稱和name屬性中任意數量的其他名稱的組合。這些名稱可以是同一個bean的等效別名,在某些情況下很有用,例如允許應用程式中的每個組件通過使用特定於該組件本身的bean名稱來引用公共依賴關係。
然而,指定實際定義bean的所有別名並不總是足夠的。有時需要為在別處定義的bean引入一個別名。這在大型系統中是常見的情況,在大型系統中,配置被分割到每個子系統中,每個子系統都有自己的一組對象定義。在基於XML的配置元數據中,您可以使用< alias/>元素來實現這一點。
<alias name="fromName" alias="toName"/>
在這種情況下,一個名為fromName的bean(在同一個容器中)在使用這個別名定義後,也可以被稱為toName。
例如,子系統A的配置元數據可以通過subsystemA-dataSource的名稱引用數據源。子系統B的配置元數據可以通過subsystemB-dataSource的名稱引用數據源。當組成使用這兩個子系統的主應用程式時,主應用程式以MyApp-數據源的名稱引用數據源。要使所有三個名稱都引用同一個對象,可以將以下別名定義添加到配置元數據中:
<alias name="myApp-dataSource" alias="subsystemA-dataSource"/> <alias name="myApp-dataSource" alias="subsystemB-dataSource"/>
現在,每個組件和主應用程式都可以通過一個唯一的名稱來引用數據源,並保證不與任何其他定義衝突(有效地創建了一個名稱空間),但它們引用的是同一個bean。
如果您使用的是Java配置,那麼@Bean注釋可以用來提供別名。有關詳細資訊,請參見使用@Bean注釋。
1.3.2.實例化beans
bean定義本質上來說是創建一個或多個對象的方法。當問及一個命名bean時,容器會查看這個方法並使用bean定義中封裝的配置元數據創建(或取得)一個實際的對象。
如果使用基於XML的配置元數據,則需要在< bean/>元素的class屬性中指定要實例化的對象的類型(或類)。這個類屬性在內部是BeanDefinition實例上的一個Class屬性,通常是強制性的。(有關異常,請參見使用實例工廠方法和Bean定義繼承的實例化。)您可以通過兩種方式之一使用Class屬性:
通常,在容器本身通過反射調用其構造函數直接創建bean的情況下,指定要構造的bean類,這在某種程度上等同於使用new操作符的Java程式碼。
要指定包含為創建對象而調用的靜態工廠方法的實際類,在容器調用類上的靜態工廠方法來創建bean的情況下就不太常見了。從靜態工廠方法調用返回的對象類型可以是相同的類,也可以完全是另一個類。
內部類名
如果要為靜態嵌套類配置bean定義,必須使用嵌套類的二進位名稱。
例如,如果您在com.example包中有一個名為Foo的類,並且這個Foo類有一個名為Bar的靜態嵌套類,那麼bean定義上的「類」屬性值將是…com.example.Foo$Bar
請注意,名稱中使用了$字元來分隔嵌套類名和外部類名。
用構造函數實例化
Spring IoC容器幾乎可以管理任何您希望它管理的類;它不限於管理真正的JavaBeans。大多數Spring用戶更喜歡實際的JavaBeans,它只有一個默認的(無參數)構造函數和適當的設置器和獲取器,它們是根據容器中的屬性建模的。您的容器中還可以有更多外來的非bean風格的類。例如,如果您需要使用一個完全不符合JavaBean規範的遺留連接池,Spring也可以管理它。
使用基於XML的配置元數據,您可以按如下方式指定bean類:
<bean id="exampleBean" class="examples.ExampleBean"/> <bean name="anotherExample" class="examples.ExampleBeanTwo"/>
有關向構造函數提供參數(如果需要)以及在構造對象後設置對象實例屬性的機制的詳細資訊,請參見注入依賴項。
用靜態工廠方法實例化
定義使用靜態工廠方法創建的bean時,可以使用class 屬性指定包含靜態工廠方法的類,並使用名為factory-method 的屬性指定工廠方法本身的名稱。您應該能夠調用此方法(帶有後面描述的可選參數)並返回一個活動對象,該對象隨後被視為是通過構造函數創建的。這種bean定義的一個用途是在遺留程式碼中調用靜態工廠
以下bean定義指定將通過調用factory-method來創建bean。定義沒有指定返回對象的類型(類),只指定包含工廠方法的類。在本例中,createInstance()方法必須是static方法。
<bean id="clientService"
class="examples.ClientService"
factory-method="createInstance"/>
public class ClientService { private static ClientService clientService = new ClientService(); private ClientService() {} public static ClientService createInstance() { return clientService; } }
有關為工廠方法提供(可選)參數以及在對象從工廠返回後設置對象實例屬性的機制的詳細資訊,請參見詳細的依賴關係和配置。
使用實例工廠方法的實例化
實例工廠方法的實例化從容器調用現有bean的非靜態方法來創建新bean。要使用這種機制,將class
屬性保留為空,並在factory-bean屬性中,指定當前(或父/祖先)容器中的bean的名稱,該容器包含要調用來創建對象的實例方法。用factory-method屬性設置工廠方法本身的名稱。
<!-- 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"/>
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"/>
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; } }
這種方法表明,工廠bean本身可以通過依賴注入(DI)來管理和配置。請參見詳細的依賴關係和配置。
在Spring文檔中,factory bean是指在Spring容器中配置的bean,它將通過實例或靜態工廠方法創建對象。相比之下,FactoryBean(注意大寫)指的是Spring特定的FactoryBean。
1.4.依賴性
1.4.1.依賴注入
依賴注入(DI)是一個過程,通過這個過程,對象定義它們的依賴關係,也就是說,它們使用的其他對象,只通過構造函數參數、工廠方法的參數,或者在對象實例被構造或從工廠方法返回後在對象實例上設置的屬性。然後,容器在創建bean時注入這些依賴項。這個過程從根本上說是控制反轉(IoC)的逆過程,即bean本身通過使用類的直接構造或服務定位器模式來控制其依賴關係的實例化或位置。
DI有兩種主要的變體,基於構造函數的依賴注入和基於Setter的依賴注入。
基於構造函數的依賴注入
基於構造函數的DI是通過容器調用一個帶有多個參數的構造函數來實現的,每個參數代表一個依賴關係。調用帶有特定參數的靜態工廠方法來構造bean幾乎是等價的,並且本討論將參數類似地對待構造函數和靜態工廠方法。下面的示例顯示了一個只能通過構造函數注入進行依賴注入的類。請注意,這個類沒有什麼特別之處,它是一個POJO,不依賴於容器特定的介面、基類或注釋。
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時將這些參數提供給適當的構造函數的順序。
package x.y; public class Foo { public Foo(Bar bar, Baz baz) { // ... } }
不存在潛在的歧義,假設Bar和Baz類沒有繼承關係。因此,以下配置工作正常,並且您不需要在< constructor-arg/>元素中顯式指定構造函數參數indexes/type。
<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無法確定值的類型,因此在沒有幫助的情況下無法按類型匹配。考慮以下類別:
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; } }
構造函數參數類型匹配
在前面的場景中,如果使用type屬性顯式指定構造函數參數的類型,容器可以使用簡單類型的類型匹配。例如:
<bean id="exampleBean" class="examples.ExampleBean"> <constructor-arg type="int" value="7500000"/> <constructor-arg type="java.lang.String" value="42"/> </bean>
構造函數參數索引
使用index屬性顯式指定構造函數參數的索引。例如:
<bean id="exampleBean" class="examples.ExampleBean"> <constructor-arg index="0" value="7500000"/> <constructor-arg index="1" value="42"/> </bean>
除了解決多個簡單值的不確定性之外,指定索引還解決了構造函數有兩個相同類型參數的不確定性。請注意,索引是基於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的依賴注入
基於Setter的DI是通過容器調用bean上的setter方法,然後調用無參數構造函數或無參數靜態工廠方法來實例化bean來實現的。
下面的示例顯示了一個只能使用純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... }
因為您可以混合基於構造函數和基於設置函數的DI,所以對於強制依賴項使用構造函數,對於可選依賴項使用設置方法或配置方法是一個很好的經驗法則。請注意,在setter方法上使用@Required注釋可以使屬性成為必需的依賴項。
Spring團隊通常提倡構造函數注入,因為它使人們能夠將應用程式組件實現為不可變的對象,並確保所需的依賴關係不為空。此外,構造函數注入的組件總是以完全初始化的狀態返回給客戶端(調用)程式碼。
Setter注入應該主要用於可選的依賴項,這些依賴項可以在類中被賦予合理的默認值。否則,必須在程式碼使用依賴關係的任何地方執行非空檢查。setter注入的一個好處是,setter方法使該類的對象可以在以後重新配置或重新注入。
使用對特定類最有意義的DI樣式。有時候,當處理第三方類時,你沒有源程式碼,選擇是為你做的。例如,如果第三方類沒有公開任何setter方法,那麼構造函數注入可能是DI唯一可用的形式。
依賴性解決過程
容器執行bean依賴關係解析,如下所示:
ApplicationContext是用描述所有beans的配置元數據創建和初始化的。配置元數據可以通過XML、Java程式碼或注釋來指定。
對於每個bean,它的依賴關係以屬性、構造函數參數或靜態工廠方法的參數的形式表示,如果您使用的是靜態工廠方法而不是普通的構造函數。這些依賴關係是在實際創建bean時提供給bean的。
每個屬性或構造函數參數都是要設置的值的實際定義,或者是對容器中另一個bean的引用。
作為值的每個屬性或構造函數參數都從其指定格式轉換為該屬性或構造函數參數的實際類型。默認情況下,Spring可以將字元串格式的值轉換為所有內置類型,如int、long、string、boolean等。
如果主要使用構造函數注入,就有可能創建一個無法解析的循環依賴場景。
比如:A類通過構造函數注入需要B類的一個實例,B類通過構造函數注入需要A類的一個實例。如果您將類A和類B的beans配置為相互注入,Spring IoC容器會在運行時檢測到此循環引用,並拋出一個BeanCurrentLincreationException。
一個可能的解決方案是編輯一些類的源程式碼,由設置者而不是構造者來配置。或者,避免構造函數注入,只使用setter注入。
與典型情況(沒有循環依賴)不同,bean A和bean B之間的循環依賴迫使其中一個bean在完全初始化之前被注入到另一個bean中(典型的先有雞還是先有蛋的場景)。
一般可以相信Spring做的是對的。它在容器載入時檢測配置問題,例如對不存在的beans和循環依賴的引用。在實際創建bean時,Spring儘可能晚地設置屬性和解決依賴關係。
這意味著,如果在創建一個對象或其依賴項時出現問題,正確載入的Spring容器可以在您請求該對象時生成異常。例如,由於缺少屬性或屬性無效,bean會引發異常。這種對某些配置問題的潛在延遲可見性是應用程式上下文實現默認預實例化單例beans的原因。在實際需要之前創建這些beans需要一些前期時間和記憶體,在創建應用程式上下文時,而不是稍後,您會發現配置問題。您仍然可以覆蓋這個默認行為,這樣單例beans將會延遲初始化,而不是預先實例化。
如果不存在循環依賴,當一個或多個協作bean被注入到一個依賴bean中時,每個協作bean在被注入到依賴bean之前都被完全配置好了。這意味著,如果bean A對bean B有依賴關係,那麼Spring IoC容器在調用bean A上的setter方法之前會對bean B進行完全配置,換句話說,bean被實例化(如果不是預實例化的單例),它的依賴關係被設置,相關的生命周期方法(比如配置的init方法或者InitializingBean回調方法)被調用。
依賴注入的例子
以下示例將基於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"/>
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文件中指定的屬性相匹配。以下示例使用基於構造函數的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被告知調用一個靜態工廠方法來返回對象的一個實例,而不是使用一個構造函數:
<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; } }
1.4.2.詳細的依賴關係和配置
您可以將bean屬性和構造函數參數定義為對其他託管bean(協作者)的引用,或者定義為內聯定義的值。Spring基於XML的配置元數據支援其< property/>和< constructor-arg/>元素中的子元素類型。
直接值(原語、字元串等)
< property/>元素的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命名空間進行更簡潔的XML配置。
<beans xmlns="//www.springframework.org/schema/beans"
xmlns:xsi="//www.w3.org/2001/XMLSchema-instance"
xmlns:p="//www.springframework.org/schema/p"
xsi:schemaLocation="//www.springframework.org/schema/beans
//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>
您還可以將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實例。這是一個很好的快捷方式,並且是Spring團隊支援使用嵌套的< value/>元素而不是value屬性樣式的少數地方之一。
idref元素
idref元素只是將容器中另一個bean的id(字元串值,而不是引用)傳遞給< 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"/>
</bean>
第一種形式優於第二種形式,因為使用idref標記允許容器在部署時驗證引用的命名bean是否確實存在。在第二種變體中,對傳遞給客戶端bean的targetName屬性的值不執行驗證。錯別字只有在客戶端bean實際實例化時才會被發現(最有可能導致致命的結果)。如果客戶端bean是一個原型bean,這個錯別字和產生的異常可能只有在部署容器之後很久才會被發現。
< idref/>元素帶來價值的一個常見之處(至少在Spring 2.0之前的版本中)是在一個ProxyFactoryBean定義中配置AOP攔截器。當您指定攔截器名稱時,使用< idref/>元素可以防止您拼錯攔截器id。
對其他beans(協作者)的引用
ref元素是<constructor-arg/>或<property/>定義元素中的最後一個元素。在這裡,您將一個bean的指定屬性的值設置為對由容器管理的另一個bean(協作者)的引用。被引用的bean是其屬性將被設置的bean的依賴項,並且在設置屬性之前根據需要對其進行初始化。(如果協作者是單例bean,它可能已經被容器初始化了。)所有引用最終都是對另一個對象的引用。作用域和驗證取決於您是否通過bbean
, local,
parent
屬性指定了另一個對象的id/name。
通過< ref/>bean屬性的值可以與目標bean的id屬性相同,也可以是目標bean的name屬性中的一個值。
<ref bean="someBean"/>
通過parent
屬性指定目標bean會創建對當前容器的父容器中的bean的引用。parent
屬性的值可以與目標bean的id屬性相同,也可以是目標bean的name屬性中的一個值,並且目標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" <!-- 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
< property/>或< constructor-arg/>元素中的< bean/>元素定義了一個所謂的內部bean。
<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中,也不可能獨立訪問它們。
集合
在<list/>, <set/>, <map/>,和<props/>元素中,分別設置了Java集合類型List
,Set
,Map
, 和Properties
的屬性和參數。
<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>
map key或value或set value的值也可以是以下任何元素:
bean | ref | idref | list | set | map | props | value | null
集合合併
子屬性集合的值集從父<props />繼承所有屬性元素,子支援值的值覆蓋父集合中的值。
集合合併的局限性
您不能合併不同的集合類型(如映射和列表),如果您嘗試這樣做,則會引發適當的異常。必須在較低的繼承子定義上指定merge屬性;在父集合定義上指定merge屬性是多餘的,並且不會導致所需的合併。
強類型集合
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>
Null 和 「」字元串
<bean class="ExampleBean"> <property name="email" value=""/> </bean>
exampleBean.setEmail("");
<bean class="ExampleBean"> <property name="email"> <null/> </property> </bean>
exampleBean.setEmail(null);
帶有p命名空間的XML快捷方式
p-namespace使您能夠使用bean元素的屬性而不是嵌套的< property/>元素來描述您的屬性值和/或協作bean。
<beans xmlns="//www.springframework.org/schema/beans" xmlns:xsi="//www.w3.org/2001/XMLSchema-instance" xmlns:p="//www.springframework.org/schema/p" xsi:schemaLocation="//www.springframework.org/schema/beans //www.springframework.org/schema/beans/spring-beans.xsd"> <bean name="classic" class="com.example.ExampleBean"> <property name="email" value="[email protected]"/> </bean> <bean name="p-namespace" class="com.example.ExampleBean" p:email="[email protected]"/> </beans>
該示例在bean定義中顯示了一個名為email的p-namespace屬性。這告訴Spring包含一個屬性聲明。如前所述,p-namespace沒有模式定義,因此您可以將屬性的名稱設置為屬性名稱。
<beans xmlns="//www.springframework.org/schema/beans" xmlns:xsi="//www.w3.org/2001/XMLSchema-instance" xmlns:p="//www.springframework.org/schema/p" xsi:schemaLocation="//www.springframework.org/schema/beans //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的引用。
帶有c-namespace的XML快捷方式
與帶有p-namespace的XML快捷方式類似,Spring 3.1中新引入的c-namespace允許使用內聯屬性來配置構造函數參數,而不是嵌套的構造函數參數元素。
<beans xmlns="//www.springframework.org/schema/beans" xmlns:xsi="//www.w3.org/2001/XMLSchema-instance" xmlns:c="//www.springframework.org/schema/c" xsi:schemaLocation="//www.springframework.org/schema/beans //www.springframework.org/schema/beans/spring-beans.xsd"> <bean id="bar" class="x.y.Bar"/> <bean id="baz" class="x.y.Baz"/> <!-- traditional declaration --> <bean id="foo" class="x.y.Foo"> <constructor-arg ref="bar"/> <constructor-arg ref="baz"/> <constructor-arg value="[email protected]"/> </bean> <!-- c-namespace declaration --> <bean id="foo" class="x.y.Foo" c:bar-ref="bar" c:baz-ref="baz" c:email="[email protected]"/> </beans>
對於構造函數參數名不可用的極少數情況(通常是在沒有調試資訊的情況下編譯位元組碼),可以使用回退到參數索引:
<!-- c-namespace index declaration -->
<bean id="foo" class="x.y.Foo" c:_0-ref="bar" c:_1-ref="baz"/>
實際上,構造函數解析機制在匹配參數方面非常有效,所以除非真的需要,否則我們建議在整個配置中使用名稱符號。
複合屬性名
設置bean屬性時,可以使用複合或嵌套屬性名,只要路徑的所有組件(除了最終屬性名)都不為空。
<bean id="foo" class="foo.Bar">
<property name="fred.bob.sammy" value="123" />
</bean>
foo bean有一個fred屬性,它有一個bob屬性,它有一個sammy屬性,最終sammy屬性被設置為值123。為了實現這一點,foo的fred屬性和fred的bob屬性在構造bean後不能為null,否則會引發NullPointerException。
1.4.3使用Using depends-on
如果一個bean是另一個bean的依賴項,這通常意味著一個bean被設置為另一個bean的屬性。通常,您可以使用基於XML的配置元數據中的< ref/>元素來實現這一點。但是,有時候beans之間的依賴關係不那麼直接;例如,類中的靜態初始化器需要被觸發,比如資料庫驅動註冊。
在初始化使用此元素的bean之前,depends-on屬性可以顯式強制初始化一個或多個bean。以下示例使用depends-on屬性來表示對單個bean的依賴:
<bean id="beanOne" class="ExampleBean" depends-on="manager"/>
<bean id="manager" class="ManagerBean" />
要表示對多個bean的依賴,請提供一個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" />
bean定義中的dependent-on屬性既可以指定初始化時的依賴關係,初始化時,被依賴的bean是先被初始化的;也可以指定相應的銷毀時的依賴關係(僅限於單例bean)。depends-on適用於表面上看起來兩個bean之間沒有使用屬性之類的強連接的bean,但是兩個bean又確實存在前後依賴關係的情況,使用了depends-on的時候,依賴他人的bean是先於被依賴bean銷毀的,因此依賴也可以控制關機順序。
depends-on 強制的說明在該Bean 初始化之前,那些Bean必須先初始化!
ref通常用在一個Bean的屬性指向另外一個Bean,這個Bean必須先初始化。
1.4.4.惰性初始化的beans
默認情況下,作為初始化過程的一部分,應用上下文實現急切地創建和配置所有的單例beans。通常,這種預實例化是可取的,因為配置或周圍環境中的錯誤會立即被發現,而不是幾小時甚至幾天後。當這種行為不可取時,您可以通過將bean定義標記為惰性初始化來防止單例bean的預實例化。惰性初始化的bean告訴IoC容器在第一次被請求時創建一個bean實例,而不是在啟動時。
在XML中,這種行為由< bean/>元素上的lazy-init屬性控制;例如:
<bean id="lazy" class="com.foo.ExpensiveToCreateBean" lazy-init="true"/>
<bean name="not.lazy" class="com.foo.AnotherBean"/>
當前面的配置被應用程式上下文使用時,名為lazy的bean在應用程式上下文啟動時不會被提前實例化,而not.lazy bean會被提前實例化。
然而,當延遲初始化的bean是未延遲初始化的單例bean的依賴項時,應用程式上下文會在啟動時創建延遲初始化的bean,因為它必須滿足單例bean的依賴項。惰性初始化的bean被注入到未惰性初始化的單例bean中。
您還可以通過在< beans/>元素上使用default-lazy-init屬性來控制容器級別的惰性初始化;例如:
<beans default-lazy-init="true">
<!-- no beans will be pre-instantiated... -->
</beans>
1.4.5.Autowiring 協作者
Spring容器可以用autowire 協作beans之間的關係。您可以允許Spring通過檢查ApplicationContext的內容來為您的bean自動解析協作者(其他bean)。自動布線有以下優點:
自動連接可以大大減少指定屬性或構造函數參數的需要。
自動布線可以隨著對象的發展更新配置。例如,如果您需要向一個類添加一個依賴項,該依賴項可以自動得到滿足,而無需修改配置。因此,自動連接在開發過程中特別有用,當程式碼庫變得更加穩定時,不排除切換到顯式連接的選項。
no (默認)無自動布線。Bean引用必須通過ref元素來定義。對於較大的部署,不建議更改默認設置,因為顯式指定協作者可以提供更好的控制和清晰度。在某種程度上,它記錄了一個系統的結構。
byName 按屬性名自動布線。Spring尋找與需要自動連接的屬性同名的bean。例如,如果一個bean定義按名稱設置為autowire,並且它包含一個master屬性(也就是說,它有一個setMaster(..)方法),Spring尋找一個名為master的bean定義,並使用它來設置屬性。
byType 如果容器中恰好存在一個屬性類型的bean,則允許自動連接屬性。如果存在多個bean,則會引發致命異常,這表明您不能對該bean使用byType自動連接。
constructor 類似於byType,但適用於構造函數參數。如果容器中沒有恰好一個構造函數參數類型的bean,則會引發致命錯誤。
自動布線autowiring的局限性和缺點
當自動布線在整個項目中一致使用時,效果最佳。如果通常不使用自動連接,開發人員可能會對使用它來連接一個或兩個bean定義感到困惑。
1、property
和constructor-arg設置中的顯式依賴關係總是覆蓋自動連接。您不能自動連接所謂的簡單屬性,如原語、字元串和類(以及此類簡單屬性的數組)。這個限制是被設計出來的。
2、自動布線不如顯式布線精確。儘管如上表所述,Spring小心翼翼地避免在可能產生意外結果的模糊情況下進行猜測,但是Spring管理的對象之間的關係不再被明確記錄。
3、布線資訊可能不適用於可能從Spring容器生成文檔的工具。
4、容器中的多個bean定義可能與要自動連接的setter方法或構造函數參數指定的類型相匹配。對於數組、集合或映射,這不一定是個問題。然而,對於期望單個值的依賴項,這種模糊性並不是任意解決的。如果沒有唯一的bean定義可用,則會引發異常。
1、放棄自動布線,支援顯式布線。
2、通過將bean定義的autowire-candidate屬性設置為false來避免自動連接bean定義。
3、通過將< bean/>元素的primary 屬性設置為true,將單個bean定義指定為主要候選對象。
4、實現基於注釋的配置中可用的更細粒度的控制。
從autowiring中排除一個bean
基於每個bean,您可以從自動連接中排除一個bean。在Spring的XML格式中,將< bean/>元素的autowire-candidate屬性設置為false該容器使特定的bean定義對自動連線基礎結構(包括注釋樣式配置,如@自動連線)不可用。
autowire-candidate屬性設計為僅影響基於類型的自動布線。它不影響按名稱的顯式引用,即使指定的bean沒有被標記為autowire-candidate對象,該引用也會被解析。因此,如果名稱匹配,按名稱自動連接仍然會注入一個bean。
您還可以根據bean名稱的模式匹配來限制自動連接候選對象。頂層< beans>元素在其default-autowire-candidates屬性中接受一個或多個模式。例如,要將自動連線候選狀態限制為名稱以存儲庫結尾的任何bean,請提供*存儲庫的值。要提供多個模式,請在逗號分隔的列表中定義它們。
bean定義autowire-candidates屬性的顯式值true或false總是優先,對於這種bean,模式匹配規則不適用。
這些技術對於您永遠不想通過自動連接注入到其他bean中的bean非常有用。這並不意味著排除的bean本身不能使用自動連接進行配置。相反,bean本身並不是自動連接其他bean的候選對象。