Instantiating Multiple Beans of the Same Class with Spring Annotations – 用Spring注解实例化同一类别的多个Bean

最后修改: 2022年 7月 8日

中文/混合/英文(键盘快捷键:t)

1. Overview

1.概述

The Spring IoC container creates and manages Spring beans, which serve as the core of our application. Creating an instance of a bean is identical to creating objects from plain Java classes. However, generating several beans of the same class can be challenging.

Spring IoC 容器创建并管理 Spring Bean,它是我们应用程序的核心。创建Bean的实例与从普通的Java类中创建对象是相同的。然而,生成同一类别的多个Bean可能是一个挑战。

In this tutorial, we’ll learn how to use annotations in the Spring framework to create multiple beans of the same class.

在本教程中,我们将学习如何在Spring框架中使用注解来创建同一类别的多个Bean。

2. Using Java Configuration

2.使用Java配置

This is the simplest and easiest way to create multiple beans of the same class using annotations. In this approach, we’ll use a Java-based configuration class to configure multiple beans of the same class.

这是使用注解创建同一类别的多个Bean的最简单、最容易的方法。在这种方法中,我们将使用一个基于Java的配置类来配置同一类别的多个Bean。

Let’s consider a simple example. We have a Person class that has two class members firstName and lastName:

让我们考虑一个简单的例子。我们有一个Person类,有两个类成员firstNamelastName

public class Person {
    private String firstName;
    private String lastName;

    public Person(String firstName, String secondName) {
        super();
        this.firstName = firstName;
        this.lastName = secondName;
    }

    @Override
    public String toString() {
        return "Person [firstName=" + firstName + ", secondName=" + lastName + "]";
    }
}

Next, we’ll construct a configuration class called PersonConfig and define multiple beans of the Person class inside it:

接下来,我们将构建一个名为PersonConfig的配置类,并在其中定义多个Person类的bean。

@Configuration
public class PersonConfig {
    @Bean
    public Person personOne() {
        return new Person("Harold", "Finch");
    }

    @Bean
    public Person personTwo() {
        return new Person("John", "Reese");
    }
}

Here, @Bean instantiates two beans with ids the same as the method names and registers them within the BeanFactory (Spring container) interface. Next, we can initialize the Spring container and request any of the beans from the Spring container.

在这里, @Bean实例化了两个id与方法名称相同的bean,并在BeanFactory(Spring容器)接口中注册了它们。接下来,我们可以初始化Spring容器并从Spring容器中请求任何一个Bean。

This strategy also makes it simple to achieve dependency injection. We can directly inject one bean, say personOne, into another bean of the same type, say personTwo using autowiring

这种策略也使得实现依赖注入变得简单。我们可以使用autowiring将一个Bean(比如说personOne,)直接注入另一个相同类型的Bean(比如说personTwo)中。

The limitation of this approach is that we need to manually instantiate beans using the new keyword in a typical Java-based configuration style. 

这种方法的局限性在于,我们需要在典型的基于Java的配置风格中使用new关键字手动实例化bean。

Therefore, if the number of beans of the same class increases, we need to register them first and create beans in the configuration class. This makes it a more Java-specific approach, rather than a Spring-specific approach.

因此,如果同一类别的Bean数量增加,我们需要先注册它们,并在配置类中创建Bean。这使得它更像是一种针对Java的方法,而不是针对Spring的方法。

3. Using @Component Annotation

3.使用@Component注释

In this approach, we’ll use the @Component annotation to create multiple beans that inherit their properties from the Person class.

在这种方法中,我们将使用@Component注解来创建多个Bean,这些Bean从Person类继承其属性。

First, we’ll create multiple subclasses, namely PersonOne and PersonTwo, that extend the Person superclass:

首先,我们将创建多个子类,即PersonOnePersonTwo,,它们扩展了Person超类。

@Component
public class PersonOne extends Person {

    public PersonOne() {
        super("Harold", "Finch");
    }
}
@Component
public class PersonTwo extends Person {

    public PersonTwo() {
        super("John", "Reese");
    }
}

Next, in the PersonConfig file, we’ll use the @ComponentScan annotation to enable component scanning throughout the entire package. This enables the Spring container to automatically create beans of any class annotated with @Component:

接下来,在PersonConfig文件中,我们将使用@ComponentScan注解来启用整个包的组件扫描。这使得Spring容器能够自动创建任何用@Component注解的类的bean:

@Configuration
@ComponentScan("com.baeldung.multibeaninstantiation.solution2")
public class PersonConfig {

}

Now, we can just use the PersonOne or PersonTwo beans from the Spring container. Everywhere else, we can use the Person class bean.

现在,我们可以直接使用Spring容器中的PersonOnePersonTwoBean。在其他地方,我们可以使用Person类bean。

The problem with this approach is that it doesn’t create multiple instances of the same class. Instead, it creates beans of classes that inherit properties from a superclass. Therefore, we can use this solution only in situations where the inherited class doesn’t have any additional properties defined. Moreover, the usage of inheritance increases the overall complexity of the code.

这种方法的问题在于它并没有创建同一个类的多个实例。因此,我们只能在继承的类没有定义任何额外属性的情况下使用这种解决方案。此外,继承的使用增加了代码的整体复杂性。

4. Using BeanFactoryPostProcessor

4.使用BeanFactoryPostProcessor

The third and final approach utilizes a custom implementation of the BeanFactoryPostProcessor interface for creating multiple bean instances of the same class. This can be achieved using the following steps:

第三种也是最后一种方法是利用BeanFactoryPostProcessor接口的自定义实现来创建同一类别的多个Bean实例。这可以通过以下步骤来实现。

  • Creating a custom bean class and configuring it using the FactoryBean interface
  • Instantiating multiple beans of the same type using BeanFactoryPostProcessor interface

4.1. Custom Bean Implementation 

4.1.自定义Bean的实现

To understand this approach better, we’ll extend the same example further. Suppose there’s a Human class that is dependent upon multiple instances of the Person class:

为了更好地理解这种方法,我们将进一步扩展同一个例子。假设有一个Human类,它依赖于Person类的多个实例。

public class Human implements InitializingBean {

    private Person personOne;

    private Person personTwo;

    @Override
    public void afterPropertiesSet() throws Exception {
        Assert.notNull(personOne, "Harold is alive!");
        Assert.notNull(personTwo, "John is alive!");
    }

    /* Setter injection */
    @Autowired
    public void setPersonOne(Person personOne) {
        this.personOne = personOne;
        this.personOne.setFirstName("Harold");
        this.personOne.setSecondName("Finch");
    }

    @Autowired
    public void setPersonTwo(Person personTwo) {
        this.personTwo = personTwo;
        this.personTwo.setFirstName("John");
        this.personTwo.setSecondName("Reese");
    }
}

The InitializingBean interface invokes the afterPropertiesSet() method to check whether BeanFactory has set all the bean properties and satisfied other dependencies. Additionally, we’re injecting and initializing two Person class beans, personOne and personTwo, using setter injection.

InitializingBean接口调用afterPropertiesSet()方法来检查BeanFactory是否已经设置了所有的Bean属性并满足其他的依赖性。此外,我们正在使用setter注入法注入并初始化两个Person类bean,personOnepersonTwo

Next, we’ll create a Person class that implements the FactoryBean interface. A FactoryBean acts as a factory for creating other beans within the IoC container.

接下来,我们将创建一个Person类,它实现了FactoryBean接口。一个FactoryBean可以作为工厂,在IoC容器中创建其他Bean。

This interface is intended to create more instances of the bean that implements it. In our case, it generates instances of the type Person class and configures it automatically:

这个接口旨在创建更多实现它的bean的实例。在我们的例子中,它生成Person类的实例并自动配置。

@Qualifier(value = "personOne, personTwo")
public class Person implements FactoryBean<Object> {
    private String firstName;
    private String secondName;

    public Person() {
        // initialization code (optional)
    }

    @Override
    public Class<Person> getObjectType() {
        return Person.class;
    }

    @Override
    public Object getObject() throws Exception {
        return new Person();
    }

    public boolean isSingleton() {
        return true;
    }

    // code for getters & setters
}

The second important thing to notice here is the use of the @Qualifier annotation that contains names or bean ids of multiple Person types at the class level. There’s a reason behind using @Qualifier at the class level, in this case, which we’re going to see next.

这里需要注意的第二件重要的事情是 使用@Qualifier注解,该注解在类级别上包含多个Person类型的名称或bean ids。在这种情况下,在类级别上使用@Qualifier是有原因的,我们接下来会看到。

4.2. Custom BeanFactory Implementation

4.2.自定义BeanFactory实现

Now, we’ll use a custom implementation of the BeanFactoryPostProcessor interface. Any class that implements BeanFactoryPostProcessor is executed before any Spring bean gets created. This allows us to configure and manipulate the bean lifecycle. 

现在,我们将使用BeanFactoryPostProcessor接口的一个自定义实现。任何实现BeanFactoryPostProcessor的类都会在任何Spring Bean被创建之前被执行。这允许我们配置和操纵Bean生命周期。

The BeanFactoryPostProcessor scans all the classes annotated with @Qualifier. Furthermore, it extracts names (bean ids) from that annotation and manually creates instances of that class type with the specified names:

BeanFactoryPostProcessor扫描了所有用@Qualifier注释的类。此外,它还从该注解中提取名称(bean id),并以指定的名称手动创建该类类型的实例:

public class PersonFactoryPostProcessor implements BeanFactoryPostProcessor {

    @Override
    public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
        Map<String, Object> map = beanFactory.getBeansWithAnnotation(Qualifier.class);
        for (Map.Entry<String, Object> entry : map.entrySet()) {
            createInstances(beanFactory, entry.getKey(), entry.getValue());
        }
    }

    private void createInstances(ConfigurableListableBeanFactory beanFactory, String beanName, Object bean) {
        Qualifier qualifier = bean.getClass().getAnnotation(Qualifier.class);
        for (String name : extractNames(qualifier)) {
            Object newBean = beanFactory.getBean(beanName);
            beanFactory.registerSingleton(name.trim(), newBean);
        }
    }

    private String[] extractNames(Qualifier qualifier) {
        return qualifier.value().split(",");
    }
}

Here, the custom BeanFactoryPostProcessor implementation gets invoked once the Spring container is initialized.

在这里,一旦Spring容器被初始化,自定义的BeanFactoryPostProcessor实现就会被调用

Next, to keep things simple, here we’ll use a Java configuration class to initialize the custom as well as BeanFactory implementations:

接下来,为了保持简单,这里我们将使用一个Java配置类来初始化自定义以及BeanFactory实现。

@Configuration
public class PersonConfig {
    @Bean
    public PersonFactoryPostProcessor PersonFactoryPostProcessor() {
        return new PersonFactoryPostProcessor();
    }

    @Bean
    public Person person() {
        return new Person();
    }

    @Bean
    public Human human() {
        return new Human();
    }
}

The limitation of this approach lies in its complexity. Moreover, it’s not encouraged to use since it’s not the natural way of configuring beans in a typical Spring application.

这种方法的局限性在于其复杂性。此外,我们不鼓励使用这种方法,因为它不是典型的Spring应用程序中配置Bean的自然方式

Despite the limitations, this approach is more Spring specific and serves the purpose of instantiating multiple beans of similar type using annotations.

尽管有局限性,但这种方法更符合Spring的特点,并能达到使用注解实例化多个类似类型的Bean的目的。

5. Conclusion

5.总结

In this article, we’ve learned about instantiating multiple beans of the same class using Spring annotations using three different approaches.

在这篇文章中,我们已经学习了使用Spring注解用三种不同的方法将同一类别的多个Bean实例化。

The first two approaches are simple and Java-specific ways to instantiate multiple Spring beans. However, the third one is a bit tricky and complex. But, it serves the purpose of bean creation using annotations.

前两种方法是简单的、针对Java的实例化多个Spring Bean的方法。然而,第三种方法有点棘手和复杂。但是,它可以达到使用注解创建Bean的目的。

As always, the source code for the examples is available over on GitHub.

像往常一样,这些例子的源代码可以在GitHub上找到