什么是循环依赖,怎么解决

tim-qtp...大约 3 分钟Spring框架

什么是循环依赖问?

A 依赖 B,B 依赖 A,或者 C 依赖 C,就成了循环依赖。

看下方的代码就知晓了

@Service
public class A {
    @Autowired
    private B b;
}

@Service
public class B {
    @Autowired
    private A a;
}

//或者自己依赖自己
@Service
public class A {
    @Autowired
    private A a;
}

原因很简单,AB 循环依赖,A 实例化的时候,发现依赖 B,创建 B 实例,创建 B 的时候发现需要 A,创建 A1 实例……无限套娃。。。。

怎么解决循环依赖呢?

Spring 通过三级缓存机制来解决循环依赖:

  1. 一级缓存:存放完全初始化好的单例 Bean。
  2. 二级缓存:存放正在创建但未完全初始化的 Bean 实例。
  3. 三级缓存:存放 Bean 工厂对象,用于提前暴露 Bean。

A 实例的初始化过程:

①、创建 A 实例,实例化的时候把 A 的对象⼯⼚放⼊三级缓存,表示 A 开始实例化了,虽然这个对象还不完整,但是先曝光出来让大家知道。

②、A 注⼊属性时,发现依赖 B,此时 B 还没有被创建出来,所以去实例化 B。

③、同样,B 注⼊属性时发现依赖 A,它就从缓存里找 A 对象。依次从⼀级到三级缓存查询 A。

发现可以从三级缓存中通过对象⼯⼚拿到 A,虽然 A 不太完善,但是存在,就把 A 放⼊⼆级缓存,同时删除三级缓存中的 A,此时,B 已经实例化并且初始化完成了,把 B 放入⼀级缓存。

④、接着 A 继续属性赋值,顺利从⼀级缓存拿到实例化且初始化完成的 B 对象,A 对象创建也完成,删除⼆级缓存中的 A,同时把 A 放⼊⼀级缓存

⑤、最后,⼀级缓存中保存着实例化、初始化都完成的 A、B 对象。

为什么要三级缓存?⼆级不⾏吗?

不行,主要是为了 ⽣成代理对象。如果是没有代理的情况下,使用二级缓存解决循环依赖也是 OK 的。但是如果存在代理,三级没有问题,二级就不行了。

涉及到动态代理(AOP) 时,直接使用二级缓存不做任何处理会导致我们拿到的 Bean 是未代理的原始对象

因为当 Spring 创建一个 Bean(比如 ABean)时,如果这个 Bean 需要 AOP 代理(例如通过 @Transactional 或其他切面),Spring 并不会直接将原始实例暴露出去。相反,它会把一个生成该 Bean 的代理对象的工厂ObjectFactory)存放到三级缓存中。

如果其他 Bean 在初始化过程中依赖 ABean,Spring 会依次从一级缓存、二级缓存查找。如果都找不到,就会去三级缓存取出 ObjectFactory,并调用它的 getEarlyBeanReference() 方法。这个方法会检查当前 Bean是否需要代理,并返回一个代理后的早期引用。随后,这个早期引用会放入二级缓存供后续使用。

如果没有第二级缓存就会出现早期实例引用被代理后的早期实例引用相互覆盖!