1、概览 {#1概览}
Spring IoC 容器创建和管理 Spring Bean,这些 Bean 是应用的核心。创建一个 Bean 实例与从普通的 Java 类创建对象相同。然而,生成多个相同类的 Bean 可能会比较麻烦一点。
本文将带你了解如何在 Spring 中使用注解创建同一个类的多个 Bean。
2、使用 Java 配置 {#2使用-java-配置}
这是使用注解创建多个同类 Bean 的最简单易行的方法。
举一个简单的例子。我们有一个 Person
类,它有两个字段:firstName
和 lastName
:
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
注解 {#3使用-component-注解}
在这种方法中,使用 @Component
注解来创建多个从 Person
类继承属性的 Bean。首先,创建多个子类,即 PersonOne
和 PersonTwo
,它们继承自 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 容器中使用 PersonOne
或 PersonTwo
的Bean。在其他地方,可以使用 Person
类的 Bean。这种方法的问题在于,它不会创建同一个类的多个实例,而创建的是继承父类属性的子类 bean。
因此,只能在继承类没有定义任何附加属性的情况下使用这种解决方案。此外,使用继承会增加代码的整体复杂性。
4、 使用 BeanFactoryPostProcessor
{#4-使用-beanfactorypostprocessor}
第三种也是最后一种方法是利用 BeanFactoryPostProcessor
接口的自定义实现来创建同一类的多个 Bean 实例。
步骤如下:
- 创建自定义 Bean 类并使用
FactoryBean
接口对其进行配置 - 使用
BeanFactoryPostProcessor
接口实例化同一类型的多个 Bean
4.1、自定义 Bean 实现 {#41自定义-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:personOne
和 personTwo
。接下来,创建一个实现 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
实现 {#42自定义-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、总结 {#5总结}
本文通过三种不同的方法介绍了使用 Spring 注解实例化多个同类 Bean。前两种方法是实例化多个 Spring Bean 的简单、特定于 Java 的方法。第三种方法有些棘手和复杂,但也达到了使用注解创建 Bean 的目的。
参考:https://www.baeldung.com/spring-same-class-multiple-beans