1.Spring如何解决循环依赖?
分析:
点出什么是循环依赖,然后说出三级缓存分别缓存了什么,然后说一下具体解决的流程,说一下 Spring 解决循环依赖有一定条件(单例和构造器,在举例子说一下),说一下为什么需要三级缓存,二级不行吗
回答:
什么是循环依赖:
循环依赖是指多个 Bean 循环引用 形成一个闭环 导致 Spring 容器无法正常初始化他们。
例如:A 要依赖 B,发现 B 没创建,于是开始创建 B,创建的过程发现 B 要依赖 A,而 A 还没创建好,因为它要等 B 创建好,这样他们就在这儿 卡 bug 了
Spring是根据三级缓存解决循环依赖的
三级缓存分别缓存了什么:
-
一级缓存:singletonObjects,存储所有已创建完成的单例 Bean(完整的 Bean)
-
二级缓存:earlySingletonObjects,存储所有仅完成实例化,但还没进行属性注入和初始化的 Bean
-
三级缓存:singletonFactories,存储一个 Bean 工厂,通过这个 Bean 工厂,可以获得仅实例化,还没有进行属性注入和初始化的Bean。因为 Bean 的创建分为 3 个阶段:实例化,属性注入,初始化。每个 Bean 实例化之后,都会向三级缓存 singletonFactories 塞入一个工厂,而调用这个工厂的 getObject 方法,就能得到这个仅进行实例化的 Bean,并将这个得到的 Bean 塞入到二级缓存。通过三级缓存,就可以将未初始化完成的 Bean 提前暴露,这也是解决循环依赖的关键
三级缓存(3 个 Map)具体是怎么解决循环依赖的:
我们以 A,B 形成循环依赖为例说明,前提是:A,B 都是在进行属性注入的时候需要对方,而且 A,B 都是单例
-
首先根据 BeanName ,A 先进行创建,然后 A 实例化成功,并且将 A 的工厂对象放入三级缓存,将 A 标记为正在创建中
-
然后进行 A 的属性注入,属性注入 B 时,去一级缓存找,没找到。然后发现 B 不再创建中,就进行 B 的创建
-
B 进行实例化之后,来到了属性注入,需要注入 A,去一级缓存找,没找到,发现 A 在创建中,去 二级缓存找,也没找到,就去三级缓存找,找到了 A 的工厂对象,于是调用 工厂对象的 getObject() 方法,得到仅仅实例化的 A,并将其放入 二级缓存,同时把 三级缓存中的工厂对象删除(因为二级缓存中已经有仅实例化的 A了)。
-
然后 B 就能拿到 仅实例化的A,就能进行正常的属性注入,完成 正常 B 的初始化,将 B 放入一级缓存
-
然后回到 A 的属性注入,这时就可以从一级缓存中拿到完整的 B 对象,完成 A 的属性注入和后续的初始化,最后将二级缓存中的 A 对象删除,同时向 一级缓存 添加完整的 A 对象。至此结束
Spring 解决循环依赖有一定条件:
-
依赖的 Bean 必须是单例的
-
依赖注入的方式,必须不全是构造器注入,且 BeanName 字母序排在前面的不能是构造器注入
先说一下为什么必须是单例的
普通类(原型类)都需要创建新对象,不能用和以前一样的对象。所以就会出现这种情况
创建 A1 需要 B1,创建 B1 又要创建 A2,创建 A2 又要创建 B2,B2 需要 A3,A3 需要 B3.... 就卡bug了,因此不能
再说一下为什么不全是构造器注入,且 BeanName 字母序排在前面的不能是构造器注入
1.如果循环依赖都是构造器注入,那么 Spring 无法处理
原因:因为如果全是构造器注入,那么就是在实例化阶段就要得到依赖的对象,但是三级缓存中每一层都找不到,因为实例化之后第三层缓存中才有 Bean 工厂,所以实例化之前 三级缓存都是空的,都找不到对方依赖的对象。所以全是构造器注入,Spring 无法解决循环依赖
2.如果循环依赖不完全是构造器注入,那么可能成功,可能失败,具体根 BeanName 的字母序有关,因为 Spring 容器是按照字母序创建 Bean 的,比如 A 的创建一定在 B 的前面
原因:
-
比如 A 构造器注入,B setter 注入,就不行。因为 A 实例化依赖 B,B setter 注入在三级缓存中找不到 A,就会报错
-
A Setter 注入,B 构造器注入就行。A 属性注入依赖 B,B 构造器注入可以在第三级缓存中拿到 A 工厂对象从而得到 仅实例化的 A,所以可以正常注入。
为什么需要三级缓存,二级不行吗(见下一题)
Spring中的循环依赖为什么需要三级缓存,二级不行吗?
分析:
先说 二级缓存也可以(具体说明),再说 AOP 代理时二级缓存违反 Bean 生命周期,再说三级缓存是为了解决 AOP 代理的问题
回答:
Spring 用三级缓存的原因,主要是为了解决 AOP 代理的问题
如果单纯是要解决循环依赖,那么二级缓存其实就够了:
在实例化Bean A之后,我在二级map里面塞入这个A,然后继续属性注入,发现A依赖B所以要创建Bean B,这时候B就能从二级map得到A,完成B的建立之后,A自然而然能完成。
但是在 AOP 代理时,就会违反 Bean 的生命周期:
我们都知道如果有代理的话,那么我们想要直接拿到的是代理对象,也就是说如果 A 需要被代理,那么 B 依赖的 A 是已经被代理的 A,所以我们不能返回 A 给 B,而是返回代理的 A 给 B。
那么应该会想到,那和三级缓存有什么关系,我可以在放入二级缓存的时候判断这个 Bean 是否需要被代理,如果是直接放代理的对象不就行了么。
好像这么看也没什么问题,但是这违反了 Bean 生命周期的原则,因为被代理的对象是在对象初始化后期,也就是 BeanPostProcessor 后置处理这个阶段生成的,所以如果提早代理了其实是违背了 Bean 定义的生命周期。
所以二级缓存出现的问题是:不管有没有循环依赖,代理对象都会被提前暴露
那么三级缓存为什么能解决代理对象提前暴露的问题呢:
第三级缓存中存放的是 Bean工厂,当我们调用 Bean 工厂的 getObject() 方法时候,这个方法会判断该对象是否需要被代理,如果需要返回代理对象,如果不需要则返回不需要代理的对象。
-
先看正常的没有循环依赖的情况下:就是正常的创建,第三级缓存的工厂根本不会被调用,所以 Bean 的生命周期就是对的,初始化完成之后才生成代理对象
-
再看产生循环依赖的情况下:循环依赖会调用 三级缓存 Bean 工厂的 getObject 方法,这个方法会返回 代理对象,代理对象提前暴露。因为产生循环依赖,代理对象就得提前暴露
所以有了三级缓存,在没有循环依赖时,代理对象不会被提前暴露,有了循环依赖,代理对象就会提前暴露
所以 三级缓存解决了 二级缓存中:不管有没有循环依赖,代理对象都会被提前暴露 的问题