💡 Spring 给我们留了很多扩展点。用好它们,很多业务逻辑根本不需要改主干代码;市面上大量第三方框架(MyBatis 集成、各种 starter、AOP、配置中心客户端),包括 Spring 自己,本质上都是在这批扩展点上做文章。对修改关闭、对扩展开放,Spring 是把它落进了接口里。
但真到动手的时候,问题往往不是"不知道有这些接口",而是这几个:同一个需求,该在 BeanFactoryPostProcessor 动手还是 BeanPostProcessor 动手?为什么我的 BeanPostProcessor 里 getBean 一下来就警告 is not eligible for getting processed by all BeanPostProcessors?@PostConstruct、afterPropertiesSet、init-method、ContextRefreshedEvent、ApplicationReadyEvent 到底谁先谁后?
这篇文章按"容器级 → Bean 级 → 就绪时机 → 动态注册 → Boot 层"这条时间线,把常用扩展点一次串起来,每个都给触发时机、能改什么、官方自己怎么用的真实例子,最后一张"需求 → 扩展点"选型表。
整体盘点一下 Spring 常用扩展点:BeanFactoryPostProcessor、BeanPostProcessor、@Enable、自动配置到底该用哪个?

1. 先把地图铺开:这是两条不同的时间线
大部分困惑来自把两条时间线混成一条。容器级扩展点在 refresh() 里跑,处理的是"一批定义";Bean 级扩展点在 getBean() 里跑,处理的是"一个对象"。
容器 refresh():
prepareBeanFactory –> invokeBeanFactoryPostProcessors –> registerBeanPostProcessors
[BDRPP 先, BFPP 后] [此时只注册, 不回调]
–> onRefresh –> finishBeanFactoryInitialization –> finishRefresh
[preInstantiateSingletons [SmartLifecycle.start
逐个 getBean,Bean 级扩展点在这里跑] + 发布 ContextRefreshedEvent]
单个 Bean 的生命周期:
实例化前钩子 –> 构造实例 –> 属性填充 –> Aware 回调 –> postProcessBeforeInitialization
[postProcessBeforeInstantiation] [postProcessProperties: @Autowired] [@PostConstruct 也在这一步]
–> afterPropertiesSet –> init-method –> postProcessAfterInitialization –> 可用 –> 销毁
[InitializingBean] [xml/@Bean initMethod] [AOP 代理在这里生成] [@PreDestroy → DisposableBean → destroy-method]
把这两条线叠在一起看,上下两排的关系就一目了然:

下面这张表是全文的索引,建议先记"作用域"和"处理对象"这两列。
| BeanDefinitionRegistryPostProcessor | 容器 | BeanDefinitionRegistry | refresh() 中,早于 BFPP | 动态注册自定义 bean(MyBatis 扫描 @Mapper) |
| BeanFactoryPostProcessor | 容器 | BeanDefinition | 实例化任何 bean 之前 | 改配置元数据:懒加载、占位符、属性覆盖 |
| InstantiationAwareBeanPostProcessor | Bean | 实例 / PropertyValues | 实例化前后、属性填充 | 换掉实例、干预注入(@Autowired 的实现) |
| BeanPostProcessor | Bean | bean 实例 | 初始化方法前后 | 改属性值、包装代理(AOP 的实现) |
| DestructionAwareBeanPostProcessor | Bean | bean 实例 | 销毁前 | 统一处理 @PreDestroy |
| ApplicationContextAware 等 Aware | Bean | 自身 | 属性填充后、初始化前 | 拿到容器 / Environment / 资源加载器 |
| InitializingBean / DisposableBean | Bean | 自身 | 属性注入完成后 / 销毁前 | 自建连接、初始化缓存 / 释放资源 |
| FactoryBean | Bean | 产品对象 | getBean 时 | 交给框架"怎么造这个 bean"(SqlSessionFactoryBean) |
| ApplicationListener / @EventListener | 容器 | 事件 | 事件发布时 | 启动完成后预热、刷新索引 |
| SmartInitializingSingleton | 容器 | 自身 | 所有单例初始化完毕 | 批量校验、预热,比容器事件更早 |
| SmartLifecycle | 容器 | 自身 | finishRefresh / 关闭时 | 消费者、定时任务的启停 |
| ImportBeanDefinitionRegistrar | 容器 | BeanDefinitionRegistry | 解析 @Configuration 时 | 自定义 @Enable* 开关 |
| Condition | 容器 | 元数据 | 解析候选 bean 时 | 按配置/类路径决定装不装 |
| HandlerInterceptor / Filter | 请求 | 单次请求 | 每次 HTTP 请求 | 鉴权、日志、统一响应 |
| EnvironmentPostProcessor / 自动配置 | 容器(Boot) | Environment / 定义 | run() 早期 / 容器启动 | starter 默认值、配置解密 |
简单理解:
改"定义"(还没 new 出来) –> BFPP / BDRPP / Registrar / @Conditional / 自动配置
改"对象"(已经 new 出来) –> BeanPostProcessor 家族 / FactoryBean
等"就绪"(都造好了) –> SmartInitializingSingleton / 容器事件 / SmartLifecycle / Runner
本文接口签名核对自本地仓库的 spring-beans 5.3.34 与 6.2.7、spring-boot 3.5.0;Spring 6 已删除 postProcessPropertyValues,改用 postProcessProperties。
2. 容器级:BeanFactoryPostProcessor 与 BeanDefinitionRegistryPostProcessor
Spring 主要提供两类容器级切面:BeanFactoryPostProcessor(下称 BFPP)与它的子接口 BeanDefinitionRegistryPostProcessor(下称 BDRPP)。
共同点:它们跑在所有普通 bean 实例化之前,所以手上只有 BeanDefinition,没有对象。
2.1 BFPP:改配置元数据
实现 BFPP,可以在 Spring 创建 bean 之前读取并修改 bean 的定义属性。当 postProcessBeanFactory 被调用时,bean 还没实例化,刚从配置文件/注解解析成 BeanDefinition。
@Component
public class TestBeanFactoryPostProcessor implements BeanFactoryPostProcessor {
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
System.out.println("IOC 容器调用了 TestBeanFactoryPostProcessor 的 postProcessBeanFactory 方法");
for (String name : beanFactory.getBeanDefinitionNames()) {
if ("yjLog".equals(name)) {
BeanDefinition beanDefinition = beanFactory.getBeanDefinition(name);
// 例如:把某个沉重的 bean 改成懒加载
beanDefinition.setLazyInit(true);
}
}
}
}
Spring 内置了不少 BFPP 实现,值得先看一眼它们各自解决什么:
| PropertySourcesPlaceholderConfigurer | BFPP | 解析 ${…} 占位符,把值写回 BeanDefinition |
| PropertyOverrideConfigurer | BFPP | 用属性文件覆盖已有 bean 的属性 |
| CustomEditorConfigurer | BFPP | 注册自定义 PropertyEditor(String → 复杂类型) |
| ConfigurationClassPostProcessor | BDRPP | 解析 @Configuration/@ComponentScan/@Import/@Bean,把注解世界翻译成 BeanDefinition |
| MapperScannerConfigurer | BDRPP | MyBatis 的 @MapperScan,扫描接口并注册成 bean |
2.2 BDRPP:动态注册 bean 的正确姿势
需要"运行时才知道要注册哪些 bean"时,只有 BDRPP 能干净地做到——它拿到的是 BeanDefinitionRegistry,可以直接往里塞定义。
public class DynamicJobRegistrar implements BeanDefinitionRegistryPostProcessor {
@Override
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {
for (JobMeta job : JobScanner.scan()) { // 例如读表 / 读配置拿到任务清单
RootBeanDefinition bd = new RootBeanDefinition(job.getBeanClass());
bd.setScope(BeanDefinition.SCOPE_SINGLETON);
bd.getPropertyValues().add("cron", job.getCron());
if (!registry.containsBeanDefinition(job.getName())) { // 幂等:同名会覆盖,先判一下
registry.registerBeanDefinition(job.getName(), bd);
}
}
}
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
// 这里才轮到改已有定义;BDRPP 继承 BFPP,两个方法都会回调
}
}
执行顺序(PostProcessorRegistrationDelegate 的实际逻辑)值得单独记住:
BDRPP(PriorityOrdered) –> BDRPP(Ordered) –> 其余 BDRPP
–> 【所有 BDRPP 的 postProcessBeanFactory】 –> BFPP(PriorityOrdered) –> BFPP(Ordered) –> 其余 BFPP
关键点:只要还有一个 BDRPP 注册出了新的后处理器定义,框架就会重新扫一轮。 这就是为什么 ConfigurationClassPostProcessor(PriorityOrdered)能先把你的 @Bean 方法注册出来,你再往后续轮次里排队。
Q1:为什么我的 BDRPP 里 getBean() 拿到的是"半成品"?
因为它跑得太早。此时 BeanPostProcessor 还没注册完,你 getBean 出来的对象不会经过 AOP 代理、不会走 @Autowired 之外的增强。BDRPP/BFPP 里只应该操作 BeanDefinition,需要实例请延后到 SmartInitializingSingleton 或容器事件里。
Q2:BFPP 和 BDRPP 到底怎么选?
| 改已有 bean 的定义(懒加载、属性值、作用域) | BeanFactoryPostProcessor |
| 新增 bean(扫描包、读表、按配置生成一批) | BeanDefinitionRegistryPostProcessor |
| 在注解类上开关一组 bean | ImportBeanDefinitionRegistrar(见第 6 节,更内聚) |
3. Bean 级:BeanPostProcessor 家族
BFPP 改"图纸",BeanPostProcessor 改"成品"。它的两个方法分别在 bean 的初始化方法(InitializingBean 或 init-method)执行前后被调用,因此容器创建对象后你还能继续修改这个对象;实际调用点在 getBean() 流程里,每个 bean 都会触发一次。
@Component
public class MyBeanPostProcessor implements BeanPostProcessor {
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
System.out.println("postProcessBeforeInitialization====>" + beanName);
// 每个 bean 都会走到这里,只处理目标 bean 时要自己判断
if ("address".equals(beanName)) {
Address address = (Address) bean;
address.setAddressName("随便一个地址名字");
}
return bean;
}
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
System.out.println("postProcessAfterInitialization======>" + beanName);
return bean;
}
}
比如你在 xml 里给 address 的 addressName 设了值,然后在 BeanPostProcessor 里改掉它,之后通过容器 getBean 拿到的就是你修改后的对象——但不要返回 null:applyBeanPostProcessorsBeforeInitialization 遇到 null 会直接返回前一个处理器手上的实例,并跳过后面所有 BeanPostProcessor,你的 bean 会静默少掉一层增强(AOP 就是这么丢的)。
BeanPostProcessor 本身只有两个方法,能力被拆进了子接口:
| InstantiationAwareBeanPostProcessor | postProcessBeforeInstantiation | 构造之前(返回非 null 则短路,后续全跳过) | AsyncAnnotationBeanPostProcessor(@Async 提前返回代理) |
| postProcessAfterInstantiation | 构造之后、注入之前(返回 false 跳过属性填充) | 自定义注入策略 | |
| postProcessProperties | 属性填充阶段 | AutowiredAnnotationBeanPostProcessor(@Autowired/@Value) | |
| MergedBeanDefinitionPostProcessor | postProcessMergedBeanDefinition | 合并定义后,收集元数据 | 注解字段/方法缓存,供后续注入使用 |
| DestructionAwareBeanPostProcessor | postProcessBeforeDestruction | 销毁前 | InitDestroyAnnotationBeanPostProcessor(@PreDestroy) |
| BeanPostProcessor | postProcessAfterInitialization | 初始化后 | AnnotationAwareAspectJAutoProxyCreator(AOP 代理)、SqlSessionFactoryBean 周边 |
Q3:为什么我在 BeanPostProcessor 里 getBean,日志提示 "is not eligible for getting processed by all BeanPostProcessors"?
这是最常见的坑。registerBeanPostProcessors 阶段会按 PriorityOrdered → Ordered → 无序 分批实例化 BPP;你在 A 这个 BPP 里 getBean(B),B 就会在这一刻被提前创建,而当时后面的 BPP 还没注册完,于是 B 只被前面一部分 BPP 处理过——它的 AOP 代理、@Autowired 可能就是不完整的。
三条处理原则:
- BPP 的依赖尽量只注入 BeanFactory 本身,不要在构造期取业务 bean。
- 在配置类里用 static @Bean 方法声明 BPP,避免整个配置类被提前实例化:
@Configuration
public class PostProcessorConfig {
// static:Spring 无需先实例化配置类就能拿到 BPP,避免"配置类过早初始化"
@Bean
public static MyBeanPostProcessor myBeanPostProcessor() {
return new MyBeanPostProcessor();
}
}
- 多个 BPP 之间有先后要求时,实现 Ordered,别指望包扫描顺序。
4. 不想写接口也能挂:Aware、初始化销毁、FactoryBean
4.1 Aware 家族:让 bean 自己声明"我要什么"
Aware 走的是"回调注入",时机在属性填充之后、初始化之前。ApplicationContextAware 并不是容器特殊照顾你,而是内置的 ApplicationContextAwareProcessor(一个 BeanPostProcessor)在 postProcessBeforeInitialization 里帮你回调的。
| BeanNameAware | 自己的 beanName | 日志打标、注册到自身工厂 |
| BeanFactoryAware | BeanFactory | 按需 getBean、拿 BeanDefinition |
| ApplicationContextAware | ApplicationContext | 发事件、取 MessageSource、按类型批量取 bean |
| EnvironmentAware | Environment | 读 profile、动态配置 |
| ResourceLoaderAware | ResourceLoader | 加载 classpath: / file: 资源 |
| EmbeddedValueResolverAware | StringValueResolver | 手动解析 ${}(@Value 之外的场景) |
能用构造注入解决的,就不要用 Aware。Aware 会让 bean 和容器耦合,单测时你得手动调 setApplicationContext。
4.2 初始化与销毁:三套写法,一次排好序
构造 –> 依赖注入 –> @PostConstruct –> afterPropertiesSet() –> init-method –> 可用
↑
销毁时反向:@PreDestroy –> destroy() –> destroy-method
| @PostConstruct / @PreDestroy | JSR-250 | 不耦合 Spring 接口,位置贴代码 | 需要 jakarta.annotation 依赖 |
| InitializingBean / DisposableBean | Spring | 无需注解,明确 | 代码侵入接口 |
| @Bean(initMethod="x", destroyMethod="y") / xml init-method | 配置 | 能管第三方类(源码不在你手上时唯一选择) | 方法名字符串,重构易漏 |
笔者(后端 & 架构)的选择:自己的类一律 @PostConstruct;第三方组件一律 @Bean(initMethod/destroyMethod);InitializingBean 基本不新增使用。
4.3 FactoryBean:把"怎么造"交给你
BeanPostProcessor 是拿到成品再改,FactoryBean 是从零决定这个对象怎么产生——适合构造过程复杂、或产品类型无法直接声明为 bean 的场景。
public class RpcReferenceBean implements FactoryBean<Object> {
private String interfaceName; // 远程代理要实现的接口
private volatile Object cached;
@Override
public Object getObject() throws Exception {
if (cached == null) {
// 这里可以动态生成 JDK 代理、建立连接池等
cached = ProxyFactory.createRef(Class.forName(interfaceName));
}
return cached;
}
@Override
public Class<?> getObjectType() {
return cached == null ? null : cached.getClass().getInterfaces()[0];
}
@Override
public boolean isSingleton() {
return true; // true 时容器只调一次 getObject 并缓存
}
}
三个容易踩的点:
- getBean("rpcUserService") 拿到的是 getObject() 的产品;要拿工厂本身,名字前加 &:getBean("&rpcUserService")。
- getObjectType() 返回 null 会让按类型注入退化,容器只能实例化后才能确认类型,容易触发提前创建。
- MyBatis 的 SqlSessionFactoryBean、Dubbo 的 ReferenceBean 都是这一模式,看懂它们就看懂了半个生态。
5. 等一切就绪:事件、SmartInitializingSingleton、Runner
很多需求本质是"别急着干活,等容器都装好"。Spring 在这条线上给了四个不同深度的挂载点。
所有单例实例化完 –> SmartInitializingSingleton –> ContextRefreshedEvent –> SmartLifecycle.start –> ApplicationStartedEvent –> ApplicationReadyEvent –> 可接流量
| SmartInitializingSingleton#afterSingletonsInstantiated | 容器 | 单例全部就绪,仍属 finishBeanFactoryInitialization 阶段 | 批量校验配置、预热本地缓存 | 只有单例会被回调;prototype 不管 |
| ApplicationListener / @EventListener | ApplicationEventPublisher | 事件发布时(容器事件默认同步) | 解耦"启动完成后通知一批人" | 默认在发布线程内同步执行,慢逻辑会拖启动 |
| @EventListener + @Async | 同上 | 交给线程池 | 上报、刷新外部缓存 | 需要 @EnableAsync,异常不会回传 |
| SmartLifecycle#start | 容器 finishRefresh | 容器事件附近,受 phase 控制顺序 | MQ 消费者、定时任务的启停对称 | stop() 必须真的能停,否则优雅下线卡住 |
| ApplicationRunner / CommandLineRunner(Boot) | SpringApplication.run | 在 ApplicationReadyEvent 之前,但晚于容器 refresh | 一次性任务、CLI 参数处理 | 抛异常会导致启动失败 |
ApplicationListener 与 @EventListener 的选择:
// 写法一:类型化监听器,泛型即事件,无需注解
@Component
public class CacheWarmUpListener implements ApplicationListener<ContextRefreshedEvent> {
@Override
public void onApplicationEvent(ContextRefreshedEvent event) {
// 注意:父子容器/多次 refresh 会被回调多次,业务里要做幂等
}
}
// 写法二:方法级监听器,一个类可以监听多个事件,还能排序
@Component
public class StartupHooks {
@EventListener(classes = ApplicationReadyEvent.class)
@Order(1)
public void ready(ApplicationReadyEvent event) { /* 注册网关、上报版本 */ }
}
Q4:ContextRefreshedEvent 和 ApplicationReadyEvent 差在哪?该听哪个?
ContextRefreshedEvent 是 Spring 容器自己的事件,只表示"这个 context 刷新完了";父子容器场景会收到多次,纯 Spring MVC(非 Boot)项目里也只有它。ApplicationStartedEvent / ApplicationReadyEvent 是 Boot 在 run() 里由 SpringApplicationRunListener 补发的,语义是"应用整体起来了(含 runner、Web 端口就绪)"。
结论:Boot 项目里"能接流量/能对外暴露"的钩子用 ApplicationReadyEvent;必须在容器阶段完成初始化的用 ContextRefreshedEvent 或直接 SmartInitializingSingleton(更早,且不依赖事件)。
6. 动态注册与开关:@Import、ImportBeanDefinitionRegistrar、@Conditional
Spring 自身大量使用第 2、3 节那些接口,但给业务方留了个更内聚的入口:注解驱动的注册。@EnableXxx 那一族基本都是 @Import + Selector/Registrar + @Conditional 的组合拳。
| @Import(Config.class) | 导入哪几个类 | 直接写死 | 模块内部装配 |
| ImportSelector | 按条件导入哪些配置类 | 返回类名字符串数组 | @EnableTransactionManagement、自动配置排序 |
| DeferredImportSelector | 同上,且晚于其他配置类解析 | 返回类名 | Boot 自动配置就靠它 |
| ImportBeanDefinitionRegistrar | 手工注册任意 BeanDefinition | 直接操作 registry | @MapperScan、自定义 @Enable* 生成一批 bean |
| @Conditional | 这个 bean 装不装 | 返回 boolean | starter 的"你没配我就不启用" |
一个能直接抄走的最小完整例子:用注解开关动态注册一个拦截器。
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Import(SignatureRegistrar.class)
public @interface EnableApiSignature {
String headerName() default "X-Sign";
boolean enabled() default true;
}
public class SignatureRegistrar implements ImportBeanDefinitionRegistrar {
@Override
public void registerBeanDefinitions(AnnotationMetadata meta, BeanDefinitionRegistry registry) {
Map<String, Object> attrs = meta.getAnnotationAttributes(EnableApiSignature.class.getName());
if (attrs == null || !Boolean.TRUE.equals(attrs.get("enabled"))) {
return; // 注解没写或显式关闭,直接不注册
}
RootBeanDefinition bd = new RootBeanDefinition(SignatureInterceptor.class);
// 只把参数写进定义,交给目标 bean 自己去要依赖 —— Registrar 阶段不要碰实例
bd.getConstructorArgumentValues().addIndexedArgumentValue(0, attrs.get("headerName"));
if (!registry.containsBeanDefinition("signatureInterceptor")) {
registry.registerBeanDefinition("signatureInterceptor", bd);
}
}
}
自定义条件:
public class OnFeatureEnabled implements Condition {
@Override
public boolean matches(ConditionContext ctx, AnnotatedTypeMetadata meta) {
return Boolean.parseBoolean(ctx.getEnvironment().getProperty("feature.sign", "false"));
}
}
@Configuration
@Conditional(OnFeatureEnabled.class)
public class SignatureConfig { /* 只有开关打开才会被解析成 bean */ }
@ConditionalOnProperty / @ConditionalOnClass / @ConditionalOnMissingBean 是 Boot 提供的现成实现,@ConditionalOnMissingBean 更是 starter 里"用户没定义我才兜底"的标准写法。
把这一节的三个角色串起来,@Enable* 的形状就是这样的:

Registrar 与 Selector 里只做"注册",不要注入依赖、不要 getBean。 需要配置值就塞进 BeanDefinition 的构造参数或属性,让被注册出来的 bean 自己用 @Value / Environment 拿。
7. Web 层与 Boot 层:还有一批高频挂载点
7.1 一次 HTTP 请求上的切面
请求 –> Filter(Servlet 容器) –> DispatcherServlet –> preHandle –> Interceptor –> Controller
–> postHandle –> afterCompletion(逆序)
| 规范来源 | Servlet 规范 | Spring MVC |
| 能拿到 handler 方法 | 不能 | 能(HandlerMethod,可读到注解) |
| 能否直接注入 Spring bean | 取决于注册方式,FilterRegistrationBean 可以 | 可以(本身由容器管理) |
| 典型用途 | 编码、XSS、跨域、限流、请求体缓存 | 鉴权、接口签名、耗时埋点 |
配套的还有 WebMvcConfigurer(addInterceptors / extendMessageConverters / addFormatters / addCorsMappings)、HandlerMethodArgumentResolver(自定义 @CurrentUser 这类参数解析)、ResponseBodyAdvice + @RestControllerAdvice(统一返回包装与加签)。
7.2 Boot 启动链上的扩展点
| EnvironmentPostProcessor | 环境准备完、容器创建前 | 配置解密、注入外部配置源(spring.config.import 之外的兜底) |
| ApplicationContextInitializer | ApplicationContext 创建后、refresh 前 | 提前注册 bean 定义、profile 补充 |
| SpringApplicationRunListener | 贯穿 run() 各阶段 | 埋点、发布自定义生命周期事件(ApplicationReadyEvent 就是它发的) |
| FailureAnalyzer | 启动失败时 | 把 BeanCreationException 翻译成人话(starter 体验优化) |
| @AutoConfiguration + AutoConfiguration.imports | 容器 refresh 时 | starter 对外暴露默认配置 |
| @ConfigurationProperties + Binder | 属性绑定期 | 结构化配置、批量校验 |
自动配置的声明文件在本地 Boot 3.5.0 的 spring-boot-autoconfigure 包里两个文件是同时存在的,实际生效项已经走 imports:
META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports # 2.7 起,自动配置专用
META-INF/spring.factories # 其余 SPI 仍在用(EnvironmentPostProcessor 等)
@ConditionalOnMissingBean 加上自动配置的排序(@AutoConfigureAfter / AutoConfigurationMetadata)决定了它"永远排在用户配置后面",这也是 starter 能"配了就赢"的原因。
7.3 需求 → 扩展点速查(如果是我,我会这么选)
| 启动时按表/配置动态注册一批 bean | BeanDefinitionRegistryPostProcessor | ImportBeanDefinitionRegistrar | 在 @PostConstruct 里 registerBeanDefinition |
| 给某类 bean 统一加代理/包装 | BeanPostProcessor#postProcessAfterInitialization | @Aspect + @Around | 手写继承覆盖 |
| 修改所有 bean 的某个属性初值 | BFPP 改 BeanDefinition | BPP 改实例 | 反射扫描 |
| 让第三方 jar 里的类可配置 | @Bean(initMethod/destroyMethod) + @ConfigurationProperties | FactoryBean | 改对方源码 |
| 启动后预热本地缓存 | SmartInitializingSingleton | ContextRefreshedEvent | 在构造函数里干活 |
| 起来之后再通知外部系统 | ApplicationReadyEvent | ApplicationRunner | @PostConstruct |
| 做一个可开关的 starter | @Enable* + Registrar + @Conditional* | 纯自动配置 | 靠 README 提醒用户注释代码 |
| 统一加签 / 鉴权 / 响应包装 | HandlerInterceptor + ResponseBodyAdvice | Filter(要读原始流时) | 每个 Controller 复制粘贴 |
一个通用原则:越靠前的扩展点越少用。 BDRPP/BFPP 只能碰定义,越早执行对后来越不可见;能在 Bean 级或事件级解决的问题,不要往容器级提前。
最后总结
- 如果只想记一句话: 改"图纸"用 BFPP / BDRPP,改"对象"用 BeanPostProcessor 家族,"等就绪"用 SmartInitializingSingleton / 容器事件 / Runner,"给别人用"用 @Import + @Conditional。
- 如果是业务代码里第一次引入扩展点: 优先 @PostConstruct + @Bean(initMethod) + ApplicationReadyEvent 这三件套,够用且耦合最小。
- 如果你在写框架或 starter: 必须掌握 BeanDefinitionRegistryPostProcessor、FactoryBean、ImportBeanDefinitionRegistrar、@Conditional,Spring 生态(MyBatis、Dubbo、Boot starter)几乎都是这四个的组合。
- 对于后端 / 架构开发者,我个人最看重的是这几点工程约束: 容器级扩展点里绝不取实例;BeanPostProcessor 用 static @Bean 暴露并显式 Ordered;所有"启动后钩子"都要写幂等(父子容器、多次 refresh、重启都会重放);注册 BeanDefinition 前先 containsBeanDefinition。
扩展点本身不值钱,值钱的是"知道哪个切面能看到什么、看不到什么"。把第 1 节那两条时间线记住,剩下的都是查表。
参考资料 & 致谢
[1] Spring 常用扩展点(CSDN) [2] 盘点 Spring/Boot 的那些常用扩展点(CSDN) [3] Spring Framework 官方文档 – Bean 容器与扩展接口 [4] Spring Boot 2.7.0 M2 Release Notes – 自动配置改用 AutoConfiguration.imports [5] Spring Boot 官方文档 – 自动配置与 starter
版本与待核实说明:
- 本文接口签名于 2026-09-26 用 javap 直接核对自本机 Maven 仓库中的 spring-beans / spring-context 5.3.34、6.2.7 与 spring-boot / spring-boot-autoconfigure 2.7.18、3.5.0 的 jar 包,不是凭记忆描述。
- 已核实事实:InstantiationAwareBeanPostProcessor 在 5.3 中仍保留 postProcessPropertyValues(已废弃),6.2.7 中已删除,只剩 postProcessBeforeInstantiation / postProcessAfterInstantiation / postProcessProperties;BeanDefinitionRegistryPostProcessor 位于 org.springframework.beans.factory.support 包且继承 BeanFactoryPostProcessor;ConfigurationClassPostProcessor 实现 BeanDefinitionRegistryPostProcessor 且为 PriorityOrdered;FactoryBean#getObjectType() 返回 Class<?>(无泛型参数)。
- 已核实事实:本地 Boot 3.5.0 的 spring-boot-autoconfigure 包内 META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports 与 META-INF/spring.factories 同时存在,前者是自动配置声明入口。
- 已核实事实(javap -c 反读字节码):AbstractAutowireCapableBeanFactory#applyBeanPostProcessorsBeforeInitialization 在某个处理器返回 null 时,会返回该处理器入参之前的实例并终止遍历剩余处理器;MergedBeanDefinitionPostProcessor 位于 org.springframework.beans.factory.support 包(不是 config 包)。
- 已核实事实:额外对 spring-beans 7.0.9 做了抽查,本文用到的接口(BeanPostProcessor、InstantiationAwareBeanPostProcessor、SmartInstantiationAwareBeanPostProcessor、DestructionAwareBeanPostProcessor、MergedBeanDefinitionPostProcessor、FactoryBean)在 7.0.9 中仍存在且形态一致。
- 未逐条核实:EnvironmentPostProcessor、ApplicationContextInitializer、SpringApplicationRunListener、FailureAnalyzer 的精确执行间隔点(本文按官方文档语义描述,未在 3.x/4.x 源码逐行对照);文中类名与方法名在上述 jar 中已确认存在。
- 未核实项:读者环境的 Spring / Spring Boot 版本未知。Spring Boot 4.x(若你已在用)中部分扩展点存在包路径与排序语义调整,本文未覆盖,请以对应版本文档为准。 版本号跨度带来的差异,请按 javap / 官方文档在你自己的依赖上复核一遍,我在 5.3 与 6.2 之间已发现一处接口删除(见上)。
- 第 3 节 MyBeanPostProcessor 的示例基于原稿代码,类名笔误(Proccessor)已修正;示例中的 Address、JobMeta、RpcReferenceBean、SignatureInterceptor 均为演示用虚构类,非可运行工程代码。
网硕互联帮助中心




评论前必须登录!
注册