Spring解决循环依赖

1. 循环依赖

1.1 什么是循环依赖

首先,什么是循环依赖?这个其实好理解,就是 Bean之间互相依赖,类似下面这样:

@Service
public class AService {
    @Autowired
    BService bService;
}
@Service
public class BService {
    @Autowired
    AService aService;
}

AService 和 BService 互相依赖:

 
 ab相互依赖

这个应该很好理解。

1.2 循环依赖的类型

一般来说,循环依赖有三种不同的形态,上面 1.1 小节是其中一种。

另外两种分别是三者依赖,如下图:

ABC相互依赖

 

循环依赖解决思路

2.1 解决思路

那么对于循环依赖该如何解决呢?如果想要解决循环依赖增加个缓存就可以了

引入缓存

 

当创建Aservice的时候,会先通过反射实例化一个A的原始实例,此时AService只是刚创建出来并未进行属性设置,此时我们将AService的实例放入到缓存中。
接下来我们对Aservice进行属性设置,发现AService依赖Bservice,那么就去创建Bservice,此时发现Bservice依赖Aservice进行属性设置,这时就去缓存中取出Aservice的实例,Bservice完成属性设置,Bservice完成初始化后Aservice也完成初始化,循环依赖得以解决。

此处描述两个问题:

1.Spring中Bean的生命周期

1.实例阶段:使用无参构造创建对象(new AServer())
2.属性赋值阶段:对象实例化完成后,spring容器会通过依赖注入机制设置各个属性值。
3.初始化阶段:属性设置完成后,Spring容器会调用Bena的初始化方法进行初始化操作。
4.使用阶段:当应用程序调用对应的Bean就是使用阶段。
5.销毁阶段:当容器关闭的时候或显示调用spring API(@PreDestroy,destroy)进行销毁时对象会进行销毁。

2.此时缓存中的对象是半成品并不是初始完成的,但是java是引用传递(也可以认为是值传递,只不过这个值是内存地址),BService 当时拿到的是 AService 的引用,说白了就是一块内存地址而已,根据这个地址找到的就是 AService,所以,后续如果 AService 创建完成后,BService 所拿到的 AService 就是完整的 AService 了。

 

那么上面提到的这个缓存池,在 Spring 容器中有一个专门的名字,就叫做 earlySingletonObjects,这是 Spring 三级缓存中的二级缓存,这里保存的是刚刚通过反射创建出来的 Bean,这些 Bean 还没有经历过完整生命周期,Bean 的属性可能都还没有设置,Bean 需要的依赖都还没有注入进来。另外两级缓存分别是:

  • singletonObjects:这是一级缓存,一级缓存中保存的是所有经历了完整生命周期的 Bean,即一个 Bean 从创建、到属性赋值、到各种处理器的执行等等,都经历过了,就存到 singletonObjects 中,当我们需要获取一个 Bean 的时候,首先会去一级缓存中查找,当一级缓存中没有的时候,才会考虑去二级缓存。
  • singletonFactories:这是三级缓存。在一级缓存和二级缓存中,缓存的 key 是 beanName,缓存的 value 则是一个 Bean 对象,但是在三级缓存中,缓存的 value 是一个 Lambda 表达式,通过这个 Lambda 表达式可以创建出来目标对象的一个代理对象。

 

2.2以上看二级缓存就可以解决循环依赖为什么还要有三级缓存呢?

1.三级缓存是为了解决存在AOP的情况下会生成一个目标对象和一个代理对象,导致代理增强失败,如:事务管理失败、日志增强失败。

备注:事务是类级别的,只要类中有任何方法需要被增强(比如使用了@Transactional注解),Spring就会为整个类创建代理对象。

@Service
public class ProductService {
    public void methodA() {
        methodB();  
    }
    
    @Transactional
    public void methodB() {
        // 数据库操作
    }
}

 

2.以上述看Spring的生命周期分为5个阶段,而AOP是在初始阶段后基于目标对象生成代理对象进行增强的,这样就会产生一个问题,对象实例化的时候会生成一个原始的对象AService,在对象完成初始化后又会生成一个代理对象AService,此时两个AService是两个对象,两个内存空间。BService运行的时候应该执行代理对象的内存空间但是确指向了原始对象的内存空间,导致代理失效。

备注:代理的两种用法:https://www.cnblogs.com/sunnycc/p/19170472

3.为了解决这个问题,Spring 引入了三级缓存 singletonFactories。

singletonFactories 的工作机制是这样的(假设 AService 最终是一个代理对象):

当我们创建一个 AService 的时候,通过反射刚把原始的 AService 创建出来之后,先去判断当前一级缓存中是否存在当前 Bean,如果不存在,则:

1.首先向三级缓存中添加一条记录,记录的 key 就是当前 Bean 的 beanName,value 则是一个 Lambda 表达式 ObjectFactory,通过执行这个 Lambda 可以给当前 AService 生成代理对象。
2.然后如果二级缓存中存在当前 AService Bean,则移除掉。

现在继续去给 AService 各个属性赋值,结果发现 AService 需要 BService,然后就去创建 BService,创建 BService 的时候,发现 BService 又需要用到 AService,于是就先去一级缓存中查找是否有 AService,如果有,就使用,如果没有,则去二级缓存中查找是否有 AService,如果有,就使用,如果没有,则去三级缓存中找出来那个 ObjectFactory,然后执行这里的 getObject 方法,这个方法在执行的过程中,会去判断是否需要生成一个代理对象,如果需要就生成代理对象返回,如果不需要生成代理对象,则将原始对象返回即可。最后,把拿到手的对象存入到二级缓存中以备下次使用,同时删除掉三级缓存中对应的数据。这样 AService 所依赖的 BService 就创建好了。

接下来继续去完善 AService,去执行各种后置的处理器,此时,有的后置处理器想给 AService 生成代理对象,发现 AService 已经是代理对象了,就不用生成了,直接用已有的代理对象去代替 AService 即可。

至此,AService 和 BService 都搞定。

本质上,singletonFactories 是把 AOP 的过程提前了。

3. 小结

总的来说,Spring 解决循环依赖把握住两个关键点:

  • 提前暴露:刚刚创建好的对象还没有进行任何赋值的时候,将之暴露出来放到缓存中,供其他 Bean 提前引用(二级缓存)。
  • 提前 AOP:A 依赖 B 的时候,去检查是否发生了循环依赖(检查的方式就是将正在创建的 A 标记出来,然后 B 需要 A,B 去创建 A 的时候,发现 A 正在创建,就说明发生了循环依赖,其实使用的是Creating Set),如果发生了循环依赖,就提前进行 AOP 处理,处理完成后再使用(三级缓存)。
posted @ 2025-10-29 23:55  爵士灬  阅读(40)  评论(0)    收藏  举报