Bean 概覽
Spring IoC 容器管理一個或多個 bean。這些 bean 使用你提供給容器的配置元資料建立(例如,以 XML <bean/> 定義的形式)。
在容器內部,這些 bean 定義表示為 BeanDefinition 物件,它們包含(除其他資訊外)以下元資料:
-
一個包限定的類名:通常是所定義 bean 的實際實現類。
-
Bean 行為配置元素,它說明了 bean 在容器中應如何表現(作用域、生命週期回撥等)。
-
bean 完成其工作所需的其他 bean 的引用。這些引用也稱為協作器或依賴項。
-
在新建立的物件中設定的其他配置設定 — 例如,池的大小限制或管理連線池的 bean 中使用的連線數。
此元資料轉換為構成每個 bean 定義的一組屬性。下表描述了這些屬性:
| 財產 | 解釋於… |
|---|---|
類 |
|
名稱 |
|
作用域 |
|
建構函式引數 |
|
屬性 |
|
自動裝配模式 |
|
延遲初始化模式 |
|
初始化方法 |
|
銷燬方法 |
除了包含如何建立特定 bean 的資訊的 bean 定義外,ApplicationContext 實現還允許註冊在容器外部建立的現有物件(由使用者建立)。這是透過訪問 ApplicationContext 的 BeanFactory,透過 getAutowireCapableBeanFactory() 方法完成的,該方法返回 DefaultListableBeanFactory 實現。DefaultListableBeanFactory 透過 registerSingleton(..) 和 registerBeanDefinition(..) 方法支援此註冊。然而,典型的應用程式只使用透過常規 bean 定義元資料定義的 bean。
|
Bean 元資料和手動提供的單例例項需要儘快註冊,以便容器在自動裝配和其他內省步驟中正確地推理它們。雖然在一定程度上支援覆蓋現有元資料和現有單例例項,但不支援在執行時註冊新 bean(與對工廠的即時訪問併發),這可能導致併發訪問異常、bean 容器中的狀態不一致,或兩者兼而有之。 |
覆蓋 Bean
當使用已分配的識別符號註冊 bean 時,會發生 bean 覆蓋。雖然 bean 覆蓋是可能的,但它會使配置更難閱讀。
| bean 覆蓋將在未來版本中棄用。 |
要完全停用 bean 覆蓋,可以在 ApplicationContext 重新整理之前將其 allowBeanDefinitionOverriding 標誌設定為 false。在此設定中,如果使用 bean 覆蓋,則會丟擲異常。
預設情況下,容器會以 INFO 級別記錄每次嘗試覆蓋 bean 的資訊,以便您可以相應地調整配置。雖然不推薦,但可以透過將 allowBeanDefinitionOverriding 標誌設定為 true 來抑制這些日誌。
| 我們承認在測試場景中覆蓋 bean 很方便,並且對此有明確的支援。請參閱此部分以獲取更多詳細資訊。 |
命名 Bean
每個 bean 有一個或多個識別符號。這些識別符號在託管 bean 的容器中必須是唯一的。一個 bean 通常只有一個識別符號。但是,如果它需要多個,額外的識別符號可以被視為別名。
在基於 XML 的配置元資料中,您可以使用 id 屬性、name 屬性或兩者來指定 bean 識別符號。id 屬性允許您指定一個確切的 id。通常,這些名稱是字母數字(“myBean”、“someService”等),但它們也可以包含特殊字元。如果您想為 bean 引入其他別名,您也可以在 name 屬性中指定它們,用逗號(,)、分號(;)或空格分隔。儘管 id 屬性定義為 xsd:string 型別,但 bean id 的唯一性由容器強制執行,而不是由 XML 解析器強制執行。
您不需要為 bean 提供 name 或 id。如果您沒有明確提供 name 或 id,容器會為該 bean 生成一個唯一的名稱。但是,如果您想透過使用 ref 元素或服務定位器樣式查詢來按名稱引用該 bean,則必須提供一個名稱。不提供名稱的原因與使用內部 bean 和自動裝配協作器有關。
在類路徑中的元件掃描中,Spring 為未命名的元件生成 bean 名稱,遵循前面描述的規則:本質上,採用簡單的類名並將其首字母改為小寫。但是,在(不尋常的)特殊情況下,當有多個字元且第一個和第二個字元都是大寫時,原始大小寫會保留。這些規則與 java.beans.Introspector.decapitalize 定義的規則相同(Spring 在此處使用)。 |
在 Bean 定義之外為 Bean 定義別名
在 bean 定義本身中,您可以透過使用 id 屬性指定最多一個名稱和 name 屬性中任意數量的其他名稱的組合,為 bean 提供多個名稱。這些名稱可以是同一 bean 的等效別名,在某些情況下很有用,例如允許應用程式中的每個元件透過使用特定於該元件本身的 bean 名稱來引用公共依賴項。
但是,在 bean 實際定義的地方指定所有別名並不總是足夠的。有時需要為在其他地方定義的 bean 引入別名。這在配置分散在各個子系統中的大型系統中很常見,每個子系統都有自己的一組物件定義。在基於 XML 的配置元資料中,您可以使用 <alias/> 元素來完成此操作。以下示例展示瞭如何操作:
<alias name="fromName" alias="toName"/>
在這種情況下,名為 fromName 的 bean(在同一容器中)在使用此別名定義後,也可以稱為 toName。
例如,子系統 A 的配置元資料可能以 subsystemA-dataSource 的名稱引用 DataSource。子系統 B 的配置元資料可能以 subsystemB-dataSource 的名稱引用 DataSource。當組合使用這兩個子系統的主應用程式時,主應用程式以 myApp-dataSource 的名稱引用 DataSource。要使所有三個名稱引用同一物件,您可以將以下別名定義新增到配置元資料中:
<alias name="myApp-dataSource" alias="subsystemA-dataSource"/>
<alias name="myApp-dataSource" alias="subsystemB-dataSource"/>
現在,每個元件和主應用程式都可以透過一個唯一且保證不會與其他任何定義衝突的名稱(有效地建立一個名稱空間)來引用 dataSource,但它們都引用同一個 bean。
例項化 Bean
bean 定義本質上是建立一或多個物件的“食譜”。當被請求時,容器會檢視命名 bean 的“食譜”,並使用該 bean 定義封裝的配置元資料來建立(或獲取)實際物件。
如果您使用基於 XML 的配置元資料,您可以在 <bean/> 元素的 class 屬性中指定要例項化的物件型別(或類)。這個 class 屬性(在內部是 BeanDefinition 例項上的 Class 屬性)通常是強制性的。(對於例外情況,請參見使用例項工廠方法進行例項化和Bean 定義繼承。)您可以以兩種方式使用 Class 屬性:
-
通常,用於指定 bean 類,在這種情況下容器本身透過反射呼叫其建構函式直接建立 bean,這有點類似於使用
new運算子的 Java 程式碼。 -
在不太常見的情況下,容器呼叫類上的
static工廠方法來建立 bean,用於指定包含用於建立物件的static工廠方法的實際類。從static工廠方法呼叫返回的物件型別可能是同一類或完全不同的類。
使用建構函式進行例項化
當您透過建構函式方式建立 bean 時,所有普通類都可以被 Spring 使用和相容。也就是說,正在開發的類不需要實現任何特定的介面或以特定的方式編碼。只需指定 bean 類就足夠了。但是,根據您為該特定 bean 使用的 IoC 型別,您可能需要一個預設(空)建構函式。
Spring IoC 容器幾乎可以管理您希望它管理的任何類。它不限於管理真正的 JavaBeans。大多數 Spring 使用者更喜歡只有預設(無引數)建構函式和根據容器中的屬性建模的適當 setter 和 getter 的實際 JavaBeans。您也可以在容器中擁有更奇特的非 bean 樣式類。例如,如果您需要使用絕對不符合 JavaBean 規範的遺留連線池,Spring 也可以管理它。
使用基於 XML 的配置元資料,您可以按如下方式指定您的 bean 類:
<bean id="exampleBean" class="examples.ExampleBean"/>
<bean name="anotherExample" class="examples.ExampleBeanTwo"/>
有關向建構函式提供引數(如果需要)以及在物件構造後設置物件例項屬性的機制的詳細資訊,請參閱注入依賴項。
| 在建構函式引數的情況下,容器可以在幾個過載的建構函式中選擇一個相應的建構函式。也就是說,為避免歧義,建議使您的建構函式簽名儘可能直接。 |
使用靜態工廠方法進行例項化
當定義一個使用靜態工廠方法建立的 bean 時,使用 class 屬性指定包含 static 工廠方法的類,並使用名為 factory-method 的屬性指定工廠方法本身的名稱。您應該能夠呼叫此方法(帶有可選引數,如下所述)並返回一個即時物件,該物件隨後被視為已透過建構函式建立。此類 bean 定義的一個用途是呼叫遺留程式碼中的 static 工廠。
以下 bean 定義指定將透過呼叫工廠方法建立 bean。該定義不指定返回物件的型別(類),而是指定包含工廠方法的類。在此示例中,createInstance() 方法必須是一個 static 方法。以下示例顯示瞭如何指定工廠方法:
<bean id="clientService"
class="examples.ClientService"
factory-method="createInstance"/>
以下示例顯示了與前面 bean 定義一起使用的類:
-
Java
-
Kotlin
public class ClientService {
private static ClientService clientService = new ClientService();
private ClientService() {}
public static ClientService createInstance() {
return clientService;
}
}
class ClientService private constructor() {
companion object {
private val clientService = ClientService()
@JvmStatic
fun createInstance() = clientService
}
}
有關向工廠方法提供(可選)引數以及在物件從工廠返回後設置物件例項屬性的機制的詳細資訊,請參閱依賴項和配置詳情。
| 在工廠方法引數的情況下,容器可以在幾個同名過載方法中選擇一個相應的方法。也就是說,為避免歧義,建議使您的工廠方法簽名儘可能直接。 |
|
工廠方法過載的一個典型問題案例是 Mockito 及其
|
使用例項工廠方法進行例項化
與透過靜態工廠方法進行例項化類似,使用例項工廠方法進行例項化會呼叫容器中現有 bean 的非靜態方法來建立新 bean。要使用此機制,請將 class 屬性留空,並在 factory-bean 屬性中指定當前(或父或祖先)容器中包含要呼叫以建立物件的例項方法的 bean 的名稱。使用 factory-method 屬性設定工廠方法本身的名稱。以下示例顯示瞭如何配置此類 bean:
<!-- the factory bean, which contains a method called createClientServiceInstance() -->
<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
-
Kotlin
public class DefaultServiceLocator {
private static ClientService clientService = new ClientServiceImpl();
public ClientService createClientServiceInstance() {
return clientService;
}
}
class DefaultServiceLocator {
companion object {
private val clientService = ClientServiceImpl()
}
fun createClientServiceInstance(): ClientService {
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
-
Kotlin
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;
}
}
class DefaultServiceLocator {
companion object {
private val clientService = ClientServiceImpl()
private val accountService = AccountServiceImpl()
}
fun createClientServiceInstance(): ClientService {
return clientService
}
fun createAccountServiceInstance(): AccountService {
return accountService
}
}
這種方法表明工廠 bean 本身可以透過依賴注入 (DI) 進行管理和配置。請參閱依賴項和配置詳情。
在 Spring 文件中,“工廠 bean”指的是在 Spring 容器中配置並透過例項或靜態工廠方法建立物件的 bean。相比之下,FactoryBean(請注意大小寫)指的是 Spring 特定的FactoryBean實現類。 |
確定 Bean 的執行時型別
特定 bean 的執行時型別並非易事。bean 元資料定義中指定的類只是一個初始類引用,可能與宣告的工廠方法結合使用,或者是一個 FactoryBean 類,這可能導致 bean 的執行時型別不同,或者在例項級工廠方法的情況下根本未設定(而是透過指定的 factory-bean 名稱解析)。此外,AOP 代理可能會用基於介面的代理包裝 bean 例項,從而有限地暴露目標 bean 的實際型別(僅其實現的介面)。
瞭解特定 bean 實際執行時型別的推薦方法是針對指定 bean 名稱呼叫 BeanFactory.getType。這會考慮上述所有情況,並返回 BeanFactory.getBean 呼叫將針對同一 bean 名稱返回的物件型別。