1. 概述
本文將介紹如何在 Spring 中創建具有運行時參數的原型作用域 Bean。
在 Spring 中,有多種 Bean 作用域可供選擇,默認作用域為 singleton,這意味着 singleton 作用域的 Bean 始終會產生相同對象。
或者,如果需要每次從容器中生成一個新的實例,我們可以使用 prototype 作用域的 Bean。然而,在大多數情況下,如果我們嘗試從 singleton Bean 中實例化 prototype Bean 或將動態參數傳遞給 prototype Bean 時,會遇到問題。
Spring 提供了多種方法來解決這些問題,我們將會在本教程中深入探討這些方法。
2. 創建具有動態參數的原型 Bean
我們有時需要使用動態參數作為輸入來初始化 Spring 中的 Bean。原型 Bean 可以通過多種方法,使用 Spring 賦予不同的動態參數。
我們將逐一探討它們各自的優缺點。
首先,讓我們創建一個原型 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. 使用應用上下文
通常,這是使用原型 Bean 的最基本且最簡單的方法,通過 ApplicationContext。
讓我們將 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
讓我們使用 <em>EmployeeFactory</em> 接口為我們的 <em>Employee</em> Bean 創建一個工廠:
public class EmployeeBeanUsingObjectFactory {
@Autowired
private ObjectFactory employeeObjectFactory;
public Employee getEmployee() {
return employeeObjectFactory.getObject();
}
}在這裏,每次調用 getEmployee() 方法,Spring 都會返回一個新的 Employee 對象。
2.3. 使用 @Lookup 標註
或者,使用依賴注入,通過使用帶有 @Lookup 標註的依賴注入方法來解決問題。任何帶有 @Lookup 標註的方法都將被 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 創建一個組件,其中將添加 name 字段到實例中:
@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>,它是現有 ObjectFactory 接口的擴展。
下面演示如何注入 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);
}3. 結論
在本教程中,我們學習了多種動態創建原型作用域 Bean 的方法,這些方法適用於 Spring 框架。