Spring IoC 进阶

939 阅读5分钟

Bean的作用域

  • 默认情况下,Spring IoC 容器创建的 bean 作用于单例(singleton)
  • 所谓单例,就是在 Spring IoC 容器中这个类只会创建一个实例

image.png

image.png

  • 可以将 scope 配置为 prototype, 表示这个 bean 是多例的。
  • 多例,就是每次从 Spring IoC 容器中获取 bean,都会创建一个新的实例

image.png

image.png

Spring Bean 的常用作用域分为:

  • singleton:单例,Spring 的默认 scope,Spring容器只创建唯一一个bean的实例,所有该对象的引用都共享这个实例,并且Spring在创建第一次后,会在Spring的IoC容器中缓存起来,之后不再创建
  • prototype:原型,也即多例,每次调用或请求这个bean都会创建一个新的实例。 还有 2 个使用较少的作用域,仅在 Web 应用程序中使用:
  • request:每一次HTTP请求都会产生一个新的bean,同时该bean仅在当前HTTP request内有效。
  • session:表示该针对每一次HTTP请求都会产生一个新的bean,同时该bean仅在当前HTTP session内有效。

Bean 的获取方式

第一种方式:通过构造函数

  • 没有显示声明构造函数时,调用无参数构造函数实例化
  • 声明了有参构造函数,未声明无参构造函数,调用有参构造函数实例化
  • 同时声明了有参、无参构造函数,调用无参构造函数实例化

image.png

image.png

第二种方式:通过 FactoryBean

  • 定义实现 FactoryBean 接口的工厂类
  • 在工厂类的 getObject 方法中定义获取 bean 的逻辑
  • Factory 也是被 Spring IoC 容器管理的 bean

image.png

image.png

工厂模式是 Spring 非常重要的设计模式,使用工厂模式来定义一些复杂对象的生产逻辑。

BeanFactory 和 FactoryBean 的区别

这是一道常见面试题

  • BeanFactory 是接口,定义了 Spring IoC 容器实现的规范,是各种 IoC 容器类的顶层接口。
  • FactoryBean 是 bean,是负责生产 bean 的 工厂 bean。

image.png

image.png

Bean 的生命周期 – 五步

image.png

image.png

image.png

说明:Spring 只对单例 bean 进行完整的生命周期管理。多例 bean Spring 只负责创建。

Bean 的生命周期 – 七步

image.png

  • BeanPostProcessor 是一个接口,我们叫 Bean 后处理器
  • BeanPostProcessor 接口有两个方法
    • postProcessBeforeInitialization,在初始化方法执行前调用
    • postProcessAfterInitialization,在初始化方法执行后调用
  • 实现了 BeanPostProcessor 的类也是一个 bean,在同一个 Spring IoC 容器中的 bean 被创建后, BeanPostProcessor 的 before、after 方法将被调用。

image.png

image.png

BeanPostProcessor 的使用场景

  • postProcessBeforeInitialization:允许程序员在 bean 初始化方法被执行前,执行一段逻辑。
  • postProcessAfterInitialization:允许程序员在 bean 初始化方法被执行后,执行一段逻辑。典型应用AOP,使用代理(Proxy)将初始化完成的 bean 包装起来。

Bean 的生命周期 – 扩展

image.png

  • 如果 bean 实现了 BeanNameAware, BeanFactoryAware, BeanClassLoaderAware
  • 那么在属性注入后 aware 相关方法会被调用

image.png

image.png

Aware 表示“意识到”、“被通知到”,Spring IoC 容器会在某些事件发生后,对关心这些事件的 bean 进行通知。例如某个 bean 实现 BeanNameAware 接口,表示这个 bean 想知道自己的 beanName,Spring IoC 会在 beanName 确定后调用 bean 的 setBeanName 方法进行通知。

Spring 解决循环依赖问题

解决循环依赖的关键

  • 把“实例化 bean”、“属性注入”这两个步骤分开
  • 实例化之后就把半成品的 bean 缓存起来

image.png

image.png

说明:Spring 解决循环依赖只对单例 bean。

  • 如果使用的是 set 注入,循环依赖可以解决
  • 如果使用的是构造函数注入,循环依赖不能解决,因为对于构造函数注入方式,“实例化 bean”和“属性注入”是同时完成的。

image.png

image.png

Spring 使用三级缓存解决循环依赖问题,所有三级缓存的数据结构都是map,key是bean id。

  • 第一级缓存:存放的是成品 bean,也就是初始化完成后的 bean,可以被外部直接使用。
  • 第二级缓存:存放的是半成品 bean,还没有完成初始化,不能被外部使用。
  • 第三级缓存:存放的是生产 bean 的 ObjectFactory, ObjectFactory 定义了一段创建 bean 的逻辑。

image.png

orange 和 watermelon 是循环依赖关系

image.png

image.png

Spring 创建 bean 源码解析

image.png

image.png

image.png

image.png

image.png

image.png

image.png

image.png

image.png

image.png

image.png

image.png

image.png

watermelon 的创建,中间过程与 orange 类似,直接进入属性注入步骤

image.png

image.png

重点:三级缓存查询的过程

image.png

调用 getSingleton 方法,获取到半成品 orange。

image.png

image.png

复习 Bean 的生命周期

  • 实例化
  • 属性注入
  • Aware
  • Bean 后处理器 before
  • 初始化
  • Bean 后处理器 after
  • 使用
  • 销毁

image.png

image.png

image.png

至此,watermelon 的创建与初始化执行完成。

image.png

回到 orange 的属性注入步骤,拿到 成品 watermelon,完成属性注入,执行初始化。 后续流程与 watermelon 类似。

过程总结:

image.png

三级缓存读写总结:

image.png

image.png

读取缓存时,在加锁(synchronized)前和后都进行了读取,这叫做“双重检查锁定”。

为什么解决循环依赖要使用三级缓存?

  • 一级缓存行不行?

一级缓存理论上可以,但在同一级缓存中混杂了成品 bean 和半成品 bean,需要使用额外标识区分,增加了设计复杂度。

  • 二级缓存缓存行不行?

二级缓存理论上可以,但违背了 Spring 的设计原则。

思考:如果我们在目标 bean 上定义了 AOP 增强,那放入缓存的是“原始 bean” 还是“代理 bean”?

  • 能被外部引用的必须放入代理 bean
  • 三级缓存中,一、二级缓存能被外部引用,放入代理 bean,三级缓存放入原始 bean。

Spring 设计原则:在 bean 初始化方法执行完成之后,再生成其 AOP 代理对象。

  • 当发生循环依赖时,Spring 被迫在初始化方法执行前(放入二级缓存时)就创建代理对象。
  • 如果不发生循环依赖,那么 Spring 还是可以在初始化完成后,放入一级缓存前创建代理对象。

如果只有两级缓存,那么不论是否发生循环依赖,Spring 都要在初始化方法执行前提前创建代理对象并放入二级缓存。因此,相较仅使用二级缓存,三级缓存的优势是:存在 AOP 时,尽可能晚地创建代理对象,仅在发生循环依赖时,提前创建代理对象。