1. 概述

在 Spring 框架中,我们可以通过多种方式注入属性:

  • 使用 @Value 注解直接注入
  • 通过 Environment 抽象访问
  • 利用 @ConfigurationProperties 绑定到结构化对象

但有个大坑:这些常规方式在 BeanFactoryPostProcessor 中完全失效! 原因是这些注解由 BeanPostProcessor 处理,而 BeanPostProcessor 本身又依赖 BeanFactoryPostProcessor 初始化,形成了循环依赖问题。

本文将教你如何通过 Environment 类在 BeanFactoryPostProcessor 中正确注入属性。针对 Spring Boot 场景,我们还会用 Binder 替代 @ConfigurationProperties。最后演示两种创建 BeanFactoryPostProcessor 的方式:@Bean@Component 注解。

2. 在 BeanFactoryPostProcessor 中使用属性

核心思路:必须手动获取 Environment 对象。获取后有两种操作方式:

  • 使用 getProperty() 方法逐个获取属性
  • 结合 BinderEnvironment 批量加载配置文件

✅ 当只需要少量属性时,getProperty() 最简单
✅ 当需要大量属性时,用 Binder 加载整个配置文件更高效
⚠️ 注意:Binder 是 Spring Boot 特有功能,普通 Spring 应用不可用

3. 使用 @Bean 注解创建 BeanFactoryPostProcessor

3.1. Environment 的 getProperty() 方法

适合属性数量较少的场景:

@Bean
public static BeanFactoryPostProcessor beanFactoryPostProcessor(Environment environment) {
    return beanFactory -> {
        String articleName = environment.getProperty("article.name", String.class);
        LOGGER.debug("通过 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 批量加载整个配置文件,模拟 @ConfigurationProperties 的效果:

@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("通过 Binder 获取应用名: " + properties.getName());

        BeanDefinitionRegistry registry = (BeanDefinitionRegistry) beanFactory;
        registry.registerBeanDefinition("applicationNameFromBeanAnnotation", 
            BeanDefinitionBuilder.genericBeanDefinition(String.class)
              .addConstructorArgValue(properties.getName())
              .getBeanDefinition());
    };
}

通过 Binder.get().bind() 加载配置文件,然后用 getter 获取应用名,最后存入 applicationNameFromBeanAnnotation bean。

4. 使用 @Component 注解创建 BeanFactoryPostProcessor

4.1. Environment 的 getProperty() 方法

@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("通过 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;
    }
}

关键点:

  • 实现 EnvironmentAware 接口获取 Environment
  • setEnvironment() 方法由 Spring 自动调用注入环境对象
  • postProcessBeanFactory() 中使用注入的 environment

4.2. Binder 方式

Spring Boot 专属方案,批量加载配置属性:

@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("通过 Binder 获取应用名: " + 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 批量加载配置(Spring Boot 特有)
  • 两种实现方式对比:
    • @Bean 注解:简单直接,适合简单场景
    • @Component + EnvironmentAware:更灵活,适合复杂逻辑

完整代码示例可在 GitHub 获取。


原始标题:Properties in BeanFactoryPostProcessor | Baeldung