Spring 在运行动态地创建 Prototype Scope Bean

1、概览

本文将带你了解如何在 Spring 中使用运行时参数创建一个 Prototype Scope Bean。

在 Spring 中,有许多不同的 Bean Scope,默认的 Scope 是 Singleton(单例)。Singleton Scope 的 Bean 将始终产生相同的对象。

如果每次都需要从容器中获得一个新实例,可以使用 Pototype Scope Bean。然而,在大多数情况下,如果我们想要从一个 Singleton Bean 实例化 Pototype Bean,或者将动态参数传递给 Pototype Bean,可能会遇到一些问题。

2、使用动态参数创建 Prototype Bean

有时,我们需要在每次初始化时将动态参数作为输入来初始化 Spring Bean。通过 Spring,可以使用多种方法为 Prototype Bean 分配不同的动态参数。

首先,创建一个 Prototype Bean Employee

public class Employee {
    private String name;

    public Employee(String name) {
        this.name = name;
    }

    public void printName() {
        System.out.println(name);
    }
}

Employee Prototype Bean 创建一个配置:

@Configuration
public class EmployeeConfig {
    @Bean(name = "Employee")
    @Scope(BeanDefinition.SCOPE_PROTOTYPE)
    public Employee createPrototype(String name) {
        return new Employee(name);
    }
}

2.1、使用 Application Context

一般来说,使用 ApplicationContext 获取 Prototype 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> 接口,用于按需生成给定类型的对象。

使用 ObjectFactoryEmployee Bean 创建一个 EmployeeFactory

public class EmployeeBeanUsingObjectFactory {
    @Autowired
    private ObjectFactory employeeObjectFactory;

    public Employee getEmployee() {
        return employeeObjectFactory.getObject();
    }
}

如上,每调用一次 getEmployee(),Spring 就会返回一个新的 Employee 对象。

2.3、使用 @Lookup

使用 @Lookup 注解注入方法也能解决问题。使用 @Lookup 注解的任何方法都将被 Spring 容器覆写,然后 Spring 容器将返回该方法的命名 Bean。

创建一个组件,并创建一个带有 @Lookup 注解的方法来获取 Employee 对象:

@Component
public class EmployeeBeanUsingLookUp {
    @Lookup
    public Employee getEmployee(String arg) {
        return null;
    }
}

注解了 @Lookup 的方法(如 getEmployee())将被 Spring 覆写。因此,Bean 会在 Application Context 中注册。每次调用 getEmployee() 方法时,都会返回一个新的 Employee 实例。

Spring 使用 CGLIB 生成字节码,类和方法都不能是 final 的。

测试给定 Prototype 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,用于在运行时创建 Prototype Bean。我们还可以将参数应用于新创建的 Prototype 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> 是现有 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);
}

3、总结

本文介绍了如何在 Spring 中动态创建地创建 Prototype Scope Bean。


Ref:https://www.baeldung.com/spring-prototype-bean-runtime-arguments