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

1. 前言

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

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

整理了一份面试笔记包括了:Java面试、Spring、JVM、MyBatis、Redis、MySQL、并发编程、微服务、Linux、Springboot、SpringCloud、MQ、Kafka 面试专题

需要全套面试笔记的【点击此处即可】即可免费获取

2. BeanFactory

2.1 Bean 的概述

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

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

13.1 Bean概述.png

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:完成了创建对象的工作,包括实例化、填充对象、初始化等操作。该类的特点是拥有自动装配的能力,通过 AutowiredAnnotationBeanPostProcessor 和 ConstructorResolver 等组件实现依赖注入的功能。

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

13.2 BeanFactory体系.png

2.3 创建对象的流程

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

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

13.3 创建流程.png

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 来表示。

13.4 属性访问.png

3.2 PropertyProcessor

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

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

3.3 TypeConverter

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

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

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

4. 自动装配

4.1 概述

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

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

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

13.5 自动装配.png

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

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

4.2 注入方式

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

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

13.6 注入方式.png

4.3 循环依赖

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

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

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

5. Bean的生命周期

5.1 初始化流程

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

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

13.7 Bean的初始化.png

5.2 销毁流程

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

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

13.8 Bean的销毁.png

6. FactoryBean

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

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

  • 11
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值