Spring三级缓存解决循环依赖
一 Spring bean对象的生命周期
二 三级缓存解决循环依赖
- 实现原理解析
spring利用singletonObjects, earlySingletonObjects, singletonFactories三级缓存去解决的,所说的缓存其实也就是三个Map
先实例化的bean会通过ObjectFactory半成品提前暴露在三级缓存中
我们假设现在有这样的场景AService依赖BService,BService依赖AService
(1) AService首先实例化,实例化通过ObjectFactory半成品暴露在三级缓存中
(2)填充属性BService,发现BService还未进行过加载,就会先去加载BService
(3)再加载BService的过程中,实例化,也通过ObjectFactory半成品暴露在三级缓存
(4)填充属性AService的时候,这时候能够从三级缓存中拿到半成品的ObjectFactory
拿到ObjectFactory对象后,调用ObjectFactory.getObject()方法最终会调用getEarlyBeanReference()方法,getEarlyBeanReference这个方法主要逻辑大概描述下如果bean被AOP切面代理则返回的是beanProxy对象,如果未被代理则返回的是原bean实例。
这时我们会发现能够拿到bean实例(属性未填充),然后从三级缓存移除,放到二级缓存earlySingletonObjects中,而此时B注入的是一个半成品的实例A对象,不过随着B初始化完成后,A会继续进行后续的初始化操作,最终B会注入的是一个完整的A实例,因为在内存中它们是同一个对象。
如果这个bean被AOP进行了切面代理,singleFactory.getObject()方法每次执行都会是一个新的代理对象,假设这里只有一级和三级缓存的话,我每次从三级缓存中拿到singleFactory对象,执行getObject()方法又会产生新的代理对象,这是不行的,因为AService是单例的,所有这里我们要借助二级缓存来解决这个问题,将执行了singleFactory.getObject()产生的对象放到二级缓存中去,后面去二级缓存中拿,没必要再执行一遍singletonFactory.getObject()方法再产生一个新的代理对象,保证始终只有一个代理对象。
- 总结:
为了解决循环依赖问题,Spring采用三级缓存(Three-Level Cache)的机制。具体步骤如下:
(1) 创建对象并放入singletonObjects缓存:当创建一个Bean时,Spring会先尝试从singletonObjects缓存中获取该Bean实例,如果找到则直接返回。如果没有找到,则进入下一步。
(2)提前暴露对象:在创建Bean的过程中,当Spring发现存在循环依赖时,会先提前暴露正在创建的Bean,并将其放入earlySingletonObjects缓存中。这样可以避免后续循环依赖时的死锁情况。
(3)创建对象并完成依赖注入:Spring会继续创建当前Bean,并进行依赖注入。如果依赖中仍然存在循环依赖,Spring会使用ObjectFactory或Provider延迟注入依赖。这样可以确保所有的依赖都已经创建完成。
(4)添加到singletonObjects缓存:当Bean创建完成后,会将其放入singletonObjects缓存中,以供后续的依赖注入使用。
- 解决办法
在Spring中,如果出现循环依赖问题,可以采取以下几种方式来解决:
(1)构造函数注入:使用构造函数注入代替字段注入或setter注入。通过将依赖关系作为构造函数的参数传递,而不是直接在类中定义成员变量,并确保依赖关系的顺序正确,可以避免循环依赖的问题。
(2)使用@Lazy注解:使用@Lazy注解延迟加载Bean。通过在循环依赖的其中一个Bean上添加@Lazy注解,使其延迟初始化,从而打破循环依赖的死锁情况。
(3)使用代理模式:当出现循环依赖时,可以通过使用代理模式来解决。Spring提供了两种类型的代理:JDK动态代理和CGLIB代理。可以根据具体情况选择合适的代理方式。
(4)使用Setter注入:将字段注入改为使用setter注入。通过在setter方法上使用@Autowired注解,显式地控制依赖关系的注入顺序,以避免循环依赖。
(5)重新设计代码结构:有时,循环依赖问题可能是由于类之间的紧密耦合导致的。在这种情况下,重新审视代码结构,将相关的功能进行合理划分,减少或消除循环依赖。