考察你對(duì) Spring 基本功掌握能力
1. 簡(jiǎn)介
在 Spring Boot 應(yīng)用程序中,有時(shí)需要為同一個(gè)類創(chuàng)建多個(gè)實(shí)例(Bean),并且每個(gè)實(shí)例可能有不同的配置或用途。雖然可以通過(guò)在 @Configuration 配置類中定義多個(gè) @Bean 方法來(lái)實(shí)現(xiàn)這一點(diǎn),但在需要?jiǎng)?chuàng)建大量實(shí)例的情況下,這種方法不僅冗余,而且難以維護(hù)。
在本篇文章中,我們將學(xué)習(xí)如何在Spring框架中使用注解來(lái)創(chuàng)建同一類的多個(gè)beans。
2. 實(shí)戰(zhàn)案例
2.1 使用Java配置
這是使用注解創(chuàng)建多個(gè)同類 bean 的最簡(jiǎn)單易行的方法。在這種方法中,我們將使用基于 Java 的配置類來(lái)配置同一類中的多個(gè) 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+ "]" ;
}
}
接下來(lái),我們將構(gòu)建一個(gè)名為 PersonConfig 的配置類,并在其中定義 Person 類的多個(gè) Bean:
@Configuration
public class PersonConfig {
@Bean
public Person person1() {
return new Person("Pack", 22) ;
}
@Bean
public Person person2() {
return new Person("xxgg", 24) ;
}
}
在這里,@Bean 注解實(shí)例化兩個(gè)bean,并將它們注冊(cè)到Spring容器中。接下來(lái),我們可以初始化Spring容器,并從Spring容器中請(qǐng)求任何bean。這種策略還使得實(shí)現(xiàn)依賴注入變得簡(jiǎn)單。我們可以使用自動(dòng)裝配直接將一個(gè)bean(例如person1)注入到同類型的另一個(gè)bean(例如person2)中。
這種方法的局限是,在典型的基于Java的配置風(fēng)格中,我們需要使用new關(guān)鍵字手動(dòng)實(shí)例化bean。因此,如果相同類的bean數(shù)量增加,我們需要先注冊(cè)它們,并在配置類中創(chuàng)建這些bean。這使得它成為一種更偏向于Java的方法,而不是Spring特有的方法。
2.2 使用@Component注解
在這種方法中,我們將使用@Component注解來(lái)創(chuàng)建多個(gè)bean,這些bean將從Person類繼承屬性。首先,我們將創(chuàng)建多個(gè)子類,即PersonOne和PersonTwo,它們擴(kuò)展自Person超類:
@Component
public class PersonOne extends Person {
public PersonOne() {
super("Pack", 22) ;
}
}
@Component
public class PersonTwo extends Person {
public PersonTwo() {
super("xxxooo", 24) ;
}
}
這種方法的問(wèn)題是,它不會(huì)為同一個(gè)類創(chuàng)建多個(gè)實(shí)例。相反,它創(chuàng)建的是從超類繼承屬性的類的bean。因此,我們只能在繼承類沒(méi)有定義任何額外屬性的情況下使用這種解決方案。此外,使用繼承會(huì)增加代碼的整體復(fù)雜性。
2.3 使用BeanFactoryPostProcessor
利用 BeanFactoryPostProcessor 接口的自定義實(shí)現(xiàn)來(lái)創(chuàng)建同一類的多個(gè) Bean 實(shí)例。我們將通過(guò)以下步驟來(lái)實(shí)現(xiàn):
- 創(chuàng)建自定義 Bean 類并使用 FactoryBean 接口對(duì)其進(jìn)行配置
- 使用 BeanFactoryPostProcessor 接口實(shí)例化同一類型的多個(gè) Bean
自定義 Bean 實(shí)現(xiàn)
為了更好地理解這種方法,我們將進(jìn)一步擴(kuò)展同一實(shí)力。假設(shè)有一個(gè) Human 類,它依賴于 Person 類的多個(gè)實(shí)例:
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 接口會(huì)調(diào)用 afterPropertiesSet() 方法來(lái)檢查 BeanFactory 是否設(shè)置了所有 Bean 屬性,以及是否滿足了其他依賴條件。此外,我們將使用setter注入法注入并初始化兩個(gè) Person 類 Bean:personOne 和 personTwo。接下來(lái),我們將創(chuàng)建一個(gè)實(shí)現(xiàn) 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 注解的使用,它包含了類級(jí)別上多個(gè) Person 類型的名稱或 bean id。在這種情況下,在類級(jí)別使用 @Qualifier 是有原因的,我們接下來(lái)就會(huì)看到。
自定義 BeanFactory 實(shí)現(xiàn)
任何實(shí)現(xiàn) BeanFactoryPostProcessor 的類都會(huì)在創(chuàng)建任何 Spring Bean 之前執(zhí)行。BeanFactoryPostProcessor 會(huì)掃描所有使用 @Qualifier 注釋的類。此外,它還會(huì)從注解中提取名稱(Bean ID),并用指定的名稱手動(dòng)創(chuàng)建該類類型的實(shí)例:
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 實(shí)現(xiàn)會(huì)在 Spring 容器初始化后被調(diào)用。
最后,配置上面定義的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() ;
}
}
這種方法的局限性在于其復(fù)雜性。所以不鼓勵(lì)使用這種方法。盡管有其局限性,但這種方法更符合 Spring 的特性,可以使用注解實(shí)例化多個(gè)相似類型的 Bean。