Bean 概觀

Spring IoC 容器管理一個或多個 bean。這些 bean 是使用您提供給容器的組態元資料建立的(例如,以 XML <bean/> 定義的形式)。

在容器本身內,這些 bean 定義表示為 BeanDefinition 物件,其中包含(除其他資訊外)以下元資料

  • 套件限定的類別名稱:通常是正在定義的 bean 的實際實作類別。

  • Bean 行為組態元素,用於說明 bean 在容器中的行為方式(作用域、生命週期回呼等等)。

  • bean 執行其工作所需的其他 bean 的參考。這些參考也稱為協作者或相依性。

  • 要在新建立的物件中設定的其他組態設定 — 例如,管理連線池的 bean 中使用的池大小限制或連線數。

此元資料轉換為構成每個 bean 定義的一組屬性。下表描述了這些屬性

表 1. Bean 定義
屬性 說明於…

Class

實例化 Bean

Name

命名 Bean

Scope

Bean 作用域

建構子引數

依賴注入

Properties

依賴注入

自動裝配模式

自動裝配協作者

延遲初始化模式

延遲初始化 Bean

初始化方法

初始化回呼

銷毀方法

銷毀回呼

除了包含如何建立特定 bean 的資訊的 bean 定義之外,ApplicationContext 實作也允許註冊在容器外部建立的現有物件(由使用者)。這是透過 getBeanFactory() 方法存取 ApplicationContext 的 BeanFactory 來完成的,該方法傳回 DefaultListableBeanFactory 實作。DefaultListableBeanFactory 透過 registerSingleton(..)registerBeanDefinition(..) 方法支援此註冊。但是,典型的應用程式僅使用透過常規 bean 定義元資料定義的 bean。

Bean 元資料和手動提供的單例實例需要盡早註冊,以便容器在自動裝配和其他內省步驟期間正確地對它們進行推理。雖然在一定程度上支援覆寫現有的元資料和現有的單例實例,但在運行時(與對 factory 的即時存取並行)註冊新的 bean 並非官方支援,並且可能導致並行存取例外、bean 容器中的不一致狀態或兩者兼而有之。

覆寫 Bean

當使用已分配的識別碼註冊 bean 時,會發生 Bean 覆寫。雖然 Bean 覆寫是可能的,但它使組態更難以閱讀。

Bean 覆寫將在未來的版本中棄用。

若要完全停用 Bean 覆寫,您可以在 ApplicationContext 重新整理之前將 allowBeanDefinitionOverriding 旗標設定為 false。在這種設定中,如果使用 Bean 覆寫,則會擲回例外狀況。

預設情況下,容器會在 INFO 層級記錄每次嘗試覆寫 bean 的動作,以便您可以相應地調整組態。雖然不建議,但您可以透過將 allowBeanDefinitionOverriding 旗標設定為 true 來靜音這些記錄。

Java 組態

如果您使用 Java 組態,則對應的 @Bean 方法始終會以靜默方式使用與 bean 類別具有相同組件名稱的掃描 bean 類別來覆寫,只要 @Bean 方法的傳回類型與該 bean 類別相符即可。這僅表示容器將呼叫 @Bean 工廠方法,而不是 bean 類別上的任何預先宣告的建構子。

我們承認在測試情境中覆寫 bean 很方便,並且自 Spring Framework 6.2 起,對此有明確的支援。請參閱本節以了解更多詳細資訊。

命名 Bean

每個 bean 都有一個或多個識別碼。這些識別碼在託管 bean 的容器中必須是唯一的。一個 bean 通常只有一個識別碼。但是,如果它需要多個識別碼,則額外的識別碼可以視為別名。

在基於 XML 的組態元資料中,您可以使用 id 屬性、name 屬性或兩者來指定 bean 識別碼。id 屬性可讓您精確指定一個 id。依照慣例,這些名稱是字母數字('myBean'、'someService' 等),但它們也可以包含特殊字元。如果您想為 bean 引入其他別名,您也可以在 name 屬性中指定它們,並以逗號 (,)、分號 (;) 或空白字元分隔。雖然 id 屬性定義為 xsd:string 類型,但 bean id 唯一性是由容器強制執行的,而不是由 XML 解析器強制執行的。

您不需要為 bean 提供 nameid。如果您未明確提供 nameid,則容器會為該 bean 產生唯一名稱。但是,如果您想透過使用 ref 元素或 Service Locator 樣式查閱按名稱參考該 bean,則必須提供名稱。不提供名稱的動機與使用內部 bean自動裝配協作者有關。

Bean 命名慣例

慣例是在命名 bean 時使用標準 Java 慣例作為實例欄位名稱。也就是說,bean 名稱以小寫字母開頭,並從那裡以駝峰式命名。此類名稱的範例包括 accountManageraccountServiceuserDaologinController 等等。

一致地命名 bean 使您的組態更容易閱讀和理解。此外,如果您使用 Spring AOP,當將建議應用於一組按名稱相關的 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。

Java 組態

如果您使用 Java 組態,則 @Bean 註解可用於提供別名。請參閱使用 @Bean 註解以了解詳細資訊。

實例化 Bean

bean 定義本質上是建立一個或多個物件的配方。容器在被要求時會查看命名 bean 的配方,並使用該 bean 定義封裝的組態元資料來建立(或取得)實際物件。

如果您使用基於 XML 的組態元資料,則可以在 <bean/> 元素的 class 屬性中指定要實例化的物件類型(或類別)。此 class 屬性(在內部是 BeanDefinition 實例上的 Class 屬性)通常是強制性的。(對於例外情況,請參閱使用實例工廠方法實例化Bean 定義繼承。)您可以透過以下兩種方式之一使用 Class 屬性

  • 通常,指定要在容器本身直接透過反射性呼叫其建構子來建立 bean 的情況下建構的 bean 類別,這在某種程度上等同於使用 new 運算子的 Java 程式碼。

  • 指定實際包含 static 工廠方法的類別,該方法被調用以建立物件,在容器調用類別上的 static 工廠方法以建立 bean 的較不常見的情況下。從 static 工廠方法的調用傳回的物件類型可以是相同的類別,也可以是完全不同的類別。

巢狀類別名稱

如果您要為巢狀類別配置 bean 定義,則可以使用巢狀類別的二進位名稱或原始碼名稱。

例如,如果您在 com.example 套件中有名為 SomeThing 的類別,並且此 SomeThing 類別具有名為 OtherThingstatic 巢狀類別,則它們可以用美元符號 ($) 或點 (.) 分隔。因此,bean 定義中 class 屬性的值將為 com.example.SomeThing$OtherThingcom.example.SomeThing.OtherThing

使用建構子實例化

當您透過建構子方法建立 bean 時,所有常規類別都可由 Spring 使用並與之相容。也就是說,正在開發的類別不需要實作任何特定的介面或以特定的方式編碼。只需指定 bean 類別即可。但是,根據您用於該特定 bean 的 IoC 類型,您可能需要預設(空)建構子。

Spring IoC 容器可以管理您想要它管理的幾乎任何類別。它不限於管理真正的 JavaBean。大多數 Spring 使用者更喜歡實際的 JavaBean,它們只有一個預設(無引數)建構子和根據容器中屬性建模的適當的 setter 和 getter。您也可以在容器中擁有更多異國情調的非 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
	}
}

關於將(可選)引數提供給工廠方法,以及在物件從工廠傳回後設定物件實例屬性的機制詳情,請參閱Dependencies and Configuration in Detail

在工廠方法引數的情況下,容器可以從多個同名的方法重載中選擇對應的方法。儘管如此,為了避免歧義,建議盡可能保持工廠方法簽名簡單明瞭。

工廠方法重載的一個典型的問題案例是 Mockito,其 mock 方法有許多重載。請選擇 mock 最具體的變體。

<bean id="clientService" class="org.mockito.Mockito" factory-method="mock">
	<constructor-arg type="java.lang.Class" value="examples.ClientService"/>
	<constructor-arg type="java.lang.String" value="clientService"/>
</bean>

使用實例工廠方法進行實例化

與透過靜態工廠方法進行實例化類似,使用實例工廠方法進行實例化會呼叫容器中現有 bean 的非靜態方法,以建立新的 bean。若要使用此機制,請將 class 屬性留空,並在 factory-bean 屬性中,指定目前(或父容器或祖先容器)容器中 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) 進行管理和配置。請參閱Dependencies and Configuration in Detail

在 Spring 文件中,「factory bean」指的是在 Spring 容器中配置,並透過實例靜態工廠方法建立物件的 bean。 相反地,FactoryBean(請注意大小寫)指的是 Spring 特有的 FactoryBean 實作類別。

判斷 Bean 的執行時期類型

特定 bean 的執行時期類型並不容易判斷。bean 元資料定義中指定的類別僅是一個初始類別參考,可能與宣告的工廠方法或作為 `FactoryBean` 類別結合,這可能導致 bean 的執行時期類型不同,或者在實例層級工廠方法的情況下根本未設定(而是透過指定的 `factory-bean` 名稱解析)。此外,AOP 代理可能會使用基於介面的代理來包裝 bean 實例,從而限制目標 bean 實際類型的暴露(僅限於其已實作的介面)。

找出特定 bean 實際執行時期類型的建議方法是針對指定的 bean 名稱呼叫 `BeanFactory.getType`。這會考慮上述所有情況,並傳回 `BeanFactory.getBean` 呼叫針對相同 bean 名稱將傳回的物件類型。