Skip to content

Spring 核心扩展点

Spring 扩展点是理解 Spring 源码和框架生态的关键。MyBatis、Spring Boot、Spring Security、事务、AOP、各种 Starter,本质上都大量使用了 Spring 提供的扩展能力。

先记住一句话:

Spring 扩展点不是让你随便插代码,而是让你在 BeanDefinition 注册、Bean 创建、属性注入、初始化、代理、事件、资源加载等关键阶段参与容器行为。

学习目标

学完本页,你应该能回答:

  1. BeanFactoryPostProcessorBeanPostProcessor 的区别。
  2. BeanDefinitionRegistryPostProcessor 为什么比普通后置处理器更早。
  3. FactoryBean 和普通 Bean 有什么区别。
  4. ImportSelectorDeferredImportSelectorImportBeanDefinitionRegistrar 分别适合什么。
  5. Aware、InitializingBean、DisposableBean、SmartLifecycle、ApplicationListener 在生命周期哪里执行。
  6. Spring Boot 自动配置为什么离不开 Spring 扩展点。
  7. 面试中如何把扩展点和 IOC、AOP、事务、Starter 串起来。
  8. PriorityOrderedOrdered 和普通处理器怎样分组执行。
  9. MergedBeanDefinitionPostProcessorSmartInstantiationAwareBeanPostProcessorDestructionAwareBeanPostProcessor 分别解决什么问题。
  10. 扩展点导致过早实例化、代理缺失、启动慢和 ClassLoader 泄漏时怎样排查。

容器生命周期视角

mermaid
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["销毁回调"]

不同扩展点对应不同阶段:

阶段扩展点能做什么
注册 BeanDefinitionBeanDefinitionRegistryPostProcessor新增、修改 BeanDefinition
修改 BeanFactoryBeanFactoryPostProcessor修改已有 BeanDefinition、属性占位符
实例化 BeanInstantiationAwareBeanPostProcessor实例化前后干预
合并定义元数据MergedBeanDefinitionPostProcessor缓存注入、生命周期等元数据
预测类型和早期引用SmartInstantiationAwareBeanPostProcessor构造器、类型、early proxy
初始化前后BeanPostProcessor包装 Bean、创建代理
销毁前DestructionAwareBeanPostProcessor在目标 Bean 销毁前清理
获取特殊 BeanFactoryBean用一个工厂对象生产真正 Bean
导入配置ImportSelector按条件导入配置类
延迟导入DeferredImportSelector在普通配置类处理后导入
注册 BeanDefinitionImportBeanDefinitionRegistrar编程式注册 BeanDefinition
感知容器对象Aware 系列接口获取 BeanName、BeanFactory、ApplicationContext
生命周期InitializingBeanDisposableBean初始化和销毁
启停控制SmartLifecycle容器启动停止时执行
事件机制ApplicationListener监听 Spring 事件

BeanDefinitionRegistryPostProcessor

它能在 BeanDefinition 注册阶段继续注册新的 BeanDefinition,比普通 BeanFactoryPostProcessor 更早。

mermaid
flowchart TD
    A["扫描到基础 BeanDefinition"] --> B["执行 RegistryPostProcessor"]
    B --> C["注册新的 BeanDefinition"]
    C --> D["执行 BeanFactoryPostProcessor"]
    D --> E["实例化 Bean"]

Demo:动态注册一个采集处理器。

java
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 不能只扫描一次,否则新注册的处理器永远没有机会参与定义阶段。

经典执行思路:

  1. 先执行通过 API 直接传入的处理器;
  2. 查找并执行 PriorityOrdered BDRPP;
  3. 查找并执行 Ordered BDRPP;
  4. 循环查找其余尚未执行的 BDRPP;
  5. 直到没有新 BDRPP;
  6. 再调用所有 BDRPP 的 postProcessBeanFactory()
  7. 最后进入普通 BFPP。
mermaid
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。

mermaid
flowchart TD
    A["BeanDefinition 已经存在"] --> B["BeanFactoryPostProcessor"]
    B --> C["修改属性、作用域、构造参数"]
    C --> D["按修改后的定义创建 Bean"]

Demo:给某个 Bean 设置懒加载。

java
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 区别:

对比BeanDefinitionRegistryPostProcessorBeanFactoryPostProcessor
执行时机更早稍后
核心能力注册新的 BeanDefinition修改已有 BeanDefinition
典型场景扫描并注册 Mapper、Feign修改属性、占位符、懒加载

BFPP 分组与过早实例化

容器中的普通 BFPP 也按 PriorityOrderedOrdered、无顺序三组发现、排序和调用。通过 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、事务代理、各种注解增强的重要基础。

mermaid
flowchart TD
    A["Bean 实例化"] --> B["属性填充"]
    B --> C["初始化前处理"]
    C --> D["初始化方法"]
    D --> E["初始化后处理"]
    E --> F["可能返回代理对象"]

Demo:给自定义注解的 Bean 包装监控。

java
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;
    }
}

为什么它重要:

  1. Spring AOP 代理通常在初始化后创建。
  2. @Autowired@Value 等注解处理也依赖后置处理器体系。
  3. 很多框架通过它给业务 Bean 增加代理、校验和包装。

常见坑:

后果
返回 null当前前置或后置处理器链提前停止,后续处理器失去机会
里面手动创建业务对象对象不受 Spring 管理
对所有 Bean 做重逻辑启动变慢
忽略代理对象AOP 和事务可能不符合预期

BPP 怎样发现、排序和注册

registerBeanPostProcessors() 大致分为:

  1. 取得全部 BPP Bean 名;
  2. 临时加入 BeanPostProcessorChecker
  3. 创建并注册 PriorityOrdered BPP;
  4. 创建并注册 Ordered BPP;
  5. 创建并注册普通 BPP;
  6. MergedBeanDefinitionPostProcessor 等内部处理器重新调整到合适位置;
  7. 最后重新注册 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 创建。

适合框架底层使用,业务项目尽量少用。

mermaid
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 获取的是它生产出来的对象。

mermaid
flowchart TD
    A["容器中注册 FactoryBean"] --> B["调用 getObject"]
    B --> C["返回真正业务对象"]
    A --> D["使用 &beanName"]
    D --> E["获取工厂本身"]

Demo:创建一个客户端代理。

java
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;
    }
}

常见应用:

框架用途
MyBatisMapper 接口代理
DubboRPC 接口代理
FeignHTTP 客户端代理
自定义 SDK动态创建客户端

面试常问:BeanFactory 和 FactoryBean 区别。

对比BeanFactoryFactoryBean
类型Spring 容器接口一个特殊 Bean
作用管理 Bean生产某个 Bean
获取方式getBeangetBean("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 用于根据条件导入配置类。

mermaid
flowchart TD
    A["@Import"] --> B["ImportSelector"]
    B --> C["返回配置类全限定名"]
    C --> D["Spring 解析导入的配置类"]

Demo:

java
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"
        };
    }
}

使用:

java
@Import(CollectImportSelector.class)
public class CollectEnableConfig {
}

适合用在 @EnableXxx 注解里。

DeferredImportSelector

DeferredImportSelector 是延迟导入选择器,会在普通配置类处理后再执行。Spring Boot 自动配置的 AutoConfigurationImportSelector 就和这个机制有关。

mermaid
flowchart TD
    A["解析普通配置类"] --> B["收集 DeferredImportSelector"]
    B --> C["普通配置处理完成"]
    C --> D["执行延迟导入"]
    D --> E["导入自动配置类"]

为什么自动配置适合延迟导入:

  1. 用户配置类先处理。
  2. 自动配置后处理。
  3. 自动配置可以通过 @ConditionalOnMissingBean 给用户 Bean 让位。

这也是 Boot 能做到“默认配置不抢用户配置”的关键之一。

ImportBeanDefinitionRegistrar

它可以编程式注册 BeanDefinition,比 ImportSelector 更灵活。

mermaid
flowchart TD
    A["@Import Registrar"] --> B["读取注解元信息"]
    B --> C["构造 BeanDefinition"]
    C --> D["注册到 BeanDefinitionRegistry"]

Demo:根据注解属性注册客户端。

java
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 名称
BeanFactoryAwareBeanFactory
ApplicationContextAwareApplicationContext
EnvironmentAwareEnvironment
ResourceLoaderAwareResourceLoader

Demo:

java
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

初始化和销毁扩展点:

mermaid
flowchart TD
    A["Bean 属性填充完成"] --> B["afterPropertiesSet"]
    B --> C["自定义 init 方法"]
    C --> D["Bean 可用"]
    D --> E["容器关闭"]
    E --> F["destroy"]

Demo:

java
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("关闭采集客户端连接池");
    }
}

更常见写法是使用注解:

java
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 可以参与容器启动和停止过程,适合管理需要随应用启停的组件。

mermaid
flowchart TD
    A["容器刷新完成"] --> B["start"]
    B --> C["组件运行中"]
    C --> D["容器关闭"]
    D --> E["stop"]

Demo:启动采集调度器。

java
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 事件适合做同进程内的解耦。

mermaid
flowchart TD
    A["业务发布事件"] --> B["ApplicationEventPublisher"]
    B --> C["事件监听器 1"]
    B --> D["事件监听器 2"]
    C --> E["写审计"]
    D --> F["刷新缓存"]

事件类:

java
public class CollectFinishedEvent {
    private final String batchNo;

    public CollectFinishedEvent(String batchNo) {
        this.batchNo = batchNo;
    }

    public String getBatchNo() {
        return batchNo;
    }
}

发布:

java
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));
    }
}

监听:

java
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 生命周期扩展点。简化理解:

mermaid
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 扩展点。

mermaid
flowchart TD
    A["Spring Boot 自动配置"] --> B["DeferredImportSelector"]
    B --> C["导入自动配置类"]
    C --> D["条件注解判断"]
    D --> E["注册 BeanDefinition"]
    E --> F["Spring IOC 创建 Bean"]
    F --> G["BeanPostProcessor 参与增强"]

所以学习顺序应该是:

  1. 先理解 IOC 和 BeanDefinition。
  2. 再理解 Bean 生命周期。
  3. 再理解后置处理器和代理。
  4. 最后理解 Spring Boot 自动配置。

商业 Starter 怎样组合扩展点

以“采集客户端 Starter”为例,不应在一个 BPP 中完成全部工作。合理拆分:

mermaid
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 依赖。

扩展点怎么选

需求推荐扩展点不推荐
动态注册一批 BeanBeanDefinitionRegistryPostProcessorImportBeanDefinitionRegistrar手写大量配置类
修改 Bean 定义BeanFactoryPostProcessorBean 创建后再改
Bean 初始化后创建代理BeanPostProcessor业务代码里手动代理
根据注解导入配置ImportSelector到处复制 @Configuration
生产特殊对象FactoryBean在业务 Service 里 new
容器启动后启动组件SmartLifecycle静态代码块启动线程
同进程事件解耦ApplicationEventPublisherService 互相强依赖

扩展点常见错误与后果

错误后果正确方向
BFPP注入普通ServiceBean过早创建、代理缺失只依赖定义和轻量元数据
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

启动慢

  1. 按 refresh 阶段区分 BDRPP/BFPP、BPP注册、Bean实例化和finishRefresh;
  2. 记录每个自定义处理器的调用次数和耗时;
  3. 检查重复classpath扫描、反射扫描和无界元数据缓存;
  4. 检查扩展点是否访问数据库、配置中心、DNS或HTTP;
  5. 使用 ApplicationStartup、JFR和连续线程栈;
  6. 不用全局Lazy掩盖定义处理器慢。

Bean没有被代理或注解没生效

  1. 检查对应BPP是否注册;
  2. 检查目标Bean是否在BPP完整注册前创建;
  3. 查看 BeanPostProcessorChecker 日志;
  4. 检查处理器顺序和是否有人返回null;
  5. 检查目标是否手工new、FactoryBean产品或父子容器对象;
  6. 检查注解保留策略、代理类型和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完成后执行,最后销毁。

java
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 构造一定晚于三个定义/工厂处理事件。

源码断点

text
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.7javax生态,兼顾spring.factories与imports机制
Spring 6 / Boot 3Java 17和jakarta生态,自动配置发现与AOT要求更严格

内部接口方法可能增加 default method 或演进,编写 Starter 要用目标版本编译和矩阵测试,不把 Spring 6 二进制直接用于 Spring 5 项目。

面试标准回答

可以这样答:

text
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 扩展点是框架生态的底层插槽。业务开发不一定每天都写这些接口,但必须知道它们存在于哪个生命周期阶段、能改变什么、不能滥用什么。理解这些扩展点后,再看 Spring Boot 自动配置、MyBatis Mapper 扫描、Feign 代理、事务 AOP,就不会觉得它们是黑盒魔法。