使用 Spring 注解实例化同一个类的多个 Bean

1、概览

Spring IoC 容器创建和管理 Spring Bean,这些 Bean 是应用的核心。创建一个 Bean 实例与从普通的 Java 类创建对象相同。然而,生成多个相同类的 Bean 可能会比较麻烦一点。

本文将带你了解如何在 Spring 中使用注解创建同一个类的多个 Bean。

2、使用 Java 配置

这是使用注解创建多个同类 Bean 的最简单易行的方法。

举一个简单的例子。我们有一个 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 + "]";
    }
}

接下来,构建一个名为 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");
    }
}

在这里,@Bean 实例化了两个 id 与方法名称相同的 Bean,并将它们注册到 BeanFactory(Spring 容器)接口中。接下来,就可以初始化 Spring 容器,并从 Spring 容器中获取任何一个 Bean。这种策略还能简单地实现依赖注入。可以使用自动装配将一个 Bean(如 personOne)直接注入到另一个相同类型的 Bean(如 personTwo)中。

这种方法的局限性在于,在典型的基于 Java 的配置风格中,需要使用 new 关键字手动实例化 Bean。因此,如果同类 Bean 的数量增加,需要先注册它们,然后在配置类中创建 Bean。这使得它更像是一种 Java 特有的方法,而不是 Spring 特有的方法。

3、使用 @Component 注解

在这种方法中,使用 @Component 注解来创建多个从 Person 类继承属性的 Bean。首先,创建多个子类,即 PersonOnePersonTwo,它们继承自 Person superclass。

@Component
public class PersonOne extends Person {

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

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

接下来,在 PersonConfig 中,使用 @ComponentScan 注解启用整个包的组件扫描。这样,Spring 容器就能自动创建任何带有 @Component 注解的类的 Bean。

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

}

现在可以直接从 Spring 容器中使用 PersonOnePersonTwo 的Bean。在其他地方,可以使用 Person 类的 Bean。这种方法的问题在于,它不会创建同一个类的多个实例,而创建的是继承父类属性的子类 bean。

因此,只能在继承类没有定义任何附加属性的情况下使用这种解决方案。此外,使用继承会增加代码的整体复杂性。

4、 使用 BeanFactoryPostProcessor

第三种也是最后一种方法是利用 BeanFactoryPostProcessor 接口的自定义实现来创建同一类的多个 Bean 实例。

步骤如下:

  • 创建自定义 Bean 类并使用 FactoryBean 接口对其进行配置
  • 使用 BeanFactoryPostProcessor 接口实例化同一类型的多个 Bean

4.1、自定义 Bean 实现

为了更好地理解这种方法,进一步扩展上述示例。假设有一个 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 注入 */
    @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");
    }
}

InitializingBean 接口会调用 afterPropertiesSet() 方法来检查 BeanFactory 是否设置了所有 Bean 属性,以及是否满足了其他依赖条件。此外,使用 setter 注入法注入并初始化两个 Person 类 Bean:personOnepersonTwo。接下来,创建一个实现 FactoryBean 接口的 Person 类。

FactoryBean 是在 IoC 容器中创建其他 Bean 的工厂。该接口旨在为实现它的 Bean 创建更多实例。在本例中,它会生成 Person 类的实例,并自动进行配置:

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

    public Person() {
        // 初始化代码,可选
    }

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

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

    public boolean isSingleton() {
        return true;
    }

    // get / set 方法
}

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

4.2、自定义 BeanFactory 实现

现在,使用 BeanFactoryPostProcessor 接口的自定义实现。

任何实现 BeanFactoryPostProcessor 的类都会在创建任何 Spring Bean 之前执行。这样,我们就可以配置和操纵 Bean 的生命周期。

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(",");
    }
}

如上,自定义 BeanFactoryPostProcessor 实现会在 Spring 容器初始化后被调用。

接着,为了方便,使用 Java 配置类来初始化自定义的 Bean 和 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();
    }
}

这种方法的局限性在于其复杂性。此外,也不鼓励使用这种方法,因为它不是在典型 Spring 应用中配置 Bean 的自然方式。尽管有这些局限性,但这种方法更具 Spring 特性,可以使用注解实例化多个相似类型的 Bean。

5、总结

本文通过三种不同的方法介绍了使用 Spring 注解实例化多个同类 Bean。前两种方法是实例化多个 Spring Bean 的简单、特定于 Java 的方法。第三种方法有些棘手和复杂,但也达到了使用注解创建 Bean 的目的。


参考:https://www.baeldung.com/spring-same-class-multiple-beans