云计算百科
云计算领域专业知识百科平台

[玩转Spring]盘点 Spring 常用扩展点:BeanFactoryPostProcessor、BeanPostProcessor、@Enable、自动配置到底该用哪个?


💡 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、自动配置到底该用哪个?

Spring Bean 生命周期


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]

把这两条线叠在一起看,上下两排的关系就一目了然:

两条时间线:容器级与 Bean 级

下面这张表是全文的索引,建议先记"作用域"和"处理对象"这两列。

扩展接口作用域处理对象触发时机典型用途
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* 的形状就是这样的:

注解驱动的注册:@Enable 组合拳

Registrar 与 Selector 里只做"注册",不要注入依赖、不要 getBean。 需要配置值就塞进 BeanDefinition 的构造参数或属性,让被注册出来的 bean 自己用 @Value / Environment 拿。


7. Web 层与 Boot 层:还有一批高频挂载点

7.1 一次 HTTP 请求上的切面

请求 –> Filter(Servlet 容器) –> DispatcherServlet –> preHandle –> Interceptor –> Controller
–> postHandle –> afterCompletion(逆序)

维度FilterHandlerInterceptor
规范来源 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 均为演示用虚构类,非可运行工程代码。
赞(0)
未经允许不得转载:网硕互联帮助中心 » [玩转Spring]盘点 Spring 常用扩展点:BeanFactoryPostProcessor、BeanPostProcessor、@Enable、自动配置到底该用哪个?
分享到: 更多 (0)

评论 抢沙发

评论前必须登录!