一、什么是循环依赖?
循环依赖就是多个对象之间的依赖组成了一个闭合的循环回路,像是A依赖B,B依赖C,C又依赖了A,类似这样的情况就属于循环依赖。如下方代码:
@Component
public class A {
@Autowired
private B b;
}
@Component
public class B {
@Autowired
private C c;
}
@Component
public class C {
@Autowired
private A a;
}
一般来说对象之间的互相依赖很正常,不会出什么问题,但是如果要将对象交给spring容器进行管理的话,由于Bean的生命周期就可能会导致对象无法正常完成创建,这样就产生了循环依赖的问题(所以循环依赖问题一般都是针对spring来阐述的)
二、spring能解决哪些形式的循环依赖?
- 1、对象循环依赖的属性都是用@Autowired注解标记的;
- 2、对象的实例化方法(构造方法)中不能包含对其他对象的依赖,除非在该实例化方法(构造方法)上加上了@Lazy注解,类似下方代码:
@Component
public class UserService {
private OrderService orderService;
// 只需要循环的任意一个节点加上了@Lazy注解就可以打破循环依赖
// @Lazy
public UserService(OrderService orderService) {
this.orderService = orderService;
}
}
@Component
public class OrderService {
private UserService userService;
// 只需要循环的任意一个节点加上了@Lazy注解就可以打破循环依赖
// @Lazy
public OrderService(UserService userService) {
this.userService = userService;
}
}
三、spring是如何解决循环依赖的?
1、了解Bean对象的创建过程
要了解spring是如何解决循环依赖的,首先要明白在spring容器中Bean对象的生命周期,下面笔者就简单介绍下spring中Bean对象的创建过程:
- 1、Spring扫描class得到BeanDefinition
- 2、根据得到的BeanDefinition去生成bean
- 3、首先根据class推断构造方法
- 4、根据推断出来的构造方法,反射,得到一个对象(暂时叫做原始对象)
- 5、填充原始对象中的属性(依赖注入)
- 6、如果原始对象中的某个方法被AOP了,那么则需要根据原始对象生成一个代理对象
- 7、把最终生成的代理对象放入单例池(源码中叫做singletonObjects)中,下次getBean时就直接从单例池拿即可
2、找到问题的冲突点
循环以来问题的冲突点就在笔者上述流程中的第5步。在依赖性注入这一步上,spring是需要拿到B对象来对A对象的属性进行依赖注入,此时B对象没有创建,所以也需要进行上述的Bean创建流程,同样也是到了第五步,B对象又需要A对象来进行依赖注入,这就造成了死循环。
3、解决办法
spring解决这个问题的办法就是在注入的时候不直接拿成品对象,而是先拿半成品(代理)对象注入到对应的属性中,这样就能跳出循环了。
说到这里就要说一下spring的三级缓存了:
- 首先是singletonObject(单例池),这个算是一级缓存,它存储所有的完成品单例对象。当没有出现循环依赖时大部分情况下的属性注入都是从这个单例池中获取对象。
- 然后是二级缓存earlySingtonObjects,该级缓存是用来存放半成品对象(原始对象、代理对象),如果出现了循环依赖的情况下,spring会先从二级缓存中获取未完成的半成品对象注入到对应的属性中让Bean的生命周期能完整的走完,如果二级缓存中没有就会去三级缓存中寻找。
- 三级缓存singletonFactories中缓存的是ObjectFactory。这个ObjectFactory其实是一个lambda表达式,它会在上方第四步的时候根据原始对象生成。这个lambda表达式会判断当前对象是否有经过AOP处理,如果有的话会返回一个代理对象,如果没有的话就会直接将原始对象返回。
经过以上的三步,spring就解决的一部分循环依赖的问题,但要注意的是:它并不能解决所有循环依赖的问题!