前言:在最近的工作当中偶然遇到了Spring的循环依赖的Bug,搜集了网上的资料参考和分析,写了一份总结进行学习和分享!
目录
一、错误概述
二、根本原因
三、解决方案
四、Spring是如何解决循环依赖的
一、错误概述
在最近的工作当中,启动项目的时候报了一个类似于以下的错误:
Description:
The dependencies of some of the beans in the application context form a cycle:
┌─────┐
| AService (field private com.example.demo.service.BService
com.example.demo.service.AService.bService)
↑ ↓
| BService (field private com.example.demo.service.AService
com.example.demo.service.BService.aService)
└─────┘
二、根本原因
在AService 注入了BService这个Bean,在BService 同时也注入了 AService 这个Bean,两个类相互引用对方,导致Spring在初始化bean的时候不知道先初始化哪 个,从而形成循环依赖注入。详细如下:
AService:
package com.example.demo.service;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service
public class AService {
@Autowired
private BService bService;
}
BService:
package com.example.demo.service;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service
public class BService {
@Autowired
private AService aService;
}
三、解决方案
1.从根本上解决这个问题,不要让它们相互依赖,重新设计代码结构,代码解耦肯定是最优解!
2.添加@Lazy注解
package com.example.demo.service;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service
public class AService {
@Lazy
@Autowired
private BService bService;
}
package com.example.demo.service;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service
public class BService {
@Lazy
@Autowired
private AService aService;
}
这个注解的原理是:当实例化对象时,如果发现参数或者属性有@Lazy注解修饰,则不会直接创建所依赖的对象,而是使用动态代理创建一个代理类 通俗的来说:AService的创建,需要依赖BService,那就不直接创建BService了,而是使用动态代理创建了一个代理类BService1,此时AService和BService就不是相互依赖了,从而变成了AService依赖了一 个代理类BService1,BService依赖AService。但因为在注入依赖时,AService并没有完全初始化结束, 实际上注入了一个代理对象,只有他当首次被使用的时候才会被完全初始化。
3.在application.properties配置当中加入
spring.main.allow-circular-references=true
注:从SpringBoot 2.6开始默认禁用了循环依赖
四、Spring是如何解决循环依赖的
单例Bean的初始化需要经历三步:
Bean初始化步骤:
注入就发生在第二步,属性赋值,结合这个过程,Spring通过三级缓存解决了循环依赖:
一级缓存 : Map<String,Object> singletonObjects,单例池,用于保存实例化、属性赋值(注入)、初始化完成的 bean 实例。
二级缓存 : Map<String,Object> earlySingletonObjects,早期曝光对象,用于保存实例化完成的 bean实例。
三级缓存 : Map<String,ObjectFactory<?>> singletonFactories,早期曝光对象工厂,用于保存 bean 创 建工厂,以便于后面扩展有机会。
当 A、B 两个类发生循环依赖时:
A实例的初始化过程:
1.创建A实例,实例化的时候把A对象工厂放入三级缓存,表示A开始实例化了,虽然我这个对象还不完整,但是先曝光出来让大家知道。
2.A注入属性时,发现依赖B,此时B还没有被创建出来,所以去实例化B。
3.同样,B注入属性时发现依赖A,它就会从缓存里找A对象。依次从一级到三级缓存查询A,从三级缓存通过对象工厂拿到A,发现A虽然不太完善,但是存在,把A放入二级缓存,同时删除三级缓存中的A,此时,B已经实例化并且初始化完成,把B放入一级缓存。
4.接着A继续属性赋值,顺利从一级缓存拿到实例化且初始化完成的B对象,A对象创建也完成,删除二级缓存中的A,同时把A放入一级缓存。