Spring的BeanFactoryPostProcessor中属性

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