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

通常,这是通过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*接口,用于按需生产指定类型的对象。

让我们使用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容器覆盖,然后返回该方法对应的命名bean。

让我们创建一个组件,并为获取Employee对象的方法添加*@Lookup*注解:

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

带有*@Lookup注解的方法,如getEmployee(),将被Spring容器重写。因此,bean会在应用程序上下文中注册。每次调用getEmployee()方法时,都会返回一个新的Employee*实例。

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

现在,让我们测试*@Lookup*方法,对于给定的原型bean,检查它是否返回不同的实例:

@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,它是现有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中动态创建原型作用域bean的几种方法。

如往常一样,本教程的完整代码可以在GitHub上找到。