请你谈谈:BeanDefinition类作为Spring Bean的建模对象,与BeanFactoryPostProcessor之间的羁绊

那么,我们如何理解Spring Bean的建模对象呢?简而言之,它是指用于描述和配置Bean实例化过程的模型对象。有人可能会提出疑问,既然只需要Class(类)就可以实例化一个对象,Class作为类的元数据,可以视作对象的建模对象,为何Spring还需要其他机制来建立Bean呢?这其中的关键在于,Class本身虽然包含了类的定义和实例化的基础信息,但它无法涵盖Spring框架中Bean的所有特性和配置。例如,Bean的作用域(Scope)、注入模型(Injection Model)、是否采用懒加载(Lazy Initialization)等属性,这些都是在Spring容器级别进行配置和管理的,而这些信息无法通过简单的Class元数据来表达。

因此,为了更全面地描述和管理Bean,Spring引入了一个名为BeanDefinition的类。BeanDefinition类作为Spring Bean的建模对象,能够抽象出Bean的各种属性和配置信息,包括但不限于作用域、生命周期回调、初始化方法、销毁方法、依赖注入等。通过BeanDefinition,Spring能够精确地控制Bean的实例化过程,确保Bean能够按照预期的方式被创建、配置和管理。
在这里插入图片描述
针对上述图示的详细文字说明如下:在一个给定的场景中,我们假设磁盘上存有N个.java源文件。首先,我们需要通过Java编译器将这些源文件逐一编译成相应的.class字节码文件。接着,当Java虚拟机(JVM)启动时,它会按照特定的类加载机制,将这些.class文件从磁盘加载到JVM的内部内存中。一旦.class文件被成功加载到内存中,JVM便能根据这些文件中定义的类模板信息来执行后续的操作。当JVM的字节码执行器在执行过程中遇到new关键字时,它会根据该关键字所指向的类的模板信息,在JVM的堆内存中为该类实例化一个对象,并为其分配相应的内存空间。这一过程确保了对象在JVM中的正确创建和初始化。

但是spring的bean实例化过程和一个普通java对象的实例化过程还是有区别的。
在这里插入图片描述
前提:假设在你的项目或者磁盘上有X和Y两个类,X是被加了spring注解的,Y没有加spring的注解;也就是正常情况下当spring容器启动之后通过getBean(X)能正常返回X的bean,但是如果getBean(Y)则会出异常,因为Y不能被spring容器扫描到不能被正常实例化;

在Spring容器启动的过程中,ConfigurationClassPostProcessor这一Bean工厂的后置处理器会被调用,以完成特定配置的扫描和解析。这里的“扫描”实际上是指Spring从类路径中识别并读取类的元数据。然而,读取到的类的元数据,如类的类型、名称和构造方法等,并不会直接存储在Class对象中。尽管Class对象确实包含了这些基本信息,但Spring在实例化Bean时,还需要考虑更多的配置属性,如作用域(scope)、延迟加载(lazy)、依赖关系(dependsOn)等。

为了满足这些需求,Spring设计了一个名为BeanDefinition的类,用于存储和管理Bean的完整配置信息。因此,当Spring读取到类的元数据后,

①它会为每个类实例化一个BeanDefinition对象,通过该对象的各种set方法将相关信息(包括从Class对象获取的元数据以及额外的配置属性)存储起来。

②在扫描过程中,每当Spring遇到一个符合规则的类,它都会实例化一个新的BeanDefinition对象,并根据类的名称自动生成一个Bean的名称(例如,对于名为IndexService的类,Spring会按照其命名规则生成一个名为indexService的Bean名称)。当然,Spring也提供了灵活的命名策略,允许开发者自定义名称生成器以覆盖默认行为。

③随后,Spring会将这个BeanDefinition对象及其对应的Bean名称存储在一个Map中,其中键(key)为Bean名称,值(value)为BeanDefinition对象。这个过程确保了Bean的元数据和配置信息在Spring容器中的有序管理和高效检索。至此,上述流程中的第①、②、③步便完成了,为后续的Bean创建和依赖注入等操作奠定了基础。

这里需要说明的是spring启动的时候会做很多工作,不仅仅是完成扫描,在扫描之前spring还干了其他大量事情;比如实例化beanFacctory、比如实例化类扫描器等等。

Appconfig.java
@ComponentScan("com.luban.beanDefinition")
@Configuration
public class Appconfig {
}

X.java
@Component
public class X {
    public X(){
     System.out.println("X Constructor");
    }
}

Y.java
public class Y {
}

Test.java
public class Test{
    public static void main(String[] args) {
        AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext();
        ac.register(Appconfig.class);
        ac.refresh();
        }
    }

在上述代码示例中,存在两个类:X和Y。其中,X类被标注了某个注解,而Y类则未被标注。在X类中,存在一个构造方法,该方法在X类实例化时会输出"X Constructor"。根据Spring框架的工作原理,当Spring容器启动时,它会执行一系列的初始化步骤,其中包括对带有特定注解的类的扫描和解析。

具体来说,在Spring容器的初始化过程中,会调用org.springframework.context.support.AbstractApplicationContext#invokeBeanFactoryPostProcessors方法。这个方法负责执行Bean工厂的后置处理器,其中就包括了对带有特定注解的类的扫描和解析。为了验证这一过程,我在该方法上设置了一个断点。

在Spring框架中,invokeBeanFactoryPostProcessors 是一个非常重要的方法,它负责在Spring容器初始化过程中调用所有实现了 BeanFactoryPostProcessor 接口的bean。这些后处理器允许在bean的定义被加载到Spring的bean工厂(BeanFactory)之后,但在bean被实例化之前,对bean定义进行修改或添加。

当应用程序启动并触发Spring容器初始化时,会调用invokeBeanFactoryPostProcessors方法。在方法执行之前,检查Spring内部的beanDefinitionMap(或类似的存储结构),发现其中并没有键为"x"的元素,这说明此时X类尚未被扫描和解析。

随着invokeBeanFactoryPostProcessors方法的执行,Spring开始扫描并解析带有注解的类。当扫描到X类时,它会将X类的信息解析为一个BeanDefinition对象,并将该对象以键为"x"的形式存储到beanDefinitionMap中。

完成扫描和解析后,我再次检查了beanDefinitionMap,发现其中已经包含了键为"x"的元素,其对应的值正是一个BeanDefinition对象。然而,此时并未发现控制台输出"X Constructor",这说明尽管X类已经被扫描并解析为一个BeanDefinition对象,但X类的实例并未被创建。

这个例子清晰地展示了Spring框架在初始化过程中的一个关键步骤:首先扫描并解析带有注解的类,将其信息存储为BeanDefinition对象并放入beanDefinitionMap中;随后,在适当的时候(如依赖注入时),才会根据这些BeanDefinition对象来实例化相应的类。关于beanDefinitionMap的详细工作原理和用途,将在后续的文章中进一步探讨。在本文中,读者只需理解它是一个专门用于存储BeanDefinition对象的集合即可。

在这里插入图片描述
在这里插入图片描述

④在Spring将类所对应的BeanDefinition对象存储到Map之后,它会进一步调用程序员提供的Bean工厂后置处理器BeanFactoryPostProcessor。那么,什么是BeanFactoryPostProcessor呢?在Spring的架构中,BeanFactoryPostProcessor是一个接口,用于定义对BeanFactory进行额外处理或修改的逻辑。任何实现了该接口的类都可以被视为一个BeanFactoryPostProcessor。关于BeanFactoryPostProcessor接口的详细源码解析,我们将在后续的文章中深入探讨。在此,我们先简要概述其基本作用。

值得注意的是,Spring内部也实现了BeanFactoryPostProcessor接口,例如ConfigurationClassPostProcessor。该类在Spring源码中扮演着举足轻重的角色,它负责执行诸如扫描配置类、解析配置信息等重要功能。在我看来,ConfigurationClassPostProcessor是理解Spring源码过程中不可或缺的关键类之一。由于其功能复杂且广泛,我们将在后续的分析中逐一深入探讨。现在,让我们先来看一下这个类的类结构图,以便对其有一个初步的了解。
在这里插入图片描述
ConfigurationClassPostProcessor作为Spring框架中的一个核心组件,实现了多个接口,其中与本文直接相关的是BeanDefinitionRegistryPostProcessor和BeanFactoryPostProcessor。尽管BeanDefinitionRegistryPostProcessor继承了BeanFactoryPostProcessor,但为了更好地理解其角色和用途,我们将其视为两个独立的接口。

在Spring的启动过程中,ConfigurationClassPostProcessor发挥了关键作用。具体来说,当Spring执行①②③步骤时,它实际上是调用了ConfigurationClassPostProcessor中实现的BeanDefinitionRegistryPostProcessor接口的postProcessBeanDefinitionRegistry方法。这一步骤负责处理与BeanDefinition注册相关的逻辑。

进入第④步时,Spring会执行BeanFactoryPostProcessor接口的postProcessBeanFactory方法。这里需要明确的是,Spring首先会调用ConfigurationClassPostProcessor自身的postProcessBeanFactory方法,这是因为它本身也实现了该接口。之后,如果应用程序开发者提供了自定义的BeanFactoryPostProcessor实现,Spring也会依次调用这些自定义的postProcessBeanFactory方法。

为了更清晰地表达这一过程,我们注意到第④步在图中是以红色虚线表示的,这是因为这一步的执行依赖于开发者是否提供了自定义的BeanFactoryPostProcessor。然而,不论开发者是否提供了自定义实现,Spring都会执行其内置的BeanFactoryPostProcessor,即ConfigurationClassPostProcessor。因此,图表在描述第④步时确实可以进一步细化,以明确表示Spring对内置和自定义BeanFactoryPostProcessor的执行。

综上所述,Spring在启动过程中通过ConfigurationClassPostProcessor的BeanDefinitionRegistryPostProcessor和BeanFactoryPostProcessor接口实现了对BeanDefinition的注册和Bean工厂的后处理。无论是内置的还是自定义的BeanFactoryPostProcessor,都在Spring的启动流程中扮演着重要角色。

重点:我们用自己的话总结一下BeanFactoryPostProcessor的执行时机(不管内置的还是程序员提供):

BeanFactoryPostProcessor的执行时机是在Spring容器创建并初始化Bean定义之后,但在实例化任何Bean之前。这是一个关键的扩展点,允许开发者在Spring容器完成基本的Bean定义加载和解析之后,对BeanFactory的内容进行额外的处理或修改。

无论是Spring内置的BeanFactoryPostProcessor实现(如ConfigurationClassPostProcessor),还是程序员自己提供的自定义BeanFactoryPostProcessor实现,它们的postProcessBeanFactory方法都会在容器准备实例化Bean之前被调用。

具体来说,当Spring容器启动时,它会首先读取配置文件或注解信息,并将这些配置转化为内部的Bean定义(BeanDefinition)。完成这一步之后,Spring会查找所有实现了BeanFactoryPostProcessor接口的类,并依次调用它们的postProcessBeanFactory方法。这些处理器可以对BeanFactory中的Bean定义进行修改、添加或删除,从而影响最终的Bean实例化过程。

因此,BeanFactoryPostProcessor为开发者提供了一个在Spring容器启动过程中干预Bean定义的机会,使得开发者能够根据自己的需求对容器进行定制和扩展。

那么第④步当中提到的执行程序员提供的BeanFactoryPostProcessor到底有什么意义呢?程序员提供BeanFactoryPostProcessor的场景在哪里?有哪些主流框架这么干过呢?

首先回答第一个问题,意义在哪里?可以看一下这个接口的方法签名:

void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) 
throws BeansException;

其实讨论这个方法的意义就是讨论BeanFactoryPostProcessor的作用或者说意义,参考这个方法的一句:

Modify the application context’s internal bean factory after its standard initialization
在应用程序上下文的标准初始化之后修改内部的BeanFactory

在Spring框架中,BeanFactoryPostProcessor(此处特指直接实现此接口的后置处理器,不涉及BeanDefinitionRegistryPostProcessor)被视为一个关键的扩展点。Spring框架通过提供这样的扩展点,旨在赋予开发者对BeanFactory初始化过程进行干预的能力,这是深入学习Spring源码并对其进行二次开发或构建优雅插件的重要一环。需要特别强调的是,这里的核心焦点在于“初始化过程”,而非“实例化过程”。这两者在Spring框架的上下文中具有显著的区别,特别是在阅读Spring源码时,应特别留意这两个术语的使用。

深入Spring的源码,我们会发现整个容器初始化过程实质上是由各种后置处理器(PostProcessor)的调用序列所构成的。这些后置处理器大致可以分为两类:一类关注于bean的实例化过程,而另一类则关注于bean的初始化过程。这种分类并非主观臆想,而是基于Spring框架对初始化和实例化概念的明确区分。熟悉Spring后置处理器体系的开发者,从其命名规则中就能洞察到Spring对这两者之间的严格区分。

严谨地阐述,BeanFactoryPostProcessor的主要作用并不在于干预BeanFactory的实例化过程,即BeanFactory如何被创建(new)出来。然而,一旦BeanFactory实例化完成,BeanFactoryPostProcessor便能够介入其后的属性配置和修改阶段,也就是我们通常所说的“初始化”过程。

具体来说,BeanFactoryPostProcessor接口中定义的方法postProcessBeanFactory接受一个类型为ConfigurableListableBeanFactory的参数,这个参数代表了已经实例化并配置好的BeanFactory对象。由于Spring在调用postProcessBeanFactory方法时传递的是已经准备好的beanFactory实例,因此开发者可以在这个方法中对BeanFactory进行进一步的配置或修改。

这意味着,通过实现BeanFactoryPostProcessor接口并覆盖postProcessBeanFactory方法,开发者可以定制BeanFactory的初始化过程,例如添加、修改或删除bean定义,调整bean的属性等。然而,需要强调的是,在操作过程中应当遵循Spring框架的设计原则和最佳实践,以确保系统的稳定性和可维护性。

在处理BeanFactory对象时,仅仅进行简单的打印(如使用System.out.println)是远远不够的,这种做法并不能充分发挥BeanFactory的功能,更不能称之为“肆意妄为”。实际上,BeanFactory提供了丰富的功能和API供开发者使用,但我们必须先深入理解其特性和API,才能有效地利用它。

尽管BeanFactory本身具有复杂性,不在此展开详细讨论,但与本文内容紧密相关的是beanDefintionMap(存储BeanDefinition的集合),这个重要的数据结构就定义在BeanFactory中。BeanFactory也提供了相应的API供程序员操作这个Map,比如允许我们修改其中已存在的BeanDefinition对象,或者向其中添加新的BeanDefinition对象。这些操作都需要开发者对BeanFactory和BeanDefinition有深入的了解,以确保在操作时遵循最佳实践,并保持系统的稳定性和可维护性。

@Component
public class TestBeanFactoryPostPorcessor implements BeanFactoryPostProcessor {
    @Override
    public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
        // 转换为子类,因为父类没有添加beanDefintion对象的api
        DefaultListableBeanFactory defaultbf =
(DefaultListableBeanFactory) beanFactory;

        // new一个Y的beanDefinition对象,方便测试动态添加
        GenericBeanDefinition y= new GenericBeanDefinition();
        y.setBeanClass(Y.class);
        // 添加一个beanDefinition对象,原本这个Y没有被spring扫描到
        defaultbf.registerBeanDefinition("y", y);

        // 得到一个已经被扫描出来的beanDefintion对象x
        // 因为X本来就被扫描出来了,所以是直接从map中获取
        BeanDefinition x = defaultbf.getBeanDefinition("x");
  
        // 修改这个X的beanDefintion对象的class为Z
        // 原本这个x代表的class为X.class;现在为Z.class
        x.setBeanClassName("com.luban.beanDefinition.Z");
    }
    
}

在项目中,我们定义了三个类:X、Y和Z。其中,仅有类X被标注了@Component注解。因此,在代码执行到特定方法时,Spring容器仅扫描并识别到了类X,导致BeanFactory中的beanDefinitionMap仅包含与类X相对应的BeanDefinition对象。

为了演示如何动态添加自定义的BeanDefinition对象,笔者首先手动创建了一个与类Y相对应的BeanDefinition实例,并通过调用registerBeanDefinition(“y”, yBeanDefinition)方法,将其注册到beanDefinitionMap中。这一步骤展示了如何向Spring容器中动态添加一个自行实例化的BeanDefinition对象。

随后,为了演示如何动态修改已扫描的BeanDefinition对象,笔者通过调用getBeanDefinition(“x”)方法获取了与类X相对应的已存在BeanDefinition对象。然后,通过调用xBeanDefinition.setBeanClassName(“Z”)方法,将原本与类X关联的BeanDefinition对象所指向的类更改为Z。这一步骤展示了如何在运行时动态修改Spring容器中已扫描和注册的BeanDefinition对象。

public static void main(String[] args) {
        AnnotationConfigApplicationContext ac =
new AnnotationConfigApplicationContext();
        ac.register(Appconfig.class);
        ac.refresh();
        //正常打印
        System.out.println(ac.getBean(Y.class));
        //正常打印
        System.out.println(ac.getBean(Z.class));
        //异常打印
        //虽然X加了注解,但是被偷梁换柱了,故而异常
        System.out.println(ac.getBean(X.class));
    }

总结上述图示内容,Spring实例化一个Bean的过程并非直接与你所提供的类相关,而是与特定的BeanDefinition对象所映射的类直接相关。通常情况下,一个BeanDefinition对象对应一个类,但特殊情况下也可能存在不同映射关系。为了更形象地解释这一点,可以借用一个比喻:假设读者你喜欢一位女性(小A),而小A则对笔者有所好感。然而,你不能因此就推断你也喜欢笔者,因为两者之间的喜好关系并不具有传递性。此外,需要强调的是,笔者在此仅作为比喻中的一个角色,与现实中的性别倾向无关,这一点应明确区分。

综上所述,BeanFactoryPostProcessor是Spring框架中一个强大的扩展点,允许程序员在Bean生命周期的特定阶段对BeanFactory进行深入的定制和修改。通过合理地利用这一特性,程序员可以构建出更加灵活、可维护的Spring应用。

  • 22
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值