1. 概述
在 Spring 框架中,我们可以通过多种方式注入属性:
- 使用
@Value
注解直接注入 - 通过
Environment
抽象访问 - 利用
@ConfigurationProperties
绑定到结构化对象
但有个大坑:这些常规方式在 BeanFactoryPostProcessor
中完全失效! 原因是这些注解由 BeanPostProcessor
处理,而 BeanPostProcessor
本身又依赖 BeanFactoryPostProcessor
初始化,形成了循环依赖问题。
本文将教你如何通过 Environment
类在 BeanFactoryPostProcessor
中正确注入属性。针对 Spring Boot 场景,我们还会用 Binder
替代 @ConfigurationProperties
。最后演示两种创建 BeanFactoryPostProcessor
的方式:@Bean
和 @Component
注解。
2. 在 BeanFactoryPostProcessor 中使用属性
核心思路:必须手动获取 Environment
对象。获取后有两种操作方式:
- 使用
getProperty()
方法逐个获取属性 - 结合
Binder
和Environment
批量加载配置文件
✅ 当只需要少量属性时,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 获取。