面试记录之Spring的三级缓存,四级缓存

关键:主要是为了解决循环依赖问题

一、产生循环依赖原因

public class A {

  private B b;

}

public class B {

  private A a;

}

在Spring中初始化一个Bean并不是简单的new A(),需要经过属性注入以及各种后置处理器的处理。

1:当我们初始化A的时候,在属性注入会发现需要注入一个对象B

2:此时Spring就会去Bean容器中找是不是有这个B对象,如果有就返回

3:如果没有就会初始化B,此时在对B进行属性注入的时候发现需要A,又要去初始化A

4:此时就造成了循环依赖的情况

二、如何解决

因为创建对象分成了初始化、填充属性、得到可用对象,有了这些步骤,所以才能解决

Spring是使用三级缓存来解决循环依赖的,准确来说应该是四层

public class DefaultSingletonBeanRegistry extends SimpleAliasRegistry implements SingletonBeanRegistry {
  ...
  // 从上至下 分表代表这“三级缓存”
  private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256); //一级缓存
  private final Map<String, Object> earlySingletonObjects = new HashMap<>(16); // 二级缓存
  private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16); // 三级缓存
  
  
  ...
  }

singletonObjects:缓存已经初始化好的Bean

earlySingletonObjects:缓存正在初始化的Bean

singletonFactories:缓存的创建Bean的ObjectFactory,表示对象工厂,表示用来创建早期bean对象的工厂。

B 在创建对象的时候,发现A在一级缓存里没有,就去二级缓存找。二级也没就去三级,都没有就通知创建。

Spring 再创建 A 的时候,先创建一个半成品 A,不填充属性 B。这个时候,B从二级缓存中拿到A,放到自己的属性填充。完成创建对象过程。这个时候 B 创建完成,A 填充属性就可以直接用 B 。

三、二层已经够了,为什么需要三层

在上面描述的过程中,其实二级缓存已经够了。为什么Spring要用三级缓存?

因为还有特殊情况,比如,如果 A 经过了AOP,那么再 B 填充的时候,我们就需要拿到 A 代理后的对象。

我们都知道动态代理的执行再填充属性后的 BeanPostProcessor 的after里去执行,这个时候是需要填充属性完成才可以执行。这就导致给B的只能是原始对象A,这就不对了。这个时候三级缓存就出来了

四、singletonFactories三级缓存的作用

singletonFactories 中存的是某个 beanName 对应的 ObjectFactory,在bean的生命周期中,生成完原始对象之后,就会构造一个 ObjectFactory存入 singletonFactories 中。这个 ObjectFactory是一个函数式接口,所以支持 Lambda 表达式: () -> getEarlyBeanReference( beanName , mbd , bean )。主要就是传入一个对象,返回这个对象的代理对象。

也就是说在初始化A的时候,在属性注入之前会提前创建A对象的ObjectFactory放到这个singletonFactories中,后续B对象在需要A对象的时候就会调用这个 () -> getEarlyBeanReference( beanName , mbd , bean ) 来获取代理后的 A 对象。此时如果这个 A 对象不需要经过AOP代理的就会生成一个普通对象,如果后续需要经过AOP代理此时就会生成一个代理对象返回给B进行属性注入。 

这个时候就会有下一个问题,如果 A 设置是单例的,就会有下面问题:

假设这个A对象是需要经过AOP代理生成一个代理对象的,B对象使用的也是A的代理对象,看起来似乎没什么问题。

但是AOP代理是在初始化之后执行的,也就是说你现在虽然提前生成了A的代理对象,但是在初始化A对象之后还要经过AOP的一次代理,那么作为单例来说,这二个代理对象是不一样的。

五、第四级别缓存 - earlyProxyReferences

在经过 () -> getEarlyBeanReference(beanName,mbd,bean) 得到的对象如果是经过AOP代理的对象就会放到这个 earlyProxyReferences 中,这样后续在初始化后置处理器中只要判断earlyProxyReferences中有没有这个Bean,如果有就不用再次做代理。

六、总结

Spring使用三级缓存来解决循环依赖问题,第一级缓存用于存储已经初始化好的Bean对象,第二级缓存用于存储正在初始化的Bean对象,第三级缓存用于存储创建Bean对象的ObjectFactory对象。然后在特殊情况下,如果对Bean对象进行动态代理,则由第三级来解决问题。第四级缓存用于存储经过AOP代理的对象,以解决单例问题。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值