Spring 扩展点:BeanFactoryPostProcessor、BeanPostProcessor、ImportSelector、FactoryBean(100)

Spring与Spring Boot约 14 分钟阅读7,830 字符
知识库文档 101 / 251

引言:为什么 Spring 的扩展点是面试必考题

如果你在简历上写了"熟悉 Spring 源码",面试官大概率会问:"Spring 有哪些扩展点?它们的执行顺序是什么?你在项目中用它们解决过什么问题?"这道题考察的不是"知道有哪些注解",而是"理解 Spring 容器从 Bean 定义加载到 Bean 实例化再到初始化完成这条链路上,每个阶段提供了什么钩子,以及这些钩子之间有什么区别"。

Spring 的扩展点体系有四个核心接口:BeanFactoryPostProcessor(修改 Bean 定义)、BeanPostProcessor(拦截 Bean 初始化)、ImportSelector(动态导入配置类)、FactoryBean(自定义 Bean 创建逻辑)。它们各自在容器的不同阶段被调用,执行顺序和适用场景完全不同。这篇文章把四个扩展点的原理、执行顺序和工程实践讲清楚。

一、BeanFactoryPostProcessor:在 Bean 实例化之前修改 Bean 定义

文章配图
文章配图

文章配图
文章配图

核心接口

java
@FunctionalInterface
public interface BeanFactoryPostProcessor {
    void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException;
}

BeanFactoryPostProcessor(简称 BFPP)在 ApplicationContext.refresh() 的 invokeBeanFactoryPostProcessors 阶段被调用。此时所有 Bean 定义已经加载到 BeanFactory 中,但还没有任何 Bean 被实例化。BFPP 的 postProcessBeanFactory 方法可以遍历所有 Bean 定义,修改其属性值、作用域、依赖关系等。

一个最常见的 BFPP 实现:PropertySourcesPlaceholderConfigurer

java
@Bean
public static PropertySourcesPlaceholderConfigurer propertyConfigurer() {
    return new PropertySourcesPlaceholderConfigurer();
}

这个 BFPP 做的事情是:扫描所有 Bean 定义中的 ${...} 占位符,将其替换为 Environment 中对应的配置值。如果没有它,@Value("${server.port}") 中的 ${server.port} 不会被解析,注入的值就是 ${server.port} 这个字符串本身。

为什么必须是 static Bean

如果你在 @Configuration 类中定义 PropertySourcesPlaceholderConfigurer,必须用 static 方法:

java
@Bean
public static PropertySourcesPlaceholderConfigurer propertyConfigurer() { ... }

原因:BFPP 的初始化顺序在 @Configuration 类的 Bean 实例化之前。如果 Bean 方法不是 static,Spring 需要先实例化 @Configuration 类才能调用这个方法——但 @Configuration 类的实例化又依赖 BFPP 已经执行完毕(因为 @Configuration 类中可能有 @Value 注入)。这是个循环依赖。static 方法不依赖类实例,Spring 可以在 @Configuration 类实例化之前就调用它。

工程实践:自定义 BFPP 修改第三方 Bean 定义

如果你需要修改一个第三方 jar 包中定义的 Bean 的作用域或属性,但无法修改其源码,BFPP 是最合适的工具:

java
@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 的增强版

核心接口

java
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 等。

java
// 这个处理器是 Spring 内部自动注册的,不需要手动声明
// 它做的事情包括:
// 1. 解析 @Configuration 类中的 @Bean 方法,注册为 Bean 定义
// 2. 处理 @ComponentScan,扫描并注册组件
// 3. 处理 @Import,导入其他配置类
// 4. 处理 @ImportResource,导入 XML 配置

BDRPP 与 BFPP 的执行顺序

在 invokeBeanFactoryPostProcessors 阶段,Spring 先执行所有 BDRPP 的 postProcessBeanDefinitionRegistry,再执行所有 BFPP 的 postProcessBeanFactory。同一类型内部按 PriorityOrdered → Ordered → 普通顺序执行。

三、BeanPostProcessor:在 Bean 初始化时拦截

文章配图
文章配图

核心接口

java
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:动态导入配置类

文章配图
文章配图

核心接口

java
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 的场景

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

java
public interface ImportBeanDefinitionRegistrar {
    void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry);
}

ImportBeanDefinitionRegistrar 的使用场景是:你需要注册的 Bean 不是通过 @Configuration 类中的 @Bean 方法定义的,而是需要动态构造 BeanDefinition 对象(比如动态指定 Bean 的类名,而这个类名在编译期不可知)。

五、FactoryBean:自定义 Bean 创建逻辑

文章配图
文章配图

核心接口

java
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 的创建逻辑

java
@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 个步骤中,四个扩展点的执行顺序如下:

code
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 就够了。