IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> Java知识库 -> Spring是如何解决循坏依赖的? -> 正文阅读

[Java知识库]Spring是如何解决循坏依赖的?

什么是循环依赖?举一个例子,所谓的循环依赖就是现在有一个A类和一个B类,A里面属性注入了B,也就是依赖了B,B里面也依赖了A,那么这就是循环依赖了,而我们要探究的是spring是怎么在实例化A并且初始化的时候注入B,然后实例化B并且初始化的时候又是怎么能够注入到A这个过程。

我们先来看getBean这个方法,因为这个方法里面实现了一个Bean实例化到初始化最终变成一个完整的bean的过程

org.springframework.beans.factory.support.AbstractBeanFactory#getBean(java.lang.String)

@Override
public Object getBean(String name) throws BeansException {
   return doGetBean(name, null, null, false);
}

org.springframework.beans.factory.support.AbstractBeanFactory#doGetBean?

Object sharedInstance = getSingleton(beanName);
if (sharedInstance != null && args == null) {
   if (logger.isTraceEnabled()) {
      if (isSingletonCurrentlyInCreation(beanName)) {
         logger.trace("Returning eagerly cached instance of singleton bean '" + beanName +
               "' that is not fully initialized yet - a consequence of a circular reference");
      }
      else {
         logger.trace("Returning cached instance of singleton bean '" + beanName + "'");
      }
   }
   bean = getObjectForBeanInstance(sharedInstance, name, beanName, null);
}

?其中doGetBean里面一开始有一段这样的代码,逻辑也很简单,就是从getSingleton方法中尝试获取bean,如果获取到了就直接返回这个bean了

org.springframework.beans.factory.support.DefaultSingletonBeanRegistry#getSingleton(java.lang.String)

org.springframework.beans.factory.support.DefaultSingletonBeanRegistry#getSingleton(java.lang.String, boolean)

@Nullable
protected Object getSingleton(String beanName, boolean allowEarlyReference) {
   Object singletonObject = this.singletonObjects.get(beanName);
   if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
      synchronized (this.singletonObjects) {
         singletonObject = this.earlySingletonObjects.get(beanName);
         if (singletonObject == null && allowEarlyReference) {
            ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
            if (singletonFactory != null) {
               singletonObject = singletonFactory.getObject();
               this.earlySingletonObjects.put(beanName, singletonObject);
               this.singletonFactories.remove(beanName);
            }
         }
      }
   }
   return singletonObject;
}

?在这里我们先对这段代码有一点印象,之后再来看这段代码

?

org.springframework.beans.factory.support.DefaultSingletonBeanRegistry#getSingleton(java.lang.String, org.springframework.beans.factory.ObjectFactory)

org.springframework.beans.factory.support.DefaultSingletonBeanRegistry#beforeSingletonCreation

继续回到doGetBean方法,该方法正常流程会走到

protected void beforeSingletonCreation(String beanName) {
   if (!this.inCreationCheckExclusions.contains(beanName) && !this.singletonsCurrentlyInCreation.add(beanName)) {
      throw new BeanCurrentlyInCreationException(beanName);
   }
}

?在getSingleton方法中首先会从单例对象池中判断是否已经有这个bean了,如果没有就会走下一步,而这个下一步里面会调用到父类的一个beforeSingletonCreation方法,在这个方法中会把当前的beanName放入到singletonsCurrentlyInCreation这个集合中,而这个集合里面存放的是当前正在创建的beanName,这个存放当前正在创建的beanName的集合对于解决循环依赖来说很重要,而为什么在这里才把这个bean放到这个集合中?我猜想是因为当spring实例化一个bean的时候走到这一步,确定了当前容器没有这个bean了,就会从这里开始标识我要开始创建bean了,就会把这个bean存入这个集合当中。在确定了这个bean需要创建的时候,就会调用到org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#createBean(java.lang.String, org.springframework.beans.factory.support.RootBeanDefinition, java.lang.Object[])

org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#doCreateBean

doGetBean就是包含了spring实例化初始化bean过程的实现方法了,这其中在bean实例化之后,属性注入之前,有一段很重要的代码

//判断这个bean是否符合循环依赖的条件
boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences &&
      isSingletonCurrentlyInCreation(beanName));
if (earlySingletonExposure) {
   if (logger.isTraceEnabled()) {
      logger.trace("Eagerly caching bean '" + beanName +
            "' to allow for resolving potential circular references");
   }
   addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
}

?在这一段代码中首先会判断这个bean是否符合循环依赖的条件,条件为这个bean必须是单例的,而且allowCircularReferences属性必须为true(这个属性可以通过外部api进行配置),以及这个bean是否是正在创建的,如果这些条件都符合的话,就会调用到addSingletonFactory方法

org.springframework.beans.factory.support.DefaultSingletonBeanRegistry#addSingletonFactory?

protected void addSingletonFactory(String beanName, ObjectFactory<?> singletonFactory) {
   Assert.notNull(singletonFactory, "Singleton factory must not be null");
   synchronized (this.singletonObjects) {
       //判断一级缓存中是否有当前的bean
      if (!this.singletonObjects.containsKey(beanName)) {
          //如果没有,则把创建这个bean的工厂放到二级缓存中
         this.singletonFactories.put(beanName, singletonFactory);
         this.earlySingletonObjects.remove(beanName);
         this.registeredSingletons.add(beanName);
      }
   }
}

这里说一下上面的bean工厂,这个bean工厂spring传入的是一段lambda表达式,具体调用这个bean工厂的getObject方法的时候,会调用到getEarlyBeanReference方法,这个方法中其实是能够得到一个bean的最终状态。好了,说了这么多,我们就需要把上面的东西都串起来,又以A和B为例子,假如上面getBean的流程是对A进行了,也就是getBean(A),那么此时当它走完上面的代码就会来到属性注入的逻辑了,也就是populateBean方法,往A里面注入B,当注入B的时候就会发现B依赖了A,那么如果不解决循坏依赖的问题就会产生一个死循坏了,当前的循坏如下面所示:

getBean(A)->实例化A->属性注入B->getBean(B)->实例化B->属性注入A->getBean(A)

而spring怎么解决这个死循坏的呢,就是通过上面的东西了,接下来重点看一下B注入A的时候调用getBean(A)的过程。重新来到getBean的代码,上面说过了B的getBean(A)会经过getSingleton的方法尝试去容器里面拿A

?

getBean(A)->实例化A->属性注入B->getBean(B)->实例化B->属性注入A->getBean(A)->getSingleton(A)->三级缓存或者二级缓存中得到A->B得到A并注入->B走完剩下的生命周期->把B放入一级缓存->回到A的属性注入生命周期,此时得到B了并注入->A走完剩下的声明周期->把A放到一级缓存

?所以说在B注入A然后调用getBean(A)的时候,在getSingleton就能拿到A了,此时B的属性注入完成,直到B的生命周期结束,然后回到A的属性注入生命周期,最后A的生命周期结束,最终循坏依赖解决,流程如下:

getBean(A)->实例化A->属性注入B->getBean(B)->实例化B->属性注入A->getBean(A)->getSingleton(A)->三级缓存或者二级缓存中得到A->B得到A并注入->B走完剩下的生命周期->把B放入一级缓存->回到A的属性注入生命周期,此时得到B了并注入->A走完剩下的声明周期->把A放到一级缓存

在这个过程中,可以发现,解决循坏依赖的关键点在于,记录了A是否是正在创建的,并且在A属性注入之前会提前暴露了A的工厂,在A注入B然后B去注入A调用getBean(A)的时候就能够拿到A之前提前暴露的工厂,从这个工厂中拿到A?

二级缓存的意义?

前面说了这个二级缓存就是存放一个bean的工厂的,这个bean工厂的作用就是生产这个bean,那么在A属性注入之前我们直接暴露A不就好了吗,反正此时A已经被实例化了,何必大费周章地去搞一个A的工厂呢?也就是说是否可以直接把实例化好的A放入到三级缓存中就好了,并不需要二级缓存?如果是普通的一个bean的话二级缓存确实是没必要的,但是我们需要考虑的一种场景就是代理

getBean(A)->实例化A->把实例A放到三级缓存中->属性注入B->getBean(B)->实例化B->属性注入A->getBean(A)->直接从三级缓存中拿到A->B属性注入A完成并且走完剩余的生命周期->A属性注入B完成继续自己的生命周期->判断A是否需要代理->生成A的代理对象->把A的代理对象放到一级缓存

可以看到如果是直接把A放入到三级缓存,然后B从三级缓存中去拿到A并注入,当A完整地走完自己的生命周期的时候你会发现此时的A已经是变成一个代理对象了,而B依赖的那个A却还只是一个普通对象,而引入二级缓存就能够解决这个问题,因为二级缓存中放的是A的工厂,而既然是工厂,那么就能产生出A的代理对象了,也就是上面一直说的A的最终形态,因为在A还没走完自己的生命周期的时候,A这个对象是有可能变的,所以我们不能够直接暴露出这个A实例,而是需要暴露出A的工厂,从这个工厂才能拿到A的最终形态!?

getBean(A)->实例化A->把A的工厂放到二级缓存中->属性注入B->getBean(B)->实例化B->属性注入A->getBean(A)->从二级缓存中拿到A的工厂,进而从工厂中拿到A的最终形态,比如A的代理对象->B属性注入A完成->B走完剩余的生命周期->把B放到一级缓存->A属性注入B完成继续自己的生命周期->判断A是否需要代理->生成A的代理对象->把A的代理对象放到一级缓存?

三级缓存的意义??

既然上面说了二级缓存存的是bean的工厂,而且这个工厂这么牛逼能够产生bean的最终形态,那干嘛还用三级缓存?直接从工厂里面拿这个最终形态不就好了吗?我们看下spring是怎么做的

singletonObject = this.earlySingletonObjects.get(beanName);
if (singletonObject == null && allowEarlyReference) {
   ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
   if (singletonFactory != null) {
      singletonObject = singletonFactory.getObject();
      this.earlySingletonObjects.put(beanName, singletonObject);
      this.singletonFactories.remove(beanName);
   }
}

spring每一次都是从一个三级缓存中拿bean,如果为空就从二级缓存中的bean工厂产生bean并且放到三级缓存中,这样做的目的就是减少性能的消耗,因为假如A依赖了100个对象,而这个100个对象又依赖了A,那么这100个对象每一次从工厂中拿一个最终形态的A都是一个极其复杂的过程,所以既然这个A也已经是个最终形态的A了,那么就能够直接缓存起来,下次需要用的需要直接从三级缓存拿就可以了?

?

  Java知识库 最新文章
计算距离春节还有多长时间
系统开发系列 之WebService(spring框架+ma
springBoot+Cache(自定义有效时间配置)
SpringBoot整合mybatis实现增删改查、分页查
spring教程
SpringBoot+Vue实现美食交流网站的设计与实
虚拟机内存结构以及虚拟机中销毁和新建对象
SpringMVC---原理
小李同学: Java如何按多个字段分组
打印票据--java
上一篇文章      下一篇文章      查看所有文章
加:2022-01-28 11:45:25  更:2022-01-28 11:46:33 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/24 9:34:52-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码