Spring的BeanFactoryPostProcessor中屬性

banq發表於2024-06-19

在 Spring 中,可以使用@Value註釋將屬性直接注入到我們的 bean 中,透過Environment抽象訪問屬性,或者透過@ConfigurationProperties將屬性繫結到結構化物件。

如果我們嘗試使用這些常規方法在BeanFactoryPostProcessor中注入屬性,它將無法工作!

這是因為這些註釋是由BeanPostProcessor處理的,這意味著它們不能在BeanPostProcessor或BeanFactoryPostProcessor型別中使用。

在本教程中,我們將學習使用Environment類在BeanFactoryPostProcessor中注入屬性。為了展示它如何與 Spring Boot 配合使用,我們將使用Binder代替@ConfigurationProperties。最後,我們將演示使用@Bean註釋和@Component註釋建立BeanFactoryPostProcessor的兩個選項。

BeanFactoryPostProcessor中的屬性
為了在BeanFactoryPostProcessor中注入屬性,我們需要檢索Environment物件。然後,使用它,我們可以:

  • 對我們希望獲取的每個屬性使用getProperty()方法
  • 使用Binder和Environment檢索整個ConfigurationFile

getProperty ()方法可以方便地獲取少量屬性。如果我們希望獲取更多屬性,使用 ConfigurationFile 會更簡單,假設所有屬性都存在於同一個檔案中。請注意,@ConfigurationFile是 Spring Boot 的一項功能,因此在簡單的 Spring 應用程式中不可用。

1、使用@Bean註解的BeanFactoryPostProcessor中的屬性
現在讓我們看看當我們使用@Bean註釋建立BeanFactoryPostProcessor時如何使用Environment抽象來獲取屬性。

首先,我們將建立一個配置檔案,在其中定義一個BeanFactoryPostProcessor型別的 bean ,並將Environment作為引數注入。然後,我們可以使用getProperty()方法或Binder,如下一節所示。

1.環境的getProperty() 方法
當只需要幾個屬性時,使用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來儲存其值。

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 中。

2、使用@Component註釋的BeanFactoryPostProcessor中的屬性
建立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。在上面的程式碼中,我們使用了前者。

1.環境的getProperty() 方法
我們可以使用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 中。

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 中。

相關文章