Spring源码学习五——循环依赖

一、产生原因

前面已经讲过Spring bean的生命周期,我们知道bean在实例化之后,要经过填充属性、初始化完整之后才能放入单例池使用。那么,假如beanA依赖beanB,beanA填充属性的时候就会触发beanB的生命周期。而beanB又依赖beanA就会发现beanA还没有准备好无法注入,就会陷入相互等待的死循环,这就是循环依赖。如图
在这里插入图片描述

二、三级缓存

Spring为了解决循环依赖,使用了三级缓存,三级缓存分别对应DefaultSingletonBeanRegistry类定义的三个Map。注意,它们的顺序不是按照一二三定义的
在这里插入图片描述

2.1 环境准备

import org.springframework.context.annotation.AnnotationConfigApplicationContext;

public class TestSpring {
    public static void main(String[] args) {
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
        Object o = context.getBean("userService");
        System.out.println(o);
    }
}

AppConfig

import org.springframework.context.annotation.ComponentScan;

@ComponentScan("com.spring.study.service")
public class AppConfig {
}

OrderService

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

@Component
public class OrderService {

    @Autowired
    private UserService userService;
}

UserService

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

@Component
public class UserService {
    @Autowired
    private OrderService orderService;
}

2.2 源码跟踪

了解了bean的生命周期,咱从getBean(“orderService”)——>doGetBean——>getSingleton进入
在这里插入图片描述

2.2.1 OrderService获取过程

OrderService先开始
在这里插入图片描述
这块已经开始使用三级缓存,一级缓存singletonObjects还没有OrderService的实例,OrderService也还没开始创建,因此第一次不会进入if,之后继续
在这里插入图片描述
这块在bean的生命周期讲过,不再赘述。在getSingleton里面会执行这个lamba,进入createBean——>doCreateBean
在这里插入图片描述
在这之前bean已经实例化,如果bean是单例的,允许循环依赖,并且正在创建中进入if。这块是最关键的地方,addSingletonFactory用于打破循环
在这里插入图片描述

将单例bean的工厂放入三级缓存singletonFactories,以便随时执行getObject()得到实例化的bean。
返回再看看getEarlyBeanReference
在这里插入图片描述
如果bean,有被AOP,这里返回的就是AOP之后的代理对象,否则就是原来的对象,AOP后面讲。
在这里插入图片描述
目前的bean已经被实例化,尚未初始化。populateBean填充属性,会触发UserService的生命周期getBean(“userService”)
在这里插入图片描述

2.2.2 UserService获取过程

UserService的过程与OrderService类似,再次进入populateBean方法,填充属性,这时需要注入一个orderService。进入populateBean–>ibp.postProcessProperties,进入到AutowiredAnnotationBeanPostProcessor处理自动装配
在这里插入图片描述
继续进入,这块处理orderService字段在这里插入图片描述
继续进入resolveDependency–>doResolveDependency–>descriptor.resolveCandidate(autowiredBeanName, type, this);
在这里插入图片描述

再次回到getBean(“orderService”)

2.2.3 再次OrderService获取过程

再次进入getSingleton,orderService正在创建中,因此进入了if,此时一、二级缓存都为空,只有三级缓存有bean的工厂在这里插入图片描述
调用getObject(),就得到了orderService提前暴露的实例,然后把实例提升到二级缓存,删除三级缓存的值。一路返回,完成了orderService的注入,再完成UserService的初始化放入到一级缓存。继续完成userService注入到OrderService,完成OrderService的初始化。
在这里插入图片描述
之后在这里将OrderService实例提升到一级缓存
在这里插入图片描述
再次删除二级和三级中的缓存。到目前为止OrderService和UserService的实例都加入了一级缓存,解决了它们之间的循环依赖。这个过程中二级缓存earlySingletonObjects,好像没啥用,userService先加入earlySingletonObjects,然后再加入一级缓存singletonObjects同时在earlySingletonObjects中删除。感觉有点多此一举,下面咱们看下为啥要三级缓存

三、为啥要三级缓存,两级可以吗

singletonObjects 一级缓存
earlySingletonObjects 二级缓存
singletonFactory 三级缓存

3.1 过程梳理

BeanA实例化–>放入三级缓存–>填充属性BeanB,BeanC–>

(触发BeanB实例化–>放入三级缓存–>填充属性BeanA–>BeanA正在创建中,从三级缓存取出–>得到AOP之后的BeanA,放入二级缓存–>进行BeanB的AOP–>完成beanB创建–>BeanB放入一级缓存)–>
(触发BeanC实例化–>放入三级缓存–>填充属性BeanA–>BeanA从二级缓存取出–>得到AOP之后的BeanA–>进行BeanB的AOP–>完成beanB创建–>BeanC放入一级缓存)–>

判断是否需要AOP–>已经提前AOP–>完成benaA的创建–>BeanA放入一级缓存

3.2 分析

  • BeanB、BeanC的AOP在填充属性完成之后
  • BeanA的AOP在三级缓存中进行,也就是在填充属性完成之前,提前AOP了
  • 如果没有二级缓存,BeanC在创建过程中再次从三级缓存获取得到的BeanA将是一个新的AOP对象,因为每次AOP之后的代理对象都是不同的,不可行
  • 如果没有三级缓存,实例化之后的BeanA直接放入二级缓存,注入BeanB,BeanC的实例就是未经过AOP的。而BeanA最后会AOP之后放入一级缓存,不可行
  • 如果没有三级缓存,实例化之后的BeanA先经过AOP再放入二级缓存,这是可以的。但是,这么做之后所有bean的AOP都提前了。实际Spring是希望只有发生循环依赖的bean才提前进行AOP
  • 所以,实例化之后的BeanA,创建一个进行AOP的工厂,将这个工厂放入三级缓存。在发生循环依赖的时候从工厂取出经过AOP的BeanA,再将经过AOP的BeanA放入二级缓存。这样BeanA中的BeanB、BeanC都可以从二级缓存取到同一个AOP之后的BeanA,只有BeanA的AOP提前了。

3.3 结论

  1. 三级缓存保证了只有发生循环依赖的bean提前AOP
  2. 二级缓存保证多个属性循环依赖可以取到同一个AOP之后的Bean
  3. 一级缓存就是最终的单例池,bean放入之后,删掉二、三级缓存的bean。
  4. 二、三级缓存为了解决循环依赖而存在,如果没有循环依赖,一级缓存就够了。
  5. 有循环依赖,没有AOP,两级缓存也够了。
  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值