考察你對 Spring 基本功掌握能力
1. 簡介
在 Spring Boot 應用程序中,有時需要為同一個類創建多個實例(Bean),并且每個實例可能有不同的配置或用途。雖然可以通過在 @Configuration 配置類中定義多個 @Bean 方法來實現這一點,但在需要創建大量實例的情況下,這種方法不僅冗余,而且難以維護。
在本篇文章中,我們將學習如何在Spring框架中使用注解來創建同一類的多個beans。
2. 實戰案例
2.1 使用Java配置
這是使用注解創建多個同類 bean 的最簡單易行的方法。在這種方法中,我們將使用基于 Java 的配置類來配置同一類中的多個 Bean,如下示例:
public class Person {
private String name ;
private Integer age ;
public Person(String name, Integer age) {
this.name = name ;
this.age = age ;
}
// getters, setters
@Override
public String toString() {
return "Person [name=" + name+ ", age=" + age+ "]" ;
}
}
接下來,我們將構建一個名為 PersonConfig 的配置類,并在其中定義 Person 類的多個 Bean:
@Configuration
public class PersonConfig {
@Bean
public Person person1() {
return new Person("Pack", 22) ;
}
@Bean
public Person person2() {
return new Person("xxgg", 24) ;
}
}
在這里,@Bean 注解實例化兩個bean,并將它們注冊到Spring容器中。接下來,我們可以初始化Spring容器,并從Spring容器中請求任何bean。這種策略還使得實現依賴注入變得簡單。我們可以使用自動裝配直接將一個bean(例如person1)注入到同類型的另一個bean(例如person2)中。
這種方法的局限是,在典型的基于Java的配置風格中,我們需要使用new關鍵字手動實例化bean。因此,如果相同類的bean數量增加,我們需要先注冊它們,并在配置類中創建這些bean。這使得它成為一種更偏向于Java的方法,而不是Spring特有的方法。
2.2 使用@Component注解
在這種方法中,我們將使用@Component注解來創建多個bean,這些bean將從Person類繼承屬性。首先,我們將創建多個子類,即PersonOne和PersonTwo,它們擴展自Person超類:
@Component
public class PersonOne extends Person {
public PersonOne() {
super("Pack", 22) ;
}
}
@Component
public class PersonTwo extends Person {
public PersonTwo() {
super("xxxooo", 24) ;
}
}
這種方法的問題是,它不會為同一個類創建多個實例。相反,它創建的是從超類繼承屬性的類的bean。因此,我們只能在繼承類沒有定義任何額外屬性的情況下使用這種解決方案。此外,使用繼承會增加代碼的整體復雜性。
2.3 使用BeanFactoryPostProcessor
利用 BeanFactoryPostProcessor 接口的自定義實現來創建同一類的多個 Bean 實例。我們將通過以下步驟來實現:
- 創建自定義 Bean 類并使用 FactoryBean 接口對其進行配置
- 使用 BeanFactoryPostProcessor 接口實例化同一類型的多個 Bean
自定義 Bean 實現
為了更好地理解這種方法,我們將進一步擴展同一實力。假設有一個 Human 類,它依賴于 Person 類的多個實例:
public class Human implements InitializingBean {
private Person personOne;
private Person personTwo;
@Override
public void afterPropertiesSet() throws Exception {
Assert.notNull(personOne, "Pack is alive!");
Assert.notNull(personTwo, "Jook is alive!");
}
@Autowired
public void setPersonOne(Person personOne) {
this.personOne = personOne ;
this.personOne.setName("Pack") ;
this.personOne.setAge(22) ;
}
@Autowired
public void setPersonTwo(Person personTwo) {
this.personTwo = personTwo ;
this.personTwo.setName("Jook") ;
this.personTwo.setAge(24) ;
}
}
InitializingBean 接口會調用 afterPropertiesSet() 方法來檢查 BeanFactory 是否設置了所有 Bean 屬性,以及是否滿足了其他依賴條件。此外,我們將使用setter注入法注入并初始化兩個 Person 類 Bean:personOne 和 personTwo。接下來,我們將創建一個實現 FactoryBean 接口的 Person 類。
@Qualifier(value = "personOne, personTwo")
public class Person implements FactoryBean<Object> {
private String name ;
private Integer age ;
public Person() {
}
public Class<Person> getObjectType() {
return Person.class ;
}
public Object getObject() throws Exception {
return new Person() ;
}
public boolean isSingleton() {
return true ;
}
}
這里要注意的是 @Qualifier 注解的使用,它包含了類級別上多個 Person 類型的名稱或 bean id。在這種情況下,在類級別使用 @Qualifier 是有原因的,我們接下來就會看到。
自定義 BeanFactory 實現
任何實現 BeanFactoryPostProcessor 的類都會在創建任何 Spring 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 容器初始化后被調用。
最后,配置上面定義的Human及BeanFactoryPostProcessor
@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。