鲁班:spring源码入门之循环依赖(讲的非常清楚,非常棒)

什么是循环依赖?(仅此问题转载来自博主:也仅为

最近学习了一下spring的循环依赖问题,这里总结一下。首先,我们应该知道什么是spring的依赖问题,想要明白这个问题,我们就要先明白spring的创建bean的过程。这里我简单说一下,spring在启动的时候会先初始化我们的项目环境,然后去扫面我们配置的包下面的所有的类,进行解析和注册到beanDecfinition中。然后在根据beanDecfinition的信息对类进行实例化,填充属性等操作变成一个bean,放到我们spring的容器中。在变成bean的过程中,会遇到对象与对象之间的依赖,比如A类中有一个属性B类,那么spring在创建A对象的时候发现了需要一个B对象,这个时候spring就会暂时停止对A的创建,会先去创建B对象,等B对象创建完之后,将B对象注入到A对象中,再接着创建A对象剩下的工作。那么,问题来了,假如A类里有一个B类属性需要注入,而B类又有一个属性A类需要注入,怎么办呢?这就是spring的循环依赖的问题。废话不多说,下面是导致循环依赖的代码

A.class

@Component
public class A {
@Autowired
B b;
}

B.class

@Component
public class B {
@Autowired
A a;
}

AppConfig.class

@ComponentScan(“com.alibaba.ctsg.aa”)
public class AppConfig {
}

Test.class

public class Test {
public static void main(String[] args) {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
A a = (A) context.getBean(“a”);
B b = (B) context.getBean(“b”);
System.out.println(“A对象中的b属性:”+a.b);
System.out.println(“B对象中的a属性:”+b.a);
}
}

结果

显然能正确打印出结果,根据前面分析,是要发生死循环的,为何正确打印呢

spring如何解决循环依赖?
前面说了spring在创建的时候应该会出现循环依赖的问题。然而我们的代码中确没有报错,正常执行了。那么spring是怎么解决的呢?
我们先说spring循环依赖分两种,一种是构造器的循环依赖,另一种是属性的循环依赖。构造器的循环依赖是没有办法解决的,本文主要介绍属性的循环依赖。
首先spring用了缓存的方式解决的循环依赖问题。小伙伴们有没有想到用这种方式呀?
我们先用A和B两个类来说明
其实spring在创建对象的时候是分为两步:1、通过反射创建出一个对象,2、往对象中的属性赋值。
我们很好理解第一步,我们得到对象的全路径名,就可以利用反射创建A对象,得到A对象中的属性。这时我们会把这个A对象标为正在创建中,并且放到缓存中(这里很重要)。
在第二步时就要给属性赋值,这时如果属性中要注入B类的话,spring会首先到容器中找有没有B类的对象,有的话就直接注入在属性上。没有的话就就要重新创建一个B对象,在创建B对象的过程中,必然也会有两步:1、通过反射创建出一个对象,2、往对象中的属性赋值。第一步和之前一样我们不说,第二步的就有可能会注入我们之前正在创建的A对象,同样也到容器中查找,没有找到,因为我们还没有创建完成,这个时候我们会发现他正在创建中,然后我们可以从缓存中取出创建好但没有注入属性值的A对象,然后就去拿着这个A对象去给当前B对象注入值。这样就完成了当前B对象的注入。B对象创建完成之后,就会把B对象注入到A中,A对象就创建完成了。
面试时把上面说出来,面试管就会明白你是懂得原理的。:

补充:在容器中拿,拿不到,表示对象还没有完全被创建出来,处于一个正在创建的状态,就会从缓存中拿。 这个时候spring会做一个判断:是否需要第三个缓存中拿,需要则开始创建对象

 

spring当中的循环依赖是怎么解决的?

            spring中是默认单例支持循环

怎么证明是默认支持的?怎么关闭循环依赖?spring怎么解决循环依赖的细节?

这个代码证明,是默认支持循环依赖的,设置为false就关闭了。代码在类里面。

标准回答:spring在生命周期的某个过程当中,也就是spring在初始化spring bean生命周期过程中,将bean实例化之后,会做一个判断,判断这个容器允不允许循环依赖,判断允不允许循环依赖是根据一个属性,这个属性在spring中默认是true,可以修改为false就关闭了循环依赖

依赖注入的功能-----在什么时候完成?是初始化容器的时候还是获取bean的时候?

初始化spring容器的时候就已经注入了。

初始化spring容器的时候,完成哪些功能呢?

1、初始化bean-----------bean有一个初始化过程(这个过程是有很多个步骤的)-------------------spring bean的生命周期

spring bean的生命周期到底在哪个步骤完成的依赖注入?(看源码)

个人理解:

一个bean,就是一个spring bean 是经过了bean的生命周期

而一个java对象,不一定是一个bean,只是一个对象。

bean一定是java对象

spring bean的生命周期------spring bean的产生过程-------------bean是由什么产生来的?

(对象一定是由class类创建的)

bean -----------

视频地址:https://www.bilibili.com/video/BV1uE411d7L5?p=2

                                      普通类的实例化过程

一个类对象(class User) 是一个java文件,经过javac的编译,变成一个.class文件,字节码,里面包含了成员变量,有构造方法和成员方法,然后通过类加载器,JVM虚拟机将磁盘里的文件加载到内存中(jvm虚拟机是通过main方法启动的),当遇到new这个关键字时,在方法区里提供的模板,在堆上分配空间存储这个对象

                                    spring bean 的实例化过程

BeanDefinition:是一个接口,这个接口有很多实现类,一般叫做子类,就是这个接口会有很多子类,比如注入模型(autoModel)、父类模型(parentName)、FactoryMethod、描述(description)

在java文件里扫描出来一个文件,就会使用classLoader将java文件细细拆分,拆成一个个小的部分,里面可以有注入模型(autoModel)、父类模型(parentName)、FactoryMethod、描述(description)。

由于java文件有很多个类,但是接口又只有一个,虽然接口下面有很多子类,可以存储一个类里的各种成员。为了达到java文件和成员的一个映射关系,就会放入Map中,在java文件里进行三四次解析,在map中就会有三四个BeanDefinition对象。

然后使用preInstantiateSingletons new object将map里的数据遍历解析出来,new对象,因为一条数据就包含了注入模型(autoModel)、父类模型(parentName)、FactoryMethod、描述(description)等等的信息。注意这里如果是单例的就是直接new出来,不是单例的就需要等到使用的时候才new出来

上图分析:首先是一个for循环,对所有的java对象,对每一个对象都进行创建一个GenericBeanDefinition对象,在这个对象里设置各种属性,类的类型(setBeanClass)、bean的名字(BeanClassName)、bean的作用域(setScope)........

然后将创建的genericBeanDefinition对象放入map中,list就是一张表,把map放在一张表里

https://www.bilibili.com/read/cv3791985?spm_id_from=333.788.b_636f6d6d656e74.20

题外话:研究spring源码的意义就是写出优秀的扩展,如何对spring进行扩展?

实现BeanFactoryPostProcessor接口,里面存储了map方法,里面存储了BeanDefination对象,BeanDefination对象里有一个属性叫beanClass,存的就是 X 的类

验证 bean ----------判断获取的genericBeanDefinition是否是单例是否是懒加载

引出的面试问题:@resource和@autowired的区别

百度回答:

老师增加:

@Autowired和是使用AutowiredAnnotationBeanPostProcessor(后置处理器)来处理 依赖注入。

但是@Resource是个例外,它使用的是CommonAnnotationBeanPostProcessor来处理依赖注入。当然,两者 都是BeanPostProcessor。

spring如何解决循环依赖的

先来理解一下三级缓存的概 念:

问题转载自:https://www.jianshu.com/p/6c359768b1dc,这篇文章非常牛逼

解决方法 (这是本章的重点)

回想上篇文章中关于Bean创建的过程,首先Spring会尝试从缓存中获取,这个缓存就是指singletonObjects,主要调用的方法是:

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 != NULL_OBJECT ? singletonObject : null);}

首先解释两个参数:

  • isSingletonCurrentlyInCreation 判断对应的单例对象是否在创建中,当单例对象没有被初始化完全(例如A定义的构造函数依赖了B对象,得先去创建B对象,或者在populatebean过程中依赖了B对象,得先去创建B对象,此时A处于创建中)
  • allowEarlyReference 是否允许从singletonFactories中通过getObject拿到对象

分析getSingleton的整个过程,Spring首先从singletonObjects(一级缓存)中尝试获取,如果获取不到并且对象在创建中,则尝试从earlySingletonObjects(二级缓存)中获取,如果还是获取不到并且允许从singletonFactories通过getObject获取,则通过singletonFactory.getObject()(三级缓存)获取。如果获取到了则

this.earlySingletonObjects.put(beanName, singletonObject);
this.singletonFactories.remove(beanName);

则移除对应的singletonFactory,将singletonObject放入到earlySingletonObjects,其实就是将三级缓存提升到二级缓存中!

addSingletonFactory(beanName, new ObjectFactory<Object>() {
   @Override   public Object getObject() throws BeansException {
      return getEarlyBeanReference(beanName, mbd, bean);
   }});

此处就是解决循环依赖的关键,这段代码发生在createBeanInstance之后,也就是说单例对象此时已经被创建出来的。这个对象已经被生产出来了,虽然还不完美(还没有进行初始化的第二步和第三步),但是已经能被人认出来了(根据对象引用能定位到堆中的对象),所以Spring此时将这个对象提前曝光出来让大家认识,让大家使用。

这样做有什么好处呢?让我们来分析一下“A的某个field或者setter依赖了B的实例对象,同时B的某个field或者setter依赖了A的实例对象”这种循环依赖的情况。A首先完成了初始化的第一步,并且将自己提前曝光到singletonFactories中,此时进行初始化的第二步,发现自己依赖对象B,此时就尝试去get(B),发现B还没有被create,所以走create流程,B在初始化第一步的时候发现自己依赖了对象A,于是尝试get(A),尝试一级缓存singletonObjects(肯定没有,因为A还没初始化完全),尝试二级缓存earlySingletonObjects(也没有),尝试三级缓存singletonFactories,由于A通过ObjectFactory将自己提前曝光了,所以B能够通过ObjectFactory.getObject拿到A对象(虽然A还没有初始化完全,但是总比没有好呀),B拿到A对象后顺利完成了初始化阶段1、2、3,完全初始化之后将自己放入到一级缓存singletonObjects中。此时返回A中,A此时能拿到B的对象顺利完成自己的初始化阶段2、3,最终A也完成了初始化,长大成人,进去了一级缓存singletonObjects中,而且更加幸运的是,由于B拿到了A的对象引用,所以B现在hold住的A对象也蜕变完美了!一切都是这么神奇!!

总结

Spring通过三级缓存加上“提前曝光”机制,配合Java的对象引用原理,比较完美地解决了某些情况下的循环依赖问题!

猜你喜欢

转载自blog.csdn.net/awodwde/article/details/108762627
今日推荐