Spring 有哪些扩展点?BeanFactoryPostProcessor 和 BeanPostProcessor 有什么区别?
简化版
Spring 之所以强大好扩展,是因为它在容器启动和 Bean 创建的各个阶段都留了「钩子」(扩展点),让你能插入自定义逻辑。最重要的两个:BeanFactoryPostProcessor(BFPP) 在「所有 Bean 定义(BeanDefinition)加载完、但 Bean 还没实例化」时执行,可以修改 Bean 的定义(如改属性、改作用域,@Value 占位符替换就靠它);BeanPostProcessor(BPP) 在「每个 Bean 实例化后、初始化前后」执行,可以加工 Bean 实例(AOP 代理、@Autowired 注入都靠它)。区别一句话:BFPP 改「Bean 的图纸」,BPP 改「造好的 Bean」。
详细版
两个核心后置处理器:
| 扩展点 | 作用时机 | 操作对象 | 典型用途 |
|---|---|---|---|
BeanFactoryPostProcessor | 所有 BeanDefinition 加载后、Bean 实例化前 | BeanDefinition(Bean 的定义/图纸) | 修改 Bean 定义,如 @Value 占位符替换、@Configuration 处理 |
BeanPostProcessor | 每个 Bean 实例化后、初始化前后 | Bean 实例(造好的对象) | AOP 代理、@Autowired/@Resource 注入、@Async 增强 |
// BeanFactoryPostProcessor:改 Bean 定义
@Component
public class MyBFPP implements BeanFactoryPostProcessor {
public void postProcessBeanFactory(ConfigurableListableBeanFactory bf) {
BeanDefinition bd = bf.getBeanDefinition("userService");
bd.setScope("prototype"); // 在 Bean 造出来之前,改它的定义
}
}
// BeanPostProcessor:改 Bean 实例
@Component
public class MyBPP implements BeanPostProcessor {
public Object postProcessBeforeInitialization(Object bean, String name) {
return bean; // 初始化前(@PostConstruct 之前)
}
public Object postProcessAfterInitialization(Object bean, String name) {
// 初始化后——AOP 代理就在这里生成,返回代理对象替换原 Bean
return bean;
}
}
其他常用扩展点:
| 扩展点 | 作用 |
|---|---|
Aware 接口族(BeanNameAware/ApplicationContextAware…) | 让 Bean 拿到容器的基础设施(自己的名字、容器引用等) |
InitializingBean / @PostConstruct / initMethod | Bean 初始化回调(三种初始化方式) |
DisposableBean / @PreDestroy / destroyMethod | Bean 销毁回调 |
ApplicationListener / @EventListener | 监听 Spring 事件 |
ApplicationContextInitializer | 容器 refresh 前的初始化 |
SmartInitializingSingleton | 所有单例初始化完成后回调 |
⚠️ BFPP 和 BPP 的执行时机差一个「实例化」的分水岭:BFPP 在 Bean 实例化之前(此时只有定义,没有对象),所以它只能改定义、不能碰实例;BPP 在 Bean 实例化之后(对象已存在),所以它能加工实例。搞反了时机就理解错了这两个扩展点。
完整版教学
一、为什么 Spring 要设这么多扩展点
Spring 的核心竞争力之一是「可扩展」——它自己的很多功能(AOP、注解注入、@Async、事务)都是通过这些扩展点实现的,你也能用同样的方式插入自定义逻辑。
Spring 的设计哲学:容器启动和 Bean 创建是一条流水线,
在流水线的每个关键节点都开一个"口子"(扩展点),
让框架自己和用户都能往里塞逻辑。
比如 AOP 不是硬编码在容器里的,而是通过一个 BeanPostProcessor 实现的
→ 你不用 AOP 就不加载它,要用就注册这个 BPP,插拔式
理解这一点很重要:Spring 的很多「魔法」不是黑盒,而是通过公开的扩展点实现的。搞懂扩展点,就搞懂了 Spring 功能的实现套路,也能自己写扩展。而扩展点里最核心、最常考的就是两个「后置处理器」——它们分别在「Bean 定义阶段」和「Bean 实例阶段」开口。
二、BeanFactoryPostProcessor:加工 Bean 的「图纸」
容器启动时,第一步是加载所有 Bean 的定义(BeanDefinition)——BeanDefinition 就像「Bean 的图纸」,记录了这个 Bean 是哪个类、什么作用域、有哪些属性值等,但此时还没有造出任何 Bean 实例。
BeanFactoryPostProcessor 就在「所有图纸加载完、但还没开始造 Bean」这个时机执行,让你能修改图纸:
时机:BeanDefinition 全部加载完 ────► [BFPP 在这里执行] ────► 开始实例化 Bean
BFPP 能做:改 BeanDefinition 的属性值、作用域、是否懒加载,甚至注册新的 BeanDefinition
最经典的实现:PropertySourcesPlaceholderConfigurer
它是个 BFPP,负责把配置里的 ${xxx} 占位符替换成实际值
→ 在 Bean 造出来之前,就把图纸里的 ${db.url} 换成真实的数据库地址
关键:BFPP 操作的是「定义」不是「实例」,因为它执行时实例还不存在。所以它适合做「批量改 Bean 配置」的事,比如统一改某类 Bean 的作用域、动态注册 Bean。@Configuration 类的处理(ConfigurationClassPostProcessor)也是一个 BFPP。
三、BeanPostProcessor:加工造好的 Bean
BeanPostProcessor 在每个 Bean 实例化之后执行,让你能加工已经造出来的 Bean 对象。它有两个方法,卡在「初始化」前后:
Bean 创建流程:
实例化(new) → 属性填充(注入) → [BPP.before] → 初始化(@PostConstruct/afterPropertiesSet) → [BPP.after] → 就绪
↑初始化前 ↑初始化后(AOP 代理在这里生成!)
postProcessBeforeInitialization:初始化前,如 @PostConstruct 之前的处理
postProcessAfterInitialization:初始化后,可以"偷梁换柱"——
返回一个代理对象替换原 Bean(AOP 就是这么做的!)
BPP 是 Spring 最重要的扩展点,因为大量核心功能都是 BPP 实现的:
- AOP:
AnnotationAwareAspectJAutoProxyCreator是个 BPP,在after方法里判断 Bean 是否需要代理,需要就返回代理对象替换原 Bean(这就是「注入的是代理」的原因)。 @Autowired注入:AutowiredAnnotationBeanPostProcessor是个 BPP,负责扫描@Autowired字段并注入。@Async、@Scheduled、@PostConstruct处理也都是各自的 BPP。
理解「BPP 能在 after 方法返回代理替换原对象」,就理解了 AOP、事务代理的实现根基。
四、BFPP vs BPP:一个「实例化」的分水岭
两者最容易混,用「实例化」这条线一分就清楚:
容器启动时间线:
加载 BeanDefinition
│
【BeanFactoryPostProcessor 执行】← 此时只有"定义",还没实例 → 改定义
│
═══ 实例化分水岭 ═══
│
对每个 Bean:实例化 → 属性注入 →【BeanPostProcessor 执行】← 此时有"实例" → 改实例
| 对比 | BeanFactoryPostProcessor | BeanPostProcessor |
|---|---|---|
| 执行时机 | 实例化之前(对整个 BeanFactory 一次) | 实例化之后(对每个 Bean 都执行) |
| 操作对象 | BeanDefinition(定义/图纸) | Bean 实例(对象) |
| 执行次数 | 少(针对整个工厂) | 多(每个 Bean 的初始化前后各一次) |
| 典型代表 | 占位符替换、@Configuration 处理 | AOP 代理、@Autowired 注入 |
一句话:BFPP 改图纸(实例化前)、BPP 改产品(实例化后)。记住「实例化」是分界点,就永远不会搞反。
五、Aware 接口族:让 Bean 感知容器
Aware 是另一类常考扩展点——普通 Bean 本应和容器解耦(不知道自己活在容器里),但有时 Bean 确实需要拿到容器的基础设施,Aware 接口就是「授权 Bean 感知某样东西」:
@Component
public class MyBean implements ApplicationContextAware, BeanNameAware {
public void setBeanName(String name) {
// 容器会回调,告诉这个 Bean 自己在容器里叫什么名字
}
public void setApplicationContext(ApplicationContext ctx) {
// 容器会回调,把容器引用递给 Bean(于是 Bean 能手动 getBean 等)
}
}
常见 Aware:BeanNameAware(知道自己的 Bean 名)、BeanFactoryAware/ApplicationContextAware(拿到容器引用)、EnvironmentAware(拿到环境配置)。它们通过一个专门的 BPP(ApplicationContextAwareProcessor)在初始化前回调注入。用途:当你的 Bean 需要「反过来操作容器」时(如动态获取 Bean、发布事件),用 Aware 拿到容器句柄。但要节制——过度用 Aware 会让 Bean 和 Spring 耦合,失去 IoC 的解耦初衷。
六、初始化与销毁回调:三种方式
Bean 的初始化和销毁也是扩展点,各有三种等价写法,执行有先后:
初始化回调(属性注入后执行),顺序:
@PostConstruct 注解 → InitializingBean.afterPropertiesSet() → @Bean(initMethod) 指定的方法
销毁回调(容器关闭时),顺序:
@PreDestroy 注解 → DisposableBean.destroy() → @Bean(destroyMethod) 指定的方法
三种方式功能相同,推荐用注解方式(@PostConstruct/@PreDestroy),因为它不侵入 Spring 接口(换框架时 Bean 不用改)、也最直观。InitializingBean/DisposableBean 会让 Bean 耦合 Spring 接口,initMethod/destroyMethod 适合配置第三方类(无法加注解时)。注意:销毁回调只对单例 Bean 生效——原型 Bean 创建后 Spring 就不再管理它的生命周期,不会调销毁方法。
记忆钩子:「BFPP 改 Bean 定义(实例化前,如占位符替换)、BPP 改 Bean 实例(实例化后,AOP/注入都靠它,能返回代理替换原对象);Aware 让 Bean 感知容器;初始化三兄弟 @PostConstruct→InitializingBean→initMethod,销毁三兄弟 @PreDestroy→DisposableBean→destroyMethod」。
七、常见误区与追问
- 误区:BeanFactoryPostProcessor 和 BeanPostProcessor 差不多。 差一个「实例化」分水岭——BFPP 在实例化前改定义(BeanDefinition),BPP 在实例化后改实例(对象),操作对象和时机都不同。
- 误区:AOP 是容器硬编码的功能。 AOP 是通过一个 BeanPostProcessor 实现的,在 postProcessAfterInitialization 里判断是否需要代理、返回代理对象替换原 Bean。
- 误区:BeanPostProcessor 只执行一次。 它对每个 Bean 的初始化前后都执行(before 和 after 各一次);BFPP 才是针对整个工厂执行少数几次。
- 误区:所有 Bean 都会调销毁回调。 只有单例 Bean 会;原型 Bean 创建后 Spring 不再管理其生命周期,不调用销毁方法。
- 追问:@Value 占位符 ${} 是怎么替换的? 通过一个 BeanFactoryPostProcessor(PropertySourcesPlaceholderConfigurer),在 Bean 实例化前把 BeanDefinition 里的 ${xxx} 替换成配置的实际值。
- 追问:@Autowired 是通过什么实现注入的? 通过 AutowiredAnnotationBeanPostProcessor(一个 BPP),在 Bean 属性填充阶段扫描 @Autowired 字段/方法并完成依赖注入。
- 追问:为什么注入进来的 Bean 有时是代理对象? AOP 的 BeanPostProcessor 在 postProcessAfterInitialization 里把原 Bean 换成了代理对象,容器缓存和注入的都是这个代理。
八、加强记忆
Spring 强在「可扩展」——它在容器启动和 Bean 创建的流水线上开了很多「口子」(扩展点),框架自身的 AOP、注解注入、事务等都靠这些口子实现,用户也能同样插入逻辑。最核心的两个后置处理器以「实例化」为分水岭:BeanFactoryPostProcessor(BFPP)在实例化前执行,操作 BeanDefinition(图纸),用于批量改 Bean 定义(@Value 占位符替换、@Configuration 处理都是它);BeanPostProcessor(BPP)在实例化后对每个 Bean 的初始化前后执行,操作 Bean 实例(产品),Spring 的 AOP 代理(在 postProcessAfterInitialization 里返回代理替换原 Bean)、@Autowired 注入都是它——所以「注入的是代理」根源在此。其他扩展点:Aware 接口族让 Bean 感知容器(拿到自己的名字/容器引用,但别滥用以免耦合),初始化三兄弟 @PostConstruct→InitializingBean→initMethod 和销毁三兄弟 @PreDestroy→DisposableBean→destroyMethod(销毁只对单例生效,推荐注解方式)。一句话「BFPP 改图纸(实例化前)、BPP 改产品(实例化后,AOP 和注入的老巢),Aware 感知容器,初始化销毁各三兄弟」。