引言:为什么 Spring 的扩展点是面试必考题
如果你在简历上写了"熟悉 Spring 源码",面试官大概率会问:"Spring 有哪些扩展点?它们的执行顺序是什么?你在项目中用它们解决过什么问题?"这道题考察的不是"知道有哪些注解",而是"理解 Spring 容器从 Bean 定义加载到 Bean 实例化再到初始化完成这条链路上,每个阶段提供了什么钩子,以及这些钩子之间有什么区别"。
Spring 的扩展点体系有四个核心接口:BeanFactoryPostProcessor(修改 Bean 定义)、BeanPostProcessor(拦截 Bean 初始化)、ImportSelector(动态导入配置类)、FactoryBean(自定义 Bean 创建逻辑)。它们各自在容器的不同阶段被调用,执行顺序和适用场景完全不同。这篇文章把四个扩展点的原理、执行顺序和工程实践讲清楚。
一、BeanFactoryPostProcessor:在 Bean 实例化之前修改 Bean 定义


核心接口
@FunctionalInterface
public interface BeanFactoryPostProcessor {
void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException;
}
BeanFactoryPostProcessor(简称 BFPP)在 ApplicationContext.refresh() 的 invokeBeanFactoryPostProcessors 阶段被调用。此时所有 Bean 定义已经加载到 BeanFactory 中,但还没有任何 Bean 被实例化。BFPP 的 postProcessBeanFactory 方法可以遍历所有 Bean 定义,修改其属性值、作用域、依赖关系等。
一个最常见的 BFPP 实现:PropertySourcesPlaceholderConfigurer
@Bean
public static PropertySourcesPlaceholderConfigurer propertyConfigurer() {
return new PropertySourcesPlaceholderConfigurer();
}
这个 BFPP 做的事情是:扫描所有 Bean 定义中的 ${...} 占位符,将其替换为 Environment 中对应的配置值。如果没有它,@Value("${server.port}") 中的 ${server.port} 不会被解析,注入的值就是 ${server.port} 这个字符串本身。
为什么必须是 static Bean
如果你在 @Configuration 类中定义 PropertySourcesPlaceholderConfigurer,必须用 static 方法:
@Bean
public static PropertySourcesPlaceholderConfigurer propertyConfigurer() { ... }
原因:BFPP 的初始化顺序在 @Configuration 类的 Bean 实例化之前。如果 Bean 方法不是 static,Spring 需要先实例化 @Configuration 类才能调用这个方法——但 @Configuration 类的实例化又依赖 BFPP 已经执行完毕(因为 @Configuration 类中可能有 @Value 注入)。这是个循环依赖。static 方法不依赖类实例,Spring 可以在 @Configuration 类实例化之前就调用它。
工程实践:自定义 BFPP 修改第三方 Bean 定义
如果你需要修改一个第三方 jar 包中定义的 Bean 的作用域或属性,但无法修改其源码,BFPP 是最合适的工具:
@Component
public class CustomBfpp implements BeanFactoryPostProcessor {
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) {
BeanDefinition bd = beanFactory.getBeanDefinition("thirdPartyBean");
bd.setScope(BeanDefinition.SCOPE_PROTOTYPE);
bd.getPropertyValues().add("timeout", 30000);
}
}
二、BeanDefinitionRegistryPostProcessor:BFP 的增强版
核心接口
public interface BeanDefinitionRegistryPostProcessor extends BeanFactoryPostProcessor {
void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException;
}
BeanDefinitionRegistryPostProcessor(简称 BDRPP)继承自 BFPP,在 BFPP 之前执行。它多了一个 postProcessBeanDefinitionRegistry 方法,可以动态注册新的 Bean 定义,而不仅仅是修改已有的。
最重要的 BDRPP 实现:ConfigurationClassPostProcessor
ConfigurationClassPostProcessor 是 Spring 容器中最重要的 BDRPP,它负责解析所有 @Configuration 类:处理 @ComponentScan、@Import、@Bean 方法、@PropertySource 等。
// 这个处理器是 Spring 内部自动注册的,不需要手动声明
// 它做的事情包括:
// 1. 解析 @Configuration 类中的 @Bean 方法,注册为 Bean 定义
// 2. 处理 @ComponentScan,扫描并注册组件
// 3. 处理 @Import,导入其他配置类
// 4. 处理 @ImportResource,导入 XML 配置
BDRPP 与 BFPP 的执行顺序
在 invokeBeanFactoryPostProcessors 阶段,Spring 先执行所有 BDRPP 的 postProcessBeanDefinitionRegistry,再执行所有 BFPP 的 postProcessBeanFactory。同一类型内部按 PriorityOrdered → Ordered → 普通顺序执行。
三、BeanPostProcessor:在 Bean 初始化时拦截

核心接口
public interface BeanPostProcessor {
@Nullable
default Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
return bean;
}
@Nullable
default Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
return bean;
}
}
BeanPostProcessor(简称 BPP)在 Bean 实例化之后、初始化阶段被调用。postProcessBeforeInitialization 在 @PostConstruct 和 InitializingBean.afterPropertiesSet() 之前执行,postProcessAfterInitialization 在它们之后执行。
BPP 的典型实现
| 实现类 | 作用 | 阶段 |
|---|---|---|
AutowiredAnnotationBeanPostProcessor |
处理 @Autowired、@Value 注入 |
before |
CommonAnnotationBeanPostProcessor |
处理 @PostConstruct、@PreDestroy、@Resource |
before |
AsyncAnnotationBeanPostProcessor |
处理 @Async 注解,创建异步代理 |
after |
AnnotationAwareAspectJAutoProxyCreator |
处理 @Aspect AOP 切面,创建代理对象 |
after |
BPP 可以返回代理对象
postProcessAfterInitialization 的返回值可以是一个代理对象。如果 BPP 返回了与原 Bean 不同的对象,Spring 会用返回的对象替换容器中的 Bean。这是 AOP 和 @Async 等代理机制的基础——AnnotationAwareAspectJAutoProxyCreator 在 postProcessAfterInitialization 中判断 Bean 是否需要 AOP 增强,如果需要则创建 JDK 动态代理或 CGLIB 代理并返回,替换原 Bean。
BPP 与 BFPP 的根本区别
| 维度 | BFPP | BPP |
|---|---|---|
| 操作对象 | BeanDefinition(配置元数据) | Bean 实例(已创建的对象) |
| 执行阶段 | refresh 早期,Bean 实例化之前 | Bean 初始化阶段,每个 Bean 一次 |
| 典型用途 | 修改 Bean 定义属性、注册新 Bean 定义 | 注入依赖、创建代理、执行初始化回调 |
| 作用于 | 所有 Bean 定义 | 所有 Bean 实例 |
四、ImportSelector:动态导入配置类

核心接口
public interface ImportSelector {
String[ ] selectImports(AnnotationMetadata importingClassMetadata);
}
ImportSelector 通过 @Import 注解引入,在 ConfigurationClassPostProcessor 解析 @Import 时被调用。selectImports 方法返回一组类名,这些类会像 @Import 直接引入的类一样被注册为 Bean 定义。
最经典的 ImportSelector:AutoConfigurationImportSelector
AutoConfigurationImportSelector 是 Spring Boot 自动配置的核心——它从 META-INF/spring/...AutoConfiguration.imports 文件中读取所有自动配置类的全限定名并返回,让 Spring 把它们当作 @Import 的类一样处理。
自定义 ImportSelector 的场景
public class MyImportSelector implements ImportSelector {
@Override
public String[ ] selectImports(AnnotationMetadata metadata) {
// 根据注解元数据动态决定导入哪些配置类
if (metadata.hasAnnotation("com.example.EnableMyFeature")) {
return new String[ ]{"com.example.MyConfig.class"};
}
return new String[0];
}
}
// 使用
@Import(MyImportSelector.class)
@Configuration
public class AppConfig { }
ImportSelector 与 ImportBeanDefinitionRegistrar
ImportSelector 只返回类名,让 Spring 处理这些类的注册逻辑。ImportBeanDefinitionRegistrar 提供了更细粒度的控制——可以直接向 BeanDefinitionRegistry 注册自定义的 BeanDefinition:
public interface ImportBeanDefinitionRegistrar {
void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry);
}
ImportBeanDefinitionRegistrar 的使用场景是:你需要注册的 Bean 不是通过 @Configuration 类中的 @Bean 方法定义的,而是需要动态构造 BeanDefinition 对象(比如动态指定 Bean 的类名,而这个类名在编译期不可知)。
五、FactoryBean:自定义 Bean 创建逻辑

核心接口
public interface FactoryBean<T> {
@Nullable
T getObject() throws Exception; // 返回创建的 Bean 实例
@Nullable
Class<?> getObjectType(); // 返回 Bean 的类型
default boolean isSingleton() { return true; }
}
FactoryBean 是一个特殊的 Bean——它本身被注册到容器中,但调用 getBean("myFactoryBean") 时返回的不是 FactoryBean 实例本身,而是 getObject() 方法的返回值。如果要获取 FactoryBean 实例本身,需要加 & 前缀:getBean("&myFactoryBean")。
工程实践:用 FactoryBean 封装复杂 Bean 的创建逻辑
@Component
public class SqlSessionFactoryBean implements FactoryBean<SqlSessionFactory> {
@Override
public SqlSessionFactory getObject() throws Exception {
// MyBatis 的 SqlSessionFactory 创建逻辑复杂(解析 XML 配置、注册 Mapper 等)
// 用 FactoryBean 封装,使用者只需 @Autowired SqlSessionFactory
return new SqlSessionFactoryBuilder().build(configuration);
}
@Override
public Class<?> getObjectType() {
return SqlSessionFactory.class;
}
}
FactoryBean 与 @Bean 方法的区别
@Bean 方法在 @Configuration 类中声明,一次调用创建一次 Bean。FactoryBean 的优势在于:它创建的 Bean 本身不需要一个 @Configuration 类来承载,可以独立注册;而且 getObject() 可以有复杂的创建逻辑(如动态代理、远程调用、连接池创建),这些逻辑不适合放在 @Bean 方法中。
六、四个扩展点的执行顺序与调用链路

在 ApplicationContext.refresh() 的 12 个步骤中,四个扩展点的执行顺序如下:
1. invokeBeanFactoryPostProcessors
├── BeanDefinitionRegistryPostProcessor.postProcessBeanDefinitionRegistry()
│ ├── ConfigurationClassPostProcessor(解析 @Configuration、@Import、@Bean)
│ │ └── ImportSelector.selectImports()(动态导入配置类)
│ └── 其他 BDRPP
└── BeanFactoryPostProcessor.postProcessBeanFactory()
└── PropertySourcesPlaceholderConfigurer(替换 ${...} 占位符)
2. registerBeanPostProcessors
└── 注册所有 BeanPostProcessor 到容器
3. finishBeanFactoryInitialization
└── 实例化所有单例 Bean
└── 对每个 Bean 执行:
├── BeanPostProcessor.postProcessBeforeInitialization()
├── @PostConstruct / InitializingBean.afterPropertiesSet()
└── BeanPostProcessor.postProcessAfterInitialization()
└── AOP 代理创建(如果适用)
一个关键的顺序问题:@Configuration 类中的 @Value
因为 BFPP 在 @Configuration 类解析之前执行,而 @Configuration 类解析(由 ConfigurationClassPostProcessor 完成)是在 BDRPP 阶段,这导致一个常见的困惑:@Configuration 类中的 @Value 注入是否能正常工作?答案是:能。因为 @Value 的处理由 AutowiredAnnotationBeanPostProcessor(一个 BPP)完成,它在 Bean 实例化之后执行,此时 BFPP 早已执行完毕,${...} 占位符已经被替换了。
七、常见陷阱


陷阱一:BFPP 中不能使用 @Autowired 注入 Bean
BFPP 在 Bean 实例化之前执行,此时容器中还没有任何业务 Bean。如果 BFPP 中 @Autowired 了一个业务 Bean,注入会失败或注入一个未初始化的半成品。BFPP 只能使用 BeanFactory 提供的方法(如 getBeanDefinition),不能依赖任何业务 Bean。
陷阱二:BPP 返回 null 会导致 Bean 被删除
postProcessBeforeInitialization 如果返回 null,Spring 会跳过后续的初始化步骤,并且该 Bean 不会被注册到容器中——相当于被静默删除了。这是 BPP 的一个"特性",但如果不小心返回了 null,排查起来非常隐蔽(没有任何报错,但 Bean 就是不存在)。
陷阱三:FactoryBean 的 getObject() 在每次 getBean() 时都调用
如果 isSingleton() 返回 false,getObject() 在每次 getBean() 调用时都会执行。这意味着如果你的 getObject() 中有耗时操作(如创建连接),每次注入都会创建新连接——不是你期望的"单例"行为。确保 isSingleton() 返回 true(默认值),除非你明确需要 prototype 行为。
陷阱四:ImportSelector 的 selectImports 在 ConfigurationClassPostProcessor 中执行
ImportSelector.selectImports() 的执行时机非常早——在 ConfigurationClassPostProcessor 的 postProcessBeanDefinitionRegistry 阶段,此时 @Configuration 类的 @Bean 方法还没有被解析,@Value 注入也还没有发生。selectImports 中不能依赖任何 Bean 实例,只能根据注解元数据(AnnotationMetadata)做决策。
小结:扩展点是"何时"与"何物"的组合
理解 Spring 的扩展点,核心是记住两个维度:何时执行(在 refresh 的哪个阶段)和 操作什么(BeanDefinition 还是 Bean 实例)。BFPP 在早期操作 BeanDefinition,BPP 在初始化阶段操作 Bean 实例,ImportSelector 在 BDRPP 阶段导入配置类,FactoryBean 在 Bean 实例化时创建自定义对象。把这两个维度对齐,你就能在面试中清楚地回答"如果我想在某个时刻对某个东西做某件事,应该用哪个扩展点"。
(以上正文阅读时长约 40-50 分钟,以下问答部分不计入正文阅读时长)
文末答疑:面试官会怎么问
问 1:**BeanFactoryPostProcessor**和**BeanPostProcessor**名字很像,但执行时机完全不同。如果面试官让你一句话解释它们的区别,你怎么说?
答:BeanFactoryPostProcessor 在 Bean 实例化之前操作 Bean 定义(BeanDefinition),可以修改 Bean 的类名、作用域、属性值等元数据;BeanPostProcessor 在 Bean 实例化之后拦截 Bean 的初始化过程,可以在 @PostConstruct 前后执行自定义逻辑,也可以返回代理对象替换原 Bean。一句话:BFPP 改"配方",BPP 改"成品"。
问 2:**@Configuration**类中的**@Bean**方法,什么时候被解析为 Bean 定义?如果是**@Component**类中的**@Bean**方法呢?
答:@Configuration 类中的 @Bean 方法在 ConfigurationClassPostProcessor 的 postProcessBeanDefinitionRegistry 阶段被解析(BDRPP 阶段)。此时 @Configuration 类本身还没有被实例化,Spring 通过解析类的字节码(使用 ASM)读取 @Bean 方法的元数据,直接注册为 Bean 定义。@Component 类中的 @Bean 方法(lite 模式)处理方式不同:@Component 类先被实例化为普通 Bean,然后 Spring 再扫描它的方法,发现有 @Bean 注解的方法就注册为 Bean 定义——但这个注册发生在 @Component 类实例化之后,时机比 @Configuration 类晚。这也解释了为什么 @Configuration 类中的 @Bean 方法调用会被 CGLIB 代理拦截(保证单例),而 @Component 类中的不会。
问 3:如果有两个**BeanPostProcessor**,一个返回代理对象,另一个在代理对象上做处理,它们的执行顺序如何保证?
答:通过实现 PriorityOrdered 或 Ordered 接口,或者用 @Order 注解。BPP 的注册顺序决定了执行顺序——先注册的 BPP 先执行 postProcessBeforeInitialization,但 postProcessAfterInitialization 也是先注册的先执行。如果 BPP-A 创建了代理对象,BPP-B 在 postProcessAfterInitialization 中收到的就是代理对象——因为 BPP-A 先执行,返回的代理对象被传递给 BPP-B。如果你希望 BPP-B 在代理对象创建之前处理原始 Bean,需要让 BPP-B 的优先级高于 AOP 的 BPP(AnnotationAwareAspectJAutoProxyCreator),即让 BPP-B 的 Order 值更小。
问 4:**FactoryBean**和**ObjectFactory**有什么区别?什么场景下用哪个?
答:FactoryBean 是 Spring 容器的扩展点,它本身是一个 Bean,但 getBean("name") 返回的是 getObject() 的返回值。ObjectFactory 是一个函数式接口(T getObject()),通常用于延迟加载——注入一个 ObjectFactory<T> 而不是直接注入 T,可以在需要时才调用 getObject() 获取实例。区别在于:FactoryBean 的创建时机由 Spring 容器控制(在 Bean 实例化时),ObjectFactory 的创建时机由调用方控制(调用 getObject() 时)。FactoryBean 适合"创建逻辑复杂、需要被 Spring 管理"的场景(如 MyBatis 的 SqlSessionFactoryBean),ObjectFactory 适合"延迟加载、避免循环依赖"的场景。
问 5:你在项目中用**BeanDefinitionRegistryPostProcessor**解决过什么问题?它的使用场景和**BeanFactoryPostProcessor**有什么不同?
答:BDRPP 的核心能力是动态注册 Bean 定义——这是 BFPP 做不到的。我用的场景是:在微服务架构中,需要根据环境变量动态决定注册哪些 RPC 客户端 Stub。BDRPP 在启动时读取环境变量,如果 rpc.provider=sofa,就注册 SOFA RPC 的 Stub Bean 定义;如果 rpc.provider=dubbo,就注册 Dubbo 的。BDRPP 和 BFPP 的关键区别:BDRPP 可以注册新的 Bean 定义,BFPP 只能修改已有的 Bean 定义。如果你的逻辑需要"根据条件决定是否注册某个 Bean",用 BDRPP;如果只需要"修改某个 Bean 的属性值",用 BFPP 就够了。