Spring循环依赖

1、为什么会出现循环依赖问题?

​ 在Spring中bean的生命周期大致为:

  1. 从文件中得到UserService.class
  2. 推断使用的构造方法(有参或无参)
  3. 获得Class普通对象
  4. 依赖注入
  5. 初始化前(@PostConstruct)
  6. 初始化(afterPropertiesSet)
  7. 初始化后(AOP等操作)
  8. 若定义了AOP,生成代理对象(与原先的Class是两个不同的对象)
  9. 将对象或代理对象放入单例池

​ 依赖注入问题主要发生在依赖注入缓解,场景如下

1
2
3
4
5
6
7
8
9
10
11
12
13
@Component
public class AService {
@Autowired
private BService bService;

}

@Component
public class BService {
@Autowired
private AService aService;

}

当AService依赖注入时,需要获取BService,由于BService尚未初始化,在单例池中找不到,需要创建,而BService中由依赖于AService,而此时AService正在创建,导致循环依赖。

2、Spring是怎么解决循环依赖问题的?

​ spring中通过三级缓存来解决循环依赖问题。

​ 三级缓存就是三个Map,分别为:

  • 一级缓存:private final Map<String, Object> singletonObjects = new ConcurrentHashMap(256);

  • 二级缓存:private final Map<String, Object> earlySingletonObjects = new ConcurrentHashMap(16);

    在出现循环依赖的情况下,保存进行提前AOP得到的代理对象

  • 三级缓存:private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap(16);

​ 打破循环,存储<beanName, ObjectFactory>结构,ObjectFactory是一个lambda表达式,相当于一个回调函数

​ 其流程大致如下:

image-20230215172402999

结论:三级缓存的作用是为了解决spring中Bean依赖注入时发生的循环依赖。如果不需要AOP,那么只需要二级缓存即可实现,如果有AOP,其实二级缓存也能够实现,但是会打破Bean的生命周期,不符合spring的原则,因为需要把AOP对象放入二级缓存中,那么就必须在所有需要AOP处理的Bean对象初始化之前就对Bean对象进行后置处理(生成AOP对象),即使没有发生循环依赖!这并不是spring想看到的,所以spring引入了三级缓存,而且存入的是<beanName, ObjectFactory>结构,ObjectFactory是一个lambda表达式,相当于一个回调函数,当发生循环依赖的时候,会进行lambda表达式的执行,获取到Bean对象或者 AOP代理对象,再将Bean对象或者 AOP代理对象存入二级缓存中,如果之后还有循环依赖指向该对象(类似 A 依赖 B , B 依赖 A和C , C 依赖 A这种情况),就直接从二级缓存里面获取,从而解决了循环依赖。(这里解释了为什么不直接在二级缓存里存放lambda表达式,因为同一个lambda表达式每执行一次,就会生成一个新的代理对象,不能保证单例)

3、提前AOP代理对象的属性填充、初始化

​ 在Spring AOP提前代理后获得的代理对象没有经过属性填充和初始化。那么这个代理又是如何保证依赖属性的注入的呢?答案回到Spring AOP最早最早讲的JDK动态代理上找,JDK动态代理时,会将目标对象target保存在最后生成的代理$proxy中,当调用$proxy方法时会回调h.invoke,而h.invoke又会回调目标对象target的原始方法。

​ 因此,其实在动态代理时,原始bean已经被保存在提前曝光代理中了。而后原始Bean继续完成属性填充和初始化操作。因为AOP代理$proxy中保存着traget也就是是原始bean的引用,因此后续原始bean的完善,也就相当于Spring AOP中的target的完善,这样就保证了Spring AOP的属性填充与初始化了!