Spring 核心扩展点
Spring 扩展点是理解 Spring 源码和框架生态的关键。MyBatis、Spring Boot、Spring Security、事务、AOP、各种 Starter,本质上都大量使用了 Spring 提供的扩展能力。
先记住一句话:
Spring 扩展点不是让你随便插代码,而是让你在 BeanDefinition 注册、Bean 创建、属性注入、初始化、代理、事件、资源加载等关键阶段参与容器行为。
学习目标
学完本页,你应该能回答:
BeanFactoryPostProcessor和BeanPostProcessor的区别。BeanDefinitionRegistryPostProcessor为什么比普通后置处理器更早。FactoryBean和普通 Bean 有什么区别。ImportSelector、DeferredImportSelector、ImportBeanDefinitionRegistrar分别适合什么。- Aware、InitializingBean、DisposableBean、SmartLifecycle、ApplicationListener 在生命周期哪里执行。
- Spring Boot 自动配置为什么离不开 Spring 扩展点。
- 面试中如何把扩展点和 IOC、AOP、事务、Starter 串起来。
PriorityOrdered、Ordered和普通处理器怎样分组执行。MergedBeanDefinitionPostProcessor、SmartInstantiationAwareBeanPostProcessor、DestructionAwareBeanPostProcessor分别解决什么问题。- 扩展点导致过早实例化、代理缺失、启动慢和 ClassLoader 泄漏时怎样排查。
容器生命周期视角
flowchart TD
A["读取配置类和扫描组件"] --> B["注册 BeanDefinition"]
B --> C["BeanDefinitionRegistryPostProcessor"]
C --> D["BeanFactoryPostProcessor"]
D --> E["实例化 Bean"]
E --> F["属性填充"]
F --> G["Aware 回调"]
G --> H["BeanPostProcessor 初始化前"]
H --> I["初始化方法"]
I --> J["BeanPostProcessor 初始化后"]
J --> K["Bean 可用"]
K --> L["销毁回调"]不同扩展点对应不同阶段:
| 阶段 | 扩展点 | 能做什么 |
|---|---|---|
| 注册 BeanDefinition | BeanDefinitionRegistryPostProcessor | 新增、修改 BeanDefinition |
| 修改 BeanFactory | BeanFactoryPostProcessor | 修改已有 BeanDefinition、属性占位符 |
| 实例化 Bean | InstantiationAwareBeanPostProcessor | 实例化前后干预 |
| 合并定义元数据 | MergedBeanDefinitionPostProcessor | 缓存注入、生命周期等元数据 |
| 预测类型和早期引用 | SmartInstantiationAwareBeanPostProcessor | 构造器、类型、early proxy |
| 初始化前后 | BeanPostProcessor | 包装 Bean、创建代理 |
| 销毁前 | DestructionAwareBeanPostProcessor | 在目标 Bean 销毁前清理 |
| 获取特殊 Bean | FactoryBean | 用一个工厂对象生产真正 Bean |
| 导入配置 | ImportSelector | 按条件导入配置类 |
| 延迟导入 | DeferredImportSelector | 在普通配置类处理后导入 |
| 注册 BeanDefinition | ImportBeanDefinitionRegistrar | 编程式注册 BeanDefinition |
| 感知容器对象 | Aware 系列接口 | 获取 BeanName、BeanFactory、ApplicationContext |
| 生命周期 | InitializingBean、DisposableBean | 初始化和销毁 |
| 启停控制 | SmartLifecycle | 容器启动停止时执行 |
| 事件机制 | ApplicationListener | 监听 Spring 事件 |
BeanDefinitionRegistryPostProcessor
它能在 BeanDefinition 注册阶段继续注册新的 BeanDefinition,比普通 BeanFactoryPostProcessor 更早。
flowchart TD
A["扫描到基础 BeanDefinition"] --> B["执行 RegistryPostProcessor"]
B --> C["注册新的 BeanDefinition"]
C --> D["执行 BeanFactoryPostProcessor"]
D --> E["实例化 Bean"]Demo:动态注册一个采集处理器。
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.BeanDefinitionRegistryPostProcessor;
import org.springframework.stereotype.Component;
@Component
public class CollectHandlerRegistryPostProcessor
implements BeanDefinitionRegistryPostProcessor {
@Override
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry)
throws BeansException {
BeanDefinition beanDefinition = BeanDefinitionBuilder
.genericBeanDefinition(DefaultCollectHandler.class)
.getBeanDefinition();
registry.registerBeanDefinition("defaultCollectHandler", beanDefinition);
}
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory)
throws BeansException {
}
}适合:
| 场景 | 说明 |
|---|---|
| 根据配置动态注册 Bean | 多数据源、多租户、多个客户端 |
| 框架扫描自定义注解 | 类似 Mapper 扫描、Feign Client 扫描 |
| 批量生成代理 BeanDefinition | 不想手写每个 Bean |
注意:这个扩展点很早执行,不能依赖普通业务 Bean。
BDRPP 为什么要循环发现
一个 RegistryPostProcessor 可以继续注册另一个 RegistryPostProcessor。Spring 不能只扫描一次,否则新注册的处理器永远没有机会参与定义阶段。
经典执行思路:
- 先执行通过 API 直接传入的处理器;
- 查找并执行
PriorityOrderedBDRPP; - 查找并执行
OrderedBDRPP; - 循环查找其余尚未执行的 BDRPP;
- 直到没有新 BDRPP;
- 再调用所有 BDRPP 的
postProcessBeanFactory(); - 最后进入普通 BFPP。
flowchart TD
A["取得PriorityOrdered BDRPP"] --> B["排序并执行Registry回调"]
B --> C["取得Ordered BDRPP"]
C --> D["排序并执行Registry回调"]
D --> E["查找其余未执行BDRPP"]
E --> F{"是否发现新处理器?"}
F -- "是" --> G["执行并再次扫描"]
G --> E
F -- "否" --> H["调用所有postProcessBeanFactory"]
H --> I["进入普通BFPP"]顺序接口只在同一阶段和同一组内发挥作用,不能用 @Order 把 BPP 移到 BFPP 阶段,也不能把实例处理器提前为定义处理器。
BeanFactoryPostProcessor
它在 Bean 实例化前执行,能修改已经存在的 BeanDefinition。
flowchart TD
A["BeanDefinition 已经存在"] --> B["BeanFactoryPostProcessor"]
B --> C["修改属性、作用域、构造参数"]
C --> D["按修改后的定义创建 Bean"]Demo:给某个 Bean 设置懒加载。
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.BeanFactoryPostProcessor;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.stereotype.Component;
@Component
public class LazyBeanFactoryPostProcessor implements BeanFactoryPostProcessor {
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory)
throws BeansException {
if (beanFactory.containsBeanDefinition("heavyClient")) {
BeanDefinition bd = beanFactory.getBeanDefinition("heavyClient");
bd.setLazyInit(true);
}
}
}和 BeanDefinitionRegistryPostProcessor 区别:
| 对比 | BeanDefinitionRegistryPostProcessor | BeanFactoryPostProcessor |
|---|---|---|
| 执行时机 | 更早 | 稍后 |
| 核心能力 | 注册新的 BeanDefinition | 修改已有 BeanDefinition |
| 典型场景 | 扫描并注册 Mapper、Feign | 修改属性、占位符、懒加载 |
BFPP 分组与过早实例化
容器中的普通 BFPP 也按 PriorityOrdered、Ordered、无顺序三组发现、排序和调用。通过 API 直接传入的处理器通常按注册语义先参与,具体应以实际版本源码为准。
BFPP 阶段完整 BeanPostProcessor 链尚未注册。若 BFPP 构造器、字段或方法依赖普通 Service,创建 BFPP 时就可能连带创建 Service,导致:
- Autowired/Resource/PostConstruct 处理不完整;
- AutoProxyCreator 尚未就绪,事务和切面缺失;
- 日志出现
not eligible for getting processed by all BeanPostProcessors; - 基础设施长期保存原始业务对象;
- 启动顺序依赖偶然注册顺序。
BFPP 应优先读取 Environment、BeanDefinition 和轻量元数据。必须共享配置时,使用不依赖普通业务 Bean 的基础设施对象。
BeanPostProcessor
BeanPostProcessor 在 Bean 初始化前后执行,是 AOP、事务代理、各种注解增强的重要基础。
flowchart TD
A["Bean 实例化"] --> B["属性填充"]
B --> C["初始化前处理"]
C --> D["初始化方法"]
D --> E["初始化后处理"]
E --> F["可能返回代理对象"]Demo:给自定义注解的 Bean 包装监控。
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.stereotype.Component;
@Component
public class MonitorBeanPostProcessor implements BeanPostProcessor {
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName)
throws BeansException {
return bean;
}
@Override
public Object postProcessAfterInitialization(Object bean, String beanName)
throws BeansException {
if (bean.getClass().isAnnotationPresent(Monitored.class)) {
System.out.println("found monitored bean: " + beanName);
}
return bean;
}
}为什么它重要:
- Spring AOP 代理通常在初始化后创建。
@Autowired、@Value等注解处理也依赖后置处理器体系。- 很多框架通过它给业务 Bean 增加代理、校验和包装。
常见坑:
| 坑 | 后果 |
|---|---|
| 返回 null | 当前前置或后置处理器链提前停止,后续处理器失去机会 |
| 里面手动创建业务对象 | 对象不受 Spring 管理 |
| 对所有 Bean 做重逻辑 | 启动变慢 |
| 忽略代理对象 | AOP 和事务可能不符合预期 |
BPP 怎样发现、排序和注册
registerBeanPostProcessors() 大致分为:
- 取得全部 BPP Bean 名;
- 临时加入
BeanPostProcessorChecker; - 创建并注册
PriorityOrderedBPP; - 创建并注册
OrderedBPP; - 创建并注册普通 BPP;
- 将
MergedBeanDefinitionPostProcessor等内部处理器重新调整到合适位置; - 最后重新注册 ApplicationListenerDetector。
Checker 发现某个普通 Bean 在全部 BPP 注册前就被创建时,会输出“不适合被所有 BPP 处理”的提示。这通常意味着某个 BPP 或其依赖过重、过早获取了业务 Bean。
BPP 返回 null 的准确语义
Spring 遍历前置或后置处理器时,如果当前处理器返回 null,通常停止该次处理器链,并返回进入当前处理器前的结果。它不是简单地把 Bean 设为 null,也不一定立即抛创建异常。
后果是后续处理器没有机会执行,可能漏掉:
- PostConstruct;
- Aware;
- AOP代理;
- 校验或框架包装。
自定义 BPP 没有替代对象时应返回传入 bean。
BPP 的线程安全和缓存
BPP 通常是 singleton。不要用成员字段保存“当前 Bean”“当前注入点”或请求数据。元数据缓存应:
- 使用线程安全结构;
- 以 class、beanName 或稳定描述符为键;
- 避免无界缓存用户动态 class;
- 在定义重置、类热替换或上下文关闭时考虑失效;
- 避免静态缓存持有应用 ClassLoader,导致重部署泄漏。
InstantiationAwareBeanPostProcessor
它比普通 BeanPostProcessor 更早,能在实例化前后干预 Bean 创建。
适合框架底层使用,业务项目尽量少用。
flowchart TD
A["准备创建 Bean"] --> B["实例化前回调"]
B --> C{"是否返回代理"}
C -- "是" --> D["跳过默认实例化"]
C -- "否" --> E["正常实例化"]
E --> F["实例化后回调"]
F --> G["属性填充"]典型作用:
| 场景 | 说明 |
|---|---|
| 提前返回代理 | 某些 AOP 场景 |
| 控制是否属性填充 | 框架底层增强 |
| 自定义注入逻辑 | 注解驱动框架 |
postProcessProperties 与依赖注入
实例化后、初始化前的 populateBean() 会让 InstantiationAwareBPP 参与属性填充。AutowiredAnnotationBeanPostProcessor 和 CommonAnnotationBeanPostProcessor 通过这一类扩展解析注入元数据、构造 DependencyDescriptor,并委托 BeanFactory 查找候选。
自定义注入处理器必须处理:
- 父类字段和方法;
- static/final等非法位置;
- required与可选语义;
- 多候选和类型错误;
- 反射访问和JDK模块边界;
- InjectionMetadata缓存;
- prototype重复创建;
- 异常中保留Bean名和注入点。
MergedBeanDefinitionPostProcessor
它在实例产生后、属性填充前,根据最终 class 和合并后的 RootBeanDefinition 准备元数据。典型用途:
- 缓存 Autowired 字段和方法;
- 缓存 Resource、PostConstruct、PreDestroy;
- 标记外部管理的初始化和销毁方法;
- 为每次创建 prototype 复用同一份扫描结果。
resetBeanDefinition 等回调用于定义重置时清理相关元数据。只缓存 class 而不考虑 ClassLoader 和定义重置,可能在热部署或测试上下文中使用旧元数据。
SmartInstantiationAwareBeanPostProcessor
它在 InstantiationAware 基础上增加框架级能力:
predictBeanType:提前预测类型,帮助按类型查找;determineCandidateConstructors:提供候选构造器;getEarlyBeanReference:循环依赖中生成一致早期引用。
AutoProxyCreator 使用 early reference 扩展,在真正发生循环依赖时提前创建代理。若某个包装处理器只在初始化后生成代理,却不支持 early reference,可能出现 raw injection despite wrapping。
DestructionAwareBeanPostProcessor
它在目标 Bean 销毁前执行,可用于:
- 调用 PreDestroy;
- 注销监听和监控;
- 清理框架为目标 Bean 建立的外部资源;
- 判断某个 Bean 是否需要销毁处理。
销毁回调必须幂等、有界,不能因一个 Bean 清理失败而阻止其他资源释放。prototype 通常不由容器统一销毁,不能只靠 DestructionAwareBPP 回收所有 prototype。
FactoryBean
FactoryBean 是“生产 Bean 的 Bean”。普通 Bean 获取的是对象本身,FactoryBean 获取的是它生产出来的对象。
flowchart TD
A["容器中注册 FactoryBean"] --> B["调用 getObject"]
B --> C["返回真正业务对象"]
A --> D["使用 &beanName"]
D --> E["获取工厂本身"]Demo:创建一个客户端代理。
import org.springframework.beans.factory.FactoryBean;
public class CollectClientFactoryBean implements FactoryBean<CollectClient> {
private String baseUrl;
public void setBaseUrl(String baseUrl) {
this.baseUrl = baseUrl;
}
@Override
public CollectClient getObject() {
return new CollectClient(baseUrl);
}
@Override
public Class<?> getObjectType() {
return CollectClient.class;
}
}常见应用:
| 框架 | 用途 |
|---|---|
| MyBatis | Mapper 接口代理 |
| Dubbo | RPC 接口代理 |
| Feign | HTTP 客户端代理 |
| 自定义 SDK | 动态创建客户端 |
面试常问:BeanFactory 和 FactoryBean 区别。
| 对比 | BeanFactory | FactoryBean |
|---|---|---|
| 类型 | Spring 容器接口 | 一个特殊 Bean |
| 作用 | 管理 Bean | 生产某个 Bean |
| 获取方式 | getBean | getBean("name") 得到产品,getBean("&name") 得到工厂 |
FactoryBean 类型预测、缓存与生命周期
FactoryBean 本身是普通 Spring Bean,经历实例化、注入、初始化和销毁。产品由 getObject() 创建:
getObjectType()影响按类型发现,返回 null 会降低启动期类型可见性;isSingleton()表示产品是否可缓存,不是工厂 Bean 自身的 scope;- singleton 产品可能进入
factoryBeanObjectCache; - 产品和工厂分别缓存,不能混为一级 singleton 的同一对象;
- 产品可能经过
postProcessObjectFromFactoryBean等后处理,但不能假设完整经历普通 Bean 的所有属性填充和生命周期; - 产品由谁销毁取决于 FactoryBean 实现和注册方式。
实现 RPC、Mapper 或 HTTP 客户端 FactoryBean 时,还要避免 getObjectType() 为确定类型而提前建立网络连接或创建昂贵产品。
ImportSelector
ImportSelector 用于根据条件导入配置类。
flowchart TD
A["@Import"] --> B["ImportSelector"]
B --> C["返回配置类全限定名"]
C --> D["Spring 解析导入的配置类"]Demo:
import org.springframework.context.annotation.ImportSelector;
import org.springframework.core.type.AnnotationMetadata;
public class CollectImportSelector implements ImportSelector {
@Override
public String[] selectImports(AnnotationMetadata importingClassMetadata) {
return new String[] {
"com.example.collect.CollectClientConfig"
};
}
}使用:
@Import(CollectImportSelector.class)
public class CollectEnableConfig {
}适合用在 @EnableXxx 注解里。
DeferredImportSelector
DeferredImportSelector 是延迟导入选择器,会在普通配置类处理后再执行。Spring Boot 自动配置的 AutoConfigurationImportSelector 就和这个机制有关。
flowchart TD
A["解析普通配置类"] --> B["收集 DeferredImportSelector"]
B --> C["普通配置处理完成"]
C --> D["执行延迟导入"]
D --> E["导入自动配置类"]为什么自动配置适合延迟导入:
- 用户配置类先处理。
- 自动配置后处理。
- 自动配置可以通过
@ConditionalOnMissingBean给用户 Bean 让位。
这也是 Boot 能做到“默认配置不抢用户配置”的关键之一。
ImportBeanDefinitionRegistrar
它可以编程式注册 BeanDefinition,比 ImportSelector 更灵活。
flowchart TD
A["@Import Registrar"] --> B["读取注解元信息"]
B --> C["构造 BeanDefinition"]
C --> D["注册到 BeanDefinitionRegistry"]Demo:根据注解属性注册客户端。
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.context.annotation.ImportBeanDefinitionRegistrar;
import org.springframework.core.type.AnnotationMetadata;
public class CollectClientRegistrar implements ImportBeanDefinitionRegistrar {
@Override
public void registerBeanDefinitions(AnnotationMetadata metadata,
BeanDefinitionRegistry registry) {
BeanDefinitionBuilder builder = BeanDefinitionBuilder
.genericBeanDefinition(CollectClient.class);
builder.addConstructorArgValue("https://default.example.com");
registry.registerBeanDefinition("collectClient", builder.getBeanDefinition());
}
}常见用途:
| 场景 | 说明 |
|---|---|
| Mapper 扫描 | 为接口生成代理 BeanDefinition |
| Feign Client | 为接口生成 HTTP 客户端代理 |
| 多租户数据源 | 根据配置注册多个数据源 |
| 自定义 Enable 注解 | 按注解属性导入不同 Bean |
三种导入扩展点怎样选择
| 扩展点 | 返回/操作 | 适合场景 |
|---|---|---|
| ImportSelector | 配置类全限定名数组 | 普通Enable模块导入 |
| DeferredImportSelector | 延迟导入配置候选,可分组 | 自动配置和统一排序过滤 |
| ImportBeanDefinitionRegistrar | 直接操作BeanDefinitionRegistry | 动态名称、构造参数、代理定义 |
Selector 应根据 AnnotationMetadata、Environment 或本地 classpath 做确定性选择,不应调用远程服务。Registrar 注册名称必须稳定并检测重复,否则多个 Enable 注解可能静默覆盖定义。
Boot 2 与 Boot 3 自动配置发现
Boot 2 时代常见通过 META-INF/spring.factories 声明自动配置;Boot 2.7/Boot 3 主线引入并使用 META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports。具体注解、排序和兼容写法要以 Boot 版本为准。
无论候选从哪个文件发现,最终仍会进入配置类解析、条件判断和 BeanDefinition 注册,之后由 Spring Framework 创建 Bean。
Aware 系列接口
Aware 让 Bean 感知 Spring 容器提供的对象。
常见接口:
| 接口 | 能拿到什么 |
|---|---|
BeanNameAware | 当前 Bean 名称 |
BeanFactoryAware | BeanFactory |
ApplicationContextAware | ApplicationContext |
EnvironmentAware | Environment |
ResourceLoaderAware | ResourceLoader |
Demo:
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.stereotype.Component;
@Component
public class SpringContextHolder implements ApplicationContextAware {
private static ApplicationContext context;
@Override
public void setApplicationContext(ApplicationContext applicationContext) {
context = applicationContext;
}
public static <T> T getBean(Class<T> type) {
return context.getBean(type);
}
}注意:能用不代表应该滥用。到处静态获取 Bean 会隐藏依赖,让代码难测试。正常业务优先构造器注入。
InitializingBean 和 DisposableBean
初始化和销毁扩展点:
flowchart TD
A["Bean 属性填充完成"] --> B["afterPropertiesSet"]
B --> C["自定义 init 方法"]
C --> D["Bean 可用"]
D --> E["容器关闭"]
E --> F["destroy"]Demo:
import org.springframework.beans.factory.DisposableBean;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.stereotype.Component;
@Component
public class CollectClientHolder implements InitializingBean, DisposableBean {
@Override
public void afterPropertiesSet() {
System.out.println("初始化采集客户端连接池");
}
@Override
public void destroy() {
System.out.println("关闭采集客户端连接池");
}
}更常见写法是使用注解:
import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
public class CollectClientHolder {
@PostConstruct
public void init() {
}
@PreDestroy
public void close() {
}
}上例按 JDK 8 + Spring 5 常见基线使用 javax.annotation。Spring 6 / Boot 3 要改用 jakarta.annotation,并要求 Java 17;不能只替换 import 后继续在 JDK 8 运行。
SmartLifecycle
SmartLifecycle 可以参与容器启动和停止过程,适合管理需要随应用启停的组件。
flowchart TD
A["容器刷新完成"] --> B["start"]
B --> C["组件运行中"]
C --> D["容器关闭"]
D --> E["stop"]Demo:启动采集调度器。
import org.springframework.context.SmartLifecycle;
import org.springframework.stereotype.Component;
@Component
public class CollectSchedulerLifecycle implements SmartLifecycle {
private volatile boolean running;
@Override
public void start() {
this.running = true;
System.out.println("启动采集调度器");
}
@Override
public void stop() {
this.running = false;
System.out.println("停止采集调度器");
}
@Override
public boolean isRunning() {
return running;
}
}适合:
| 场景 | 说明 |
|---|---|
| 后台消费线程 | 随容器启停 |
| 连接管理器 | 启动连接,关闭释放 |
| 调度器 | 应用启动后开启,停机前关闭 |
不要把复杂业务流程都塞进 start(),否则会影响应用启动和停机。
phase、自动启动和异步停止
isAutoStartup()决定 refresh 时是否自动启动;getPhase()控制相对顺序,启动通常 phase 小的先,停止通常反向;- 显式依赖关系仍可能影响顺序;
stop(Runnable callback)支持异步停止,真实停止完成后必须调用 callback;- callback 不调用会让容器等待到关闭超时。
商业消费者关闭顺序通常是:先停止拉取新消息,再等待在途任务,最后关闭线程池、MQ客户端和数据库连接池。每一步都要有超时和幂等保证。
ApplicationListener 和事件机制
Spring 事件适合做同进程内的解耦。
flowchart TD
A["业务发布事件"] --> B["ApplicationEventPublisher"]
B --> C["事件监听器 1"]
B --> D["事件监听器 2"]
C --> E["写审计"]
D --> F["刷新缓存"]事件类:
public class CollectFinishedEvent {
private final String batchNo;
public CollectFinishedEvent(String batchNo) {
this.batchNo = batchNo;
}
public String getBatchNo() {
return batchNo;
}
}发布:
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.stereotype.Service;
@Service
public class CollectService {
private final ApplicationEventPublisher publisher;
public CollectService(ApplicationEventPublisher publisher) {
this.publisher = publisher;
}
public void finish(String batchNo) {
publisher.publishEvent(new CollectFinishedEvent(batchNo));
}
}监听:
import org.springframework.context.event.EventListener;
import org.springframework.stereotype.Component;
@Component
public class CollectEventListener {
@EventListener
public void onCollectFinished(CollectFinishedEvent event) {
System.out.println("采集批次完成:" + event.getBatchNo());
}
}注意:Spring 事件默认是同进程内事件,不等于 MQ。跨服务可靠事件要用消息队列、本地消息表或事务消息。
同步、异步和事务边界
默认 ApplicationEventMulticaster 常在发布线程同步调用监听器:
- 监听器慢会拖慢发布者;
- 监听器抛异常可能传播给发布者;
- 发布发生在数据库事务中时,同步监听器通常仍在当前事务线程;
- 普通
@Async监听器切换线程后,不再自动共享原线程事务和ThreadLocal。
@TransactionalEventListener 可以绑定事务阶段,例如 AFTER_COMMIT,但数据库提交后应用仍可能在外部消息发送前宕机,所以它不能替代 Outbox、事务消息或 CDC。
事件对象应尽量不可变,不携带可延迟修改的 JPA 实体、请求对象或敏感信息。异步监听还要设计线程池、拒绝策略、重试、幂等和上下文传播。
扩展点和 AOP 的关系
AOP 代理对象的创建依赖 Bean 生命周期扩展点。简化理解:
flowchart TD
A["普通 Bean 创建"] --> B["初始化后处理器"]
B --> C{"是否需要 AOP 增强"}
C -- "否" --> D["返回原对象"]
C -- "是" --> E["创建代理对象"]
E --> F["容器中暴露代理对象"]这解释了很多面试问题:
| 问题 | 原因 |
|---|---|
| 事务为什么要通过代理调用 | 容器中暴露的是代理对象,代理负责事务增强 |
| 自调用为什么事务失效 | this.method() 没经过代理对象 |
| final 方法为什么不能被 CGLIB 增强 | 子类代理无法重写 final 方法 |
扩展点和 Spring Boot 自动配置的关系
Spring Boot 不是独立于 Spring 的另一套容器,它大量复用 Spring 扩展点。
flowchart TD
A["Spring Boot 自动配置"] --> B["DeferredImportSelector"]
B --> C["导入自动配置类"]
C --> D["条件注解判断"]
D --> E["注册 BeanDefinition"]
E --> F["Spring IOC 创建 Bean"]
F --> G["BeanPostProcessor 参与增强"]所以学习顺序应该是:
- 先理解 IOC 和 BeanDefinition。
- 再理解 Bean 生命周期。
- 再理解后置处理器和代理。
- 最后理解 Spring Boot 自动配置。
商业 Starter 怎样组合扩展点
以“采集客户端 Starter”为例,不应在一个 BPP 中完成全部工作。合理拆分:
flowchart TD
A["AutoConfiguration.imports发现自动配置"] --> B["条件注解判断类和配置"]
B --> C["ConfigurationProperties绑定配置"]
C --> D["Bean方法注册客户端工厂"]
D --> E["FactoryBean或普通Bean创建客户端"]
E --> F["BPP增加监控或代理"]
F --> G["SmartInitializingSingleton校验客户端集合"]
G --> H["SmartLifecycle启动连接或消费"]
H --> I["Actuator暴露健康和指标"]设计原则:
- 用户显式 Bean 优先,默认配置使用 ConditionalOnMissingBean;
- 配置属性有前缀、校验、默认值和弃用迁移;
- 不在 ImportSelector、Registrar、BFPP 中访问远程服务;
- 网络连接放在明确生命周期组件,带超时;
- BPP 只处理带标记的目标 Bean,不扫描和包装全部对象;
- 自动配置类只负责组装,不承载业务;
- 失败消息说明缺失属性、条件和修复方式;
- 使用 ApplicationContextRunner 测试有无用户 Bean、属性、classpath和冲突场景;
- Boot 2/3 使用匹配的自动配置发现文件和 javax/jakarta 依赖。
扩展点怎么选
| 需求 | 推荐扩展点 | 不推荐 |
|---|---|---|
| 动态注册一批 Bean | BeanDefinitionRegistryPostProcessor 或 ImportBeanDefinitionRegistrar | 手写大量配置类 |
| 修改 Bean 定义 | BeanFactoryPostProcessor | Bean 创建后再改 |
| Bean 初始化后创建代理 | BeanPostProcessor | 业务代码里手动代理 |
| 根据注解导入配置 | ImportSelector | 到处复制 @Configuration |
| 生产特殊对象 | FactoryBean | 在业务 Service 里 new |
| 容器启动后启动组件 | SmartLifecycle | 静态代码块启动线程 |
| 同进程事件解耦 | ApplicationEventPublisher | Service 互相强依赖 |
扩展点常见错误与后果
| 错误 | 后果 | 正确方向 |
|---|---|---|
| BFPP注入普通Service | Bean过早创建、代理缺失 | 只依赖定义和轻量元数据 |
| BPP处理所有Bean并做I/O | 启动极慢 | 标记目标、缓存元数据、禁止远程I/O |
| BPP返回null | 后续处理器链提前结束 | 通常返回原Bean |
| Registrar使用随机Bean名 | 重复和覆盖不可预测 | 稳定命名并检测冲突 |
| FactoryBean.getObjectType创建产品 | 类型扫描触发重初始化 | 轻量预测类型 |
| ImportSelector访问配置中心 | 定义阶段被网络阻塞 | 只读取本地环境和元数据 |
| 静态ApplicationContextHolder | 测试污染、ClassLoader泄漏 | 构造器注入或ObjectProvider |
| SmartLifecycle不调stop callback | 关闭一直等待 | 真正停止后回调且有超时 |
| 事件监听直接发可靠消息 | 提交后宕机丢消息 | Outbox、事务消息或CDC |
| Starter无条件注册默认Bean | 覆盖用户实现 | MissingBean条件与回归测试 |
生产排查 Runbook
启动慢
- 按 refresh 阶段区分 BDRPP/BFPP、BPP注册、Bean实例化和finishRefresh;
- 记录每个自定义处理器的调用次数和耗时;
- 检查重复classpath扫描、反射扫描和无界元数据缓存;
- 检查扩展点是否访问数据库、配置中心、DNS或HTTP;
- 使用 ApplicationStartup、JFR和连续线程栈;
- 不用全局Lazy掩盖定义处理器慢。
Bean没有被代理或注解没生效
- 检查对应BPP是否注册;
- 检查目标Bean是否在BPP完整注册前创建;
- 查看 BeanPostProcessorChecker 日志;
- 检查处理器顺序和是否有人返回null;
- 检查目标是否手工new、FactoryBean产品或父子容器对象;
- 检查注解保留策略、代理类型和Pointcut。
重复BeanDefinition或覆盖
记录定义来源、Registrar、扫描包、自动配置条件和实际Bean名。稳定框架应在注册前检测冲突,错误信息包含导入类、注解属性和建议修复,不能依赖后注册覆盖。
重部署后内存不释放
检查自定义扩展点的 static Map、ThreadLocal、线程池、事件监听、JDBC Driver和缓存键是否持有应用 ClassLoader。关闭上下文时注销监听、停止线程、清 ThreadLocal,并避免全局静态缓存 Class/Method。
关闭卡住
采集 LifecycleProcessor、SmartLifecycle和destroy线程栈,检查stop callback、无界等待、锁和远程关闭超时。先停止入口,再等待在途任务,最后关闭被依赖资源。
JDK 8 + Spring 5 扩展点顺序 Demo
这个 Demo 证明:Registry回调早于Factory回调,普通BFPP早于业务Bean实例化,BPP围绕初始化,SmartInitializingSingleton在普通singleton完成后执行,最后销毁。
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanNameAware;
import org.springframework.beans.factory.DisposableBean;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.SmartInitializingSingleton;
import org.springframework.beans.factory.config.BeanFactoryPostProcessor;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.BeanDefinitionRegistryPostProcessor;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.PriorityOrdered;
public class ExtensionPointDemo {
private static final List<String> EVENTS = new ArrayList<String>();
static class SampleBean implements BeanNameAware, InitializingBean,
SmartInitializingSingleton, DisposableBean {
SampleBean() { EVENTS.add("4.constructor"); }
public void setBeanName(String name) {
EVENTS.add("5.beanNameAware:" + name);
}
public void afterPropertiesSet() {
EVENTS.add("7.afterPropertiesSet");
}
public void afterSingletonsInstantiated() {
EVENTS.add("9.afterSingletonsInstantiated");
}
public void destroy() { EVENTS.add("11.destroy"); }
}
static class DynamicRegistrar implements
BeanDefinitionRegistryPostProcessor, PriorityOrdered {
public int getOrder() { return 0; }
public void postProcessBeanDefinitionRegistry(
BeanDefinitionRegistry registry) throws BeansException {
EVENTS.add("1.registryPostProcessor");
registry.registerBeanDefinition("sampleBean",
BeanDefinitionBuilder.genericBeanDefinition(SampleBean.class)
.getBeanDefinition());
}
public void postProcessBeanFactory(
ConfigurableListableBeanFactory beanFactory)
throws BeansException {
EVENTS.add("2.registryFactoryCallback");
}
}
static class TraceBpp implements BeanPostProcessor, PriorityOrdered {
public int getOrder() { return 0; }
public Object postProcessBeforeInitialization(
Object bean, String name) throws BeansException {
if (bean instanceof SampleBean) EVENTS.add("6.bppBefore");
return bean;
}
public Object postProcessAfterInitialization(
Object bean, String name) throws BeansException {
if (bean instanceof SampleBean) EVENTS.add("8.bppAfter");
return bean;
}
}
@Configuration
static class Config {
@Bean
static DynamicRegistrar dynamicRegistrar() {
return new DynamicRegistrar();
}
@Bean
static BeanFactoryPostProcessor normalFactoryProcessor() {
return beanFactory -> EVENTS.add("3.normalFactoryProcessor");
}
@Bean
static TraceBpp traceBpp() { return new TraceBpp(); }
}
public static void main(String[] args) {
EVENTS.add("0.beforeContext");
AnnotationConfigApplicationContext context =
new AnnotationConfigApplicationContext(Config.class);
EVENTS.add("10.refreshReturned");
context.close();
List<String> expected = Arrays.asList(
"0.beforeContext",
"1.registryPostProcessor",
"2.registryFactoryCallback",
"3.normalFactoryProcessor",
"4.constructor",
"5.beanNameAware:sampleBean",
"6.bppBefore",
"7.afterPropertiesSet",
"8.bppAfter",
"9.afterSingletonsInstantiated",
"10.refreshReturned",
"11.destroy");
if (!expected.equals(EVENTS)) {
throw new AssertionError("unexpected events: " + EVENTS);
}
System.out.println(EVENTS);
}
}使用 static @Bean 注册基础处理器,避免为了调用配置类实例方法而过早实例化配置类。期望输出从 registryPostProcessor 开始,业务 Bean 构造一定晚于三个定义/工厂处理事件。
源码断点
PostProcessorRegistrationDelegate.invokeBeanFactoryPostProcessors
PostProcessorRegistrationDelegate.registerBeanPostProcessors
ConfigurationClassPostProcessor.postProcessBeanDefinitionRegistry
AbstractAutowireCapableBeanFactory.resolveBeforeInstantiation
AbstractAutowireCapableBeanFactory.applyMergedBeanDefinitionPostProcessors
AbstractAutowireCapableBeanFactory.populateBean
AbstractAutowireCapableBeanFactory.initializeBean
FactoryBeanRegistrySupport.getObjectFromFactoryBean
DefaultLifecycleProcessor.startBeans
DefaultLifecycleProcessor.stopBeans观察处理器分组、order、已处理集合、BeanDefinition数量、BPP列表、原始对象、代理对象和生命周期phase。
版本边界
| 技术线 | 边界 |
|---|---|
| JDK 7 + Spring 4 | 传统扩展主线,Demo不能使用lambda |
| JDK 8 + Spring 5.3 | 本页主要源码和Demo基线 |
| Boot 2.7 | javax生态,兼顾spring.factories与imports机制 |
| Spring 6 / Boot 3 | Java 17和jakarta生态,自动配置发现与AOT要求更严格 |
内部接口方法可能增加 default method 或演进,编写 Starter 要用目标版本编译和矩阵测试,不把 Spring 6 二进制直接用于 Spring 5 项目。
面试标准回答
可以这样答:
Spring 核心扩展点要按容器生命周期理解。BeanDefinitionRegistryPostProcessor 可以在 BeanDefinition 注册阶段继续注册新的 BeanDefinition;BeanFactoryPostProcessor 可以在 Bean 实例化前修改 BeanDefinition;BeanPostProcessor 在 Bean 初始化前后执行,AOP 代理、事务代理等增强都和它有关;FactoryBean 是一个特殊 Bean,用来生产真正的业务对象;ImportSelector、DeferredImportSelector、ImportBeanDefinitionRegistrar 用于导入配置类或编程式注册 BeanDefinition,Spring Boot 自动配置就和 DeferredImportSelector 思路有关。
我使用扩展点时会先判断要干预哪个阶段。如果要动态注册 Mapper 或客户端代理,用 Registrar 或 RegistryPostProcessor;如果要包装 Bean,用 BeanPostProcessor;如果只是业务初始化,用 InitializingBean、PostConstruct 或 SmartLifecycle。扩展点能解决框架级复用问题,但业务代码不要滥用,否则会增加排查难度。追问:
| 追问 | 回答要点 |
|---|---|
| BeanFactoryPostProcessor 和 BeanPostProcessor 区别 | 前者处理 BeanDefinition,后者处理 Bean 实例 |
| FactoryBean 和 BeanFactory 区别 | 前者是生产 Bean 的特殊 Bean,后者是容器 |
| DeferredImportSelector 为什么适合自动配置 | 用户配置先处理,自动配置后导入,方便默认让位 |
| AOP 和 BeanPostProcessor 什么关系 | AOP 代理通常在 Bean 初始化后由后置处理器创建 |
| Spring 事件能替代 MQ 吗 | 不能,Spring 事件是进程内解耦,跨服务可靠消息要用 MQ |
关联知识点
- Spring IOC:理解 BeanDefinition 和 BeanFactory。
- Bean 生命周期:理解扩展点所处阶段。
- Spring AOP:理解代理对象如何产生。
- Spring 事务:理解事务代理和失效原因。
- Spring Boot 自动配置:理解 Boot 如何复用 Spring 扩展点。
- Spring Boot 扩展点:理解 Boot 层面的工程扩展。
本章小结
Spring 扩展点是框架生态的底层插槽。业务开发不一定每天都写这些接口,但必须知道它们存在于哪个生命周期阶段、能改变什么、不能滥用什么。理解这些扩展点后,再看 Spring Boot 自动配置、MyBatis Mapper 扫描、Feign 代理、事务 AOP,就不会觉得它们是黑盒魔法。
