使用運行時參數建立 Spring 原型範圍 Bean
1. 概述
在這篇簡短的文章中,我們將學習如何在 Spring 中建立帶有運行時參數的原型範圍 bean。
在Spring中,有許多不同的bean作用域,但預設作用域是單例,這意味著單例作用域的bean將始終產生相同的物件。
或者,如果每次都需要來自容器的新實例,我們可以使用原型範圍的 bean。然而,在大多數情況下,如果我們想要從單例 bean 實例化原型或將動態參數傳輸到原型 bean,我們會遇到問題。
Spring 提供了許多方法來實現這些目標,我們將在本教程中深入討論。
2. 使用動態參數建立原型 Bean
有時我們需要在每次初始化時使用動態參數作為輸入來初始化 Spring bean。可以使用多種方法透過 Spring 為原型 bean 指派不同的動態參數。
我們將一一分析它們,看看它們的優點和缺點。
首先,我們先建立一個原型 bean Employee
:
public class Employee {
private String name;
public Employee(String name) {
this.name = name;
}
public void printName() {
System.out.println(name);
}
}
另外,讓我們為Employee
原型 bean 建立一個配置:
@Configuration
public class EmployeeConfig {
@Bean(name = "Employee")
@Scope(BeanDefinition.SCOPE_PROTOTYPE)
public Employee createPrototype(String name) {
return new Employee(name);
}
}
2.1.使用應用程式上下文
一般來說,這是使用ApplicationContext
來取得原型 bean 的最基本、最簡單的方法。
讓我們將ApplicationContext
注入到我們的元件中:
@Component
public class UseEmployeePrototype {
private ApplicationContext applicationContext;
@Autowired
public UseEmployeePrototype(ApplicationContext applicationContext) {
this.applicationContext = applicationContext;
}
public void usePrototype() {
Employee employee = (Employee) applicationContext.getBean("Employee", "sachin");
employee.printName();
}
}
正如我們在這裡看到的,我們將 bean 建立與ApplicationContext
緊密耦合。因此,如果我們更改 bean 實現,則該方法可能會受到影響。
2.2.使用工廠方法
Spring 提供了[ObjectFactory<T>](https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/beans/factory/config/ObjectFactoryCreatingFactoryBean.html)
介面來按需產生給定類型的物件。
讓我們使用ObjectFactory
為Employee
bean 建立一個EmployeeFactory
:
public class EmployeeBeanUsingObjectFactory {
@Autowired
private ObjectFactory employeeObjectFactory;
public Employee getEmployee() {
return employeeObjectFactory.getObject();
}
}
在這裡,每次呼叫getEmployee()
時,Spring 都會傳回一個新的Employee
物件。
2.3.使用@Lookup
或者,使用[@Lookup](https://docs.spring.io/spring-framework/reference/core/beans/dependencies/factory-method-injection.html#beans-factory-lookup-method-injection)
註釋的方法注入可以解決該問題。我們使用@Lookup
註解注入的任何方法都會被Spring容器覆蓋,然後Spring容器將傳回該方法的命名bean。
讓我們建立一個元件並建立一個帶有Lookup
註解的方法來取得Employee
物件:
@Component
public class EmployeeBeanUsingLookUp {
@Lookup
public Employee getEmployee(String arg) {
return null;
}
}
以Lookup
註解的方法,例如getEmployee
(),將被 Spring 覆蓋。結果,bean 被註冊到應用程式上下文。每次呼叫getEmployee
() 方法時都會傳回一個新的Employee
實例。
Spring將使用CGLIB產生字節碼,而且類別和方法都不能是final的。
現在,讓我們測試給定原型 bean 的@Lookup
方法並檢查它是否返回不同的實例:
@Test
public void givenPrototypeBean_WhenLookup_ThenNewInstanceReturn() {
AbstractApplicationContext context = new AnnotationConfigApplicationContext(EmployeeConfig.class);
EmployeeBeanUsingLookUp firstContext = context.getBean(EmployeeBeanUsingLookUp.class);
EmployeeBeanUsingLookUp secondContext = context.getBean(EmployeeBeanUsingLookUp.class);
Employee firstInstance = firstContext.getEmployee("sachin");
Employee secondInstance = secondContext.getEmployee("kumar");
Assert.assertTrue(firstInstance != secondInstance);
}
2.4.使用Function
Spring 提供了另一個選項Function
,用於在運行時建立原型 bean。我們也可以將參數應用於新建立的原型 bean 實例。
首先,讓我們使用Function
建立一個元件,其中名稱欄位將會新增到實例中:
@Component
public class EmployeeBeanUsingFunction {
@Autowired
private Function<String, Employee> beanFactory;
public Employee getEmployee(String name) {
Employee employee = beanFactory.apply(name);
return employee;
}
}
此外,現在讓我們在 bean 配置中新增一個新的beanFactory()
:
@Configuration
public class EmployeeConfig {
@Bean
@Scope(value = "prototype")
public Employee getEmployee(String name) {
return new Employee(name);
}
@Bean
public Function<String, Employee> beanFactory() {
return name -> getEmployee(name);
}
}
最後,我們將檢查實例是否不同:
@Test
public void givenPrototypeBean_WhenFunction_ThenNewInstanceReturn() {
AbstractApplicationContext context = new AnnotationConfigApplicationContext(EmployeeConfig.class);
EmployeeBeanUsingFunction firstContext = context.getBean(EmployeeBeanUsingFunction.class);
EmployeeBeanUsingFunction secondContext = context.getBean(EmployeeBeanUsingFunction.class);
Employee firstInstance = firstContext.getEmployee("sachin");
Employee secondInstance = secondContext.getEmployee("kumar");
Assert.assertTrue(firstInstance != secondInstance);
}
2.5.使用ObjectProvider
Spring 提供了[ObjectProvider<T>](https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/beans/factory/ObjectProvider.html)
,它是現有ObjectFactory
介面的擴充。
讓我們注入ObjectProvider
並使用ObjectProvider
來取得Employee
物件:
public class EmployeeBeanUsingObjectProvider {
@Autowired
private org.springframework.beans.factory.ObjectProvider objectProvider;
public Employee getEmployee(String name) {
Employee employee = objectProvider.getObject(name);
return employee;
}
}
現在,讓我們測試並檢查實例是否不同:
@Test
public void givenPrototypeBean_WhenObjectProvider_ThenNewInstanceReturn() {
AbstractApplicationContext context = new AnnotationConfigApplicationContext(EmployeeConfig.class);
EmployeeBeanUsingObjectProvider firstContext = context.getBean(EmployeeBeanUsingObjectProvider.class);
EmployeeBeanUsingObjectProvider secondContext = context.getBean(EmployeeBeanUsingObjectProvider.class);
Employee firstInstance = firstContext.getEmployee("sachin");
Employee secondInstance = secondContext.getEmployee("kumar");
Assert.assertTrue(firstInstance != secondInstance);
}
三、結論
在這個簡短的教程中,我們學習了在 Spring 中動態建立原型範圍 bean 的幾種方法。
與往常一樣,本教學的完整程式碼位於 GitHub 上。