1.什么是三级缓存?

IoC,主要是将对象的创建、管理都交给了 Spring 来管理,能够解决对象之间的耦合问题,对开发人员来说也是省时省力的。
AOP,主要是在不改变原有业务逻辑情况下,增强横切逻辑代码,也是解耦合,避免横切逻辑代码重复;也是对 OOP 的延续、补充。

Spring底层管理bean的实例化方式是通过反射实现的。而实例化的方式有很多种,使用构造器实例化的方式是不能解决循环依赖的,因为构造器实例化必须保证参数已经被实例化过,容易形成死循环。例如:A与B构成循环依赖。B b = new B(a),A a = new A(b)。无法解决循环依赖。所以Spring解决循环依赖的方式是通过setter方式。思路:

  • A a = new A(),说明 A 只是实例化,还未初始化
  • 同理,B b = new B() 也只是实例化,并未初始化
  • a.setB(b);, 对 a 的属性赋值,完成 a 的初始化
  • b.setA(a);, 对 b 的属性赋值,完成 b 的初始化

image.png

  1. /**
  2. * 单例对象的缓存:bean 名称——bean 实例,即:所谓的单例池。
  3. * 表示已经经历了完整生命周期的 Bean 对象
  4. * <b>第一级缓存</b>
  5. */
  6. Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);
  7. /**
  8. * 早期的单例对象的高速缓存:bean 名称——bean 实例。
  9. * 表示 Bean 的生命周期还没走完(Bean的属性还未填充)就把这个 Bean 存入该缓存中
  10. * 也就是实例化但未初始化的 bean 放入该缓存里
  11. * <b>第二级缓存</b>
  12. */
  13. Map<String, Object> earlySingletonObjects = new HashMap<>(16);
  14. /**
  15. * 单例工厂的高速缓存:bean 名称——ObjectFactory。
  16. * 表示存放生成 bean 的工厂
  17. * <b>第三级缓存</b>
  18. */
  19. Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16);

第一级缓存(也叫单例池):Map singletonObjects,存放已经经历了完整生命周期的 Bean 对象
第二级缓存:Map earlySingletonObjects,存放早期暴露出来的 Bean 对象,Bean 的生命周期未结束(属性还未填充完)
第三级缓存:Map> singletonFactories,存放可以生成 Bean 的工厂

Spring 管理的 Bean 其实默认都是单例的,也就是说 Spring 将最终可以使用的 Bean 统一放入第一级缓存中,也就是 singletonObjects(单例池)里,以后凡是用到某个 Bean 了都从这里获取就行了。

2.可以只使用一级缓存吗?

不能,因为一级缓存虽然存的是完全的初始化好的Bean。如果我们直接将未完成初始化的Bean放入singleObjects里边,这个未初始化完的bean很有可能会被其他类拿去使用,他由于未完成初始化,很大概率抛出NPE。例如:

A、B、C三个对象

  • 先实例化 A 类,叫 a
  • 将 a 放入 singletonObjects 中(此时 a 中的 b 属性还是空的呢)
  • C 类需要使用 A 类,去 singletonObjects 获取,且获取到了 a
  • C 类使用 a,拿出 a 类的 b 属性,然后 NPE了

    3.可以使用二级缓存?

    image.png
    图中的缓存是二级缓存。

    创建A—->实例化A之后将A加入二级缓存—->开始填充A的属性发现需要B—->去缓存中查询没有B的实例—->创建B—->进行B的实例化—->将实例化的结果放入二级缓存—>填充B的属性发现需要A—->去缓存中查询发现有A的实例——>从缓存中取出A设置到B的属性上——>完成B的初始化—->返回A填充A的属性B—->完成A的初始化

4.为什么要用三级缓存

和Spring的生命周期有关

Spring 中 Bean 的生命周期,指的就是 Bean 从创建到销毁的一系列生命活动。那么由 Spring 来管理 Bean,要经过的主要步骤有:

  • 1.Spring 根据开发人员的配置,扫描哪些类由 Spring 来管理,并为每个类生成一个 BeanDefintion,里面封装了类的一些信息,如全限定类名、哪些属性、是否单例等等
  • 2.根据 BeanDefintion 的信息,通过反射,去实例化 Bean(此时就是实例化但未初始化 的 Bean)
  • 3.填充上述未初始化对象中的属性(依赖注入)
  • 4.如果上述未初始化对象中的方法被 AOP 了,那么就需要生成代理类(也叫包装类)
  • 5.最后将完成初始化的对象存入缓存中(此处缓存 Spring 里叫: singletonObjects),下次用从缓存获取 ok 了

    如果没有涉及到AOP,那么第四步就没有生成代理类,将第三步完成初始化的对象填入缓存中。

4.1.二级缓存会有什么问题

如果 Bean 没有 AOP,那么用二级缓存其实没有什么问题的,一旦有上述生命周期中第四步,就会导致的一个问题。因为 AOP 处理后,往往是需要生成代理对象的,代理对象和原来的对象根本就不是 1 个对象。

以二级缓存的场景来说,假设 A 类的某个方法会被 AOP,过程就是这样的:
image.png

  • 1.生成 a 的实例,然后放入缓存,a 需要 b
  • 2.再生成 b ,填充 b 的时候,需要 a,从缓存中取到了 a,完成 b 的初始化;
  • 3.紧接着 a 把初始化好的 b 拿过来用,完成 a 的属性填充和初始化
  • 4.由于 A 类涉及到了 AOP,再然后 a 要生成一个代理类,这里就叫:代理 a 吧

结果就是:a 最终的产物是代理 a,那 b 中其实也应该用代理 a,而现在 b 中用的却是原始的 a 代理 a 和原始的 a 不是一个对象,现在这就有问题了。

4.2.使用三级缓存如何解决?

二级缓存还是有问题,那就再加一层缓存,也就是第三级缓存:Map> singletonFactories,在 bean 的生命周期中,创建完对象之后,就会构造一个这个对象对应的 ObjectFactory 存入 singletonFactories 中。
singletonFactories 中存的是某个 beanName 及对应的 ObjectFactory,这个 ObjectFactory 其实就是生成这个 Bean 的工厂。实际中,这个 ObjectFactory 是个 Lambda 表达式:() -> getEarlyBeanReference(beanName, mbd, bean),而且,这个表达式并没有执行。
getEarlyBeanReference 具体做了什么事情?

  • 第一步:根据 beanName 将它对应的实例化后且未初始化完的 Bean,存入 Map earlyProxyReferences = new ConcurrentHashMap<>(16);
  • 第二步:生成该 Bean 对应的代理类返回

这个 earlyProxyReferences其实就是用于记录哪些 Bean 执行过 AOP,防止后期再次对 Bean 进行 AOP。那么getEarlyBeanReference 什么时候被触发,什么时候执行?
在二级缓存示例中,填充 B 的属性时候,需要 A,然后去缓存中拿 A,此时先去第三级缓存中去取 A,如果存在,此时就执行 getEarlyBeanReference 函数,然后该函数就会返回 A 对应的代理对象。
后续再将该代理对象放入第二级缓存中,也就是 Map earlySingletonObjects里
image.png

创建A—>实例化A—->将A加入三级缓存—->填充A属性发现需要B

image.png

  • A绑定到ObjectFactory 注册到工厂缓存singletonFactory中,
  • B在填充A时,先查成品缓存有没有,再查半成品缓存有没有,最后看工厂缓存有没有单例工厂类,有A的ObjectFactory。调用getObject ,执行扩展逻辑,可能返回的代理引用,也可能返回原始引用。
  • 成功获取到A的早期引用,将A放入到半成品缓存中,B填充A引用完毕。
  • 代理问题, 循环依赖问题都解决了。