首页 > 其他分享 >【重写SpringFramework】第一章beans模块:本章小结(chapter 1-13)

【重写SpringFramework】第一章beans模块:本章小结(chapter 1-13)

时间:2024-06-30 19:58:53浏览次数:3  
标签:chapter 13 依赖 对象 Spring 创建对象 SpringFramework 接口 注入

1. 前言

在 Spring 框架中,beans 模块是仅次于 core 模块的基础模块。我们知道,IOC 机制是 Spring 框架的两大基石之一,beans 模块的主要任务就是实现控制反转和依赖注入的功能。从具体实现来说,BeanFactory 接口是整个模块的核心接口,几乎所有功能都是围绕对象展开的。BeanFactory 提供了创建对象的功能,并对一部分对象进行管理,这是控制反转的基础。此外,在创建对象的过程中完成依赖注入,这时我们可以说 BeanFactory 是一个典型的 IOC 容器。

上述内容只是 beans 模块的主线,在实现这些功能的过程中,还涉及到了众多的问题。有的问题得到了解决,有的问题只是开了个头,将在后续模块进行处理。我们将对本章所涉及的知识点进行回顾和梳理,巩固加深对 beans 模块的理解。

2. BeanFactory

2.1 Bean 的概述

BeanFactory 是管理 Bean 的容器,Spring 根据作用域对 Bean 进行划分,大体上分为三类:

  • 单例:由 Spring 容器负责管理,每个单例的 beanName 是唯一的。
  • 原型:Spring 容器只负责创建对象,不负责管理,每次调用 getBean 方法都会创建一个新的对象。
  • 其他作用域:Spring 还定义了 request、session 等作用域,且允许用户自定义作用域。

在这里插入图片描述

Spring 容器负责对单例进行管理,单例的注册有两种途径。一是由外界创建对象,然后注册到 Spring 容器中。二是由 BeanFactory 自行创建对象,包括普通对象和 FactoryBean 工厂对象两种形式。BeanDefinition 描述了创建对象的相关信息,我们关心两种实现:

  • RootBeanDefinition 是创建对象的标准依据,各种形式的 BeanDefinition 实现都会转换成 RootBeanDefinition
  • AnnotatedBeanDefinition 接口通过注解声明的方式加载组件,这一过程是由 Spring 框架自行完成的。该接口有三个实现类,使用方式更为灵活便捷。

2.2 BeanFactory 体系

BeanFactory 的继承体系十分庞大,我们对原始的继承体系进行精简,将整个体系分为三个部分。第一部分对单例进行管理,第二部分对 Bean 进行管理,第三部分对 BeanDefinition 进行管理。Spring 容器的继承层次看起来很复杂,但每个类的职责划分非常明确,整体的设计思路是值得借鉴的。这里简单描述一下各个类的作用,如下所示:

  • DefaultSingletonBeanRegistry:负责存储单例,包括 FactoryBean 工厂对象。此外,该类使用三级缓存来处理循环依赖的问题。

  • FactoryBeanRegistrySupport:主要为 FactoryBean 提供支持,负责存储 FactoryBean 创建的对象。

  • AbstractBeanFactory:实现了核心的 getBean 工厂方法,实际的查询缓存和创建对象的工作是由父类和子类完成的。该类完成了一些辅助工作,比如获取 RootBeanDefinition 作为创建对象的依据,以及区分普通单例和 FactoryBean

  • AbstractAutowireCapableBeanFactory:完成了创建对象的工作,包括实例化、填充对象、初始化等操作。该类的特点是拥有自动装配的能力,通过 AutowiredAnnotationBeanPostProcessorConstructorResolver 等组件实现依赖注入的功能。

  • DefaultListableBeanFactory:默认的实现类,完成了依赖解析等工作。该类还实现了 BeanDefinitionRegistry 接口,负责管理 BeanDefinition

在这里插入图片描述

2.3 创建对象的流程

创建对象的流程是 Spring 容器的核心部分,也是控制反转和依赖注入的体现。创建对象的过程比较复杂,我们尤其关心以下三个步骤:

  • 实例化:Spring 提供了多种实例化对象的方式,一是通过 InstantiationStrategy 组件以反射的方式调用无参构造器,二是通过工厂方法来创建对象,三是调用构造器来创建对象。后两种方式是由 ConstructorResolver 组件完成的,且工厂方法和构造器的参数会被依赖解析。
  • 填充对象:对象实例化之后,此时的对象只是一个空对象,需要对属性进行赋值。填充对象包括两个部分,一是属性访问,二是依赖注入。从数据来源来说,属性访问的数据是事先准备的,存储在 BeanDefinitionpropertyValues 属性中。依赖注入的数据来源于 Spring 容器中的单例,以及环境变量中的属性。
  • 初始化:在填充对象之后,此时的对象是基本可用的,Spring 容器提供了一个机会供用户进行自定义操作。这一过程包括感知接口注入、三种初始化的方式,以及初始化后的回调。

在这里插入图片描述

2.4 扩展组件

BeanPostProcessor 接口提供了若干钩子方法,在 Spring 容器创建对象的不同阶段进行回调。BeanPostProcessor 不仅被 Spring 框架使用,同时作为面向用户的扩展接口,允许用户进行自定义操作。BeanPostProcessor 接口的实现类有着广泛地应用,本章介绍了两个比较重要的实现类,如下所示:

  • AutowiredAnnotationBeanPostProcessor:负责解析 @Autowired@Value 等注解,在填充对象阶段调用,主要解决依赖注入的问题。

  • InitDestroyAnnotationBeanPostProcessor:负责对象的初始化和销毁操作,支持对 @PostConstruct@PreDestroy 注解的解析。

BeanFactoryPostProcessor 接口的作用是在 BeanFactory 实例创建之后,允许以回调的方式对容器进行设置。该接口实际上是提供给 ApplicationContext 使用的,比如配置的处理就是通过子类 ConfigurationClassPostProcessor 完成的。

3. 属性访问

3.1 概述

对于一个 Java Bean 来说,可以调用 setter 方法为对象赋值。但 Spring 容器在创建对象的过程中,用户不能直接进行干预,因此我们需要一种替代方案。属性访问是将一个空对象和一组数据关联起来,并自动完成赋值的操作。

  • PropertyProcessor:负责为对象的属性进行赋值,可以处理复杂的嵌套属性。
  • TypeConverter:在赋值的过程中,外部数据可能与对象的字段类型不一致,因此需要进行类型转换。
  • PropertyValues:对属性值的来源进行抽象,每一组属性可以使用 key 和 value 来表示。

在这里插入图片描述

3.2 PropertyProcessor

属性访问的强大之处在于可以处理多层嵌套的复杂对象,在实际应用中,Spring Boot 的属性类就是通过 PropertyProcessor 处理的。PropertyProcessor 接口有两个实现类,如下所示:

  • BeanWrapperImpl:通过内省的方式调用 getter/setter 方法安全地访问对象的属性
  • DirectFieldAccessor:通过反射的方式直接访问字段

3.3 TypeConverter

TypeConverter 提供了类型转换的功能,其底层的转换逻辑主要由属性编辑器和转换器完成的。其中属性编辑器是由 JDK 提供的内省操作的 API,允许以反射的方式安全地访问字段。转换器则是 Spring 核心包定义的一系列转换器,并通过 ConversionService 提供类型转换的服务。下表列出了属性编辑器和转换器各自的特点:

转换方向转换类型线程安全
属性编辑器双向转换自定义类型与 String 互转
转换器单向转换任意两个类型互转

在 Spring 框架中,TypeConverterConversionService 是一起使用的。这是因为属性编辑器不是线程安全的,所以虽然 TypeConverter 的功能很强大,但如果需要保证线程安全,可单独使用 ConversionService

4. 自动装配

4.1 概述

自动装配又称依赖注入,与控制反转共同构成了 IOC 机制。总的来说,自动装配的流程可以分为两个阶段,首先需要对注入目标进行解析,使用以下两个类来描述:

  • InjectionMetadata 表示一个类的注入信息,称为注入元数据。注入元数据持有一组 InjectedElement,每个 InjectedElement 代表一个注入目标,可以是一个字段或 setter 方法。

  • DependencyDescriptor 描述了依赖项的相关信息,作用与 BeanDefinition 类似。

在这里插入图片描述

在得到注入目标的信息之后,需要对依赖项进行解析。由于 Spring 容器负责管理各项资源,因此依赖解析的工作是由 DefaultListableBeanFactory 完成的。从功能上来说,依赖解析可以分为三种:

  • 延迟依赖解析:如果依赖项是 ObjectProviderProvider 这种包装类型,不立即解析依赖项,而是推迟到第一次调用对象时再解析。这是一种辅助功能,适用于特殊情况,即依赖项的实例可能尚未存在,因此需要推迟。
  • 字符串解析:对声明了 @Value 注解的字段进行解析,通过 StringValueResolver 组件寻找环境变量中的属性值。
  • 对象解析:最常用的形式,包括单一类型和集合类型,其中集合类型建立在单一类型的基础之上。

4.2 注入方式

Spring 实现了四种依赖注入的方式,根据用途和执行时机可以分为两组。第一组是字段注入与 setter 方法注入,作用是为字段赋值,在填充对象的流程中执行。第二组是工厂方法注入和构造器注入,作用是创建对象,在实例化的流程中执行。

  • 字段注入:在字段上声明 @Value@Autowired 等注解
  • setter 方法注入:在方法上声明 @Autowired 等注解。有时需要在 setter 方法中进行额外操作,可以选择这种方式
  • 工厂方法注入:在方法上声明 @Bean 注解,作用是创建对象,这种方式是代替 FactoryBean 的声明式实现
  • 构造器注入:在构造器上声明 @Autowired 注解

在这里插入图片描述

4.3 循环依赖

在依赖解析的过程中,可能遇到一种特殊情况,即当两个对象相互依赖时,如果不进行处理,那么代码的执行会陷入无限循环。Spring 的解决思路是临时存储创建中的对象,在寻找依赖项时返回创建中的对象,从而切断循环依赖的链条。DefaultSingletonBeanRegistry 定义了三级缓存,功能如下所示:

  • singletonObjects 为一级缓存,负责存储创建完毕的单例。
  • earlySingletonObjects 为二级缓存,存储尚处于创建中单例,用于解决普通对象的循环依赖。
  • singletonFactories 为三级缓存,存储单例工厂,用于解决代理对象的循环依赖。

需要注意的是,二级缓存的作用是确保三级缓存中的 ObjectFactory 对象只回调一次,因为对于代理对象来说,代理对象的创建过程只能有一次。如果是普通对象,只保留一级和三级缓存就够了,因为不管回调多少次,获得的都是同一个对象。

5. Bean的生命周期

5.1 初始化流程

在填充对象之后,此时的对象是基本可用的,Spring 容器提供了一个机会供用户进行自定义操作,这一过程称之为对象的初始化。初始化操作包括以下四个方面:

  • 感知接口注入:为实现了 Aware 接口的对象注入组件,比如 BeanFactoryAware 接口的实现类会被注入 BeanFactory 实例。
  • 初始化前处理:调用 BeanPostProcessor 接口的 postProcessBeforeInitialization 方法,比如 InitDestroyAnnotationBeanPostProcessor 组件处理声明了 @PostConstruct 注解的实例,并调用初始化方法。
  • 初始化操作:包括两个部分,其一,如果当前实例实现了 InitializingBean 接口,则调用 afterPropertiesSet 方法。其二,获取 BeanDefinitioninitMethodName 属性,如果存在,则以反射的方式调用方法。
  • 初始化后处理:调用 BeanPostProcessor 接口的 postProcessAfterInitialization 方法,最典型的应用是 AbstractAutoProxyCreator 创建代理对象。

在这里插入图片描述

5.2 销毁流程

Bean 的销毁流程包括两个阶段,首先在创建实例的最后阶段,将待销毁的单例包装成 DisposableBeanAdapter 适配器对象,并注册到 Spring 容器中。其次,调用 ConfigurableBeanFactory 接口的 destroySingletons 方法触发销毁流程。单例的销毁操作是由 DisposableBeanAdapter 适配器对象完成的,一共处理了四种情况。如下所示:

  • 通过 InitDestroyAnnotationBeanPostProcessor 组件处理声明了 @PreDestroy 注解的方法
  • 调用 DisposableBean 接口的销毁方法
  • 调用指定的销毁方法,分为两种情况。一是自定义的销毁方法,即 BeanDefinitiondestroyMethodName 属性。二是默认的销毁方法,即 AutoCloseable 接口实现类的 closeshutdown 方法。

在这里插入图片描述

6. FactoryBean

Spring 容器主要通过 BeanDefinition 来创建对象,这种方式有两个特点,一是对象的构建过程较为简单,二是通过依赖注入完成了大部分构建工作。对于一些复杂对象来说,需要更加灵活的创建方式。Spring 的解决思路是将创建对象的权力「转包」出去,这实际上也是工厂模式的体现,即屏蔽了创建对象的细节。

Spring 提供了两种实现方式。一是 FactoryBean 接口,二是配置类中的工厂方法。关于后者将在第三章 context 模块进行讨论,其前置技术就是自动装配的工厂方法注入的功能。


关注公众号【Java编程探微】,加群一起讨论。
在这里插入图片描述

标签:chapter,13,依赖,对象,Spring,创建对象,SpringFramework,接口,注入
From: https://blog.csdn.net/Stimd/article/details/139829950

相关文章

  • AI数据分析013:根据时间序列数据生成动态条形图
    文章目录一、介绍二、输入内容三、输出内容一、介绍动态条形竞赛图(BarChartRace)是一种通过动画展示分类数据随时间变化的可视化工具。它通过动态条形图的形式,展示不同类别在不同时间点的数据排名和变化情况。这种图表非常适合用来展示时间序列数据的变化,能够直......
  • Arturia - FX Collection 5 v5.0.0 VST, VST3, AAX x64 {R2R} [13.06.2024]
    Arturia-FXCollection5v5.0.0forWindowsmac【【新品发布+小广告】ArturiaFXCollection5超强音乐制作插件套装34款产品逐一点评】https://www.bilibili.com/video/B...4d4e7f5c56f93e901cd    包括BusEXCITER-104BusFORCEBusPEAKChorusDIMENSION-DCh......
  • [题解]CF1312E Array Shrinking
    思路本题为P3146变式,也算是一道很经典的区间DP题了。因为\(n\leq500\),考虑区间DP。定义\(dp_{i,j}\)表示操作\([i,j]\)区间剩余长度的最小值。那么,我们可以枚举一个中间值\(k\),可以显然地得到一个状态转移方程(即不能合二为一的情况):\[dp_{i,j}=\min(dp_{i,......
  • 基于SpringBoot的高校大学生学科竞赛管理系统+53135(免费领源码)可做计算机毕业设计JAVA
    springboot高校大学生学科竞赛管理系统的设计与实现摘 要随着互联网趋势的到来,各行各业都在考虑利用互联网将自己推广出去,最好方式就是建立自己的互联网系统,并对其进行维护和管理。在现实运用中,应用软件的工作规则和开发步骤,采用Java技术建设高校大学生学科竞赛管理系统。......
  • Klipper RP2040 display ssd1306 0.96 屏幕配置
    接线屏幕接线parampinGNDGNDVCCVCCSCLSDA编码器接线parampinGNDGNDEN1VCCEN2CLklipper配置#显示屏及旋钮[display]lcd_type:ssd1306#i2c_bus:i2c0dencoder_pins:^gpio24,^gpio23encoder_steps_per_detent:2c......
  • 【2024-06-13】端午叙事
    20:00让我们天亮就起,按时吃早餐,心平气和而又心中坦然,任人来人往,任钟鸣孩子闹一下定决心好好地过一天。我们为什么要被击垮甚至自甘堕落呢?                                           ......
  • 代码随想录算法训练营第45天 | 198.打家劫舍 、213.打家劫舍II 、337.打家劫舍III
    今天就是打家劫舍的一天,这个系列不算难,大家可以一口气拿下。198.打家劫舍视频讲解:https://www.bilibili.com/video/BV1Te411N7SXhttps://programmercarl.com/0198.打家劫舍.html/***@param{number[]}nums*@return{number}*/varrob=function(nums){const......
  • LeetCode热题100(136.只出现一次数字)
    一.只出现一次数字给你一个非空整数数组nums,除了某个元素只出现一次以外,其余每个元素均出现两次。找出那个只出现了一次的元素。你必须设计并实现线性时间复杂度的算法来解决此问题,且该算法只使用常量额外空间。示例1:输入:nums=[2,2,1]输出:1示例2:输入:nums=......
  • 第13关:存储过程1、第14关:存储过程2。(2021数据库期末一)
    目录首先需要学习和了解的知识第13关:存储过程1任务描述答案 第14关:存储过程2任务描述答案本篇博客的答案博主是学习别人得来的,敢于借鉴和学习哈哈!!首先需要学习和了解的知识了解什么是存储过程以及存储过程的基本语法。(作者博客专栏或者b站学习)了解在命令行中,执......
  • 【雷丰阳-谷粒商城 】【分布式高级篇-微服务架构篇】【13】压力压测JMeter-性能监控jv
    持续学习&持续更新中…守破离【雷丰阳-谷粒商城】【分布式高级篇-微服务架构篇】【13】压力压测JMeter-性能监控jvisualvm压力测试概述性能指标JMeter基本使用添加线程组添加HTTP请求添加监听器启动压测&查看分析结果JMeterAddressAlreadyinuse错误解决性......