BeanFactoryPostProcessor 中的屬性
1. 概述
在 Spring 中,屬性可以使用@Value註解直接注入到我們的 bean 中,透過Environment抽象訪問,或透過@ConfigurationProperties綁定到結構化物件。如果我們嘗試使用這些傳統方式在BeanFactoryPostProcessor中註入屬性,這是行不通的!這是因為這些註解是由BeanPostProcessor處理的,這表示它們不能在BeanPostProcessor或BeanFactoryPostProcessor類型中使用。
在本教程中,我們將學習使用Environment類別在BeanFactoryPostProcessor中註入屬性。為了展示它如何與 Spring Boot 配合使用,我們將使用Binder代替@ConfigurationProperties 。最後,我們將示範使用@Bean註解和@Component註解來建立BeanFactoryPostProcessor的兩個選項。
2. BeanFactoryPostProcessor中的屬性
要在BeanFactoryPostProcessor中註入屬性,我們需要檢索Environment物件。然後,使用它,我們可以:
- 對我們希望取得的每個屬性使用
getProperty()方法 - 使用
Binder和Environment檢索整個ConfigurationFile
getProperty()方法可以方便地取得少量屬性。如果我們希望獲取更多信息,那麼使用ConfigurationFile會更簡單,假設所有屬性都存在於同一個文件中。請注意, @ConfigurationFile是 Spring Boot 的功能,因此在簡單的 Spring 應用程式中不可用。
3. BeanFactoryPostProcessor中的屬性使用@Bean註解
現在讓我們看看當我們使用@Bean註解建立BeanFactoryPostProcessor時如何使用Environment抽象來取得屬性。
首先,我們將建立一個設定文件,在其中定義BeanFactoryPostProcessor類型的 bean,並將Environment作為參數注入。然後,我們可以使用the getProperty()方法或Binder ,如下一節所示。
3.1. Environment的getProperty() M
當只需要幾個屬性時,使用getProperty()最有用:
@Bean
public static BeanFactoryPostProcessor beanFactoryPostProcessor(Environment environment) {
return beanFactory -> {
String articleName = environment.getProperty("article.name", String.class);
LOGGER.debug("Article name, using environment::getProperty: " + articleName);
BeanDefinitionRegistry registry = (BeanDefinitionRegistry) beanFactory;
registry.registerBeanDefinition("articleNameFromBeanAnnotation", BeanDefinitionBuilder.genericBeanDefinition(String.class)
.addConstructorArgValue(articleName)
.getBeanDefinition());
};
}
在上面的範例中,我們使用environment.getProperty()來取得article.name屬性,然後建立一個名為articleNameFromBeanAnnotation的新虛擬bean 來儲存其值。
3.2. Binder
我們也可以結合Binder和Environment來載入整個設定檔。這樣,我們就可以利用Spring Boot應用程式的@ConfigurationFile註解:
@Bean
public static BeanFactoryPostProcessor beanFactoryPostProcessor(Environment environment) {
return beanFactory -> {
BindResult<ApplicationProperties> result = Binder.get(environment)
.bind("application", ApplicationProperties.class);
ApplicationProperties properties = result.get();
LOGGER.debug("Application name, using binder to access ApplicationProperties: " + properties.getName());
BeanDefinitionRegistry registry = (BeanDefinitionRegistry) beanFactory;
registry.registerBeanDefinition("applicationNameFromBeanAnnotation", BeanDefinitionBuilder.genericBeanDefinition(String.class)
.addConstructorArgValue(properties.getName())
.getBeanDefinition());
};
}
在這個範例中,我們使用Binder的get()和bind()方法來載入目前環境的設定檔。然後,我們使用設定檔 getter 來檢索應用程式名稱。我們最終將其值儲存在名為applicationNameFromBeanAnnotation的虛擬 bean 中。
4. BeanFactoryPostProcessor中的屬性使用@Component註解
另一種建立BeanFactoryPostProcessor方法是使用@Component註解。在這種情況下,與使用@Bean註解類似,我們需要Environment抽象。不同之處在於我們無法注入Environment ,因為BeanFactoryPostProcessor僅附帶一個無參數建構子。解決方案是使用EnvironmentAware介面來注入環境:
@Component
public class PropertiesWithBeanFactoryPostProcessor implements BeanFactoryPostProcessor, EnvironmentAware {
private Environment environment;
@Override
public void postProcessBeanFactory(@NonNull ConfigurableListableBeanFactory beanFactory) throws BeansException {
String articleName = environment.getProperty("article.name", String.class);
LOGGER.debug("Article name, using environment::getProperty: " + articleName);
BeanDefinitionRegistry registry = (BeanDefinitionRegistry) beanFactory;
registry.registerBeanDefinition("articleNameFromComponentAnnotation", BeanDefinitionBuilder.genericBeanDefinition(String.class)
.addConstructorArgValue(articleName)
.getBeanDefinition());
}
@Override
public void setEnvironment(@NonNull Environment environment) {
this.environment = environment;
}
}
這提供了setEnvironment()方法,這是在 Spring 應用程式中註入 bean 的另一種方法。然後,我們將Environment的注入值儲存在一個欄位中。在postProcessBeanFactory()中,我們可以呼叫getProperty()方法或使用該欄位的Binder 。在上面的程式碼中,我們使用了前者。
4.1. Environment的getProperty() M
我們可以使用getProperty()方法來檢索少量屬性:
@Override
public void postProcessBeanFactory(@NonNull ConfigurableListableBeanFactory beanFactory) throws BeansException {
String articleName = environment.getProperty("article.name", String.class);
LOGGER.debug("Article name, using environment::getProperty: " + articleName);
BeanDefinitionRegistry registry = (BeanDefinitionRegistry) beanFactory;
registry.registerBeanDefinition("articleNameFromComponentAnnotation", BeanDefinitionBuilder.genericBeanDefinition(String.class)
.addConstructorArgValue(articleName)
.getBeanDefinition());
}
在此範例中,我們使用environment.getProperty()從屬性載入文章名稱並將其儲存在articleNameFromComponentAnnotation bean 中。
4.2. Binder
在 Spring Boot 應用程式中,我們可以使用Binder和Environment來檢索包含一組屬性的設定檔:
@Override
public void postProcessBeanFactory(@NonNull ConfigurableListableBeanFactory beanFactory) throws BeansException {
BindResult<ApplicationProperties> result = Binder.get(environment)
.bind("application", ApplicationProperties.class);
ApplicationProperties properties = result.get();
LOGGER.debug("Application name, using binder to access ApplicationProperties: " + properties.getName());
BeanDefinitionRegistry registry = (BeanDefinitionRegistry) beanFactory;
registry.registerBeanDefinition("applicationNameFromComponentAnnotation", BeanDefinitionBuilder.genericBeanDefinition(String.class)
.addConstructorArgValue(properties.getName())
.getBeanDefinition());
}
使用Binder.get().bind()載入ApplicationProperties ,然後使用其 getter 將應用程式名稱儲存在applicationNameFromComponentAnnotation bean 中。
5. 結論
在本文中,我們討論了直接在BeanFactoryPostProcessor中註入屬性的問題。我們示範如何利用Environment抽象、 Binder 、 BeanNameAware和@ConfigurationFile等概念來克服這些困難。最後,我們討論了每個選項的權衡。
與往常一樣,所有原始程式碼都可以在 GitHub 上取得。