在 Bean 的生命周期中,BeanPostProcessor 作用于 Bean 实例化之后、初始化前后,而 BeanFactoryPostProcessor 则介入得更早——它工作在 容器加载完所有 Bean 的定义、但尚未创建任何 Bean 实例之前。这个时机使得我们有机会对 Bean 的元数据(BeanDefinition)进行集中修改或增强,影响后续整个容器的行为。
4.5.1 理解 BeanFactoryPostProcessor 的执行时机
当 ApplicationContext 启动时,大体执行顺序如下:
- 读取配置(XML、注解、Java Config 等)
- 解析为
BeanDefinition并注册到BeanFactory - 调用所有
BeanFactoryPostProcessor的实现,允许它们修改或新增BeanDefinition - 根据最终的
BeanDefinition依次实例化、注入依赖、执行初始化回调、生成代理等
因此,BeanFactoryPostProcessor 可以看作是 容器对 Bean 定义进行“二次加工”的扩展点。它操作的对象是尚未实例化的“蓝图”,而非成品实例。
4.5.2 核心接口与典型实现
接口定义
@FunctionalInterface
public interface BeanFactoryPostProcessor {
void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException;
}
ConfigurableListableBeanFactory 提供了访问和修改 Bean 定义的能力,比如 getBeanDefinition(String beanName)、getBeanDefinitionNames() 等。
Spring 内置了几个非常重要的 BeanFactoryPostProcessor,它们直接影响了日常开发体验:
ConfigurationClassPostProcessor:解析@Configuration、@ComponentScan、@Import、@Bean等注解,将其转换为BeanDefinition并注册。PropertySourcesPlaceholderConfigurer(已由 Spring Boot 自动配置):处理${...}占位符,用Environment中的属性值进行替换,使得application.properties中的值能被注入到 Bean 定义中。
自定义 BeanFactoryPostProcessor 的场景
当你需要在容器启动前,动态地调整 Bean 的定义属性(如作用域、懒加载、构造参数),或者根据运行环境条件性地移除、替换某个 Bean 时,自定义 BeanFactoryPostProcessor 便非常有用。它与 @Conditional 不同,提供的是更底层、程序化的控制能力。
4.5.3 实战:动态修改 Bean 的作用域
假设项目中所有 Repository 类型默认都是单例,但在多租户环境下需要将其改为 prototype 作用域,使每个请求获得独立的数据访问对象。通过 BeanFactoryPostProcessor 可以实现统一调整:
@Component
public class RepositoryScopeModifier implements BeanFactoryPostProcessor {
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) {
String[] beanNames = beanFactory.getBeanDefinitionNames();
for (String beanName : beanNames) {
BeanDefinition bd = beanFactory.getBeanDefinition(beanName);
// 获取原始类名(如果存在)
String beanClassName = bd.getBeanClassName();
if (beanClassName != null && beanClassName.endsWith("Repository")) {
bd.setScope(BeanDefinition.SCOPE_PROTOTYPE);
System.out.println("Changed scope of " + beanName + " to prototype");
}
}
}
}
这段代码会在所有 Bean 定义加载完毕后执行,统一将类名以 Repository 结尾的 Bean 作用域修改为 prototype。业务代码本身不需要添加任何额外注解,修改逻辑完全集中在后处理器中。
4.5.4 注意事项与最佳实践
1. 不要试图获取 Bean 实例
在 postProcessBeanFactory 方法中绝对不能调用 beanFactory.getBean() 来获取实际的 Bean,因为此时 Bean 实例尚未创建。这样做会触发 Bean 的提前实例化,破坏容器生命周期的秩序,并可能导致其他 BeanFactoryPostProcessor 还未执行就被跳过。该方法的职责纯粹是操作 定义层面 的元数据。
2. 实现 Ordered 接口保证执行顺序
如果多个 BeanFactoryPostProcessor 存在,且它们的修改存在依赖关系(例如一个处理器生成的 Bean 定义,需要被另一个处理器进一步调整),可以通过实现 Ordered 接口或标注 @Order 来控制执行顺序。数字越小,优先级越高,越早执行。
@Component
@Order(1)
public class EarlyPostProcessor implements BeanFactoryPostProcessor { ... }
3. 谨慎移除或替换必需的 Bean
直接删除容器必须的 Bean 定义(如基础设施 Bean)可能导致意料之外的错误。通常的做法是“新增”或“修改”属性,而非删除。如果确实需要移除,确保上下游没有强依赖。
4. 与 BeanPostProcessor 的区别
用一个简单表格对比:
| 处理器 | 操作对象 | 执行时机 | 适用场景 |
|-----------------------------|--------------------|----------------------------------|----------------------------------|
| BeanFactoryPostProcessor | BeanDefinition | 所有 Bean 定义加载后,实例化前 | 修改 Bean 元数据,如作用域、属性 |
| BeanPostProcessor | 实例化的 Bean | 每个 Bean 初始化前后 | 生成代理、注入自定义逻辑 |
两者经常结合使用:BeanFactoryPostProcessor 注册一个新的 BeanPostProcessor 定义,再由后者在实例化阶段介入。
4.5.5 实际应用场景总结
- 动态切换实现类:根据外部配置或运行环境,将某个接口定义指向不同的实现类。
- 统一添加属性:为所有数据源 Bean 额外设置连接属性(如 JDBC 连接参数)而无需修改每个配置处。
- 条件性注册 Bean:在启动阶段检测依赖的外部服务是否可用,不可用时注册一个降级实现。
- 框架整合:类似 MyBatis 的
MapperScannerConfigurer,它本身是一个BeanDefinitionRegistryPostProcessor(BeanFactoryPostProcessor的子接口),负责扫描接口并动态注册BeanDefinition,让 MyBatis 代理能与 Spring 无缝集成。
掌握 BeanFactoryPostProcessor 后,你对容器的控制能力便提升到了“元数据级”。它赋予了开发者在 Bean 实例化之前对容器进行编程式干预的能力,是实现高级框架整合和基础设施自定义的利器。结合后续章节的 BeanPostProcessor,你将形成一套完整的容器扩展方案。