BeanFactoryPostProcessor源码分析

BeanFactoryPostProcessor

一、基本信息

✒️ 作者 - Lex 📝 博客 - 我的CSDN 📚 文章目录 - 所有文章 🔗 源码地址 - BeanFactoryPostProcessor源码

二、接口描述

BeanFactoryPostProcessor 是一个接口,任何实现此接口的类都必须提供postProcessBeanFactory方法的实现。此方法提供了一个机会,在bean实例化之前修改bean的定义。

三、接口源码

BeanFactoryPostProcessor 是 Spring 框架自 06.07.2003 开始引入的一个核心接口。此接口提供了一个强大的方式来修改bean的属性和依赖关系,使得我们可以根据特定的配置或环境条件动态地调整这些值。

/**
 * BeanFactoryPostProcessor 是一个核心接口,允许用户在Spring容器初始化bean之前修改bean定义。
 * 它提供了一个强大的方式来修改bean的属性和依赖关系,使得我们可以根据特定的配置或环境条件动态地调整这些值。
 * 
 * @author Juergen Hoeller
 * @author Sam Brannen
 * @since 06.07.2003
 * @see BeanPostProcessor
 * @see PropertyResourceConfigurer
 */
@FunctionalInterface
public interface BeanFactoryPostProcessor {

    /**
     * 在应用上下文的内部bean工厂进行其标准初始化后修改它。
     * 此时,所有bean定义都已加载,但尚未实例化任何bean。
     * 这允许用户即使对于急切初始化的beans也可以覆盖或添加属性。
     * 
     * @param beanFactory 应用上下文使用的bean工厂
     * @throws org.springframework.beans.BeansException 如果发生错误
     */
    void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException;

}

四、主要功能

  1. 修改Bean定义

    • 在Spring加载所有bean定义后,但在它开始实例化这些bean之前,BeanFactoryPostProcessor会被调用。这意味着我们可以使用它来修改这些bean定义。
  2. 更改属性值

    • 我们可以更改bean的属性或依赖,这在某些场景下,如需要根据环境或其他外部因素动态地配置bean时,会非常有用。
  3. 添加或删除Bean定义

    • 不仅可以修改现有的bean定义,还可以添加新的bean定义或删除现有的bean定义。
  4. 应用多个BeanFactoryPostProcessors

    • 如果有多个BeanFactoryPostProcessor,我们可以通过实现Ordered接口来控制它们的执行顺序。

五、最佳实践

首先来看看启动类入口,上下文环境使用AnnotationConfigApplicationContext(此类是使用Java注解来配置Spring容器的方式),构造参数我们给定了一个MyConfiguration组件类。然后我们会调用mySimpleBean1mySimpleBean2中的show()方法,我们可以判断MySimpleBean的作用域是单例还是原型。如果它们指向同一个实例,那么它是单例的;否则,它是原型的。

public class BeanFactoryPostProcessorApplication {

    public static void main(String[] args) {
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(MyConfiguration.class);

        MySimpleBean mySimpleBean1 = context.getBean(MySimpleBean.class);
        MySimpleBean mySimpleBean2 = context.getBean(MySimpleBean.class);

        mySimpleBean1.show();
        mySimpleBean2.show();
    }
}

这里使用@Bean注解,定义了两个Bean,是为了确保MySimpleBeanMyBeanFactoryPostProcessor 被 Spring 容器执行

@Configuration
public class MyConfiguration {

    @Bean
    public MySimpleBean mySimpleBean(){
        return new MySimpleBean();
    }

    @Bean
    public static MyBeanFactoryPostProcessor myBeanFactoryPostProcessor(){
        return new MyBeanFactoryPostProcessor();
    }
}

MySimpleBean类中的show方法在被调用时,会在控制台输出“MySimpleBean instance”和当前对象的实例地址(通过this关键字)。这有助于我们了解每次获取bean时是否返回相同的实例(单例)还是新的实例(原型)。

public class MySimpleBean {

    public void show() {
        System.out.println("MySimpleBean instance: " + this);
    }
}

这个 MyBeanFactoryPostProcessor 类是一个简单的 BeanFactoryPostProcessor 的实现,它在被调用时,会从beanFactory工厂中获取名为mySimpleBean的bean定义,默认情况下,所有的bean都是单例的,然后将mySimpleBean的作用域从单例改为原型。在实际应用中,我们可能会在 postProcessBeanFactory 方法内部执行更复杂的操作,例如修改 bean 的属性、对Bean对象进行代理做功能增强处理、更改它们的作用域或添加新的 bean 定义等。

public class MyBeanFactoryPostProcessor implements BeanFactoryPostProcessor {

    @Override
    public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
        System.out.println("修改bean的定义");
        BeanDefinition beanDefinition = beanFactory.getBeanDefinition("mySimpleBean");
        beanDefinition.setScope(BeanDefinition.SCOPE_PROTOTYPE);
        System.out.println("将mySimpleBean从默认的单例修改成多例");
        System.out.println("修改bean的定义已完成");
    }
}

运行结果发现,由于mySimpleBean现在是原型作用域,[com.xcs.spring.config.MySimpleBean@11392934][com.xcs.spring.config.MySimpleBean@6892b3b6]将是两个不同的地址,说明MySimpleBean的两个实例是不同的。

修改bean的定义
将mySimpleBean从默认的单例修改成多例
修改bean的定义已完成
MySimpleBean instance: com.xcs.spring.config.MySimpleBean@11392934
MySimpleBean instance: com.xcs.spring.config.MySimpleBean@6892b3b6

六、时序图

sequenceDiagram
    Title: BeanFactoryPostProcessor时序图
    participant BeanFactoryPostProcessorApplication
    participant AnnotationConfigApplicationContext
    participant AbstractApplicationContext
    participant PostProcessorRegistrationDelegate
    participant MyBeanFactoryPostProcessor
    participant ConfigurableListableBeanFactory
    
    BeanFactoryPostProcessorApplication->>AnnotationConfigApplicationContext:AnnotationConfigApplicationContext(componentClasses)<br>启动上下文
    AnnotationConfigApplicationContext->>AbstractApplicationContext:refresh()
    AbstractApplicationContext->>AbstractApplicationContext:invokeBeanFactoryPostProcessors(beanFactory)<br>触发整个BeanFactoryPostProcessor调用的流程
    AbstractApplicationContext->>PostProcessorRegistrationDelegate:invokeBeanFactoryPostProcessors(...)<br>确保正确的顺序触发BeanFactoryPostProcessor调用的流程
    PostProcessorRegistrationDelegate->>PostProcessorRegistrationDelegate:invokeBeanFactoryPostProcessors(postProcessors,beanFactory)<br>最终对BeanFactoryPostProcessor接口回调
    PostProcessorRegistrationDelegate->>MyBeanFactoryPostProcessor:postProcessBeanFactory(beanFactory)<br>执行自定义的逻辑
    MyBeanFactoryPostProcessor-->>ConfigurableListableBeanFactory:访问和修改bean定义
    ConfigurableListableBeanFactory-->>MyBeanFactoryPostProcessor:修改已完成
    PostProcessorRegistrationDelegate-->>AbstractApplicationContext: 调用Bean工厂后置处理器完成
    AnnotationConfigApplicationContext->>BeanFactoryPostProcessorApplication:初始化完成

七、源码分析

首先来看看启动类入口,上下文环境使用AnnotationConfigApplicationContext(此类是使用Java注解来配置Spring容器的方式),构造参数我们给定了一个MyConfiguration组件类。从Spring上下文中获取MySimpleBean的两个实例,调用这两个实例的show方法,如果MySimpleBean是单例的,那么这两个实例应该是同一个对象,反之则不是。

public class BeanFactoryPostProcessorApplication {

    public static void main(String[] args) {
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(MyConfiguration.class);

        MySimpleBean mySimpleBean1 = context.getBean(MySimpleBean.class);
        MySimpleBean mySimpleBean2 = context.getBean(MySimpleBean.class);

        mySimpleBean1.show();
        mySimpleBean2.show();
    }
}

org.springframework.context.annotation.AnnotationConfigApplicationContext#AnnotationConfigApplicationContext构造函数中,执行了三个步骤,我们重点关注refresh()方法。

public AnnotationConfigApplicationContext(Class<?>... componentClasses) {
    this();
    register(componentClasses);
    refresh();
}

org.springframework.context.support.AbstractApplicationContext#refresh方法中我们重点关注一下finishBeanFactoryInitialization(beanFactory)这方法会对实例化所有剩余非懒加载的单列Bean对象,其他方法不是本次源码阅读的重点暂时忽略。

@Override
public void refresh() throws BeansException, IllegalStateException {
     // ... [代码部分省略以简化]
     // 调用在上下文中注册为bean的工厂处理器
     invokeBeanFactoryPostProcessors(beanFactory);
     // ... [代码部分省略以简化]
}

org.springframework.context.support.AbstractApplicationContext#invokeBeanFactoryPostProcessors方法中,又委托了PostProcessorRegistrationDelegate.invokeBeanFactoryPostProcessors()进行调用。

protected void invokeBeanFactoryPostProcessors(ConfigurableListableBeanFactory beanFactory) {
    PostProcessorRegistrationDelegate.invokeBeanFactoryPostProcessors(beanFactory, getBeanFactoryPostProcessors());
    // ... [代码部分省略以简化]
}

org.springframework.context.support.PostProcessorRegistrationDelegate#invokeBeanFactoryPostProcessors方法中,主要是对BeanDefinitionRegistryPostProcessorBeanFactoryPostProcessor这两个接口的实现类进行回调,至于为什么这个方法里面代码很长呢?其实这个方法就做了一个事就是对处理器的执行顺序在做处理。比如说要先对实现了PriorityOrdered.class类回调,在对实现了Ordered.class类回调,最后才是对没有实现任何优先级的处理器进行回调。

public static void invokeBeanFactoryPostProcessors(
        ConfigurableListableBeanFactory beanFactory, List<BeanFactoryPostProcessor> beanFactoryPostProcessors) {

    // ... [代码部分省略以简化]

    // 获取所有实现了BeanFactoryPostProcessor接口的bean的名称。
    String[] postProcessorNames =
        beanFactory.getBeanNamesForType(BeanFactoryPostProcessor.class, true, false);

    // 创建集合以区分不同类型的BeanFactoryPostProcessors
    List<BeanFactoryPostProcessor> priorityOrderedPostProcessors = new ArrayList<>();
    List<String> orderedPostProcessorNames = new ArrayList<>();
    List<String> nonOrderedPostProcessorNames = new ArrayList<>();

    // 对BeanFactoryPostProcessors进行分类
    for (String ppName : postProcessorNames) {
        if (processedBeans.contains(ppName)) {
            // 如果这个bean已经被处理,直接跳过
        }
        else if (beanFactory.isTypeMatch(ppName, PriorityOrdered.class)) {
            // 优先排序的BeanFactoryPostProcessors
            priorityOrderedPostProcessors.add(beanFactory.getBean(ppName, BeanFactoryPostProcessor.class));
        }
        else if (beanFactory.isTypeMatch(ppName, Ordered.class)) {
            // 有排序的BeanFactoryPostProcessors
            orderedPostProcessorNames.add(ppName);
        }
        else {
            // 没有排序的BeanFactoryPostProcessors
            nonOrderedPostProcessorNames.add(ppName);
        }
    }

    // 调用实现了PriorityOrdered接口的BeanFactoryPostProcessors
    sortPostProcessors(priorityOrderedPostProcessors, beanFactory);
    invokeBeanFactoryPostProcessors(priorityOrderedPostProcessors, beanFactory);

    // 调用实现了Ordered接口的BeanFactoryPostProcessors
    List<BeanFactoryPostProcessor> orderedPostProcessors = new ArrayList<>(orderedPostProcessorNames.size());
    for (String postProcessorName : orderedPostProcessorNames) {
        orderedPostProcessors.add(beanFactory.getBean(postProcessorName, BeanFactoryPostProcessor.class));
    }
    sortPostProcessors(orderedPostProcessors, beanFactory);
    invokeBeanFactoryPostProcessors(orderedPostProcessors, beanFactory);

    // 调用其他所有的BeanFactoryPostProcessors
    List<BeanFactoryPostProcessor> nonOrderedPostProcessors = new ArrayList<>(nonOrderedPostProcessorNames.size());
    for (String postProcessorName : nonOrderedPostProcessorNames) {
        nonOrderedPostProcessors.add(beanFactory.getBean(postProcessorName, BeanFactoryPostProcessor.class));
    }
    invokeBeanFactoryPostProcessors(nonOrderedPostProcessors, beanFactory);

    // 清除元数据缓存,因为BeanFactoryPostProcessors可能已修改bean定义
    beanFactory.clearMetadataCache();
}

下面是我画的一个关于BeanFactoryPostProcessor排序回调过程时序图大家可以参考一下。

sequenceDiagram
    Title: BeanFactoryPostProcessor回调排序时序图
    participant Init as invokeBeanFactoryPostProcessors
    participant BFPP_PO as BFPP(PriorityOrdered)
    participant BFPP_O as BFPP(Ordered)
    participant BFPP as 其余的BFPP

    Init->>BFPP_PO: 回调
    BFPP_PO-->>Init: 完成
    Init->>BFPP_O: 回调
    BFPP_O-->>Init: 完成
    Init->>BFPP: 回调
    BFPP-->>Init: 完成
    
    Note right of BFPP: 提示: 
    Note right of BFPP: BFPP = BeanFactoryPostProcessor

org.springframework.context.support.PostProcessorRegistrationDelegate#invokeBeanFactoryPostProcessors方法中,循环调用了实现BeanFactoryPostProcessor接口中的postProcessBeanFactory(registry)方法

private static void invokeBeanFactoryPostProcessors(
            Collection<? extends BeanFactoryPostProcessor> postProcessors, ConfigurableListableBeanFactory beanFactory) {

    for (BeanFactoryPostProcessor postProcessor : postProcessors) {
        StartupStep postProcessBeanFactory = beanFactory.getApplicationStartup().start("spring.context.bean-factory.post-process")
            .tag("postProcessor", postProcessor::toString);
        postProcessor.postProcessBeanFactory(beanFactory);
        postProcessBeanFactory.end();
    }
}

最后执行到我们自定义的逻辑中,在实际应用中,我们可以在 postProcessBeanFactory 方法内部执行更复杂的操作,例如修改 bean 的属性、对Bean对象进行代理做功能增强处理、更改它们的作用域或添加新的 bean 定义等。

public class MyBeanFactoryPostProcessor implements BeanFactoryPostProcessor {

    @Override
    public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
        System.out.println("修改bean的定义");
        BeanDefinition beanDefinition = beanFactory.getBeanDefinition("mySimpleBean");
        beanDefinition.setScope(BeanDefinition.SCOPE_PROTOTYPE);
        System.out.println("将mySimpleBean从默认的单例修改成多例");
        System.out.println("修改bean的定义已完成");
    }
}

八、注意事项

  1. 考虑其他的BeanFactoryPostProcessor

    • 在大型应用程序中,可能存在多个BeanFactoryPostProcessor。我们需要确保它们不会互相冲突或导致不一致的bean定义。
  2. 注意执行顺序

    • 如果有多个BeanFactoryPostProcessor,它们的执行顺序可能会影响结果。使用Ordered接口或PriorityOrdered接口来明确设置执行顺序。
  3. 避免循环依赖

    • 修改bean定义可能会引入循环依赖。确保我们充分理解bean之间的依赖关系,并尝试避免修改这些关系。比如当我们修改了BeanDefinition构造函数,等等情况都有可能引入循环依赖。
  4. 不要过度使用

    • 虽然BeanFactoryPostProcessor是一个非常强大接口,但这并不意味着我们就应该频繁使用它。只在真正需要的时候使用它,并考虑是否有其他更简单、更直观的方法可以达到同样的目的。
  5. 谨慎使用

    • 虽然BeanFactoryPostProcessor是一个非常强大接口,允许我们修改bean的定义。这意味着我们可以更改bean的类、作用域、属性等。我们要在做这些更改时要非常小心,想想为什么要修改?影响的范围有多少?,以免引入不一致或不可预测的行为。

九、总结

最佳实践总结

  1. 初始化与配置

    • 使用AnnotationConfigApplicationContext, 我们成功地启动了Spring容器并加载了MyConfiguration配置。在MyConfiguration中, 我们定义了两个核心bean:MySimpleBeanMyBeanFactoryPostProcessor.
  2. 修改Bean的作用域

    • 虽然MySimpleBean默认是单例,但通过MyBeanFactoryPostProcessor,我们改变了这一默认行为,将其转变为原型作用域。这种转变是通过覆盖postProcessBeanFactory方法并更改mySimpleBean的bean定义来完成的。
  3. 验证修改

    • 当我们从主应用程序获取MySimpleBean的两个实例并调用它们的show方法时,输出的实例地址明确地告诉我们这两个bean是不同的实例。

源码分析总结

  1. 启动与上下文

    • 利用 AnnotationConfigApplicationContext,我们初始化了Spring容器,并加载了MyConfiguration作为主要配置。
  2. 核心调用

    • Spring容器的refresh方法中,invokeBeanFactoryPostProcessors(beanFactory)确保所有的BeanFactoryPostProcessor得到适当的调用。
  3. 回调顺序

    • 在Spring中,实现了BeanFactoryPostProcessor接口的bean不是随机调用的。Spring确保它们按照PriorityOrderedOrdered和无顺序的层次结构进行分类和调用。
  4. 自定义逻辑

    • 当我们实现BeanFactoryPostProcessor接口并提供自定义逻辑时(例如更改Bean的作用域),该逻辑将在上述过程的适当阶段被调用。
  5. 具体实践

    • 通过具体示例,我们查看了如何利用BeanFactoryPostProcessor在bean实例化前更改bean定义。在我们的例子中,MySimpleBean的作用域从单例被更改为原型。
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 213,711评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,079评论 3 387
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 159,194评论 0 349
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,089评论 1 286
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,197评论 6 385
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,306评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,338评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,119评论 0 269
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,541评论 1 306
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,846评论 2 328
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,014评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,694评论 4 337
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,322评论 3 318
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,026评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,257评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,863评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,895评论 2 351

推荐阅读更多精彩内容