Skip to content

Spring 核心扩展点 ​

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

先记住一句话:

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

学习目标 ​

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

  1. BeanFactoryPostProcessor 和 BeanPostProcessor 的区别。
  2. BeanDefinitionRegistryPostProcessor 为什么比普通后置处理器更早。
  3. FactoryBean 和普通 Bean 有什么区别。
  4. ImportSelector、DeferredImportSelector、ImportBeanDefinitionRegistrar 分别适合什么。
  5. Aware、InitializingBean、DisposableBean、SmartLifecycle、ApplicationListener 在生命周期哪里执行。
  6. Spring Boot 自动配置为什么离不开 Spring 扩展点。
  7. 面试中如何把扩展点和 IOC、AOP、事务、Starter 串起来。
  8. PriorityOrdered、Ordered 和普通处理器怎样分组执行。
  9. MergedBeanDefinitionPostProcessor、SmartInstantiationAwareBeanPostProcessor、DestructionAwareBeanPostProcessor 分别解决什么问题。
  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
生命周期InitializingBean、DisposableBean初始化和销毁
启停控制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 也按 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、事务代理、各种注解增强的重要基础。

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

扩展点怎么选 ​

需求推荐扩展点不推荐
动态注册一批 BeanBeanDefinitionRegistryPostProcessor 或 ImportBeanDefinitionRegistrar手写大量配置类
修改 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,就不会觉得它们是黑盒魔法。