您当前的位置:首页 > 电脑百科 > 程序开发 > 框架

Spring源码分析之IOC循环依赖详解

时间:2021-04-27 10:24:19  来源:微信公众号  作者:Java耕耘者

 

1.什么是循环依赖?

当多个Bean相互依赖时则构成了循环依赖,例如A,B两个Bean。其中A中存在属性B,B中存在属性A,当Spring在实例化A时发现A中存在属性B,就去实例化B,实例化B时又发现存在属性A,一直在循环注入依赖,导致循环依赖问题出现。

2.Spring是怎么解决循环依赖的?

Spring中会通过各种Bean中间状态来达到Bean还未实例化完成时提前将Bean提前注入到依赖Bean的属性中,假设说Bean有三种状态分别是青年态(一级缓存)、胚胎态(二级缓存)、小蝌蚪态(三级缓存)其中青年态代表Bean已经实例化完成,可以直接使用了,胚胎态代表Bean已经存在了但是还在创建中,还未创建完毕,小蝌蚪态代表还未开始创建,但是随时可以进行创建,三个状态就类似于三个等级,可以逐步提升从小蝌蚪状态提升到胚胎状态然后再提升到青年态,然后Spring开始创建Bena时会提前将Bean存放到小蝌蚪态的缓存集合中,当发现存在循环依赖时会使用存在于小蝌蚪状态缓存集合中的Bean,提前

3.循环依赖的案例

假设例子中存在BeanA、BeanB、BeanC、BeanD四个Bean,其中

  • BeanA依赖着BeanB,C
  • BeanB依赖着BeanC
  • BeanC依赖着BeanA
  • BeanD依赖着BeanA,B,C

BeanA beanA = beanFactory.getBean("beanA",BeanA.class); BeanB beanB = beanFactory.getBean("beanB",BeanB.class); BeanC beanC = beanFactory.getBean("beanC",BeanC.class); BeanD beanD = beanFactory.getBean("beanD",BeanD.class);

那么他们的实例化流程是什么样的呢?

Spring源码分析之IOC循环依赖详解

 

发现了吧,Spring解决循环依赖的法宝就是缓存。

3.代码解析(只保留相关代码)

1.检查缓存中是否已经存在实例化完毕的Bean

protected Object getSingleton(String beanName, boolean allowEarlyReference) {

   //首先检查一级缓存中是否存在
   Object singletonObject = this.singletonObjects.get(beanName);

   /**
    *  如果一级缓存中不存在代表当前 Bean 还未被创建或者正在创建中
    *  检查当前 Bean 是否正处于正在创建的状态中(当Bean创建时会将Bean名称存放到 singletonsCurrentlyInCreation 集合中)
   */
   if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
      synchronized (this.singletonObjects) {
         //检查二级缓存中是否存在
         singletonObject = this.earlySingletonObjects.get(beanName);

         /**
          * @@如果二级缓存中不存在 并且 允许使用早期依赖
          * allowEarlyReference : 它的含义是是否允许早期依赖
          * @@那么什么是早期依赖? 
          * 就是当Bean还未成为成熟的Bean时就提前使用它,在实例化流程图中我们看到在添加缓存前刚刚实例化Bean但是还未依赖注入时的状态
         */
         if (singletonObject == null && allowEarlyReference) {
            
            //获取三级缓存中的 Bean ObjectFactory
            ObjectFactory singletonFactory = this.singletonFactories.get(beanName);
            //如果 Bean 对应的 ObjectFactory 存在
            if (singletonFactory != null) {
               //使用 getObject 方法获取到 Bean 的实例
               singletonObject = singletonFactory.getObject();
               //将 bean 从三级缓存提升至二级缓存
               this.earlySingletonObjects.put(beanName, singletonObject);
               this.singletonFactories.remove(beanName);
            }
         }
      }
   }
   return singletonObject;
}

 

 

2.创建Bean

protected Object doCreateBean(final String beanName, final RootBeanDefinition mbd, final @Nullable Object[] args)
      throws BeanCreationException {

   /**
    * 我们一开始通过 getSingleton() 方法中获取三级缓存中存放的Bean,这里就是向三级缓存中添加 bean 的地方
    * 流程:
    * 1.检查当前 bean 是否为单例模式,并且是否允许循环引用[讲解1],并且当前是否正在创建中(在getSingleton方法中添加的)
    * 2.如果允许提前曝光[讲解2],addSingletonFactory() 方法向缓存中添加当前 bean 的 ObjectFactory
    *
    * [讲解1]:当前 Bean 如果不允许循环引用(循环依赖也就是被依赖),则这里就不会提前曝光,对应的 ObjectFactory
    * 则当发生循环依赖时会抛出 BeanCreationException 异常
    *
    * [讲解2]:提前曝光的含义就是说当 bean 还未创建完毕时就先将创建中状态的bean放到指定缓存中,为循环依赖提供支持
   */
   boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences &&
         isSingletonCurrentlyInCreation(beanName));
   //需要提前曝光
   if (earlySingletonExposure) {
      /**
       * 向缓存(三级缓存)中添加当前 bean 的 ObjectFactory
      */
      addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
   }

   /**
    * Initialize the bean instance.
    * 初始化 Bean 实例阶段
   */
   Object exposedObject = bean;
   try {
      /**
       *  依赖注入这时会递归调用getBean
      */
      populateBean(beanName, mbd, instanceWrApper);
      //调用初始化方法,如:init-method
      exposedObject = initializeBean(beanName, exposedObject, mbd);
   }
   
   /**
    * 当允许提前曝光时进入判断
    * @这里做了什么?
    * 1.检查当前bean是否经历了一场循环依赖
    *   - 通过 getSingleton(beanName,false) 获取缓存中的 bean,传入 false 代表不获取三级缓存中的bean
    *   - 为什么说 检查当前bean是否经历了一场循环依赖呢? 因为上述说了传入 false 代表不获取三级缓存的
    *   - 那么什么情况下才会存在与一级缓存和二级缓存呢?答案就是循环依赖后 [解释1] 和bean实例化完成后
    *   - 所以如果 getSingleton 返回的 bean 不为空,则这个bean就是刚刚经历了循环依赖
    *
    * 2.检查提前曝光的bean和当前的Bean是否一致
    *   - 下面有个判断 if (exposedObject == bean) ,这个判断从缓存中获取的bean 和 经历过初始化后的 bean
    *   - 是否一致,可能我们有点晕,这里解释一下,缓存从的bean是什么时候存进去的?是在 addSingletonFactory 方法(649行)
    *   - 然后这里存进去的 bean 只是提前曝光的 bean,还没有依赖注入和初始化,但是在依赖注入和初始化时都是可能直接改变
    *   - 当前 bean 的实例的,这意味着什么?意味着经历了依赖注入和初始化的bean很可能和缓存中的bean就已经完全不是一个 bean了
    *   下面讲解当一致或不一致时的逻辑:
    * 2.1 一致:
    *  不是很理解,直接赋值,可是经历了各种 BeanPostProsser 或者依赖注入和初始化后不是就不一样了吗
    * 2.2 不一致:
    *  看下方对于 else if 代码块的解释
    *
    * @[解释1]
    * 当循环依赖时,A依赖着B,B依赖着A,实例化A首先将A放到三级缓存中然后发现依赖着B,然后去实例化B,发现依赖着A
    * 发现A在三级缓存,然后获取三级缓存中的bean并且将A从三级缓存中提升到二级缓存中,实例化B完成,接着实例化A也完成。
    *
    * @通俗讲解
    * 假设我们业务上对某种数据加了缓存,假设 i 在缓存中存的值为1,当我在数据库中把 i 的值改成 2 时,缓存中的 i 还没有被改变还是 1
    * 这时的数据已经和我们的真实数据偏离了,不一致了,这时有两种解决方式:1.服务器检查到数据不一致抛出异常。(也就是进入else if 代码块)
    * 2.直接使用原始值也就是1(也就是将 allowRawInjectionDespiteWrapping 改成 true),当然这两种方式明显不是我们正常数据库的操作,只是
    * 为了说明当前的这个例子而已。
    *
   */
   if (earlySingletonExposure) {
      //获取缓存中(除三级缓存) beanName 对应的 bean
      Object earlySingletonReference = getSingleton(beanName, false);
      //当经历了一场循环依赖后 earlySingletonReference 就不会为空
      if (earlySingletonReference != null) {
         //如果 exposedObject 没有在初始化方法中被改变,也就是没有被增强
         if (exposedObject == bean) {
            //直接赋值? 可是经历了各种 BeanPostProsser 或者依赖注入和初始化后不是就不一样了吗
            exposedObject = earlySingletonReference;
         }
         /**
          *
          * 走到 else if  时说明 当前 Bean 被 BeanPostProessor 增强了
          * 判断的条件为:
          * 1.如果允许使用被增强的
          * 2.检查是否存在依赖当前bean的bean
          *
          * 如果存在依赖的bean已经被实例化完成的,如果存在则抛出异常
          * 为什么抛出异常呢?
          * 因为依赖当前bean 的bean 已经在内部注入了当前bean的旧版本,但是通过初始化方法后这个bean的版本已经变成新的了
          * 旧的哪个已经不适用了,所以抛出异常
          *
         */
         else if (!this.allowRawInjectionDespiteWrapping && hasDependentBean(beanName)) {
            String[] dependentBeans = getDependentBeans(beanName);
            Set actualDependentBeans = new LinkedHashSet<>(dependentBeans.length);
            for (String dependentBean : dependentBeans) {
               if (!removeSingletonIfCreatedForTypeCheckOnly(dependentBean)) {
                  actualDependentBeans.add(dependentBean);
               }
            }
            if (!actualDependentBeans.isEmpty()) {
               throw new BeanCurrentlyInCreationException(beanName,
                     "Bean with name '" + beanName + "' has been injected into other beans [" +
                     StringUtils.collectionToCommaDelimitedString(actualDependentBeans) +
                     "] 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 " +
                     "'getBeanNamesOfType' with the 'allowEagerInit' flag turned off, for example.");
            }
         }
      }
   }

   try {
      /**
       * Register bean as disposable.
       * 注册 Bean 的销毁方法拓展
      */
      registerDisposableBeanIfNecessary(beanName, bean, mbd);
   }
   catch (BeanDefinitionValidationException ex) {
      throw new BeanCreationException(
            mbd.getResourceDescription(), beanName, "Invalid destruction signature", ex);
   }

   return exposedObject;
}

结束



Tags:IOC循环   点击:()  评论:()
声明:本站部分内容及图片来自互联网,转载是出于传递更多信息之目的,内容观点仅代表作者本人,如有任何标注错误或版权侵犯请与我们联系(Email:2595517585@qq.com),我们将及时更正、删除,谢谢。
▌相关推荐
1.什么是循环依赖?当多个Bean相互依赖时则构成了循环依赖,例如A,B两个Bean。其中A中存在属性B,B中存在属性A,当Spring在实例化A时发现A中存在属性B,就去实例化B,实例化B时又发现存...【详细内容】
2021-04-27  Tags: IOC循环  点击:(211)  评论:(0)  加入收藏
▌简易百科推荐
近日只是为了想尽办法为 Flask 实现 Swagger UI 文档功能,基本上要让 Flask 配合 Flasgger, 所以写了篇 Flask 应用集成 Swagger UI 。然而不断的 Google 过程中偶然间发现了...【详细内容】
2021-12-23  Python阿杰    Tags:FastAPI   点击:(6)  评论:(0)  加入收藏
文章目录1、Quartz1.1 引入依赖<dependency> <groupId>org.quartz-scheduler</groupId> <artifactId>quartz</artifactId> <version>2.3.2</version></dependency>...【详细内容】
2021-12-22  java老人头    Tags:框架   点击:(11)  评论:(0)  加入收藏
今天来梳理下 Spring 的整体脉络啦,为后面的文章做个铺垫~后面几篇文章应该会讲讲这些内容啦 Spring AOP 插件 (了好久都忘了 ) 分享下 4ye 在项目中利用 AOP + MybatisPlus 对...【详细内容】
2021-12-07  Java4ye    Tags:Spring   点击:(14)  评论:(0)  加入收藏
&emsp;前面通过入门案例介绍,我们发现在SpringSecurity中如果我们没有使用自定义的登录界面,那么SpringSecurity会给我们提供一个系统登录界面。但真实项目中我们一般都会使用...【详细内容】
2021-12-06  波哥带你学Java    Tags:SpringSecurity   点击:(18)  评论:(0)  加入收藏
React 简介 React 基本使用<div id="test"></div><script type="text/javascript" src="../js/react.development.js"></script><script type="text/javascript" src="../js...【详细内容】
2021-11-30  清闲的帆船先生    Tags:框架   点击:(19)  评论:(0)  加入收藏
流水线(Pipeline)是把一个重复的过程分解为若干个子过程,使每个子过程与其他子过程并行进行的技术。本文主要介绍了诞生于云原生时代的流水线框架 Argo。 什么是流水线?在计算机...【详细内容】
2021-11-30  叼着猫的鱼    Tags:框架   点击:(21)  评论:(0)  加入收藏
TKinterThinter 是标准的python包,你可以在linx,macos,windows上使用它,你不需要安装它,因为它是python自带的扩展包。 它采用TCL的控制接口,你可以非常方便地写出图形界面,如...【详细内容】
2021-11-30    梦回故里归来  Tags:框架   点击:(26)  评论:(0)  加入收藏
前言项目中的配置文件会有密码的存在,例如数据库的密码、邮箱的密码、FTP的密码等。配置的密码以明文的方式暴露,并不是一种安全的方式,特别是大型项目的生产环境中,因为配置文...【详细内容】
2021-11-17  充满元气的java爱好者  博客园  Tags:SpringBoot   点击:(25)  评论:(0)  加入收藏
一、搭建环境1、创建数据库表和表结构create table account(id INT identity(1,1) primary key,name varchar(20),[money] DECIMAL2、创建maven的工程SSM,在pom.xml文件引入...【详细内容】
2021-11-11  AT小白在线中  搜狐号  Tags:开发框架   点击:(29)  评论:(0)  加入收藏
SpringBoot开发的物联网通信平台系统项目功能模块 功能 说明 MQTT 1.SSL支持 2.集群化部署时暂不支持retain&will类型消 UDP ...【详细内容】
2021-11-05  小程序建站    Tags:SpringBoot   点击:(55)  评论:(0)  加入收藏
相关文章
    无相关信息
最新更新
栏目热门
栏目头条