javascript
Spring —— IoC 容器详解
引言
本篇博客總結自官網的《The IoC Container》,其中會結合王富強老師的《Spring揭秘》融入自己的語言和理解,爭取通過這一篇文章徹底掃除spring IOC的盲區。
本文介紹什么是 IoC 容器,什么是 Bean,依賴,Bean Definition,Bean Factory 等概念知識。
文中會大量出現 xml 的容器配置文件,雖然由于目前 Spring Boot 項目的流行,人們已經很少使用 xml 配置,而且更喜好 Java Config 的配置,但配置只是形式的不同,其內部邏輯都是共通的!
一、什么是 Spring IoC 容器?什么是 Bean?
IoC 也可以理解為依賴注入(dependency injection)(參考《控制反轉 IOC 與依賴注入 DI》),它是一個只通過構造函數參數、工廠方法參數、或通過屬性賦值等方式去定義它們的依賴的一個過程,簡言之,就是屬性對象賦值。
【《Spring揭秘》:在Spring中,存在三種依賴注入的方式接口注入、構造器注入、setter注入
1、接口注入:要求實現某個接口才能完成依賴注入,本身帶有很強的侵入性,目前已經“退役”
2、構造方法注入:優點是對象在構造完成之后,即已進入就緒狀態,可以馬上使用。缺點是,當依賴對象比較多的時候,構造方法的參數列表會比較長。而通過反射構造對象的時候,對相同類型的參數的處理會比較困難,維護和使用上也比較麻煩。而且在Java中,構造方法無法被繼承,無法設置默認值。
3、setter方法注入:setter方法描述性更強,可以被繼承,允許設置默認值。缺點是對象無法在構造完成后馬上進入就緒狀態。】
用一句話概括IOC可以給我們帶來什么?IOC是一種可以幫助我們解耦各業務對象間依賴關系的對象綁定方式。——《Spring解密》
IoC容器會在創建對象的時候注入這些依賴,這基本上是 bean 自己去控制實例化的一個反向,因此叫 Inversion of control 控制反轉。
org.springframework.beans 和 org.springframework.context 包是 Spring IoC 容器的基礎。BeanFactory 接口提供了一種高級的配置機制,使得 Spring 有能力去管理對象的類型。
ApplicationContext 是 BeanFactory 的一個子接口,它添加了一些額外的特性:
1、更簡單的 Spring AOP 特性的集成
2、信息資源處理(用于國際化)
3、事件發布
4、應用程序層面的特定上下文,例如 WebApplicationContext 之于 Web 項目。
簡而言之,BeanFactory 提供了配置框架和基本功能,ApplicationContext 加入更適合企業級特性的功能。
【《Spring揭秘》:
兩種容器類型的基本區別:
1、BeanFactory:基礎類型IOC容器。默認采用延遲初始化策略(lazy-load)(受管對象直到客戶端請求時才進行初始化及依賴注入)。容器啟動速度較快,所需資源有限。
2、ApplicationContext:繼承自BeanFactory,擁有BeanFactory的所有支持,并擴展了高級特性。該類型容器啟動后,默認對受管對象全部初始化并綁定完成。容器啟動速度較慢,所需資源更多。
對容器的比喻(如何看待bean工廠的存在?):
BeanFactory就像一個汽車生產廠。你將汽車零件送入這個汽車生產廠,最后,只需要從生產線的終點取得成品汽車就可以了。】
在 Spring 中,組成應用主干并且由 Spring IoC 容器管理的對象被稱為 Bean。bean 是一個由 spring IoC 容器實例化、裝配和管理的對象。否則,bean 就是一個應用程序中許許多多對象中的一個普通一員。Bean 和它的依賴都通過IoC容器使用的配置文件來描述。
org.springframework.context.ApplicationContext 接口就代表 Spring 的 IoC 容器,它的職責就是 實例化、配置、裝配各種 bean。容器通過讀取配置文件獲得實例化、配置、裝配什么對象的指令。配置信息可以使用 xml、Java 注解、或者 Java 代碼來描述。
Spring 已經提供了一些 ApplicationContext 接口的實現。在單體應用中,通常會創建 ClassPathXmlApplicationContext 或 FileSystemXmlApplicationContext 的實例對象。
在更多數的應用場景中,不需要顯式的用戶代碼來實例化一個或多個 Spring IoC 容器。下面的圖解展示了 Spring 是如何工作的:
二、用于描述 IoC 容器的配置信息
如前面圖解所示,spring IoC 容器需要使用一些配置信息。這些配置信息表示你將要告訴 IoC 容器如何去實例化、配置、裝配應用程序中的對象。
配置信息通常以簡單直觀的XML格式提供,本文也會以這種配置形式來講解關鍵概念和 IoC 容器的特性。
提示:xml 并不是唯一的配置形式。不同的配置形式與 Spring 框架本身完全解耦,目前,很多開發者更偏好使用基于Java? Config 的配置形式。?
1、基于注解(如@Service、@Component等)的配置,是 Spring 2.5 引入的支持以注解形式描述配置信息的一種方式。
2、基于Java 的配置,從 Spring 3.0 開始,JavaConfig 提供的許多特性已經變成 Spring Framework 的核心。這樣,你可以通過 Java 而不是 xml 文件來定義應用程序中的各種類。例如 @Configuration、@Bean、@Import 和 @DependsOn 等。
Spring 容器的配置由至少一個 bean 組成。xml 形式的配置以<bean/> 標簽來描述這些 bean 定義,并以頂級標簽<beans/> 來包裹他們。JavaConfig 形式的配置則需要在一個標記了@Configuration 的類中的 bean定義的方法上標記@Bean。
這些 bean 定義直接對應著組成你的應用程序的真正的對象。最典型的,就是定義 Service 層對象、DAO對象(data access objects),基礎設施對象如Hibernate 的 SessionFactories,JMS 的 Queues 等等。通常,并不需要配置細粒度的域對象(domain objects 即實體類對象),因為創建和加載它們通常是 DAO 和業務邏輯的職責。
下面的例子展示了 XML 結構的配置(id 屬性是?bean 的唯一標識,class 屬性以全類名定義 bean的類型。):
<?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/beanshttps://www.springframework.org/schema/beans/spring-beans.xsd"><bean id="..." class="..."> <!-- 內部配置協作者和配置信息 --></bean><bean id="..." class="..."><!-- 內部配置協作者和配置信息 --></bean><!-- 更多的bean配置 --> </beans>三、容器的應用
3.1 實例化一個容器
ApplicationContext 構造函數允許傳入配置信息的位置路徑,可以是系統路徑,也可以是項目的 classpath 下。
ApplicationContext context = new ClassPathXmlApplicationContext("services.xml", "daos.xml");提示:Spring Resource 抽象提供了一種更便捷的機制,允許從一個 URI 定位信息中以 InputStream 來讀取資源。更多描述參考:Application Contexts and Resource Paths
services.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/beanshttps://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/beanshttps://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>bean 的定義可以跨越多個配置文件。如上所示,services.xml 中定義的 “petStore” 依賴了 daos.xml 中定義的 “accountDao ”和 “itemDao”。通常,每個單獨的 XML? 配置文件都代表一個邏輯層或者應用架構中的一個模塊。
可以使用應用程序上下文構造器來加載所有這些 xml 片段中的 bean 定義。這些構造器可接收多個 Resource 位置,如:
ApplicationContext context = new ClassPathXmlApplicationContext("services.xml", "daos.xml");或者,使用 <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。所有的位置路徑都是執行導入操作文件(以下簡稱“執行 import 文件”)的相對路徑,所以 services.xml 必須和執行 import 文件在相同的文件夾,messageSource.xml 和 themeSource.xml 必須在執行 import 文件所在路徑下的 resources 文件夾中。?如你所見,開頭的斜杠可以忽略。但是給定的路徑一定是相對路徑,所以最好的形式就是使用不帶斜杠的形式。包括頂層<beans>標簽一起都會導入進來,要求被導入的一定要是有效的 xml bean 定義文件。
提示:如果想引用一個父文件夾中的文件,使用 “../” 的形式是可以的,但并不推薦。因為這么做會創建一個相對于當前應用程序之外的文件依賴。尤其不推薦引用路徑相對于 classpath: URL(例如:classpath:../services.xml),這樣會讓運行時解析程序既要選擇“最近的” classpath 根路徑,還要去它的父路徑中檢查。類路徑配置更改可能導致選擇不同的、不正確的目錄。
你也可以使用絕對路徑,例如,file:C:/config/services.xml 或者 classpath:/config/services.xml。但是,你要知道這么做會讓你的應用程序配置和特殊的絕對路徑耦合。
3.2 使用 IoC 容器(updated on 2020-10-14)
【《Spring揭秘》BeanFactory的對象注冊與依賴綁定方式:
相關接口:
BeanFactory接口定義了最基本的獲取bean的方法,包括各種方式的getBean,以及對bean的一些判斷,包括isSingleton、containsBean等。
BeanDefinitionRegistry接口定義了基本的管理bean的方法,包括注冊、移除等,從名字可以看出,這個接口針對的都是BeanDefinition類型,這也是bean在容器中的信息模板對象。
DefaultListableBeanFactory類是默認的通用bean工廠,(間接)實現了前面兩個接口,具有了獲取bean和注冊bean的能力。
如何理解BeanFactory和BeanDefinitionRegistry的關系?
打個比方,BeanDefinitionRegistry就像圖書館的書架,所有的書都放在書架上,借書還書都是跟圖書館(BeanFactory)打交道,但書架才是圖書館存放書籍的地方,即:BeanDefinitionRegistry就是BeanFactory的書架。
(BeanFactory只是一個接口,我們最終需要一個該接口的實現來進行實際的bean管理,DefaultListableBeanFactory就是這樣一個比較通用的BeanFactory實現類。它除了間接實現了BeanFactory接口,還實現了BeanDefinitionRegistry接口,該接口才是在BeanFactory的實現中擔當bean注冊管理的角色。
基本上,BeanFactory接口只定義如何訪問容器內管理的bean的方法,各個BeanFactory的具體實現類負責具體bean的注冊以及管理工作。BeanDefinitionRegistry接口定義抽象了bean的注冊邏輯。通常情況下,具體的BeanFactory實現類會實現這個接口來管理bean的注冊)】
ApplicationContext 是一個接口,是一個更高級的對象工廠(advanced factory),可以維護各種 bean 和它們的依賴。使用 getBean(String name, Class<T> requiredType)方法,你可以拿到你已經定義好的 bean。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();你可以使用 getBean 來取得 bean 的實例。ApplicationContext 接口有一些其他方法來取得 bean ,但是,理想狀態下,你的應用程序代碼永遠也不應該使用這些方法。是的,你的應用程序根本不應該使用 getBean() 方法,這樣就不會耦合 Spring API。例如,Spring 集成了 web 框架,提供了各種 web 框架組件的依賴注入,例如 controller 和 JSF-managed bean,允許你通過元數據(如自動注入注解 @Autowired)來聲明一個特殊的bean。
四、Bean 概述
4.1 什么是 Bean Definition?(updated on 2020-10-14)
一個 IoC 容器管理一個或多個 bean。這些 bean 由你提供給容器的配置信息創建(例如,在 xml 中由 <bean/> 定義)。
在容器內部,這些 bean 的定義被描述為 BeanDefinition 實例(RootBeanDefinition和ChildBeanDefinition是BeanDefinition的兩個主要實現類),包含以下這些元數據(部分):
1、一個全類名。通常是定義的 bean 的實際實現類。
2、Bean 的行為配置元素,聲明bean在容器中的狀態行為(作用域、生命周期回調,等等)。
3、對bean執行其工作所需的其他bean的引用。這些引用也稱為協作者或依賴項。
4、其他配置,例如,池的大小限制或在管理連接池的bean中使用的連接數。
這些元數據被轉換為一系列的組成 BeanDefinition 的屬性。下表描述了這些屬性信息:
| Class | Instantiating Beans |
| Name | Naming Beans |
| Scope | Bean Scopes |
| Constructor arguments | Dependency Injection |
| Properties | Dependency Injection |
| Autowiring mode | Autowiring Collaborators |
| Lazy initialization mode | Lazy-initialized Beans |
| Initialization method | Initialization Callbacks |
| Destruction method | Destruction Callbacks |
除了 bean definition 這種包含了如何創建一個具體對象的 bean 定義之外,ApplicationContext 的實現也允許容器之外的已存在的對象注冊進來。只需要使用 ApplicationContext 的 BeanFactory——getBeanFactory() 方法。這個方法會返回 DefaultListableBeanFactory 對象,它是 BeanFactory的實現類。DefaultListableBeanFactory 中的 registerSingleton(..) 和 registerBeanDefinition(..) 方法支持這種容器外注冊操作。然而,典型的應用程序僅使用通過常規bean定義元數據定義的bean。
提示:bean 的元數據和手動提供的單例實例需要盡早注冊,這是為了容器能夠在自動裝配和其他自省步驟中正確地對它們進行推理。雖然在某種程度上支持覆蓋存在的元數據和存在的單例對象,但在運行時(并發地實時訪問工廠)注冊新的 bean不受官方支持,并可能導致并發訪問異常,或容器中 bean 的狀態不一致,或者兩者都有可能。
4.2 Bean 的命名(updated on 2020-10-14)
每個 bean 都有一個或多個標識(identifiers)。這些標識在 bean 定義的所屬容器中必須是唯一的。一個 bean 通常只有一個 標識,但是,如果它需要更多的標識,其他的可視為別名。
在 xml 配置中,你可以使用 id 屬性 和 name 屬性,或者同時使用。id 屬性代表唯一 id 。按照慣例,這些名稱是字母數字('myBean'、'someService'等),但它們也可以包含特殊字符。如果你想為這些 bean 引入其他別名,也可以指定 name 屬性,以逗號、分號 或空白字符間隔即可。但不建議指定過多的別名,這會造成一定的兼容問題。
其實你并不需要為 bean 指定 name 或 id ,容器會自動為這些 bean 生成一個唯一名稱。但是如果你想通過名稱來引用另一個 bean,可以使用 ref 或 Service Locator 風格查找,就必須為 bean 指定一個名稱。不提供名稱往往和使用內部 bean(inner bean) 和自動裝配協作者(autowiring collaborators)有關。
Bean 的命名約定
bean 命名約定遵循標準的 Java 對實例屬性名稱的命名慣例。也就是,bean 的名稱以小寫字母開頭配合駝峰命名法。例如:accountManager、accountService、userDao、loginController 等等。
一致的 bean 命名風格可以讓你的配置更容易閱讀和理解。同樣,如果你使用 Spring?AOP,這樣的命名在將通知應用到一組名稱相關的 bean 時很有幫助。
提示:通過類路徑(classpath)掃描,Spring 會為未命名的組件生成一個名字。遵從的規則就是前面的規則 :取得類名,然后把首字母變成小寫。但是如果一些特殊情況,比如,有一個以上的字符,且第一個和第二個字符都是大寫,那么會保留原始大小寫。這個規則和 java.beans.Introspector.decapitalize 的規則一致。
Java Config 配置 Bean 的名稱
?如果使用 Java Config 配置 bean,可以使用 @Bean 注解的 name 屬性,支持傳入多個別名:
public @interface Bean {@AliasFor("name")String[] value() default {};@AliasFor("value")String[] name() default {};// initMethod()、destroyMethod() ... }如果只有一個名稱,可以像這樣配置:@Bean("accountService")。
4.3 Bean 的實例化(updated on 2020-10-14)
一個 bean 定義本質上就是一個創建一個或多個對象的“配方”(recipe,或食譜)。當被請求時,容器查看已命名bean的“配方”,并使用該 bean 定義封裝的配置元數據來創建(或獲取)實際對象。
<bean>標簽的 class 屬性表示該 bean 的類型,這個屬性是必須的。在 BeanDefinition 內部,就是 Class 屬性。你可以使用下面兩種方式其一來使用 Class 屬性。
1、大多數情況,容器通過反射調用對象的構造器來創建 bean ,class 用于指定一個構造類型,有點類似于 new 操作。
2、指定一個包含靜態工廠方法的類,這個靜態工廠方法會創建對象,這中情況不多見。從靜態工廠方法返回的對象類型,可以是本類,也可以完全是另一個類。
內部類名稱
如果你想為一個靜態內部類(static nested class)配置一個 bean,那必須使用該內部類的二進制名稱(the binary name of the nested class)?。
例如,如果你有一個類叫做 SomeThing ,包名是 com.example,而且這個 SomeThing 類有一個靜態內部類叫做 OtherThing,那么 bean 定義的 class 屬性就應該是:com.example.SomeThing$OtherThing。
注意 $ 符號的使用,它將內部類的名稱與外部類的名稱分隔開。
4.3.1 使用構造器實例化 bean
Spring 極大的兼容了這種創建 bean 的方式,也就是說,被實例化的類不需要實現任何特定的接口,或以特殊的方式進行編碼。簡單地指定聲明 bean 的類型就足夠。但是,視創建指定bean 的?IOC 容器的類型而定,你可能會需要一個默認 bean 構造器。
Spring IoC容器實際上可以管理你希望它管理的任何類。而不限于 JavaBean。絕大多數 Spring 用戶更喜歡實際的 JavaBean,它只有一個默認的(無參數的)構造器和適當的setter和getter方法,這些方法是根據容器中的屬性建模的。你還可以在容器中使用更多奇異的非bean樣式的類。例如,你需要使用絕對不符合JavaBean規范的遺留的連接池,Spring也可以管理它。
以 xml 的形式,你可以像下面這樣:
<bean id="exampleBean" class="examples.ExampleBean"/> <bean name="anotherExample" class="examples.ExampleBeanTwo"/>更多關于如何向構造器提供參數(如果需要的話)和對象構造結束后為對象賦值的機制,參考:Injecting Dependencies。?
4.3.2 使用靜態工廠方法實例化 bean
當定義一個使用靜態工廠方法創建的 bean 時,使用 class 屬性指定包含了靜態工廠方法的類,然后再使用 factory-method 屬性來指定工廠方法的名字。如下所示:
<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;} }更多有關向工廠方法提供參數和返回對象后如何賦值的機制,參考:?Dependencies and Configuration in Detail。?
4.3.3?使用實例工廠方法實例化 bean
還有一種方法是通過實例工廠方法創建 bean。和靜態工廠方法類似,使用實例工廠方法進行實例化會調用一個“非靜態”的已存在容器內的bean的方法來創建對象。
使用這種機制時,class 屬性為空即可,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"/> 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;} }?在 Spring 文檔中,“factory bean” 指的是在 Spring 容器中配置的可以通過實例工廠或靜態工廠方法創建對象的一種 bean。相對的,FactoryBean(注意大小寫)指的是 Spring 框架特有的 FactoryBean 接口及實現類。
?【《Spring揭秘》關于FactoryBean:
當某些對象的實例化過程過于繁瑣,通過xml配置過于復雜,就可以實現FactoryBean接口,給出對象實例化邏輯代碼。FactoryBean是Spring提供的對付這種情況的“制式裝備”(正規軍)。
FactoryBean的三個抽象方法:
1、getObject()方法會返回該FactoryBean“生產”的對象實例,我們需要實現這個方法以給出自己的對象實例化邏輯。
2、getObjectType()方法返回對象的類型,如果預先無法確定,則返回null。
3、isSingleton()方法用于表明getObject()返回的對象是否以singleton形式存在。
案例:
public class NextDayDateFactoryBean implements FactoryBean{ public Object getObject() throws Exception{return new DateTime().plusDays(1); } public Class getObjectType(){ return DateTime.class; }public boolean isSingleton(){ return false; } } <bean id=”displayer” class=”...Displayer”><property name=”nextDay”><ref bean=”nextDayDate”></property> </bean> <bean id=”nextDayDate” class=”...NextDayDateFactoryBean”> </bean> public class Displayer {private DateTime nextDay;// ... }提示:FactoryBean類型的bean定義,通過正常的 id 引用,容器返回的是FactoryBean所“生產”的對象類型,而非FactoryBean類型本身】
4.4 判斷 Bean 的運行時類型(updated on 2020-10-14)
具體 bean 的運行時類型并不容易確定。bean 定義中指定的 class 只不過是一個初始類型參考,可能潛在綁定了一個聲明的工廠方法,或者作為一個可能會導致一個不同運行時 bean 類型的?FactoryBean,或在實例工廠方法中根本不設置(如前所述)。此外,AOP的代理可能會使用基于接口的代理對象包裹 bean 實例,這也會限制目標 bean 的真實類型的暴露。
找出指定 bean 的真實運行時類型的方法,推薦使用 BeanFactory.getType 方法,使用 bean 的名稱作為參數。上述所有情況都包括在內,這個方法可以返回相同 bean 名稱的對象的類型。
五、依賴
5.1 Dependency Injection 依賴注入
依賴注入是一個只通過構造函數參數、工廠方法參數、或通過屬性賦值等方式去定義它們的依賴的一個過程,簡言之,就是屬性賦值。
容器會在創建 bean 的時候注入那些它需要的依賴。這基本是由對象自己控制實例化的反向操作,因此得名控制反轉。
使用 DI 原則會讓代碼更加整潔,而且,當對象被它們的依賴所提供時,也可以更好的解耦。對象不再需要去查找它的依賴,也不需要知道這些依賴的位置和類型信息。因此,你的類就會更容易的去做測試。
DI 存在兩種主要的變體:構造器依賴注入和 Setter 依賴注入。
構造器依賴注入通過容器調用一定數量參數的構造來完成,每一個參數都代表一個依賴項。調用靜態工廠方法,傳入指定參數來構造 Bean 的形式和這差不多。下面代碼展示了只能用構造器注入的依賴注入模式:
public class SimpleMovieLister {// the SimpleMovieLister has a dependency on a MovieFinderprivate MovieFinder movieFinder;// a constructor so that the Spring container can inject a MovieFinderpublic SimpleMovieLister(MovieFinder movieFinder) {this.movieFinder = movieFinder;}// business logic that actually uses the injected MovieFinder is omitted... }注意,上述代碼并沒有任何特別之處。這就是一個 POJO ,沒有依賴容器的任何特定接口、基礎類或注解。
使用參數類型就會觸發構造器參數類型解析匹配。如果bean定義的構造函數參數中不存在潛在的歧義,那么構造函數參數在bean定義中定義的順序就是實例化bean時將這些參數提供給適當的構造函數的順序。考慮以下類:
public class ThingOne {public ThingOne(ThingTwo thingTwo, ThingThree thingThree) {// ...} }假設 ThingTwo 和 ThingThree 類沒有繼承關系,沒有潛在的歧義存在。這樣,下面的配置就可以很好的工作,你不需要在<constructor-args/>標簽中顯式地指定構造器參數下標或類型。
<beans><bean id="beanOne" class="x.y.ThingOne"><constructor-arg ref="beanTwo"/><constructor-arg ref="beanThree"/></bean><bean id="beanTwo" class="x.y.ThingTwo"/><bean id="beanThree" class="x.y.ThingThree"/> </beans>當另一個 bean 被引入,且類型已知,那么就會匹配到。當用到一個簡單類型,如<value>true</value>,Spring 無法判斷值的類型,也因此無法自主匹配。如下所示:
public class ExampleBean {// Number of years to calculate the Ultimate Answerprivate int years;// The Answer to Life, the Universe, and Everythingprivate 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 屬性來顯式指定構造器參數的下標,注意下標從 0 開始。如下所示:
<bean id="exampleBean" class="examples.ExampleBean"><constructor-arg index="0" value="7500000"/><constructor-arg index="1" value="42"/> </bean>下標有時還可解決構造器具有相同簡單類型時出現的歧義問題。
也可以使用參數名稱來消除歧義:
<bean id="exampleBean" class="examples.ExampleBean"><constructor-arg name="years" value="7500000"/><constructor-arg name="ultimateAnswer" value="42"/> </bean>注意,這種方式需要配合 debug 標志,以便 Spring 可以找到構造器的參數名,你可以使用 @ConstructorProperties JDK注解來顯式地命名你的構造器參數,如下所示:
public class ExampleBean {@ConstructorProperties({"years", "ultimateAnswer"})public ExampleBean(int years, String ultimateAnswer) {this.years = years;this.ultimateAnswer = ultimateAnswer;} }Setter 依賴注入,通過容器調用無參構造或無參靜態工廠方法實例化 bean 之后調用 setter 方法來完成。
下面的 demo 展示只能通過純 setter 方法實現依賴注入的方式:
public class SimpleMovieLister {// the SimpleMovieLister has a dependency on the MovieFinderprivate MovieFinder movieFinder;// a setter method so that the Spring container can inject a MovieFinderpublic void setMovieFinder(MovieFinder movieFinder) {this.movieFinder = movieFinder;}// business logic that actually uses the injected MovieFinder is omitted... }ApplicationContext 支持基于構造器和基于 Setter 方法來對它所管理的 bean 進行依賴注入,同時也支持兩種混合使用。
你可以BeanDefinition的形式配置依賴,結合 PropertyEditor 實例,將屬性從一種形式轉化為另一種形式。然而,絕大多數 Spring 用戶并不會以編程的方式直接使用這些類,而是使用 XML 的bean definition、注解式組件(即 @Component、@Controller等等)、或在@Configuration 標記的類中使用 @Bean 的形式(即 Java Config)。這些元信息依然會在內部轉化為 BeanDefinition 實例,并且被用于加載整個 Spring IOC 容器實例。
選擇構造器 DI 還是 Setter DI?
由于可以混合使用基于構造器和基于setter的DI,對于強制依賴項使用構造器,而對于可選依賴項使用setter方法或配置方法,這是一個很好的經驗法則。請注意,在setter方法上使用@Required注釋可以使屬性成為必需的依賴項,但是,更好的方式是使用構造器然后對參數進行編程驗證。
Spring 團隊更提倡使用構造器注入,因為這樣可以將應用程序組件實現為不可變對象,并確保所需的依賴項不為空。此外,構造器注入的組件總是會返回完全初始化的狀態給調用代碼。另外提一句,根據馬丁福勒的重構原理,攜帶大量參數的構造器是一種“壞味道”,這意味著類可能有太多的職責,應當適當進行重構,分離一部分依賴。
Setter注入應該主要用于可選的依賴項,這些依賴項可以在類中分配合理的默認值。否則,必須在代碼使用依賴項的任何地方執行非空檢查。setter注入的一個好處是,setter方法使該類的對象易于稍后重新配置或重新注入。
5.2 依賴解析處理
容器會以下面的方式執行 bean 依賴解析操作:
1、ApplicationContext 通過使用 bean 配置信息(可以是 XML、注解、Java Config)進行創建和初始化。
2、對于每一個 bean ,它的依賴描述為屬性、構造器函數參數、或者靜態工廠方法中的其中一種形式。這些依賴會當 bean 真正創建的時候提供給 bean。
3、每個屬性或構造函數參數都是要設置的值的實際定義,或者是對容器中另一個bean的引用。
4、作為值的每個屬性或構造函數參數都從其指定的格式轉換為該屬性或構造函數參數的實際類型。默認情況下,Spring可以將字符串格式提供的值轉換為所有內置類型,比如int、long、string、boolean等等。
Spring 容器會在創建時驗證每個 bean 的配置。但只有在真正實例化 bean 的時候才會為這些 bean 屬性賦值。容器在創建之初,會將 bean 創建為 單例(singleton-scoped)且設置為“預實例化”(默認)。Scope 被定義在 Bean Scopes。否則,bean 只有在被請求時才會創建。創建 bean 可能會導致一系列的 bean 都被創建,因為bean 本身需要依賴,而依賴也需要依賴。這些依賴項的解析工作可能會在其后發生,即第一次創建受影響的 bean 時。(Creation of a bean potentially causes a graph of beans to be created, as the bean’s dependencies and its dependencies' dependencies (and so on) are created and assigned. Note that resolution mismatches among those dependencies may show up late?—?that is, on first creation of the affected bean.)
循環依賴
如果你使用顯式構造器注入,可能會出現無法解析的循環依賴問題。
例如,A 對象需要通過構造器注入一個 B 對象,并且 B 也需要通過構造器注入一個 A 對象。如果使用 spring 容器配置 A 和 B 注入彼此,那么 IOC 容器會在運行時檢測到這樣的循環引用,并拋出 BeanCurrentlyInCreationException。
一個可能的解決方案是編輯源碼,將構造器注入改為 setter。要么,避免使用構造器注入,只允許使用 setter 注入。換句話說,盡管并不推薦 setter 注入,但 setter 注入卻可以解決循環依賴問題。
和典型的應用場景不同,循環依賴問題描述的是 bean A 和 bean B 之間,強制其中一個要在完全初始化之前注入到另一個對象中(這是典型的先有雞還是先有蛋的問題)。
Spring 容器可以在加載時檢測配置問題,例如引用了不存在的 bean 和循環依賴問題。當 bean 被真正創建之時,Spring 會盡可能晚的為屬性賦值和解析依賴。也就是說,當你請求一個對象時,如果創建該對象或其依賴發生了問題,Spring 容器會晚一點產生一個異常,例如一個找不到或無效的屬性,就會拋出一個異常。這些都是一些潛在的延遲問題,這也是為什么 ApplicationContext 實現要默認采用預實例化單例 bean 的原因。在實際需要之前創建這些bean需要花費一些前期時間和內存,但可以在創建 ApplicationContext 之時發現配置問題,而不是稍后發現。但也可以覆蓋這個默認行為,以便單例 bean 可以惰性地初始化,而不是預先實例化。
如果不存在循環依賴關系,那么當一個或多個協作bean被注入到依賴bean中時,每個協作bean在被注入到依賴bean之前都已被完全配置。也就是說,如果bean A依賴于bean B,那么容器會在調用bean A上的setter方法之前完全配置bean B。換句話說,這個 bean B 會被實例化(如果它不是預實例化的),它的依賴會設置完畢,它的相關生命周期回調方法(例如配置的init 方法或 InitializingBean 回調方法)都會被調用執行。
以下代碼是使用 xml 描述的 setter 注入:
<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 {// a private constructorprivate 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;} }5.3 depends-on
如果一個 bean 是另一個 bean 的依賴,通常意味著這個 bean 需要作為一個屬性設置到 另一個 bean 中。一般你可以使用 <ref> 標簽配置這樣的信息。但是,有時候 bean 之間的依賴關系并不那么直接。例如,當一個靜態初始化器需要注冊時,例如 數據庫驅動注冊。那么 depends-on 屬性就可以顯式地強制在初始化使用此元素的bean之前初始化一個或多個bean。如下所示:
<bean id="beanOne" class="ExampleBean" depends-on="manager"/> <bean id="manager" class="ManagerBean" />當需要描述多個依賴時,可以這樣寫:
<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" />提示:depends-on屬性既可以指定初始化階段依賴項,也可以指定對應的銷毀階段依賴項(僅在單例bean中)。在銷毀給定bean本身之前,首先銷毀與給定bean定義依賴關系的依賴bean。因此,依賴還可以控制關機順序。
5.4 懶加載 Bean
默認情況,作為初始化過程的一部分,ApplicationContext 的實現會餓漢式創建和配置所有單例 bean。通常,這種預實例化的操作是好的,因為這樣可以立刻發現配置中或者環境中的問題和錯誤,而不是幾小時甚至幾天后。當不適合用這種方式時,你可以將 bean definition 設置為懶加載來避免預實例化。一個懶加載 bean 會告訴 IOC 容器,只在第一次請求對象的時候創建對象,而不是一啟動的時候。如下設置:
<bean id="lazy" class="com.something.ExpensiveToCreateBean" lazy-init="true"/> <bean name="not.lazy" class="com.something.AnotherBean"/>但是注意,如果懶加載 bean 恰好是非懶加載 bean 的依賴時,那么IOC 容器會忽略懶加載屬性,而直接在啟動時創建這個懶加載 bean,因為它必須滿足作為一個依賴的身份。
也就是說,延遲初始化設置會由于被其他非延遲初始化bean依賴而失效!
另外還可以通過使用<beans/>元素的 default-lazy-init 屬性來控制容器級別的延遲初始化,如下面的示例所示:
<beans default-lazy-init="true"><!-- no beans will be pre-instantiated... --> </beans>5.5 自動裝配依賴
Spring 容器可以自動裝配協作者之間的關系。自動裝配有以下優點:
1、自動裝配可以顯著減少指定屬性或構造函數參數的需要。
2、自動裝配可以隨著對象的發展更新配置。例如,如果需要向類添加依賴項,則無需修改配置即可自動滿足該依賴項。因此,自動裝配在開發過程中特別有用。
5.6 方法注入(Method Injection)
大多數應用場景,容器中的 bean 都是單例的。當一個單例 bean 需要和另一個單例 bean 協同工作,或一個非單例 bean 需要和另一個非單例bean 協同工作,你一般會將依賴定義為另一個 bean 的屬性。
但當 bean 的生命周期不同時,就會出現問題。
假設單例對象 A 需要使用非單例(原型)對象 B,可能在 A 的每個方法調用上。容器只會創建 A 一次,這樣也就只會有一次機會為屬性賦值。那么容器無法每次在 A 需要使用 B 的時候都實例化一個 B 提供給 A。
一種解決方法是,放棄一定的控制反轉。你可以令 A 實現ApplicationContextAware 接口來讓 A 知曉容器的存在,并且在每次 A 需要使用 B 的時候,通過 getBean("B") 來請求一個新的 B 對象。如下所示:
public class CommandManager implements ApplicationContextAware {private ApplicationContext applicationContext;public Object process(Map commandState) {// grab a new instance of the appropriate CommandCommand command = createCommand();// set the state on the (hopefully brand new) Command instancecommand.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 框架,并與之耦合。方法注入是一個IOC 容器的高級特性,可以讓你干凈利落的處理這樣的情況。
【《Spring揭秘》bean的scope使用陷阱:
依賴于“prototype”對象的客戶端對象第一次從容器中取得該prototype對象后,會一直持有該對象引用。因此需要注意,后續從客戶端對象中使用的該“prototype”對象總是同一個,即prototype只針對每次從容器中取得才可返回新對象。
方法注入(Method Injection)的含義是,容器通過指定的方法將bean注入。
普通的getBean方法會使容器帶有侵入性,因此,當我們獲取prototype對象時,往往會結合方法注入來使用,指定某個方法代替getBean從容器中取得prototype對象。
其底層邏輯是Spring會通過Cglib動態代理該類并重寫該注入方法,因此,Spring要求聲明的注入方法需要符合固定格式:
<public|protected> [abstract] <return-type> injectMethodName(no-args);對于一般的getter方法,就符合這樣的格式。因此,可以通過<lookup-method>標簽告知Spring容器,使用getXxx()方法完成指定bean對象的注入:
<bean id=”b” class=”...B” singleton=”false”></bean> <bean id=”a” class=”...A”><lookup-method name=”getB” bean=”b”/> </bean>lookup-method標簽的name屬性指定需要注入的方法名,bean屬性指定需要注入的對象。這樣,在每次調用a.getB()的時候,程序都會向容器請求一個新的B對象并返回。
】
六、Bean Scope 作用域
scope用來描述容器中的對象的限定場景或存活時間。
打個比方:我們都處于社會(容器)中,如果把中學教師作為一個類定義,那么當容器初始化這些類之后,中學教師只能局限在中學這樣的場景中。中學,就可以看做中學教師的scope。——《Spring揭秘》
當你創建一個 bean definition ,你實際上是創建了一個用于實例化的食譜(或處方)。這個“食譜”的思想很重要,因為這意味著,就像類一樣,你可以從一個單獨的食譜中創建許多對象。
Spring 中支持 6 種scope,singleton和prototype是Spring最開始最遲的兩種scope類型,在Spring2.0 之后又加入了另外四種只在支持 web 的ApplicationContext中使用的scope類型。你也可以自定義 scope:
| singleton | (默認) Scopes a single bean definition to a single object instance for each Spring IoC container. |
| prototype | Scopes a single bean definition to any number of object instances. |
| request | Scopes a single bean definition to the lifecycle of a single HTTP request. That is, each HTTP request has its own instance of a bean created off the back of a single bean definition. Only valid in the context of a web-aware Spring?ApplicationContext. |
| session | Scopes a single bean definition to the lifecycle of an HTTP?Session. Only valid in the context of a web-aware Spring?ApplicationContext. |
| application | Scopes a single bean definition to the lifecycle of a?ServletContext. Only valid in the context of a web-aware Spring?ApplicationContext. |
| websocket | Scopes a single bean definition to the lifecycle of a?WebSocket. Only valid in the context of a web-aware Spring?ApplicationContext. |
【《Spring揭秘》以下給出部分關鍵scope類型的精簡說明:
1、singleton:首先,同一個IOC容器只存在一個singleton的bean。其次,容器啟動時創建,第一次請求時初始化,容器不退出,對象就就一直存在。
2、prototype:每次重新生成一個新的對象給請求方。實例化、屬性設置等工作由容器負責,返回后,容器不再擁有該對象的引用,請求方需自行負責后續(如銷毀)生命周期活動。通常,聲明為prototype的bean都含有一些可變狀態。
3、request:WebApplicationContext會為每個http請求創建一個scope為request的對象,請求結束,對象的生命周期也將結束。從不嚴格的意義上說,request可以看做是prototype的一種特例,除了場景更加具體,語義上差不多。
4、session:容器會為每個會話創建scope為session的實例。與request相比,除了更長的存活時間,其他方面沒什么差別。】?
6.1 單例作用域
單例很好理解,即只有一個實例對象,spring 容器在每次請求該 bean 的時候都只返回同一個對象。
換句話說,當你定義了一個 bean ,且它的 scope 屬性是 singleton,那么 IOC 容器只會為其創建一個實例。這個實例被存儲在一個用于存儲這種單例 bean 的緩存區,后續所有的請求或引用,都會返回已緩存的單例對象。下圖展示了單例作用域的工作模式:
Spring 中的單例 bean 的概念與GoF的單例模式有些不同。GoF 單例對對象的作用域進行硬編碼,這樣每個類加載器都會創建一個且只有一個特定類的實例。Spring單例的作用域最好描述為每個容器和每個bean。這意味著,如果你在單個Spring容器中為特定類定義了一個bean,那么Spring容器將創建由該 bean 定義的類的一個且僅一個實例。
<bean id="accountService" class="com.something.DefaultAccountService"/><!-- the following is equivalent, though redundant (singleton scope is the default) --> <bean id="accountService" class="com.something.DefaultAccountService" scope="singleton"/>6.2 原型作用域
原型作用域的 bean 會在每次請求后返回一個新的 bean 實例。你應該對所有有狀態的 bean 使用原型作用域,對無狀態的 bean 使用單例作用域。下圖展示了原型作用域:
數據訪問對象(DAO)通常不配置為原型,因為典型的DAO不持有任何會話狀態。
與其他作用域相比,Spring不管理原型bean的完整生命周期。容器實例化、配置和組裝原型對象并將其交給客戶端,而不進一步記錄該原型實例。因此,盡管初始化的回調方法在所有對象上都被調用,但在原型的情況下,配置的銷毀回調不會被調用。為了讓 Spring 容器釋放被原型 bean 所持有的資源,請使用自定義bean的后置處理器(bean post-processor),它持有一個需要被清理的 bean的引用。
在一些方面,Spring 容器的原型bean 被視為 Java new 操作符的替代品。
6.3 單例bean依賴原型bean
當使用原型 bean 作為單例 bean 的依賴項時,請注意,依賴關系是在實例化時解析的。因此,如果你將一個原型 bean 依賴式地注入到一個 單例 bean 中,那么就會創建一個新的原型 bean 然后注入到單例 bean中。原型實例是提供給單例 bean的唯一實例。
但是,假設你想讓單例 bean 在運行時反復依賴原型 bean ,你不能依賴式地注入原型 bean,因為注入只會發生一次,即只會在IOC 容器實例化該單例 bean 并解析、注入它的依賴時。如果想在運行時多次請求原型 bean ,請使用方法注入(前面有介紹)。
6.4 Request、Session、Application? 和 WebSocket 作用域
參考:Request, Session, Application, and WebSocket Scopes
6.5 自定義作用域
bean 的 Scope 機制是可擴展的。你可以定義你自己的作用域,或重新定義現有的作用域,但是后者并不推薦。而且,你無法重寫內建的 singleton 和 prototype 作用域。
為了能夠將你自定義的 scope 整合到 IOC 容器,你必須實現 org.springframework.beans.factory.config.Scope 接口。
Scope 接口包含 4 個方法,可以從作用域中獲取對象,從作用域中移除對象,也可以銷毀他們。
以 Session 作用域的實現為例,會返回一個 session 作用域的 bean(如果它不存在,方法就會返回一個新的 bean 實例,然后將它綁定到 session 上,以便后續訪問)。
Object get(String name, ObjectFactory<?> objectFactory)更多內容參考:Custom Scopes
七、Bean 的生命周期與 Aware
Spring 提供了一些接口用來幫助開發者自定義 bean 的一些性質。這一節主要講解:
生命周期回調;ApplicationContextAware 和 BeanNameAware;以及其他的 Aware 接口。
7.1 生命周期回調
要與容器對 bean 的生命周期管理交互,你需要實現 InitializingBean 接口和?DisposableBean 接口。容器對前者調用afterPropertiesSet(),對后者調用destroy(),讓bean在初始化和銷毀bean時執行某些操作。
在內部,Spring框架使用BeanPostProcessor處理任何回調接口的實現,它可以找到并調用適當的方法。如果你需要定制特性或Spring默認不提供的其他生命周期行為,可以自己實現BeanPostProcessor。除了初始化和銷毀回調之外,spring管理的對象還可以實現生命周期接口,以便這些對象可以參與啟動和關閉過程,這是由容器自己的生命周期驅動的。
初始化回調
org.springframework.beans.factory.InitializingBean 接口在容器設置了bean的所有必要屬性之后,讓bean執行初始化工作。該接口只有一個方法:
void afterPropertiesSet() throws Exception;我們不建議你使用 InitializingBean 接口,因為它令業務代碼與 Spring 框架建立了不必要的耦合。還有另一種方式,我們更建議使用 @PostConstruct 注解或指定一個POJO 初始化方法。在 xml 形式的配置中,你可以使用 init-method 屬性來指定該方法的名稱,該方法需要具備無返回值(void)、無參數(no-arguments)的特點。如果使用 JavaConfig ,你可以使用 @Bean 中的 initMethod 屬性。
<bean id="exampleInitBean" class="examples.ExampleBean" init-method="init"/> public class ExampleBean {public void init() {// do some initialization work} }上面的例子和下面的例子效果完全相同,以下是實現 InitializingBean 接口的版本:
<bean id="exampleInitBean" class="examples.AnotherExampleBean"/> public class AnotherExampleBean implements InitializingBean {@Overridepublic void afterPropertiesSet() {// do some initialization work} }但是,更推薦第一個例子,因為更解耦。
銷毀回調
和初始化回調類似,Spring 同樣提供了兩種實現方式,既可以實現?DisposableBean 接口,也可以直接在 配置中指定 destroy-method 屬性。以下片段分別是實現?DisposableBean 接口和 destroy-method 屬性配置兩種不同方式,請任選其一:
---XML配置: <bean id="exampleInitBean" class="examples.AnotherExampleBean"/> ---Java 代碼: public class AnotherExampleBean implements DisposableBean {@Overridepublic void destroy() {// do some destruction work (like releasing pooled connections)} } ---XML配置: <bean id="exampleInitBean" class="examples.ExampleBean" destroy-method="cleanup"/> ---Java 代碼: public class ExampleBean {public void cleanup() {// do some destruction work (like releasing pooled connections)} }默認的初始化和銷毀方法
不需要實現 InitializingBean 接口,也不需要指定 init-method 屬性,Spring 提供了以約定命名為基礎的初始化和銷毀回調方法。
只需要在 bean 中加入 init()、destroy() 方法,Spring 就會自動查找并執行對應生命周期的回調。例如 init() 方法:
public class DefaultBlogService implements BlogService {private BlogDao blogDao;public void setBlogDao(BlogDao blogDao) {this.blogDao = blogDao;}// this is (unsurprisingly) the initialization callback methodpublic void init() {if (this.blogDao == null) {throw new IllegalStateException("The [blogDao] property must be set.");}} }那么在配置中就不需要顯式聲明任何初始化回調方法:
<bean id="blogService" class="com.something.DefaultBlogService"><property name="blogDao" ref="blogDao" /> </bean>Spring 容器保證在 bean 配置完全部依賴后,會立即執行配置好的初始化回調方法。因此,初始化回調是在原始 bean 上調用的,這意味著 AOP 攔截器等還沒有使用到 bean 。
混合使用生命周期機制
Spring 2.5 之后,你有三種選擇來控制 bean 生命周期行為:
1、InitializingBean 和 DisposableBean 回調接口
2、自定義 init() 和 destroy() 方法
3、@PostConstruct 和 @PreDestroy 注解。你可以混合這些機制來控制一個給定的 bean 。
如果 bean 配置了使用不同的初始化方法的多個生命周期機制,它們會以下面的順序調用:
1、@PostConstruct 注解標記的方法
2、InitializingBean回調接口定義的afterPropertiesSet()
3、自定義的 init() 方法
銷毀方法以下面的順序執行:
1、@PreDestroy 注解標記的方法
2、DisposableBean 回調接口定義的 destroy()
3、自定義的 destroy() 方法
啟動和關閉回調
Lifecycle 接口為任何具有它們自己的生命周期需求的對象定義了一些基本方法(例如啟動或停止一些后臺處理):
public interface Lifecycle {void start();void stop();boolean isRunning(); }任何由Spring 管理的對象都可以實現 Lifecycle 接口。當 ApplicationContext 接收到開始或停止信息時(例如,在運行過程中的 stop/restart 場景),就會將這些請求串聯到對應上下文中定義的 Lifecycle 實現上。這是通過探測 LifecycleProcessor 來實現的,如下所示:
public interface LifecycleProcessor extends Lifecycle {void onRefresh();void onClose(); }啟動的調用順序和關閉的調用順序可能很重要。如果兩個對象之間存在依賴關系,依賴端要在依賴項之后開始,在依賴項之前停止。然而,有時候這種直接的依賴關系并不清晰。你可能只知道某個對象應該在另一個對象之前啟動。這種情況下, SmartLifecycle 接口就派上用場了,它是 Phased 接口的子接口:
public interface Phased {int getPhase(); }public interface SmartLifecycle extends Lifecycle, Phased {boolean isAutoStartup();void stop(Runnable callback); }當啟動時,具有最低相位的對象先開始。當停止時,順序則反過來。因此,如果一個對象實現了 SmartLifecycle 并且它的 getPhase() 方法返回一個 Integer.MIN_VALUE ,那么它就會第一個啟動,最后一個停止。相反的,如果 getPhase() 的值返回Integer.MAX_VALUE ,則表明這個對象應該最后一個啟動,并且第一個停止。當考慮phase值時,同樣重要的是要知道對于任何沒有實現SmartLifecycle的“正常”生命周期對象,其默認的phase是0。因此,任何負相位值都表示一個對象應該在那些標準組件之前開始(并在它們之后停止)。對于任何正相位值,情況正好相反。
7.2 ApplicationContextAware 和 BeanNameAware
當 ApplicationContext 創建了一個實現了 ApplicationContextAware 接口的 bean 實例,那么該實例同樣也會拿到一個 ApplicationContext 的引用。以下是 ApplicationContextAware 接口:
public interface ApplicationContextAware {void setApplicationContext(ApplicationContext applicationContext) throws BeansException; }這樣, bean 就可以通過 ApplicationContext 接口或將引用轉化為它的已知子類(如ConfigurableApplicationContext)以編程的方式來操作創建它們的 ApplicationContext 對象。
當 ApplicationContext 創建了一個實現了 BeanNameAware 接口的 bean 實例,那么該實例也可以拿到一個由關聯的 bean definition 定義的 bean name。下面代碼片段是 BeanNameAware 接口:
public interface BeanNameAware {void setBeanName(String name) throws BeansException; }這個回調方法會在填充普通bean屬性之后,在初始化回調(如InitializingBean、afterPropertiesSet或自定義初始化方法)之前調用執行。
7.3 其他的 Aware 接口
除了 ApplicationContextAware 和 BeanNameAware,Spring 提供了廣泛的感知回調接口,好讓 bean 能夠指示容器,它們需要一個基礎設施依賴。作為一般規則,名稱表示依賴類型。下表總結了最有用的 Aware 接口:
| ApplicationContextAware | Declaring?ApplicationContext. | ApplicationContextAware?and?BeanNameAware |
| ApplicationEventPublisherAware | Event publisher of the enclosing?ApplicationContext. | Additional Capabilities of the?ApplicationContext |
| BeanClassLoaderAware | Class loader used to load the bean classes. | Instantiating Beans |
| BeanFactoryAware | Declaring?BeanFactory. | ApplicationContextAware?and?BeanNameAware |
| BeanNameAware | Name of the declaring bean. bean 的名稱 | ApplicationContextAware?and?BeanNameAware |
| BootstrapContextAware | Resource adapter?BootstrapContext?the container runs in. Typically available only in JCA-aware?ApplicationContext?instances. | JCA CCI |
| LoadTimeWeaverAware | Defined weaver for processing class definition at load time. | Load-time Weaving with AspectJ in the Spring Framework |
| MessageSourceAware | Configured strategy for resolving messages (with support for parametrization and internationalization). | Additional Capabilities of the?ApplicationContext |
| NotificationPublisherAware | Spring JMX notification publisher. | Notifications |
| ResourceLoaderAware | Configured loader for low-level access to resources. | Resources |
| ServletConfigAware | Current?ServletConfig?the container runs in. Valid only in a web-aware Spring?ApplicationContext. | Spring MVC |
| ServletContextAware | Current?ServletContext?the container runs in. Valid only in a web-aware Spring?ApplicationContext. | Spring MVC |
注意,使用這些接口會將你的代碼與 spring api 進行耦合,且不符合控制反轉的風格。因此,我們建議將它們用于需要對容器進行編程訪問的基礎設施bean。
八、容器擴展性指導
一般,應用開發者不需要繼承 ApplicationContext 的實現類。而是通過插入特殊的繼承接口來擴展IOC 容器。
8.1 使用 BeanPostProcessor 來自定義 Bean
BeanPostProcessor,Bean 的后置處理器接口定義了回調方法,你可以實現該接口并提供你的初始化 bean 的邏輯(也可以重寫容器的默認實現)、依賴解析邏輯等等。如果你想在 容器完成實例化、裝配、初始化 bean 之后實現一些自定義邏輯,你可以插入一個或多個自定義的 BeanPostProcessor 實現。
你可以配置多個 BeanPostProcessor 實例,并且通過設置 order 屬性來控制這些實例的執行順序。只有當 BeanPostProcessor 實現了 Ordered 接口才可以設置該屬性。如果你需要自定義 BeanPostProcessor,你就也需要考慮實現 Ordered 接口。
BeanPostProcessor 實例基于 bean 實例執行操作。也就是說,IOC 容器實例化一個 bean ,然后 BeanPostProcessor 才能工作。
如果在一個容器中定義BeanPostProcessor,那么它只對該容器中的bean進行后處理,也就是說,BeanPostProcessor 的作用域是單獨的容器。
BeanPostProcessor 接口由兩個回調方法構成。當一個類在容器中注冊為一個 post-processor ,那么它的每一個實例,在容器初始化方法之前和 bean 初始化回調之后,都會從容器獲得一個回調。后置處理器可以對bean實例采取任何操作,包括完全忽略回調。bean 后置處理器通常檢查回調接口,或者用代理包裝bean。為了提供代理包裝邏輯,一些Spring AOP基礎設施類被實現為bean 后置處理器。
ApplicationContext 會自動檢測到 bean 中實現了 BeanPostProcessor 接口。ApplicationContext 會將這些 bean 注冊為 post-processor,以便后面在 bean 創建的時候進行調用。bean 的后置處理器可以像其他 bean 一樣部署在容器中。
注意,當使用 @Bean 聲明了一個 BeanPostProcessor,返回值類型應該是后置處理器的實現類,至少也應該是 BeanPostProcessor 接口類型,清楚地表明該 bean 的后置處理器特性。否則, ApplicationContext 無法自動檢測到它。由于BeanPostProcessor需要盡早實例化,以便應用于上下文中其他bean的初始化,因此這種早期類型檢測非常關鍵。
編程式注冊 BeanPostProcessor?
雖然推薦的 BeanPostProcessor 注冊方式是通過 ApplicationContext 自動檢測,但你依然可以通過一個 ConfigurableBeanFactory的 addBeanPostProcessor() 方法以編程的方式注冊后置處理器。當你需要在注冊前計算條件邏輯,或在跨層次結構的上下文復制后置處理器時,這可能非常有用。但是注意,以編程方式注冊后置處理器不遵從 Ordered 接口。那么注冊順序就決定了執行順序。而且也要注意,編程式注冊的后置處理器永遠在自動檢測注冊的后置處理器之前執行,會忽略任何顯式的順序聲明。
BeanPostProcessor 實例和 AOP 自動代理
實現了 BeanPostProcessor 接口的類會被容器特殊對待。所有的 BeanPostProcessor 實例和直接引用的 bean,都會在容器啟動時實例化,并作為 ApplicationContext 啟動階段的一個特殊部分。
Next, all?BeanPostProcessor?instances are registered in a sorted fashion and applied to all further beans in the container. Because AOP auto-proxying is implemented as a?BeanPostProcessor?itself, neither?BeanPostProcessor?instances nor the beans they directly reference are eligible for auto-proxying and, thus, do not have aspects woven into them.
For any such bean, you should see an informational log message:?Bean someBean is not eligible for getting processed by all BeanPostProcessor interfaces (for example: not eligible for auto-proxying).
If you have beans wired into your?BeanPostProcessor?by using autowiring or?@Resource?(which may fall back to autowiring), Spring might access unexpected beans when searching for type-matching dependency candidates and, therefore, make them ineligible for auto-proxying or other kinds of bean post-processing. For example, if you have a dependency annotated with?@Resource?where the field or setter name does not directly correspond to the declared name of a bean and no name attribute is used, Spring accesses other beans for matching them by type.
8.2 使用 BeanFactoryPostProcessor 來自定義配置數據
在語義上,BeanFactoryPostProcessor 和 BeanPostProcessor 相似,只有一個不同點,那就是 BeanFactoryPostProcessor 在 bean 的配置數據上進行操作。也就是說,Spring IOC 容器允許 BeanFactoryPostProcessor 讀取配置數據,并在容器實例化任何 bean (除了BeanFactoryPostProcessor實例)之前改變配置。
你可以配置多個 BeanFactoryPostProcessor 實例,也設置 order 屬性來可以控制運行順序,但也必須要實現 Ordered 接口才可以。
如果你想改變實際的 bean 實例,那么你需要使用 BeanPostProcessor(如前所述)。雖然在BeanFactoryPostProcessor中使用bean實例在技術上是可行的(例如,通過使用BeanFactory.getBean()),但是這樣做會導致過早的bean實例化,違反標準的容器生命周期。這可能會導致負面的副作用,比如繞過bean的后處理。
另外,BeanFactoryPostProcessor實例的作用域為每個容器。這只有在使用容器層次結構時才有用。如果您在一個容器中定義了BeanFactoryPostProcessor,那么它只應用于該容器中的bean定義。一個容器中的Bean定義不會被另一個容器中的BeanFactoryPostProcessor實例進行后處理,即使這兩個容器屬于同一層次結構。
當Bean 工廠后置處理器在 ApplicationContext 中聲明,它將自動執行,以便對定義容器的配置數據執行更改。Spring包括許多預定義的bean工廠后處理器,如 PropertyOverrideConfigurer 和 PropertySourcesPlaceholderConfigurer。當然也可以自定義。
ApplicationContext自動檢測部署到其中實現BeanFactoryPostProcessor接口的任何bean。在適當的時候,它將這些bean用作bean工廠的后置處理器。可以像部署任何其他bean一樣部署這些后處理bean。
九、注解式容器配置
注解要比XML更適合配置Spring嗎?
簡單的回答是:看情況。詳細的回答是,每種方式都有其利弊,通常,這取決于開發者覺得那種方式更適合。
由于其定義方式,注解在其聲明中提供了大量上下文,從而使配置更短、更簡潔。但是,XML擅長在不改動源代碼或重新編譯組件的情況下連接組件。有些開發者喜歡將配置貼近源碼,但也有一些人認為這樣帶注解的類已不再是 POJO ,而且配置也會變得更分散,難以控制。
但不論那種方式,Spring 都可以適應,甚至是混用。值得說明的是,通過 Java Config 的引入,Spring 可以讓注解以一種非侵入式的方式進行配置,不需要接觸目標組件。
注解式配置為 xml 配置提供了另一種選擇。與 xml 不同,開發者可以將配置以注解的方式放到組件類上、方法上、域上。例如 Spring 2.0 引入了 @Required 注解,這讓配置必須屬性成為可能。Spring 2.5 使遵循相同的通用方法來驅動依賴注入成為可能。從本質上,@Autowired注解提供了與Autowiring協作器中描述的相同的功能,但是擁有更細粒度的控制和更廣泛的適用性。Spring 2.5 也增加了對 JSR-250 注解的支持,如 @PostConstruct 和 @PreDestroy。Spring 3.0 增加了對 JSR-330(Java 依賴注入)的支持,包含了 javax.inject 包,如 @Inject 和 @Named。
提示:注釋注入在XML注入之前執行。這樣,xml 配置就可以對同時使用這兩種方式的屬性覆蓋掉注解配置。
和往常一樣,你可以單獨地注冊 bean 定義,但也可以通過通過下面的標簽隱式地注冊到基于 xml 配置的Spring 容器中(注意包含的 context 命名空間)。
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xmlns:context="http://www.springframework.org/schema/context"xsi:schemaLocation="http://www.springframework.org/schema/beanshttps://www.springframework.org/schema/beans/spring-beans.xsdhttp://www.springframework.org/schema/contexthttps://www.springframework.org/schema/context/spring-context.xsd"><context:annotation-config/></beans>(隱式注冊的后置處理器包括 AutowiredAnnotationBeanPostProcessor、CommonAnnotationBeanPostProcessor、PersistenceAnnotationBeanPostProcessor、和前面提到的 RequiredAnnotationBeanPostProcessor)
提示:<context:annotation-config/> 只查找定義在同一個應用程序 context 上下文的 bean 上的注解。也就是說,如果你為DispatcherServlet 在 WebApplicationContext 中配置了<context:annotation-config>標簽,那么它只檢查你的 controller 中 @Autowired 的 bean,而不會檢查 service。
9.1 @Required
@Required 注解可以放在 setter 方法上,例如:
public class SimpleMovieLister {private MovieFinder movieFinder;@Requiredpublic void setMovieFinder(MovieFinder movieFinder) {this.movieFinder = movieFinder;} }這個注解表示必須在配置時填充受影響的bean屬性,要么通過顯式地 bean definition 要么通過 自動注入。如果 bean 屬性沒有成功注入,那么容器就會拋出一個異常。這種餓漢式明確的失敗,避免了空指針的發生。我們仍然建議將斷言放到bean類本身中(例如,放到init方法中)。這樣做會強制執行那些必需的引用和值,即使你在容器之外使用該類。
提示: @Required 注解在 Spring 5.1 被正式廢棄,我們更支持使用構造器注入必需配置(或者自定義實現 InitializingBean.afterPropertiesSet() 和 setter 方法)
9.2 使用 @Autowired
你可以在構造器上使用 @Autowired,例如:
public class MovieRecommender {private final CustomerPreferenceDao customerPreferenceDao;@Autowiredpublic MovieRecommender(CustomerPreferenceDao customerPreferenceDao) {this.customerPreferenceDao = customerPreferenceDao;} }提示:從Spring 4.3 開始,如果目標 bean 只定義了一個構造器,就沒必要將@Autowired 注解標記在這樣的構造器上。然而,如果有兩個或多個構造器,且沒有默認構造器,那么至少要在一個構造器上標記該注解,以便告訴容器要使用哪個。
你也可以將@Autowired 注解使用在 setter 方法上:
public class SimpleMovieLister {private MovieFinder movieFinder;@Autowiredpublic void setMovieFinder(MovieFinder movieFinder) {this.movieFinder = movieFinder;} }也可以將@Autowired 注解放在任意方法名的多參方法上:
public class MovieRecommender {private MovieCatalog movieCatalog;private CustomerPreferenceDao customerPreferenceDao;@Autowiredpublic void prepare(MovieCatalog movieCatalog,CustomerPreferenceDao customerPreferenceDao) {this.movieCatalog = movieCatalog;this.customerPreferenceDao = customerPreferenceDao;} }你可以可以將@Autowired 放在域上,甚至是混用在構造器上:
public class MovieRecommender {private final CustomerPreferenceDao customerPreferenceDao;@Autowiredprivate MovieCatalog movieCatalog;@Autowiredpublic MovieRecommender(CustomerPreferenceDao customerPreferenceDao) {this.customerPreferenceDao = customerPreferenceDao;} }你可以通過向需要該類型數組的字段或方法添加@Autowired注解來指示Spring從ApplicationContext中提供所有特定類型的bean:
public class MovieRecommender {@Autowiredprivate MovieCatalog[] movieCatalogs; }類型集合也可以:
public class MovieRecommender {private Set<MovieCatalog> movieCatalogs;@Autowiredpublic void setMovieCatalogs(Set<MovieCatalog> movieCatalogs) {this.movieCatalogs = movieCatalogs;}}提示:如果想讓數組或集合中的bean 有序,你可以讓目標 bean 實現 Ordered 接口或使用 @Order 注解或使用 標準的@Priority 注解。否則,只能以注冊時的順序來排序這些 bean。
你可以在目標類型上指定 @Order 注解,或者 @Bean 方法上。 @Order 注解的 value 可能影響注入點的優先級,但不能影響單例的啟動順序,單例的啟動順序是由依賴關系和@DependsOn 決定的。
注意,標準 @Priority 注解不能使用在 @Bean 級別上,因為它無法聲明在方法上。
Map 的實例也可以自動注入,只要key 類型是 String。Map 的key 應該保存對應 bean 的名字,如下所示:
public class MovieRecommender {private Map<String, MovieCatalog> movieCatalogs;@Autowiredpublic void setMovieCatalogs(Map<String, MovieCatalog> movieCatalogs) {this.movieCatalogs = movieCatalogs;} }默認情況,如果在注入點沒有匹配的候選 bean 可用,那么自動注入就會失敗。聲明的如果是數組、集合或 Map,至少需要有一個匹配的 bean 元素。
默認標記 @Autowired 注入的依賴是必需項。你可以改變這種默認設置,使框架跳過非必需依賴注入,如下所示:
public class SimpleMovieLister {private MovieFinder movieFinder;@Autowired(required = false)public void setMovieFinder(MovieFinder movieFinder) {this.movieFinder = movieFinder;} }如果依賴項不可用,或其中一個依賴項的 setter 方法有多個參數,那么非必需方法將根本不會被調用。在這種情況下,將根本不填充非必需字段,保留其默認值。
從 5.0 開始,你也可以使用 @Nullable 注解:
public class SimpleMovieLister {@Autowiredpublic void setMovieFinder(@Nullable MovieFinder movieFinder) {...} }你也可以使用 @Autowired 注解注入這些已知的框架相關依賴:BeanFactory、ApplicationContext、Environment、ResourceLoader、ApplicationEventPublisher,和 MessageSource。這些接口及其子接口,例如ConfigurableApplicationContext 或 ResourcePatternResolver,已經自動解析完畢,不需要特殊的設置:
public class MovieRecommender {@Autowiredprivate ApplicationContext context;public MovieRecommender() {} }提示:@Autowired、@Inject、@Value? 和 @Resource 注解,都會被 Spring 自己的 BeanPostProcessor 實現來處理。也就是說,你無法在自己的BeanPostProcessor或BeanFactoryPostProcessor類型(如果有的話)中應用這些注釋。這些類型必須通過使用XML或Spring @Bean方法顯式地“連接起來”。
9.3 使用 @Primary 來微調基于注解的自動注入(updated on 2020-10-11)
因為通過類型來完成自動注入可能會有多個“候選項”(注:如接口類型可能會有多個實現子類),因此也就有必要有更多的控制手段。一種方法是通過 @Primary 注解來完成此項操作。@Primary 表示當單值依賴(single-valued dependency)注入時有多個候選項,那么應該只提供一個特定的 bean 引用。如果這些候選項中有一個確定的“主要的”(primary)bean ,那么它就會是那個被注入的值。
考慮下面的用例:
@Configuration public class MovieConfiguration {@Bean@Primarypublic MovieCatalog firstMovieCatalog() { ... }@Beanpublic MovieCatalog secondMovieCatalog() { ... }}基于上面的配置用例,下面的 MovieRecommender 電影推薦類就會注入 firstMovieCatalog 類型的 bean:
public class MovieRecommender {@Autowiredprivate MovieCatalog movieCatalog; }對應的 bean 定義如下:
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xmlns:context="http://www.springframework.org/schema/context"xsi:schemaLocation="http://www.springframework.org/schema/beanshttps://www.springframework.org/schema/beans/spring-beans.xsdhttp://www.springframework.org/schema/contexthttps://www.springframework.org/schema/context/spring-context.xsd"><context:annotation-config/><bean class="example.SimpleMovieCatalog" primary="true"><!-- inject any dependencies required by this bean --></bean><bean class="example.SimpleMovieCatalog"><!-- inject any dependencies required by this bean --></bean><bean id="movieRecommender" class="example.MovieRecommender"/></beans>9.4 使用 @Qualifier 微調基于注解的自動注入(updated on 2020-10-11)
@Primary 是一種可以處理通過類型判斷自動注入時多個候選項只注入主要候選項的有效方式(或者可以描述為默認候選項注入)。當你需要更有控制力的方式來影響注入 bean 的匹配過程時,可以使用 Spring 提供的 @Qualifier 注解。你可以為 @Qualifier 的 value 屬性指定特殊的值,從而縮小類型匹配的范圍,以便為每個參數選擇特定的bean。例如下面用例:
public class MovieRecommender {@Autowired@Qualifier("main")private MovieCatalog movieCatalog;}或者用在構造器上,指定注入的參數類型:
public class MovieRecommender {private MovieCatalog movieCatalog;private CustomerPreferenceDao customerPreferenceDao;@Autowiredpublic void prepare(@Qualifier("main") MovieCatalog movieCatalog,CustomerPreferenceDao customerPreferenceDao) {// ...} }對應的 bean 定義如下:
<?xml version="1.0" encoding="UTF-8"?> <beans ... ><context:annotation-config/><bean class="example.SimpleMovieCatalog"><qualifier value="main"/><!-- inject any dependencies required by this bean --></bean><bean class="example.SimpleMovieCatalog"><qualifier value="action"/> <!-- inject any dependencies required by this bean --></bean><bean id="movieRecommender" class="example.MovieRecommender"/></beans>對于降級匹配(fallback match),bean 的名稱被視為一個默認的限定值(qualifier value)。這樣,你可以將 bean 的 id 屬性定義為 “main” 而不是使用內嵌的<qualifier> 標簽,來達到同樣的匹配效果。
然而,雖然你可以使用這種約定,通過名稱來引入一個特定的 bean,但 @Autowired 基本上還是一個以類型驅動(type-driven)的注入方式,只是帶有可選的語義限定符(optional semantic qualifier)。這也就是說,限定值即使帶有名稱降級匹配,但也總是在類型匹配集中具有收縮語義(narrowing semantics)。它們在語義上并不表達唯一的 bean 的 id。
好的限定值是 main、EMEA、或 persistent,表示獨立于bean id 的特定組件的特征,在匿名bean定義(如前面示例中的bean定義)的情況下,可以自動生成。
@Qualifier 同樣可以用于類型化集合的注入情況。例如, Set<MovieCatalog>。在這種情況下,所有匹配的 bean,根據聲明的限定值,都會被注入到集合中。這也就是說,限定值不必唯一,它們組成了某個過濾條件。例如,你可以定義多個 MovieCatalog 類的 bean,使用相同的限定值“action”,那么所有這些 bean 都會被注入到聲明了 @Qualifier("action") 的 Set<MoiveCatalog> 集合中。
提示:在類型匹配候選中選擇指定名稱的?bean,不需要在注入點使用 @Qualifier 注解。如果沒有其他解析指示器(例如 @Qualifier 或 @Primary),對于非唯一依賴情況,Spring將注入點名稱(即字段名稱或參數名稱)與目標 bean 名稱匹配,并選擇同名的候選對象,也就是說字段名稱本身就默認是一種限定值。
也就是說,如果你打算通過名稱來表示注解注入,不要都用 @Autowired,雖然它的確可以在類型匹配的候選中選擇指定名稱的 bean。相反地,應該使用 JSR-250 標準的 @Resource 注解,這個注解在語義上定義為通過惟一名稱標識特定的目標組件,聲明的類型與匹配過程無關。@Autowired 注解有著相當不同的語義:在類型匹配結束后,指定的 String 類型限定符值只會在類型匹配的候選項中選擇。
對于定義為集合、Map 或 數組類型的 bean,@Resource 是一個很好的解決方式,通過唯一的名稱引入集合或數組 bean。也就是說,從 Spring 4.3 開始,你依然可以使用 @Autowired 的類型匹配算法來匹配集合、Map 和 數組類型,只要元素類型信息保存在@Bean返回類型簽名或集合繼承層次結構中。這種情況下,你可以使用限定符在相同類型的集合中進行選擇,如前一段所述。
?
?
?
?
?
?
?
?
?
?
?
?
?
?
?
?
?
?
?
?
?
?
?
?
?
?
?
?
?
?
?
?
?
?
?
?
?
?
?
?
?
?
?
?
?
?
?
?
?
?
?
?
?
?
?
?
?
?
?
?
?
?
?
?
?
?
?
?
?
?
?
?
?
?
?
?
?
?
?
?
?
總結
以上是生活随笔為你收集整理的Spring —— IoC 容器详解的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 扫地机器人划伤地板_扫地机器人哪个牌子好
- 下一篇: postman 使用_Postman使用