这是我参与8月更文挑战的第31天,活动详情查看:8月更文挑战
前言
经典的面试题, 如何解决循环依赖?前面介绍了Spring IOC的生命周期,Bean容器的加载过程,那么我们从循环依赖的概念着手一步一步剖析Spring解决循环依赖的原理。
1. 循环依赖场景
循环依赖, 顾名思义, 其实就是Bean对象之间引用存在循环.
1.1 单例Bean的属性注入
@Service
public class OrderService {
@Resource
private StringRedisTemplate stringRedisTemplate;
@Resource
private PayService payService;
public void saveOrder() {
// ...
payService.pay(...)
}
}
@Service
public class PayService {
@Resource
private OrderService orderService;
public void pay(Long orderNo) {
// 查询订单信息
orderService.query(...)
}
}
这是一个非常常见的循环依赖的例子, OrderService 和 PayService相互依赖运行正常, 具体原理下面分析.
1.2 多实例Bean的属性注入
@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
@Service
public class ScopePrototypeService1 {
@Resource
private ScopePrototypeService2 service2;
public void doSomething() {
}
}
@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
@Service
public class ScopePrototypeService2 {
@Resource
private ScopePrototypeService1 service1;
public void doSomething() {
}
}
直接写单元测试运行报错, 存在循环依赖. 但是如果没有单例引用这个多实例Bean的话,容器是可以启动成功的, 因为我们知道容器的启动过程是多实例Bean不能去初始化.
注意:这种循环依赖问题是无法解决的,因为它没有用缓存,每次都会生成一个新对象。
1.3 构造器注入
@Service
public class ConstructInjectService1 {
public ConstructInjectService1(ConstructInjectService2 service2) {
}
public void execute() {
}
}
@Service
public class ConstructInjectService2 {
public ConstructInjectService2(ConstructInjectService1 service1) {
}
public void execute() {
}
}
测试代码
@SpringBootTest
public class ConstructInjectTest {
@Resource
private ConstructInjectService1 service1;
@Test
public void test() {
}
}
结果:
1.4 单例代理对象的属性注入
这个错误平时开发中可能比较常见, 比如@Async注解的场景,会通过AOP自动生成代理对象。平时我们开发的时候不注意的话可能会造成应用启动不起来.
先启用 @EnableAsync
@Service
public class PayService {
@Resource
private OrderService orderService;
@Async
public void pay(Long orderNo) {
}
}
@Service
public class OrderService {
@Resource
private PayService payService;
public void saveOrder() {
payService.pay(1L);
}
}
单元测试
@SpringBootTest
public class AsyncServiceTest {
@Resource
private OrderService orderService;
@Test
public void test() {
}
}
报错信息:
Caused by: org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name 'orderService': Bean with name 'orderService' has been injected into other beans [payService] in its raw version as part of a circular reference, but has eventually been wrapped. This means that said other beans do not use the final version of the bean. This is often the result of over-eager type matching - consider using 'getBeanNamesForType' with the 'allowEagerInit' flag turned off, for example.
1.5 @DependsOn 注解循环依赖
还有一种有些特殊的场景,比如我们需要在实例化Bean A之前,先实例化Bean B,这个时候就可以使用@DependsOn注解。
2. 循环依赖解决方案
2.1 生成代理对象产生的循环依赖解决方案
-
- 使用
@Lazy注解,延迟加载
- 使用
-
- 使用
@DependsOn注解,指定加载先后关系
- 使用
-
- 修改文件名称,改变循环依赖类的加载顺序
2.2 使用@DependsOn产生的循环依赖 的解决方案
只能修改代码解决
2.3 多例循环依赖 的解决方案
看具体场景,是否可以改为单例Bean解决
2.4 构造器循环依赖的解决方案
lazy 或者改为属性注入的方式
3. Spring单例如何解决循环依赖的?
三级缓存:
- singletonObjects 一级缓存,用于保存实例化、注入、初始化完成的bean实例
- earlySingletonObjects 二级缓存,用于保存实例化完成的bean实例
- singletonFactories 三级缓存,用于保存bean创建工厂,以便于后面扩展有机会创建代理对象。
最后
以上就是Spring循环依赖的一些知识点, 主要从问题的场景分析, 然后产生循环依赖的解决方案, 避免大家在开发中踩坑