spring循环依赖的理解


前言

“兴趣是最好的老师”-----佚名


  1. JDK11 (JDK8的新特性还没写,就用上11了)
  2. spring 5.2.X (应该是此类文章的最新版本了)
  3. Gradle 6.5.1 (gradle要是玩不好的话构建这个源码要花一番功夫 )

一、Spring解决循环依赖的前置条件

  1. 出现循环依赖的Bean必须要是单例对象(singleton),如果依赖prototype原型对象就不会有这个需求,需要就new就完事儿。
  2. 依赖注入的方式不能全是构造器注入的方式(只能解决setter方法的循环依赖,这个说法不对。)

关于第二点的说明:
最重要的一点就是,AB循环依赖,在spring默认按照beanName的英文字母顺序实例化的时候,A注入B用的有参构造,B注入A用是setter方法时无法注入成功,但是反过来却可以。

|  |  |](https://img-blog.csdnimg.cn/202101072152188.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2hlcm9faXNfbWU=,size_16,color_FFFFFF,t_70)

在这里插入图片描述

二、学习是生命长河中泛起的波光粼粼

1.凭印象说说

在这里插入图片描述

  • 正常实例化一个对象,先编译成class,类加载器加载到jvm的堆(分配内存)中,再把引用指针指向栈的引用对象。(这方面看的不多暂时这么理解)
  • spring实例化对象,有一套很规范很长的流程(毕竟要考虑到咱开发者的所有需求的前提下管理众多的bean对象)。
  1. spring会解析需要实例化的对象(OOO表示),把它的元数据(别人介绍你的时候不会指着你说这就是你,还会附带的介绍你是不是富二代,有几个前女友,是不是程序员,这样的就属于元数据,spring官方用beanDefintion对象表示)解析出来。
  2. 在实例化之前的一些验证过后开始实例化
  3. 验证处理完毕后开始实例化对象,先标记下此对象处于正在创建的过程中,createBean的时候会遇到spring的后置处理BeanPostProcessors处理(spring实例化一个对象一共会经过九个后置处理对象的依次处理)
  4. 创建对象后,紧接着在第四次后置处理器中addSingletonFactory(了解过spring三级缓存的同学应该知道,这里就是向第二级缓存中添加此对象的单例工厂。singletonObjects:一级缓存指的是所有创建好了的单例Bean,单例池也是小白理解的spring容器),紧接着就是再remove掉这个beanName对应的earlySingletonObjects(也就是三级缓存,存放的是未注入属性的Bean)中的对象(看了下代码就一个地方有put对应的有两个地方remove,暂且理解为确保此对象只有一个bean工厂吧)。
  5. 到这一步是属性注入了,在populateBean自动注入方法中会执行第五次第六次后置处理器BeanPostProcessors,此处是循环引用的重点了。因为属性注入发现引用了另一个未实例化的对象。那就开始属性注入吧,here we go。
  6. 属性注入的时候,spring把所有的后置处理拿出来挨个判断,挨个处理,结果有个叫做AutowiredAnnotationBeanPostProcessor的后置处理器表示也要来尝试下,结果发现你用了@Autowired注解注入了一个没有实例化的XXX对象。那么这个后置处理器开始表演了。(面试官问@Autowired和@Resoure注入属性的区别时除了可以说依据类型和名字注入的区别外还可以说一个是spring后置处理器处理一个是jdk的annotation下的注解,很牛掰有没有)。在后置处理器AutowiredAnnotationBeanPostProcessor的metadata.inject方法中开始属性注入。
  7. inject方法会循环遍历所有此类型需要注入的对象,经过一系列的验证之后发现这个XXX对象现在就要注入,那没办法只能给他注入。这里会判断是不是AutowireCapableBeanFactory类型的BeanFactory,是就用这种类型处理不是就自个儿类型处理(不太重要)。
  8. 接下来进入getSingleton()方法里进入三个缓存斗地主环节(谁有牌谁出,都没有的话轮到singletonFactories出牌),此处会返回最开始要实例化对象OOO的代理对象给到XXX对象完成属性注入,然后再返回XXX对象给OOO完成属性注入。到此整个循环引用走完了。(有不准确的地方欢迎指正,感谢。)

2.源码流程

  1. 先看下笔者的自定义类(三个初始化实例和一个启动类)

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

  1. 当然从refresh()开始 spring体系过于庞大,此处只研究循环依赖,其它的断点直接过了.
    在这里插入图片描述

  2. 毫无疑问实例化自定义对象从这个对象开finishBeanFactoryInitialization() 部分spring自带的对象(后置处理器等)在此之前已经实例化,在单例池已经存在。
    在这里插入图片描述
    从这里开始

  3. 可以看到前期准备的需要实例化的beanNames一共有7个,后三个是咱的 循环依赖是最后两个
    在这里插入图片描述

  4. 它首先会去单例池这边看下 发现前四个beanName都有了直接get出来美滋滋,这四个也不是咱的关注对象 直接过了。
    在这里插入图片描述

  5. 这里是咱的重头戏 开始实例化indexService对象,先判断是不是抽象类单例和懒加载已经bean工厂类型,很显然都不是所以直接进入开始实例化普通的bean, getBean(beanName) 方法

在这里插入图片描述

  1. 然后走到这里在单例池中找 ,很明显现在啥也没有,也没有被标记为正在创建中,直接返回null
    在这里插入图片描述
    在这里插入图片描述

  2. beanNames里前四个是可以直接从单例池中获取到,所以进入这个getObjectForBeanInstance()方法,咱是null所以打扰了
    在这里插入图片描述

  3. 接着是个小插曲,会判断是不是在创建过程中很显然是在创建但是没有标记,然后查找对应的BeanFactory也没有,然后在标记此对象正在创建。private final Set alreadyCreated = Collections.newSetFromMap(new ConcurrentHashMap<>(256));放进这个Set里就叫在标记了
    在这里插入图片描述

  4. 接下来getMergedLocalBeanDefinition(beanName)合并bean获取到beanName对应的BeanDefinition,可以看到经过这个操作已经获取到了这个beanName对应的beanClass对象里面包含类加载器和相应的构造方法,为后面spring反射实例化此对象做准备,接下来会看下有没有dependsOn注解,我们没有,过了
    在这里插入图片描述

  5. getSingleton()中会检查是否在单例池中然后放入此Set集合 表示正在创建private final Set singletonsCurrentlyInCreation =
    Collections.newSetFromMap(new ConcurrentHashMap<>(16));
    这里添加的的singletonsCurrentlyInCreation就是步骤7里判断的那个集合。也就是说下次的场景中再去getSingleton(“indexService”)的时候,是可以通过第一个if的;
    在这里插入图片描述

  6. 可以看到经过上图中的createBean()里的resolveBeanClass方法后从beanDefintion中获取了IndexService的class。
    但是这个class经过spring的第一个后置处理器的处理过后没有得到一个对象(没看这里面是啥情况),接下来spring会通过接下来的后置处理器通过反射来得到需要的IndexService的JavaBeans包装类BeanWrapper

在这里插入图片描述

  1. 此处第一次调用spring的后置处理器 ,九个处理器内容过多以后有时间再写吧。

在这里插入图片描述

  1. 可以看到没有找到这里合适的构造函数。
    在这里插入图片描述

  2. 使用默认的构造函数实例化bean
    在这里插入图片描述

  3. 最后会来的这个方法里用instantiateClass下的ctor.newInstance(argsWithDefaultValues)通过反射获得对象BeanWrapper
    在这里插入图片描述

  4. 可以看到此处获取了Object bean 是IndexService@2247
    此处仅仅是实例化出来了这个bean, 没有属性注入,aop,调用初始化方法
    , @PostConstruct生命周期的初始化回调方法, 都没做。

在这里插入图片描述

  1. 接下来会添加这个对象的单例工厂到二级缓存中addSingletonFactory
    在这里插入图片描述

  2. 在populateBean() 中填充属性,也就是自动注入,里面会完成第五次第六次后置处理器的调用,但是之前的代码显示UserService还未实例化且注入了IndexService中,所以此处的属性注入就引出了循环依赖的问题,继续往里面看。

在这里插入图片描述

  1. 可以看到在populateBean()方法中 会把spring所有的后置处理器拿出来遍历,处理的操作例如aop,PostConstruct初始化方法,属性填充, @Autowaire注解对应的处理器都在此处开始干活。
    在这里插入图片描述

  2. 当轮询到AutowiredAnnotationBeanPostProcessor后置处理器的时候,会处理我们用@Autowired注解注入的 Userservice userservice 对象 但是这个对象还没有被实例化,所以在这个后置处理器中会实例化这个对象
    在这里插入图片描述
    在这里插入图片描述

  3. 可以看到需要注入的目标对象target中Userservice=null
    在这里插入图片描述

  4. AutowiredAnnotationBeanPostProcessor后置处理器在获取Userservice的RootBeanDefinition后在
    isAutowireCandidate的resolveBeanClass方法中获取Userservice的class,获取了一顿操作发现什么都没有因为这个对象压根还没创建,最后又去单例池里找没找到。

在这里插入图片描述

在这里插入图片描述

  1. 尝试去单例池中获取 发现没有这个实例 ,这就跟我们当初找IndexService一样样的,找不到就给他实例化,流程和上面IndexService一样样的重复的就不看了(下面主要是看下省略截图的调用栈。)
    在这里插入图片描述

  2. 这里到了重要环节 换作给Userservic进行属性注入了

在这里插入图片描述

  1. 可以看到要注入的目标对象desc是IndexService,被注入的对象beanName是userService。
    在这里插入图片描述

  2. 可以看到AutowiredAnnotationBeanPostProcessor后置处理器此处再此去单例池中找IndexService了 上次是去找IndexService中的UserviceService,发现没有并且不在创建过程中然后返回了null但是这次不一样了 ,IndexService在上面创建的过程中已经标记为正在创建过程中,且创建了对应的beanFactory。
    在这里插入图片描述
    在这里插入图片描述

看到上图 beanFactory.getBean(beanName),一定就很熟悉了有木有。

  1. 可以看到,IndexService的singletonObject虽然为null 但在isSingletonCurrentlyInCreation是否是在创建过程中判断通过.
    在这里插入图片描述
    在这里插入图片描述

  2. 然后在单例池singletonObjects和三级缓存earlySingletonObjects(半成品)中均未获得这个对象,最后去二级缓存singletonFactories中获取 ,很显然有对应的bean工厂可以获取到,用IndexService的singletonFactory获取到它的代理对象放进三级缓存,然后删除IndexService的singletonFactory。

在这里插入图片描述

  1. 接下来就是逐层返回对象,把IndexService注入到UserService,再把UserService注入到IndexService,整个循环依赖解决

总结

所有的成果都是为了提高体验,技术也不例外,相信技术与艺术的长河不久会汇聚到一起,而不是受困于当下。额外感谢路神的技术博客,好好学习,天天向上。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
循环依赖是指在程序中存在两个或多个类之间相互依赖的情况,形成一个循环的依赖链。当程序启动时,Spring Context加载所有的Bean时,会尝试按照它们的工作顺序创建Bean。在没有循环依赖的情况下,这个过程是正常的。但是当存在循环依赖时,就会出现问题。 循环依赖通常发生在使用构造函数注入的情况下,其他注入方式不会出现这个问题,因为其他方式会在程序使用到这个依赖时才会注入,而不是在Spring Context加载阶段发生。 解决循环依赖问题的方法有多种。一种方法是重新设计你的程序,确保分层问题得到正确处理。另一种常用的解决方案是使用@Lazy注解。你可以将@Lazy注解标记在构造函数的参数内,这样Spring会懒惰地初始化这个Bean,即给这个Bean创建一个代理,只有在真正使用到这个Bean时才会完全创建。 所以,如果之前没有报循环依赖的错误,可能是因为之前的代码没有出现循环依赖的情况。但是如果现在出现了循环依赖的错误,你可以尝试重新设计你的程序或者使用@Lazy注解来解决这个问题。 #### 引用[.reference_title] - *1* *2* *3* [spring boot 运行、打包报循环依赖错误异常](https://blog.csdn.net/GAOXINXINGgaoxinxing/article/details/97640441)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^insertT0,239^v3^insert_chatgpt"}} ] [.reference_item] [ .reference_list ]

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值