使用 @Bean 註解

@Bean 是方法級別的註解,直接對應 XML 的 <bean/> 元素。該註解支援 <bean/> 元素提供的某些屬性,例如

您可以在 @Configuration 註解的類或 @Component 註解的類中使用 @Bean 註解。

宣告 Bean

要宣告一個 Bean,您可以用 @Bean 註解標記一個方法。您可以使用此方法在 ApplicationContext 中註冊一個 Bean 定義,其型別由方法的返回值指定。預設情況下,Bean 的名稱與方法名稱相同。以下示例顯示了一個 @Bean 方法宣告

  • Java

  • Kotlin

@Configuration
public class AppConfig {

	@Bean
	public TransferServiceImpl transferService() {
		return new TransferServiceImpl();
	}
}
@Configuration
class AppConfig {

	@Bean
	fun transferService() = TransferServiceImpl()
}

上述配置完全等同於以下 Spring XML

<beans>
	<bean id="transferService" class="com.acme.TransferServiceImpl"/>
</beans>

這兩種宣告方式都會在 ApplicationContext 中提供一個名為 transferService 的 Bean,它繫結到型別為 TransferServiceImpl 的物件例項,如下面的文字圖所示

transferService -> com.acme.TransferServiceImpl

您還可以使用預設方法來定義 Bean。這允許透過實現介面並在預設方法上定義 Bean 來組合 Bean 配置。

  • Java

public interface BaseConfig {

	@Bean
	default TransferServiceImpl transferService() {
		return new TransferServiceImpl();
	}
}

@Configuration
public class AppConfig implements BaseConfig {

}

您也可以使用介面(或基類)作為 @Bean 方法的返回型別,如下例所示

  • Java

  • Kotlin

@Configuration
public class AppConfig {

	@Bean
	public TransferService transferService() {
		return new TransferServiceImpl();
	}
}
@Configuration
class AppConfig {

	@Bean
	fun transferService(): TransferService {
		return TransferServiceImpl()
	}
}

然而,這會將提前型別預測的可見性限制為指定的介面型別(TransferService)。完整的型別(TransferServiceImpl)僅在受影響的單例 Bean 例項化後才為容器所知。非延遲載入的單例 Bean 會按照其宣告順序例項化,因此根據其他元件何時嘗試按非宣告型別(例如 @Autowired TransferServiceImpl,它僅在 transferService Bean 例項化後才能解析)進行匹配,您可能會看到不同的型別匹配結果。

如果您始終透過宣告的服務介面引用型別,則 @Bean 返回型別可以安全地採用該設計決策。但是,對於實現多個介面的元件或可能透過其實現型別引用的元件,宣告最具體的返回型別(至少要滿足引用您的 Bean 的注入點所需)會更安全。

Bean 依賴

@Bean 註解的方法可以有任意數量的引數來描述構建該 Bean 所需的依賴。例如,如果我們的 TransferService 需要一個 AccountRepository,我們可以透過方法引數來實現該依賴,如下例所示

  • Java

  • Kotlin

@Configuration
public class AppConfig {

	@Bean
	public TransferService transferService(AccountRepository accountRepository) {
		return new TransferServiceImpl(accountRepository);
	}
}
@Configuration
class AppConfig {

	@Bean
	fun transferService(accountRepository: AccountRepository): TransferService {
		return TransferServiceImpl(accountRepository)
	}
}

解析機制與基於建構函式的依賴注入非常相似。有關更多詳細資訊,請參閱相關部分

接收生命週期回撥

任何使用 @Bean 註解定義的類都支援常規的生命週期回撥,並且可以使用 JSR-250 中的 @PostConstruct@PreDestroy 註解。有關更多詳細資訊,請參閱JSR-250 註解

常規的 Spring 生命週期回撥也得到全面支援。如果 Bean 實現了 InitializingBeanDisposableBeanLifecycle 介面,則容器會呼叫其對應的方法。

標準的 *Aware 介面集(如 BeanFactoryAwareBeanNameAwareMessageSourceAwareApplicationContextAware 等)也得到全面支援。

@Bean 註解支援指定任意初始化和銷燬回撥方法,非常類似於 Spring XML 中 bean 元素的 init-methoddestroy-method 屬性,如下例所示

  • Java

  • Kotlin

public class BeanOne {

	public void init() {
		// initialization logic
	}
}

public class BeanTwo {

	public void cleanup() {
		// destruction logic
	}
}

@Configuration
public class AppConfig {

	@Bean(initMethod = "init")
	public BeanOne beanOne() {
		return new BeanOne();
	}

	@Bean(destroyMethod = "cleanup")
	public BeanTwo beanTwo() {
		return new BeanTwo();
	}
}
class BeanOne {

	fun init() {
		// initialization logic
	}
}

class BeanTwo {

	fun cleanup() {
		// destruction logic
	}
}

@Configuration
class AppConfig {

	@Bean(initMethod = "init")
	fun beanOne() = BeanOne()

	@Bean(destroyMethod = "cleanup")
	fun beanTwo() = BeanTwo()
}

預設情況下,使用 Java 配置定義的、具有公共 closeshutdown 方法的 Bean 會自動註冊銷燬回撥。如果您的 Bean 有公共 closeshutdown 方法,但不希望在容器關閉時呼叫它,您可以在 Bean 定義中新增 @Bean(destroyMethod = "") 來停用預設的(推斷)模式。

對於透過 JNDI 獲取的資源,您可能希望預設執行此操作,因為其生命週期在應用程式外部管理。特別是,請務必始終為 DataSource 執行此操作,因為眾所周知它在 Jakarta EE 應用伺服器上存在問題。

以下示例展示瞭如何阻止 DataSource 的自動銷燬回撥

  • Java

  • Kotlin

@Bean(destroyMethod = "")
public DataSource dataSource() throws NamingException {
	return (DataSource) jndiTemplate.lookup("MyDS");
}
@Bean(destroyMethod = "")
fun dataSource(): DataSource {
	return jndiTemplate.lookup("MyDS") as DataSource
}

此外,使用 @Bean 方法時,您通常會使用程式設計式 JNDI 查詢,可以透過 Spring 的 JndiTemplateJndiLocatorDelegate 助手,或者直接使用 JNDI 的 InitialContext,但不使用 JndiObjectFactoryBean 變體(這會迫使您將返回型別宣告為 FactoryBean 型別而非實際目標型別,從而使得在其他 @Bean 方法中交叉引用此處提供的資源變得困難)。

對於上面註釋中示例的 BeanOne,在構造期間直接呼叫 init() 方法同樣有效,如下例所示

  • Java

  • Kotlin

@Configuration
public class AppConfig {

	@Bean
	public BeanOne beanOne() {
		BeanOne beanOne = new BeanOne();
		beanOne.init();
		return beanOne;
	}

	// ...
}
@Configuration
class AppConfig {

	@Bean
	fun beanOne() = BeanOne().apply {
		init()
	}

	// ...
}
當您直接在 Java 中工作時,您可以對物件做任何想做的事情,並不總是需要依賴容器的生命週期。

指定 Bean 作用域

Spring 包含 @Scope 註解,以便您可以指定 Bean 的作用域。

使用 @Scope 註解

您可以指定使用 @Bean 註解定義的 Bean 具有特定的作用域。您可以使用Bean 作用域部分中指定的任何標準作用域。

預設作用域是 singleton,但您可以使用 @Scope 註解覆蓋它,如下例所示

  • Java

  • Kotlin

@Configuration
public class MyConfiguration {

	@Bean
	@Scope("prototype")
	public Encryptor encryptor() {
		// ...
	}
}
@Configuration
class MyConfiguration {

	@Bean
	@Scope("prototype")
	fun encryptor(): Encryptor {
		// ...
	}
}

@Scopescoped-proxy

Spring 提供了一種方便的方式來透過作用域代理處理具有作用域的依賴。在使用 XML 配置時,建立此類代理的最簡單方法是使用 <aop:scoped-proxy/> 元素。在 Java 中使用 @Scope 註解配置 Bean 提供了等效的支援,透過 proxyMode 屬性實現。預設值是 ScopedProxyMode.DEFAULT,這通常表示不應建立作用域代理,除非在元件掃描指令級別配置了不同的預設值。您可以指定 ScopedProxyMode.TARGET_CLASSScopedProxyMode.INTERFACESScopedProxyMode.NO

如果您將 XML 參考文件中的作用域代理示例(請參閱作用域代理)移植到我們使用 Java 的 @Bean,它類似於以下內容

  • Java

  • Kotlin

// an HTTP Session-scoped bean exposed as a proxy
@Bean
@SessionScope
public UserPreferences userPreferences() {
	return new UserPreferences();
}

@Bean
public Service userService() {
	UserService service = new SimpleUserService();
	// a reference to the proxied userPreferences bean
	service.setUserPreferences(userPreferences());
	return service;
}
// an HTTP Session-scoped bean exposed as a proxy
@Bean
@SessionScope
fun userPreferences() = UserPreferences()

@Bean
fun userService(): Service {
	return SimpleUserService().apply {
		// a reference to the proxied userPreferences bean
		setUserPreferences(userPreferences())
	}
}

自定義 Bean 命名

預設情況下,配置類使用 @Bean 方法的名稱作為生成的 Bean 的名稱。然而,可以使用 name 屬性來覆蓋此功能,如下例所示

  • Java

  • Kotlin

@Configuration
public class AppConfig {

	@Bean("myThing")
	public Thing thing() {
		return new Thing();
	}
}
@Configuration
class AppConfig {

	@Bean("myThing")
	fun thing() = Thing()
}

Bean 別名

正如命名 Bean 中討論的,有時希望給單個 Bean 多個名稱,也稱為 Bean 別名。 @Bean 註解的 name 屬性為此目的接受一個 String 陣列。以下示例顯示瞭如何為一個 Bean 設定多個別名

  • Java

  • Kotlin

@Configuration
public class AppConfig {

	@Bean({"dataSource", "subsystemA-dataSource", "subsystemB-dataSource"})
	public DataSource dataSource() {
		// instantiate, configure and return DataSource bean...
	}
}
@Configuration
class AppConfig {

	@Bean("dataSource", "subsystemA-dataSource", "subsystemB-dataSource")
	fun dataSource(): DataSource {
		// instantiate, configure and return DataSource bean...
	}
}

Bean 描述

有時,為 Bean 提供更詳細的文字描述會很有幫助。當 Bean 用於監控目的而暴露(例如透過 JMX)時,這尤其有用。

要向 @Bean 新增描述,您可以使用 @Description 註解,如下例所示

  • Java

  • Kotlin

@Configuration
public class AppConfig {

	@Bean
	@Description("Provides a basic example of a bean")
	public Thing thing() {
		return new Thing();
	}
}
@Configuration
class AppConfig {

	@Bean
	@Description("Provides a basic example of a bean")
	fun thing() = Thing()
}