Spring中容器的创建流程详细解读

 更新时间:2023年10月30日 09:08:16   作者:荆轲刺秦  
这篇文章主要介绍了Spring中容器的创建流程详细解读,Spring 框架其本质是作为一个容器,提供给应用程序需要的对象,了解容器的诞生过程,有助于我们理解 Spring 框架,也便于我们“插手”这个过程,需要的朋友可以参考下

前言

在容器准备阶段,需要告知容器从哪里读取 Bean 信息以及环境信息。尽管,有时我们并未提供这些信息,但容器依然能正确创建。这得益于一些默认行为(约定大于配置),所以,也不需要由我们主动告知。

1. Bean 的准备

Bean 的准备阶段离不开 BeanDefinationRegistry 和 BeanFactory。

这里需要明白 BeanDefination 对象代表的是 Bean 的描述信息。Bean 并不是从一开始就实例化放入容器的。相反,最开始容器只是拥有它的描述信息,所以我们将此阶段称为 Bean 的准备阶段。根据这些描述信息,Spring 能够创建 Bean,并使它更“完整”。

BeanDefination 在 Spring 中的地位类似于我们的 Class 对象在 JVM 中的地位。

1.1 BeanDefinationRegistry

容器是通过 BeanDefinationRegistry 来注册 BeanDefination 的。 我们可以通过 xml 文件或者 Java 配置的方式来声明 BeanDefination,并在创建容器的时候指定 xml 文件或者 Java 类,然后,容器会扫描并注册 BeanDefination。

那么,此阶段我们的机会在哪里呢?其实,Spring 已经包含了足够完善的从定义的 Bean 信息到 BeanDefination 注册。但如果你仍然想自定义 BeanDefination,或者修改注册的 BeanDefination ,可以通过获取 BeanDefinationRegistry 对象。

Spring 通过回调的方式,调用实现 BeanDefinitionRegistryPostProcessor 接口的类,所以,此类交由 Spring 管理。该类将在后面介绍的刷新阶段回调,此为机会一。

Spring 自身也定义了实现 BeanDefinitionRegistryPostProcessor 接口的类来完成和我们一样想要在运行时自定义加载 BeanDefination 的功能,像 ConfigurationClassPostProcessor,调用时机和我们自定义的一样,被调用的顺序依赖实现 PriorityOrdered 或 Order 接口。

平等对待第三方,在 dubbo 中见过这种说法,或许对于框架开发,这种做法一直很重要。

1.2 BeanFactory

那么,容器中持有的 Bean 一定需要对应的 BeanDefination 嘛?这不一定,我们可以直接通过 BeanFactory 来注册实例化对象。Spring 在将一些环境信息对象放入容器中,也是这样做的。

此时,我们的机会又在哪里呢?两种方式:

  • 实现 BeanFactoryAware 接口。
  • 实现 BeanFactoryPostProcessor 接口。

实现用于拓展的接口的类都必须交由 Spring 管理才能触发回调,此为机会二。上述两个回调的时机也都在刷新阶段,后续将详细叙述。

先做个小结,现在,我们知道了,我们可以通过 BeanDefinationRegistry 操作 BeanDefination,通过 BeanFactory 操作 Bean,那么,如何获得这两个对象便是我们的机会所在。

环境信息

环境信息包括 profile 和 property,前者可以看作对环境信息做的分类,因为我们需要在不同时候,激活不同的环境,像生产和开发环境。后者是属性,属性可来自不同的属性源。并且,通过环境信息,我们可以将 ${} 占位符替换为具体的属性值。

那么,此时的机会在哪里呢?我们可以自定义一个实现 EnvironmentAware 接口的类,此为机会三。

2. 刷新

刷新阶段涉及到很多回调,我们将一一分析。

2.1 调用 BeanFactoryPostProcessors

此阶段包含了 BeanDefinitionRegistryPostProcessor 接口的回调,因为 BeanDefinitionRegistryPostProcessor 接口继承自 BeanFactoryPostProcessor。

调用顺序为:

  • 调用 BeanDefinitionRegistryPostProcessor;
  • 调用 BeanFactoryPostProcessor;实现相同接口的对象之间调用顺序将按照 PriorityOrdered 和 Ordered 接口来。

2.2 注册 BeanPostProcessor

BeanPostProcessor 作用于对象初始化前后,我们实现这个接口便可以在对象初始化前后做处理。同样,实现了该接口后,需要交由 Spring 管理就好。 此阶段仅仅是将 BeanPostProcessor 注册到 BeanFactory 中,并不调用。注册顺序为:

  1. 实现了 PriorityOrdered 的 BeanPostProcessor;
  2. 实现了 Ordered 的 BeanPostProcessor;
  3. 仅实现 BeanPostProcessor;
  4. 实现了 MergedBeanDefinitionPostProcessor

MergedBeanDefinitionPostProcessor 继承自 BeanPostProcessor。

2.3 实例化所有非延迟实例化的单例

在实例化单例 Bean 时,第一个回调机会给了 InstantiationAwareBeanPostProcessor 接口,该接口的 postProcessBeforeInstantiation() 方法如果返回了一个对象,该对象将用于存入容器,此为机会四。

如果上面的接口未返回对象,则还允许我们在根据 BeanDefination 创建对象前,再修改 BeanDefination。第二个回调机会便给了 MergedBeanDefinitionPostProcessor 接口的 postProcessMergedBeanDefinition() 方法。此为机会五。

还有,在存在循环依赖的时候, 如果你想修改你已经放入容器的对象,可以实现 SmartInstantiationAwareBeanPostProcessor 接口,该接口的 getEarlyBeanReference() 方法将被调用。此接口的使用能够保证其它对象依赖注入的是你修改后的对象,容器中也是你修改后的对象。

比如说 A 依赖了 B,B 依赖了 A。在检测到 A 依赖 B 的时候,A 已经实例化完成,这时候该去走创建B 的过程,然后在创建 B 的过程,发现 A 已经创建完成,可以依赖注入,B 的过程正常结束。回到创建 A 的过程,这时,A 也正常注入 B,循环依赖过程完成了。若此时,你再通过 BeanPostProcessor 重新实例化了对象 A 放入容器,这就会导致容器中的 A 和 B 依赖的 A 不是同一个对象。这违背了单例原则,所以,我们可以通过上述描述的接口来保证。

紧接着的回调机会给了 InstantiationAwareBeanPostProcessor 接口的 postProcessAfterInstantiation() 方法,此时还未给属性赋值,但我们可以操作实例化的对象了。

再后来,给了我们修改属性值的机会,回调 InstantiationAwareBeanPostProcessor 接口的 postProcessProperties() 方法。如果此方法返回 null ,还可再回调 postProcessPropertyValues 方法。

接下来的过程就是初始化了。初始化之前,如果 Bean 实现了以下接口,那么会回调接口对应的方法:

  • BeanNameAware;
  • BeanClassLoaderAware;
  • BeanFactoryAware;

然后,再回调 BeanPostProcessor 的 postProcessBeforeInitialization 方法。 其实,关于其它 Aware 接口的回调,便是通过 ApplicationContextAwareProcessor 来做的,它是一个 BeanPostProcessor。所以,如果你自定义了一个 Bean,并且实现了 PriorityOrdered 接口,如果它的优先级过高,那么它可能在 ApplicationContextAwareProcessor 之前调用,这样你某些实现了 Aware 接口的 Bean,可能还尚未获取 aware 注入的对象。

紧接着,就是调用 Bean 的初始化方法,实现了 InitializingBean 的 afterPropertiesSet() 方法或者指定的 init-method 方法将被调用。

初始化完后,便是 BeanPostProcessor 的 postProcessAfterInitialization() 的方法调用。

至此,容器创建过程的回调就介绍完了。

3. 写在最后

我们的机会在于 BeanDefinationRegistry、BeanFactory 以及处理 Bean。需要注意各个回调接口的调用时机以及条件,这里整理下具体的回调顺序如下:

  • 前期(BeanFactoryPostProcessor)
    • 处理 BeanDefination: BeanDefinitionRegistryPostProcessor.postProcessBeanDefinitionRegistry();
    • 处理 BeanFactory: BeanFactoryPostProcessor.postProcessBeanFactory();
  • 创建 Bean(BeanPostProcessor)
    • 自定义实例化:InstantiationAwareBeanPostProcessor.postProcessBeforeInstantiation(); 自定义实例化过程有很多实现方式,比如 FactoryBean 或者提供一个 Supplier;
    • 实例化前修改 BeanDefination: MergedBeanDefinitionPostProcessor.postProcessMergedBeanDefinition() ;
    • 实例化后,属性赋值前:InstantiationAwareBeanPostProcessor.postProcessAfterInstantiation()
    • 修改属性(PropertyValue):InstantiationAwareBeanPostProcessor.postProcessProperties(),如果该方法返回 null,还有可以通过 postProcessPropertyValues() 方法来修改。
    • 属性赋值。
    • 初始化前:BeanNameAware,BeanClassLoaderAware,BeanFactoryAware,BeanPostProcessor.postProcessBeforeInitialization()。
    • 初始化:InitializingBean.afterPropertiesSet() 或者 init-method。
    • 初始化后:BeanPostProcessor.postProcessAfterInitialization()。

梳理完上述过程,再结合我们自己的需要,就知道我们如何拓展了。

一个容器的诞生,这个名字听起来很舒服,这让我想到一部电影-传奇的诞生。尽管我所写的是再平凡不过,但我仍愿为此赋予一个美好的寓意。

到此这篇关于Spring中容器的创建流程详细解读的文章就介绍到这了,更多相关Spring容器的创建流程内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • 使用jenkins+maven+git发布jar包过程详解

    使用jenkins+maven+git发布jar包过程详解

    这篇文章主要介绍了使用jenkins+maven+git发布jar包过程详解,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
    2020-07-07
  • Java微服务Nacos Config配置中心超详细讲解

    Java微服务Nacos Config配置中心超详细讲解

    配置文件相对分散。在一个微服务架构下,配置文件会随着微服务的增多变的越来越多,而且分散 在各个微服务中,不好统一配置和管理。每一个环境所使用的配置理论上都是不同的,一旦需要修改,就需要我们去各个微服务下手动维护
    2023-02-02
  • 使用maven运行Java Main的三种方法解析

    使用maven运行Java Main的三种方法解析

    这篇文章主要介绍了使用maven运行Java Main的三种方式的相关内容,具有一定参考价值,需要的朋友可以了解下。
    2017-10-10
  • 关于maven的用法和几个常用的命令

    关于maven的用法和几个常用的命令

    这篇文章主要介绍了关于maven的用法和几个常用的命令,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2022-10-10
  • Spring Data默认值的错误解决

    Spring Data默认值的错误解决

    本文主要介绍了Spring Data默认值的错误解决,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2023-01-01
  • Spring Boot产生环形注入的解决方案

    Spring Boot产生环形注入的解决方案

    这篇文章主要介绍了Spring Boot产生环形注入的解决方案,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2021-09-09
  • SpringBoot集成阿里巴巴Druid监控的示例代码

    SpringBoot集成阿里巴巴Druid监控的示例代码

    这篇文章主要介绍了SpringBoot集成阿里巴巴Druid监控的示例代码,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧
    2018-04-04
  • 如何使用Java调用Linux系统命令

    如何使用Java调用Linux系统命令

    这篇文章主要介绍了如何使用Java调用Linux系统命令,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2021-11-11
  • Java程序开发中abstract 和 interface的区别详解

    Java程序开发中abstract 和 interface的区别详解

    abstract class和interface在Java语言中都是用来进行抽象类。但是两者有什么区别呢,接下来小编给大家带来了abstract 和 interface的区别详解,感兴趣的朋友一起学习吧
    2016-06-06
  • java锁升级过程过程详解

    java锁升级过程过程详解

    这篇文章主要介绍了Java锁升级的实现过程,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2021-08-08

最新评论