欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

Spring中循环依赖的解决方法详析

程序员文章站 2023-11-24 21:07:58
前言 说起spring中循环依赖的解决办法,相信很多园友们都或多或少的知道一些,但当真的要详细说明的时候,可能又没法一下将它讲清楚。本文就试着尽自己所能,对此做出一个较详...

前言

说起spring中循环依赖的解决办法,相信很多园友们都或多或少的知道一些,但当真的要详细说明的时候,可能又没法一下将它讲清楚。本文就试着尽自己所能,对此做出一个较详细的解读。另,需注意一点,下文中会出现类的实例化跟类的初始化两个短语,为怕园友迷惑,事先声明一下,本文的实例化是指刚执行完构造器将一个对象new出来,但还未填充属性值的状态,而初始化是指完成了属性的依赖注入。

一、先说说spring解决的循环依赖是什么

java中的循环依赖分两种,一种是构造器的循环依赖,另一种是属性的循环依赖。

构造器的循环依赖就是在构造器中有属性循环依赖,如下所示的两个类就属于构造器循环依赖:

@service
public class student {
  @autowired
  private teacher teacher;

  public student (teacher teacher) {
    system.out.println("student init1:" + teacher);
  }

  public void learn () {
    system.out.println("student learn");
  }
}
@service
public class teacher {
  @autowired
  private student student;

  public teacher (student student) {
    system.out.println("teacher init1:" + student);

  }

  public void teach () {
    system.out.println("teach:");
    student.learn();
  }
}

这种循环依赖没有什么解决办法,因为jvm虚拟机在对类进行实例化的时候,需先实例化构造器的参数,而由于循环引用这个参数无法提前实例化,故只能抛出错误。

spring解决的循环依赖就是指属性的循环依赖,如下所示:

@service
public class teacher {
  @autowired
  private student student;

  public teacher () {
    system.out.println("teacher init1:" + student);

  }

  public void teach () {
    system.out.println("teach:");
    student.learn();
  }
  
}
@service
public class student {
  @autowired
  private teacher teacher;

  public student () {
    system.out.println("student init:" + teacher);
  }

  public void learn () {
    system.out.println("student learn");
  }
}

测试扫描类:

 @componentscan(value = "mypackage")
 public class scanconfig {
 
 }

测试启动类:

public class springtest {

  public static void main(string[] args) {
    annotationconfigapplicationcontext applicationcontext = new annotationconfigapplicationcontext(scanconfig.class);
    applicationcontext.getbean(teacher.class).teach();

  }
}

测试类执行结果:

 student init:null
 teacher init:null
 teach:
 student learn

可以看到,在构造器执行的时候未完成属性的注入,而在调用方法的时候已经完成了注入。下面就一起看看spring内部是在何时完成的属性注入,又是如何解决的循环依赖。

二、循环依赖与属性注入

1、对于非懒加载的类,是在refresh方法中的 finishbeanfactoryinitialization(beanfactory) 方法完成的包扫描以及bean的初始化,下面就一起追踪下去。

 protected void finishbeanfactoryinitialization(configurablelistablebeanfactory beanfactory) {
 // 其他代码
 
 // instantiate all remaining (non-lazy-init) singletons.
 beanfactory.preinstantiatesingletons();
 }

可以看到调用了beanfactory的一个方法,此处的beanfactory就是指我们最常见的那个defaultlistablebeanfactory,下面看它里面的这个方法。

2、defaultlistablebeanfactory的preinstantiatesingletons方法

public void preinstantiatesingletons() throws beansexception {
    
    list<string> beannames = new arraylist<>(this.beandefinitionnames);

    // trigger initialization of all non-lazy singleton beans...
    for (string beanname : beannames) {
      rootbeandefinition bd = getmergedlocalbeandefinition(beanname);
      if (!bd.isabstract() && bd.issingleton() && !bd.islazyinit()) { // 判断为非抽象类、是单例、非懒加载 才给初始化
        if (isfactorybean(beanname)) {
          // 无关代码(针对factorybean的处理)
        }
        else {
          // 重要!!!普通bean就是在这里初始化的
          getbean(beanname);
        }
      }
    }

    // 其他无关代码 
  }

可以看到,就是在此方法中循环spring容器中所有的bean,依次对其进行初始化,初始化的入口就是getbean方法

3、abstractbeanfactory的getbean跟dogetbean方法

追踪getbean方法:

 public object getbean(string name) throws beansexception {
 return dogetbean(name, null, null, false);
 }

可见引用了重载的dogetbean方法,继续追踪之:

protected <t> t dogetbean(final string name, @nullable final class<t> requiredtype,
      @nullable final object[] args, boolean typecheckonly) throws beansexception {

    final string beanname = transformedbeanname(name);
    object bean;
        
     // 方法1)从三个map中获取单例类
    object sharedinstance = getsingleton(beanname);
    // 省略无关代码
    }
    else {
      // 如果是多例的循环引用,则直接报错
      if (isprototypecurrentlyincreation(beanname)) {
        throw new beancurrentlyincreationexception(beanname);
      }
      // 省略若干无关代码
      try {
        // create bean instance.
        if (mbd.issingleton()) {
          // 方法2) 获取单例对象
          sharedinstance = getsingleton(beanname, () -> {
            try { //方法3) 创建objectfactory中getobject方法的返回值
              return createbean(beanname, mbd, args);
            }
            catch (beansexception ex) {
              // explicitly remove instance from singleton cache: it might have been put there
              // eagerly by the creation process, to allow for circular reference resolution.
              // also remove any beans that received a temporary reference to the bean.
              destroysingleton(beanname);
              throw ex;
            }
          });
          bean = getobjectforbeaninstance(sharedinstance, name, beanname, mbd);
        }
     }
    // 省略若干无关代码
    return (t) bean;
  }

该方法比较长,对于解决循环引用来说,上面标出来的3个方法起到了至关重要的作用,下面我们挨个攻克。

3.1) getsingleton(beanname)方法: 注意该方法跟方法2)是重载方法,名字一样内部逻辑却大相径庭。

protected object getsingleton(string beanname, boolean allowearlyreference) {
    object singletonobject = this.singletonobjects.get(beanname);// 步骤a
    if (singletonobject == null && issingletoncurrentlyincreation(beanname)) {
      synchronized (this.singletonobjects) {
        singletonobject = this.earlysingletonobjects.get(beanname);// 步骤b
        if (singletonobject == null && allowearlyreference) {
          objectfactory<?> singletonfactory = this.singletonfactories.get(beanname);// 步骤c
          if (singletonfactory != null) {
            singletonobject = singletonfactory.getobject();
            this.earlysingletonobjects.put(beanname, singletonobject);
            this.singletonfactories.remove(beanname);
          }
        }
      }
    }
    return singletonobject;
  }

通过上面的步骤可以看出这三个map的优先级。其中singletonobjects里面存放的是初始化之后的单例对象;earlysingletonobjects中存放的是一个已完成实例化未完成初始化的早期单例对象;而singletonfactories中存放的是objectfactory对象,此对象的getobject方法返回值即刚完成实例化还未开始初始化的单例对象。所以先后顺序是,单例对象先存在于singletonfactories中,后存在于earlysingletonobjects中,最后初始化完成后放入singletonobjects中。

当debug到此处时,以上述teacher和student两个循环引用的类为例,如果第一个走到这一步的是teacher,则从此处这三个map中get到的值都是空,因为还未添加进去。这个方法主要是给循环依赖中后来过来的对象用。

3.2)getsingleton(string beanname, objectfactory<?> singletonfactory)方法:

public object getsingleton(string beanname, objectfactory<?> singletonfactory) {
    assert.notnull(beanname, "bean name must not be null");
    synchronized (this.singletonobjects) {
      object singletonobject = this.singletonobjects.get(beanname);
      if (singletonobject == null) {
        // 省略无关代码
        beforesingletoncreation(beanname); // 步骤a
        boolean newsingleton = false;
        // 省略无关代码
        try {
          singletonobject = singletonfactory.getobject();// 步骤b
          newsingleton = true;
        }
        // 省略无关代码
        finally {
          if (recordsuppressedexceptions) {
            this.suppressedexceptions = null;
          }
          aftersingletoncreation(beanname);// 步骤c
        }
        if (newsingleton) {
          addsingleton(beanname, singletonobject);// 步骤d
        }
      }
      return singletonobject;
    }
  }

获取单例对象的主要逻辑就是此方法实现的,主要分为上面四个步骤,继续挨个看:

步骤a:

 protected void beforesingletoncreation(string beanname) {
 // 判断,并首次将beanname即teacher放入singletonscurrentlyincreation中
 if (!this.increationcheckexclusions.contains(beanname) && !this.singletonscurrentlyincreation.add(beanname)) {
 throw new beancurrentlyincreationexception(beanname);
 }
 }

步骤c:

 protected void aftersingletoncreation(string beanname) {
 // 得到单例对象后,再讲beanname从singletonscurrentlyincreation中移除
 if (!this.increationcheckexclusions.contains(beanname) && !this.singletonscurrentlyincreation.remove(beanname)) {
 throw new illegalstateexception("singleton '" + beanname + "' isn't currently in creation");
 }
 }

步骤d:

protected void addsingleton(string beanname, object singletonobject) {
    synchronized (this.singletonobjects) {
      this.singletonobjects.put(beanname, singletonobject);//添加单例对象到map中
      this.singletonfactories.remove(beanname);//从早期暴露的工厂中移除,此map在解决循环依赖中发挥了关键的作用
      this.earlysingletonobjects.remove(beanname);//从早期暴露的对象map中移除
      this.registeredsingletons.add(beanname);//添加到已注册的单例名字集合中
    }
  }

步骤b:

此处调用了objectfactory的getobject方法,此方法是在哪里实现的呢?返回的又是什么?且往回翻,找到3中的方法3,对java8函数式编程有过了解的园友应该能看出来,方法3 【createbean(beanname, mbd, args)】的返回值就是getobject方法的返回值,即方法3返回的就是我们需要的单例对象,下面且追踪方法3而去。

3.3)abstractautowirecapablebeanfactory#createbean(java.lang.string, org.springframework.beans.factory.support.rootbeandefinition, java.lang.object[]) 方法

protected object createbean(string beanname, rootbeandefinition mbd, @nullable object[] args)
      throws beancreationexception {

    // 省略无关代码
    try {
      object beaninstance = docreatebean(beanname, mbdtouse, args);
      return beaninstance;
    }
    // 省略无关代码
  }

去掉无关代码之后,关键方法只有docreatebean方法,追踪之:

protected object docreatebean(final string beanname, final rootbeandefinition mbd, final @nullable object[] args)
      throws beancreationexception {

    beanwrapper instancewrapper = null;
    // 省略代码
    if (instancewrapper == null) {
      // 实例化bean
      instancewrapper = createbeaninstance(beanname, mbd, args);
    }
    boolean earlysingletonexposure = (mbd.issingleton() && this.allowcircularreferences &&
        issingletoncurrentlyincreation(beanname));
    if (earlysingletonexposure) {
      // 重点!!!将实例化的对象添加到singletonfactories中 
      addsingletonfactory(beanname, () -> getearlybeanreference(beanname, mbd, bean));
    }
    // 初始化bean
    object exposedobject = bean;
    try {
      populatebean(beanname, mbd, instancewrapper);//也很重要
      exposedobject = initializebean(beanname, exposedobject, mbd);
    }
    // 省略无关代码
    return exposedobject;
}

上面注释中标出的重点是此方法的关键。在addsingletonfactory方法中,将第二个参数objectfactory存入了singletonfactories供其他对象依赖时调用。然后下面的populatebean方法对刚实例化的bean进行属性注入(该方法关联较多,本文暂时不展开追踪了,有兴趣的园友自行查看即可),如果遇到spring中的对象属性,则再通过getbean方法获取该对象。至此,循环依赖在spring中的处理过程已经追溯完毕,下面我们总结一下。

小结

属性注入主要是在populatebean方法中进行的。对于循环依赖,以我们上文中的teacher中注入了student、student中注入了teacher为例来说明,假定spring的加载顺序为先加载teacher,再加载student。

getbean方法触发teacher的初始化后:

a. 首先走到3中的方法1),此时map中都为空,获取不到实例;

b. 然后走到方法2)中,步骤a、步骤c、步骤d为控制map中数据的方法,实现简单,可暂不关注。其中步骤b的getobject方法触发对方法3)的调用;

c. 在方法3)中,先通过createbeaninstance实例化teacher对象,又将该实例化的对象通过addsingletonfactory方法放入singletonfactories中,完成teacher对象早期的暴露;

d. 然后在方法3)中通过populatebean方法对teacher对象进行属性的注入,发现它有一个student属性,则触发getbean方法对student进行初始化

e. 重复a、b、c步骤,只是此时要初始化的是student对象

f. 走到d的时候,调用populatebean对student对象进行属性注入,发现它有一个teacher属性,则触发getbean方法对teacher进行初始化;

g. 对teacher进行初始化,又来到a,但此时map已经不为空了,因为之前在c步骤中已经将teacher实例放入了singletonfactories中,a中得到teacher实例后返回;

h.完成f中对student的初始化,继而依次往上回溯完成teacher的初始化;

完成teacher的初始化后,student的初始化就简单了,因为map中已经存了这个单例。

至此,spring循环依赖的总结分析结束,一句话来概括一下:spring通过将实例化后的对象提前暴露给spring容器中的singletonfactories,解决了循环依赖的问题。

总结

以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作具有一定的参考学习价值,谢谢大家对的支持。