• Spring中的循环依赖解决详解


     前言

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

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

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

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

     
     1 @Service
     2 public class Student {
     3     @Autowired
     4     private Teacher teacher;
     5 
     6     public Student (Teacher teacher) {
     7         System.out.println("Student init1:" + teacher);
     8     }
     9 
    10     public void learn () {
    11         System.out.println("Student learn");
    12     }
    13 }
     
     1 @Service
     2 public class Teacher {
     3     @Autowired
     4     private Student student;
     5 
     6     public Teacher (Student student) {
     7         System.out.println("Teacher init1:" + student);
     8 
     9     }
    10 
    11     public void teach () {
    12         System.out.println("teach:");
    13         student.learn();
    14     }
    15 }

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

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

     
     1 @Service
     2 public class Teacher {
     3     @Autowired
     4     private Student student;
     5 
     6     public Teacher () {
     7         System.out.println("Teacher init1:" + student);
     8 
     9     }
    10 
    11     public void teach () {
    12         System.out.println("teach:");
    13         student.learn();
    14     }
    15     
    16 }
     
     
     1 @Service
     2 public class Student {
     3     @Autowired
     4     private Teacher teacher;
     5 
     6     public Student () {
     7         System.out.println("Student init:" + teacher);
     8     }
     9 
    10     public void learn () {
    11         System.out.println("Student learn");
    12     }
    13 }
     

    测试扫描类:

    1 @ComponentScan(value = "myPackage")
    2 public class ScanConfig {
    3     
    4 }

    测试启动类:

     
    1 public class SpringTest {
    2 
    3     public static void main(String[] args) {
    4         AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(ScanConfig.class);
    5         applicationContext.getBean(Teacher.class).teach();
    6 
    7     }
    8 }
     

    测试类执行结果:

    1 Student init:null
    2 Teacher init:null
    3 teach:
    4 Student learn

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

    二、循环依赖与属性注入

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

    1 protected void finishBeanFactoryInitialization(ConfigurableListableBeanFactory beanFactory) {
    2         // 其他代码
    3 
    4         // Instantiate all remaining (non-lazy-init) singletons.
    5         beanFactory.preInstantiateSingletons();
    6     }

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

    2、DefaultListableBeanFactory的preInstantiateSingletons方法

     
     1 public void preInstantiateSingletons() throws BeansException {
     2         
     3         List<String> beanNames = new ArrayList<>(this.beanDefinitionNames);
     4 
     5         // Trigger initialization of all non-lazy singleton beans...
     6         for (String beanName : beanNames) {
     7             RootBeanDefinition bd = getMergedLocalBeanDefinition(beanName);
     8             if (!bd.isAbstract() && bd.isSingleton() && !bd.isLazyInit()) { // 判断为非抽象类、是单例、非懒加载 才给初始化
     9                 if (isFactoryBean(beanName)) {
    10                     // 无关代码(针对FactoryBean的处理)
    11                 }
    12                 else {
    13                     // 重要!!!普通bean就是在这里初始化的
    14                     getBean(beanName);
    15                 }
    16             }
    17         }
    18 
    19         // 其他无关代码  
    20     }
     

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

    3、AbstractBeanFactory的getBean跟doGetBean方法

    追踪getBean方法:

    1 public Object getBean(String name) throws BeansException {
    2         return doGetBean(name, null, null, false);
    3     }

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

     
     1 protected <T> T doGetBean(final String name, @Nullable final Class<T> requiredType,
     2             @Nullable final Object[] args, boolean typeCheckOnly) throws BeansException {
     3 
     4         final String beanName = transformedBeanName(name);
     5         Object bean;
     6                
     7          // 方法1)从三个map中获取单例类
     8         Object sharedInstance = getSingleton(beanName);
     9         // 省略无关代码
    10         }
    11         else {
    12             // 如果是多例的循环引用,则直接报错
    13             if (isPrototypeCurrentlyInCreation(beanName)) {
    14                 throw new BeanCurrentlyInCreationException(beanName);
    15             }
    16             // 省略若干无关代码
    17             try {
    18                 // Create bean instance.
    19                 if (mbd.isSingleton()) {
    20                     // 方法2) 获取单例对象
    21                     sharedInstance = getSingleton(beanName, () -> {
    22                         try { //方法3) 创建ObjectFactory中getObject方法的返回值
    23                             return createBean(beanName, mbd, args);
    24                         }
    25                         catch (BeansException ex) {
    26                             // Explicitly remove instance from singleton cache: It might have been put there
    27                             // eagerly by the creation process, to allow for circular reference resolution.
    28                             // Also remove any beans that received a temporary reference to the bean.
    29                             destroySingleton(beanName);
    30                             throw ex;
    31                         }
    32                     });
    33                     bean = getObjectForBeanInstance(sharedInstance, name, beanName, mbd);
    34                 }
    35          }
    36         // 省略若干无关代码
    37         return (T) bean;
    38     }

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

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

     
     1 protected Object getSingleton(String beanName, boolean allowEarlyReference) {
     2         Object singletonObject = this.singletonObjects.get(beanName);// 步骤A
     3         if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
     4             synchronized (this.singletonObjects) {
     5                 singletonObject = this.earlySingletonObjects.get(beanName);// 步骤B
     6                 if (singletonObject == null && allowEarlyReference) {
     7                     ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);// 步骤C
     8                     if (singletonFactory != null) {
     9                         singletonObject = singletonFactory.getObject();
    10                         this.earlySingletonObjects.put(beanName, singletonObject);
    11                         this.singletonFactories.remove(beanName);
    12                     }
    13                 }
    14             }
    15         }
    16         return singletonObject;
    17     }

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

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

    3.2)getSingleton(String beanName, ObjectFactory<?> singletonFactory)方法

     
     1 public Object getSingleton(String beanName, ObjectFactory<?> singletonFactory) {
     2         Assert.notNull(beanName, "Bean name must not be null");
     3         synchronized (this.singletonObjects) {
     4             Object singletonObject = this.singletonObjects.get(beanName);
     5             if (singletonObject == null) {
     6                 // 省略无关代码
     7                 beforeSingletonCreation(beanName); // 步骤A
     8                 boolean newSingleton = false;
     9                 // 省略无关代码
    10                 try {
    11                     singletonObject = singletonFactory.getObject();// 步骤B
    12                     newSingleton = true;
    13                 }
    14                 // 省略无关代码
    15                 finally {
    16                     if (recordSuppressedExceptions) {
    17                         this.suppressedExceptions = null;
    18                     }
    19                     afterSingletonCreation(beanName);// 步骤C
    20                 }
    21                 if (newSingleton) {
    22                     addSingleton(beanName, singletonObject);// 步骤D
    23                 }
    24             }
    25             return singletonObject;
    26         }
    27     }

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

    步骤A:

    1 protected void beforeSingletonCreation(String beanName) {
    2         // 判断,并首次将beanName即teacher放入singletonsCurrentlyInCreation中
    3         if (!this.inCreationCheckExclusions.contains(beanName) && !this.singletonsCurrentlyInCreation.add(beanName)) {
    4             throw new BeanCurrentlyInCreationException(beanName);
    5         }
    6     }

    步骤C:

    1 protected void afterSingletonCreation(String beanName) {
    2         // 得到单例对象后,再讲beanName从singletonsCurrentlyInCreation中移除
    3         if (!this.inCreationCheckExclusions.contains(beanName) && !this.singletonsCurrentlyInCreation.remove(beanName)) {
    4             throw new IllegalStateException("Singleton '" + beanName + "' isn't currently in creation");
    5         }
    6     }

    步骤D:

     
    1 protected void addSingleton(String beanName, Object singletonObject) {
    2         synchronized (this.singletonObjects) {
    3             this.singletonObjects.put(beanName, singletonObject);//添加单例对象到map中
    4             this.singletonFactories.remove(beanName);//从早期暴露的工厂中移除,此map在解决循环依赖中发挥了关键的作用
    5             this.earlySingletonObjects.remove(beanName);//从早期暴露的对象map中移除
    6             this.registeredSingletons.add(beanName);//添加到已注册的单例名字集合中
    7         }
    8     }
     

    步骤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[]) 方法

     
     1 protected Object createBean(String beanName, RootBeanDefinition mbd, @Nullable Object[] args)
     2             throws BeanCreationException {
     3 
     4         // 省略无关代码
     5         try {
     6             Object beanInstance = doCreateBean(beanName, mbdToUse, args);
     7             return beanInstance;
     8         }
     9         // 省略无关代码
    10     }

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

     
     1 protected Object doCreateBean(final String beanName, final RootBeanDefinition mbd, final @Nullable Object[] args)
     2             throws BeanCreationException {
     3 
     4         BeanWrapper instanceWrapper = null;
     5         // 省略代码
     6         if (instanceWrapper == null) {
     7             // 实例化bean
     8             instanceWrapper = createBeanInstance(beanName, mbd, args);
     9         }
    10         boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences &&
    11                 isSingletonCurrentlyInCreation(beanName));
    12         if (earlySingletonExposure) {
    13             // 重点!!!将实例化的对象添加到singletonFactories中 
    14             addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
    15         }
    16         // 初始化bean
    17         Object exposedObject = bean;
    18         try {
    19             populateBean(beanName, mbd, instanceWrapper);//也很重要
    20             exposedObject = initializeBean(beanName, exposedObject, mbd);
    21         }
    22         // 省略无关代码
    23         return exposedObject;
    24 }

            上面注释中标出的重点是此方法的关键。在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,解决了循环依赖的问题

  • 相关阅读:
    docker实例之mysql的使用
    使用Dockerfile创建ssh服务的镜像02
    添加ssh服务构建新镜像-docker commit 方式01
    Keepalived
    ubuntu网卡配置
    升级openssl
    源码安装nginx env
    dockerfile
    shell字符截取
    MYSQL导入/迁移后事件不执行
  • 原文地址:https://www.cnblogs.com/leeego-123/p/12165278.html
Copyright © 2020-2023  润新知