一、整体推导思路
为了彻底搞懂Spring是如何利用三级缓存来解决循环依赖问题的,要么去找三级缓存的设计者了解其设计的初衷,要么利用反推法来进行倒推(即一级缓存为啥不行,二级缓存为啥也不合适)。
为了让大家能有一个更清晰的理解脉路,下面将先从反推法来介绍下一级缓存为啥不行、二级缓存为啥也不合适,然后再介绍为啥三级缓存适合解决循环依赖的问题,通过前后对比,理解起来也会更加清晰明了。
二、缓存的四个基本问题
另外,讲到缓存,都必定涉及到以下四个基本问题:
问题1:为啥要用缓存,即缓存的作用?
回答:为了加速不同使用者对共享资源的访问。通过把不需要多次创建的共享资源(如数据库连接、日志记录对象等),在其首次被创建后,按照key(资源名称)-value(资源实体)的方式缓存起来,以便下次能快速根据key来检索到该资源,而非再次创建。
问题2:何时进行缓存?
回答:当该对象是业务上需要缓存的共享资源,且首次被创建出来后,即可进行缓存
问题3:缓存的内容是啥?
回答:缓存的内容是资源实体对象。
问题4:缓存啥时候失效?
回答:当该资源实体对象在系统中没有存在的必要了;或者构成资源实体的基本信息有变化,需要根据变化后的基本信息来重新构建时,旧的缓存就失效了。
在Spring循环依赖这个讨论背景下,共享资源就是Spring容器管理的bean对象。
三、一级缓存为啥解决不了循环依赖的问题(缓存完整的bean对象)
如果设计成一级缓存,若存在循环依赖时,两个bean对象相互依赖,都需要拿到对方的bean对象,才能进行自身bean对象的创建,这时仅通过一级缓存的设计方案(完整的bean对象),两个bean对象的创建动作将永远无法结束,就像死锁一样无限期的死等下去。
而且,每个bean对象在创建完成后,无论后续是否被会被其他bean对象引用,都会无脑存放到一级缓存中,随着bean对象的不断增加,缓存的内容越来越多,势必对系统资源造成越来越大的压力。
四、二级缓存可以解决为啥也不合适(缓存尚未进行属性注入的早期bean对象)
如果设计成二级缓存,若存在循环依赖,两个bean对象相互依赖,因为有二级缓存的存在,不需要再等到构建出完整的bean对象,而是可以提前获取到尚未进行属性注入的早期的bean对象,这时两个bean对象的创建动作将不会因为获取不到对方的bean对象就无限期死等下去,而是可以正常进行下去。
注意:二级缓存是可以解决Spring的循环依赖问题,但是还存在资源消耗的问题以及当涉及代理对象时对象创建和对象缓存的职责耦合的复杂度问题。
但是,同一级缓存一样,在构造出的尚未进行属性注入的早期bean对象后,无论后续是否被会被其他bean对象引用,都会无脑存放到二级缓存中,随着bean对象的不断增加,缓存的内容越来越多,势必也会对系统资源造成越来越大的压力。
而且,因为有可能涉及到代理对象的逻辑,所以如果只设计成二级缓存,那么代理对象的创建和对象缓存这两个职责势必会耦合在一块,代理逻辑复杂度也会上升。
五、三级缓存为啥合适(缓存构建bean的工厂对象)
如果设计成三级缓存,若存在循环依赖,两个bean对象相互依赖,因为有二级缓存的存在,不需要再等到构建出完整的bean对象,而是可以提前获取到尚未进行属性注入的早期的bean对象,这时两个bean对象的创建动作将不会因为获取不到对方的bean对象就无限期死等下去,而是可以正常进行下去。
而且,因为有三级缓存的存在,只有在其他bean对象需要该bean对象的时候,才会到三级缓存获取bean工厂开始bean对象的缓存(此时可以进行代理对象的处理,如果是代理对象则创建代理对象并放到二级缓存,否则通过反射获取原始对象放到二级缓存,并清除三级缓存),而非在bean对象创建后就无脑的进行缓存。即只有在真正需要读取缓存内容的时候才进行缓存,即将缓存的动作延迟到了需要读取缓存的时候。这样,就极大了减少了因为缓存给系统资源带来的压力。
并且,因为有三级缓存的存在,还可以将代理对象的创建和对象缓存的职责解耦,代理逻辑也更加清晰明了。
六、三级缓存的整体流程
在bean对象创建时,会将bean的工厂对象缓存到三级缓存里,当有其他bean对象首次需要引用该bean对象时,会首先检查一级缓存是否存在完整的bean对象,若有则获取并返回;否则检查二级缓存是否存在尚未进行属性注入的早期bean对象,若有则获取并返回;否则检查三级缓存是否存在bean的工厂对象,若有则判断如果是代理对象则创建代理对象并放到二级缓存,否则通过反射获取原始对象放到二级缓存,并清除三级缓存。
这个流程图描述了Bean对象创建和引用时的缓存检查过程。当一个Bean对象被创建时,它的工厂对象会被缓存到三级缓存中。当其他Bean对象需要引用这个Bean时,会按照一级缓存、二级缓存和三级缓存的顺序进行检查,并根据情况获取Bean对象或创建代理对象。
七、总结
一级缓存(缓存完整的bean对象),无法解决循环依赖问题,而且存在资源浪费;
二级缓存(缓存尚未进行属性注入的早期bean对象),可以解决循环依赖问题,但是存在资源浪费,而且存在当涉及代理对象时对象创建和对象缓存的职责耦合的复杂度问题;
三级缓存(缓存构建bean的工厂对象),不仅可以解决循环依赖问题,而且通过将缓存的动作延迟到了需要读取缓存的时候,极大减少了因缓存给系统资源带来的压力。