精华内容
下载资源
问答
  • spring源码系列()——spring循环引用

    万次阅读 多人点赞 2019-09-30 21:22:06
    spring循环依赖如何解的?

    觉得之前那篇阅读性比价差,主要第一次用csdn博客,很多语法不懂,导致文章可读性不好,我彻底更新一下;打算把spring集合写完;
    长文警告
    正文开始

    众所周知spring在默认单例的情况下是支持循环引用的

    为了节省图片大小我把那些可以动得gif图片做成了只循环一次,如果看到图片不动了请右键选择在新标签打开,那么图片就会动,手机用户则更简单,直接手指点击图片便能看到动图,每张gif我都标识了,如果没有标识则为静态图片;

    Appconfig.java类的代码

    @Configurable
    @ComponentScan("com.shadow")
    public class Appconfig {
    }
    

    X.java类的代码

    package com.shadow.service;
    
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.stereotype.Component;
    
    @Component
    public class X {
    
    	@Autowired
    	Y y;
    
    	public X(){
    		System.out.println("X create");
    	}
    }
    

    Y.java了的代码

    package com.shadow.service;
    
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.stereotype.Component;
    
    @Component
    public class Y {
    	@Autowired
    	X x;
    	
    	public Y(){
    		System.out.println("Y create");
    	}
    }
    

    这两个类非常简单,就是相互引用了对方,也就是我们常常的说的循环依赖,spring是允许这样的循环依赖(前提是单例的情况下的,非构造方法注入的情况下)

    运行这段代码的结果下图

    注意这是张gif,如果你看着不动请参考我上面说的方法

    在这里插入图片描述
    上面代码从容器中能正常获取到Xbean,说明循环依赖成功。但是spring的循环依赖其实是可以关闭的,spring提供了api来关闭循环依赖的功能。当然你也可以修改spring源码来关闭这个功能,这里笔者为了提高逼格,就修改一下spring的源码来关闭这个功能,老话说:要想高明就得装逼。
    下图是我修改spring源码运行的结果
    我在AnnotationConfigApplicationContext的构造方法中加了一行setAllowCircularReferences(false);结果代码异常,循环依赖失败

    在这里插入图片描述

    那么为什么setAllowCircularReferences(false);会关闭循环依赖呢?首要明白spring的循环依赖是怎么做到的呢?spring源码当中是如何处理循环依赖的? 分析一下所谓的循环依赖其实无非就是属性注入,或者就是大家常常说的自动注入, 故而搞明白循环依赖就需要去研究spring自动注入的源码;spring的属性注入属于spring bean的生命周期一部分;怎么理解spring bean的生命周期呢?注意笔者这里并不打算对bean的生命周期大书特书,只是需要读者理解生命周期的概念,细节以后在计较;
    要理解bean的生命周期首先记住两个概念
    请读者一定记住两个概念——spring bean(一下简称bean)和对象;
    1、spring bean——受spring容器管理的对象,可能经过了完整的spring bean生命周期(为什么是可能?难道还有bean是没有经过bean生命周期的?答案是有的,具体我们后面文章分析),最终存在spring容器当中;一个bean一定是个对象
    2、对象——任何符合java语法规则实例化出来的对象,但是一个对象并不一定是spring bean;

    所谓的bean的生命周期就是磁盘上的类通过spring扫描,然后实例化,跟着初始化,继而放到容器当中的过程;
    我画了一张简单图来阐述一下spring bean的生命周期大概有哪些步骤

    在这里插入图片描述
    上图就是spring容器初始化bean的大概过程(至于详细的过程,后面文章再来介绍);
    文字总结一下:
    1:实例化一个ApplicationContext的对象;
    2:调用bean工厂后置处理器完成扫描;
    3:循环解析扫描出来的类信息;
    4:实例化一个BeanDefinition对象来存储解析出来的信息;
    5:把实例化好的beanDefinition对象put到beanDefinitionMap当中缓存起来,以便后面实例化bean;
    6:再次调用bean工厂后置处理器;
    7:当然spring还会干很多事情,比如国际化,比如注册BeanPostProcessor等等,如果我们只关心如何实例化一个bean的话那么这一步就是spring调用finishBeanFactoryInitialization方法来实例化单例的bean,实例化之前spring要做验证,需要遍历所有扫描出来的类,依次判断这个bean是否Lazy,是否prototype,是否abstract等等;
    8:如果验证完成spring在实例化一个bean之前需要推断构造方法,因为spring实例化对象是通过构造方法反射,故而需要知道用哪个构造方法;
    9:推断完构造方法之后spring调用构造方法反射实例化一个对象;注意我这里说的是对象、对象、对象;这个时候对象已经实例化出来了,但是并不是一个完整的bean,最简单的体现是这个时候实例化出来的对象属性是没有注入,所以不是一个完整的bean;
    10:spring处理合并后的beanDefinition(合并?是spring当中非常重要的一块内容,后面的文章我会分析);
    11:判断是否支持循环依赖,如果支持则提前把一个工厂存入singletonFactories——map;
    12:判断是否需要完成属性注入
    13:如果需要完成属性注入,则开始注入属性
    14:判断bean的类型回调Aware接口
    15:调用生命周期回调方法
    16:如果需要代理则完成代理
    17:put到单例池——bean完成——存在spring容器当中

    用一个例子来证明上面的步骤,结合一些运行时期的动态图片

    为了节省图片大小我把那些可以动得gif图片做成了只循环一次,如果看到图片不动了请右键选择在新标签打开,那么图片就会动,手机用户则更简单,直接手指点击图片便能看到动图,每张gif我都标识了,如果没有标识则为静态图片;

    Z.java的源码

    package com.shadow.service;
    
    import org.springframework.beans.BeansException;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.context.ApplicationContext;
    import org.springframework.context.ApplicationContextAware;
    import org.springframework.stereotype.Component;
    
    import javax.annotation.PostConstruct;
    
    @Component
    public class Z implements ApplicationContextAware {
    	@Autowired
    	X x;//注入X
    
        //构造方法
    	public Z(){
    		System.out.println("Z create");
    	}
    
        //生命周期初始化回调方法
    	@PostConstruct
    	public void zinit(){
    		System.out.println("call z lifecycle init callback");
    	}
    
    	//ApplicationContextAware 回调方法
    	@Override
    	public void setApplicationContext(ApplicationContext ac) {
    		System.out.println("call aware callback");
    	}
    }
    
    
    

    来看看Z的生命周期,注意下图当中的字幕,会和上面的17个步骤一一对应
    下图是第一步到第六步,请自行对应
    接下来我们通过各种图片分析一下springbean的生命周期,读者只需要看图搞明白流程,至于图中涉及的源码,分析完流程之后再来解释;

    图① 注意这是张gif,如果你看着不动请参考我上面说的方法

    在这里插入图片描述
    在研究其他步骤之前,首先了解spring大概在什么时候实例化bean的

    图② 注意这是张gif,如果你看着不动请参考我上面说的方法

    在这里插入图片描述
    上图可以知道spring在AbstractApplicationContext#finishBeanFactoryInitialization方法中完成了bean的实例化。这点需要记住

    然后通过图片来说明一下第7

    图③ 注意这是张gif,如果你看着不动请参考我上面说的方法

    在这里插入图片描述
    接下来spring需要推断构造方法,然后通过推断出来的构造方法反射实例化对象,也就是上面说的8步和第9

    当然有可能推断不出来构造方法;关于这块知识博主后面更新文章

    图④ 注意这是张gif,如果你看着不动请参考我上面说的方法

    在这里插入图片描述
    上图说明spring是通过createBeanInstance(beanName, mbd, args);完成了推断构造方法和实例化的事情那么接下来便要执行第10步处理合并后的beanDefinition对象,这一块内容特别多,读者可以先不必要理解,后面文章会解释;

    图⑤ 注意这是张gif,如果你看着不动请参考我上面说的方法

    在这里插入图片描述
    仔细看上图,其实这个时候虽然Z被实例化出来了,但是并没有完成属性的注入;其中的X属性为null,而且里面的Aware接口的方法也没有调用,再就是@PostConstruct方法也没有调用,再一次说明他不是一个完整的bean,这里我们只能说z是个对象;
    继而applyMergedBeanDefinitionPostProcessors方法就是用来处理合并后的beanDefinition对象;

    跟着第11,判断是否支持循环依赖,如果支持则提前暴露一个工厂对象,注意是工厂对象

    图⑥ 注意这是张gif,如果你看着不动请参考我上面说的方法

    在这里插入图片描述
    12步,spring会判断是否需要完成属性注入(spring默认是需要的,但是程序员可以扩展spring,根据情况是否需要完成属性注入);如果需要则spring完成13步——属性注入,也就是所谓的自动注入;

    图⑦ 注意这是张gif,如果你看着不动请参考我上面说的方法

    在这里插入图片描述
    14、15、16

    图⑧ 注意这是张gif,如果你看着不动请参考我上面说的方法

    在这里插入图片描述
    默认情况 至此一个bean完成初始化,被put到单例池,也是对上文说的17个步骤的一个证明;这说明一个bean在spring容器当中被创建出来是有一个过程的,这个过程就是所谓的bean的生命周期,我们的循环依赖也是在这个生命周内完成的。下面我们具体来分析这些步骤

    由于bean的生命周期特别复杂本文只对涉及到循环依赖的步骤做分析,其他生命周期的步骤我会在后续博客中分析,可以继续关注博主

    回顾上面的图②图③ 我们知道spring的bean是在AbstractApplicationContext#finishBeanFactoryInitialization()方法完成的初始化,即循环依赖也在这个方法里面完成的。该方法里面调用了一个非常重要的方法 doGetBean的方法

    照例用图片来说明一下吧

    图⑨ 注意这是张gif,如果你看着不动请参考我上面说的方法

    在这里插入图片描述
    doGetBean方法内容有点多,这个方法非常重要,不仅仅针对循环依赖,甚至整个spring bean生命周期中这个方法也有着举足轻重的地位,读者可以认真看看笔者的分析。需要说明的是我为了更好的说清楚这个方法,我把代码放到文章里面进行分析;但是删除了一些无用的代码;比如日志的记录这些无关紧要的代码。下面重点说这个doGetBean方法

    首先笔者把精简后的代码贴出来方便大家阅读

    protected <T> T doGetBean(final String name, 
    					@Nullable final Class<T> requiredType,
          				@Nullable final Object[] args, 
          				boolean typeCheckOnly)
          				throws BeansException {
        //读者可以简单的认为就是对beanName做一个校验特殊字符串的功能
        //我会在下次更新博客的时候重点讨论这个方法
        //transformedBeanName(name)这里的name就是bean的名字
       final String beanName = transformedBeanName(name);
       
       //定义了一个对象,用来存将来返回出来的bean
       Object bean;
    
    	//deGetBean-1
       Object sharedInstance = getSingleton(beanName);
       
    	//deGetBean-2
    	if (sharedInstance != null && args == null) {
          bean = getObjectForBeanInstance(sharedInstance, name, beanName, null);
       }else{
       		deGetBean-3
       		if (isPrototypeCurrentlyInCreation(beanName)) {
             	throw new BeanCurrentlyInCreationException(beanName);
          }else{
          	//doGetBean-4
          	if (mbd.isSingleton()) {
                sharedInstance = getSingleton(beanName, () -> {
                   try {
                      return createBean(beanName, mbd, args);
                   }
                   catch (BeansException ex) {
                      destroySingleton(beanName);
                      throw ex;
                   }
                });
                
          }
       }
     }
    

    注意:上面的代码是我对doGetBean方法进行了删减的代码,只保留了和本文讨论的循环依赖有关的代码,完整版可以参考spring的源码org.springframework.beans.factory.support.AbstractBeanFactory#doGetBean

    接着笔者对上述代码逐行来解释

    1、deGetBean-1

    Object sharedInstance = getSingleton(beanName);
    在这里插入图片描述
    首先这行代码上有一句spring作者写的注释

    Eagerly check singleton cache for manually registered singletons.
    

    大概的意思就是检查一下单例池当中有没有手动注册的单例对象,说白了spring在创建一个bean之前先检查一下beanName是否被手动注册过到单例池当中;别小看这句spring作者写的javadoc背后的意义,其实这里有两重意思;要搞清楚这两重意思首先知道当代码执行到这里的时候其实是spring在初始化的时候执行过来的;既然spring在初始化的时候他肯定知道这个类X.java肯定没有在容器当中,为什么还需要去验证一下呢?好比说你第一次去天上人间,你几乎都能确定这是你一次去你不可能跑到那里问一下前台你有没有办会员吧?但是spring确这样做了,他问了,他问问自己有没有办会员;为什么呢?回到你自己,如果你去问自己有没有办会员无非就是怕别人拿着你的身份证去办了一个会员,或者各种原因阴差阳错别人吧身份证名字写错了,导致你成了天上人间的会员;其实spring也是这个意思,因为一个bean被put到单例池的渠道有很多;除了spring容器初始化—扫描类----实例化-----put到容器这条线之外还有很多方法可以把一个对象put到单例池;我这里只列举一种,其他的有机会再讨论,看下图 注意注释;
    在这里插入图片描述
    这就相当于在你第一次抱着紧张心态去天上人间的时候,发现你朋友以前拿着你的身份证去那里办了一个会员卡一样;

    所以上面提到的这句注释的两重意思①第一重意思判断spring当前正准备初始化的bean有没有提前被put到容器;
    那么第二重意思是什么呢?既然这里用来做spring初始化的工作,为什么这个方法名叫做doGetBean呢?讲道理应该叫做createBean啊才合理啊;有读者可能会说这个方法命名可能作者乱写的,请注意spring之所以经久不衰命名规范绝对是一个重要原因,作者是不会这么乱给方法命名的。诚然有的读者会说讨论这个的意义不大,其实博主觉得讨论这个非常重要;之所这里叫做doGetBean的原因就是因为这个方法就是用来获取bean的,他主要的工作不仅仅服务于spring bean的初始化;这个方法的作用不仅仅是为了spring 在初始化bean的过程中去判断一下这个bean是否被注册了这么简单;笔者认为这个方法最主要的作用是为了从容器中得到一个bean,也就是说当我们在spring代码中调用getBean(“a”)其背后的意义就是调用这个doGetBean,同样用一段代码来证明

    图⑩ 注意这是张gif,如果你看着不动请参考我上面说的方法

    在这里插入图片描述
    可以看到当我调用ac.getBean(“x”)的时候,底层其实就调用doGetBean获取这X对象的;spring之所以这么设计就是因为判断bean是否初始化好和get一个bean都需要从单例池当中获取,所以创建bean和getBean都需要调用这个doGetBean方法;也就是第②重意思,这个方法其实就是程序员getBean的底层实现;

    换成天上人间,你第一次跑去前台,人家前台直接说:先生请出示会员卡;你可能会奇怪——我是来全套的,你应该问我要什么服务,不是问会员卡;但是人家前台的职责有两,办会员和问你要什么服务;所以才会说出这句话;doGetBean也是这个意思,于是解释了这个方法名的意义了;

    总结一下 Object sharedInstance = getSingleton(beanName);目前看来主要是用于在spring初始化bean的时候判断bean是否在容器当中;以及供程序员直接get某个bean。

    注意笔者这里用了 目前这个词;因为getSingleton(beanName);这个方法代码比较多;他里面的逻辑是实现循环依赖最主要的代码,文章下面我会回过头再来讲这个方法的全部意义;

    请注意我们当前代码的场景,当前代码是spring容器在初始化的时候,初始化X这个bean的场景;运行到了Object sharedInstance = getSingleton(beanName);
    根据上面的分析,这个时候我的X Bean肯定没有被创建,所以这里返回sharedInstance = =null;

    跟着解析 //deGetBean-2

    //deGetBean-2
    if (sharedInstance != null && args == null) {
      bean = getObjectForBeanInstance(sharedInstance, name, beanName, null);
    

    由于 sharedInstance = =null 故而不会进入这个if分支,那么什么时候不等于null呢?两种情况1、在spring初始化完成后程序员调用getBean(“x”)的时候得到的sharedInstance 就不等于null;2、循环依赖的时候第二次获取对象的时候这里也不等于空;比如X 依赖 Y;Y依赖X;spring做初始化第一次执行到这里的时候X 肯定等于null,然后接着往下执行,当执行到属性注入Y的时候,Y也会执行到这里,那么Y也是null,因为Y也没初始化,Y也会接着往下执行,当Y执行到属性注入的时候获取容器中获取X,也就是第二次执行获取X;这个时候X则不为空;至于具体原因,读者接着往下看;

    至于这个if分支里面的代码干了什么事情,本文不讨论,放到后面写factoryBean的时候讨论,现在你可以理解if分支里面就把sharedInstance 原原本本的返回出来就行;即这个if分支没有意义;

    上文说了本次不进入if分支,所以这行代码解析完毕;

    接下解析 doGetBean -3

    else{
       		deGetBean-3
       		if (isPrototypeCurrentlyInCreation(beanName)) {
             	throw new BeanCurrentlyInCreationException(beanName);
          }
    如果把throw删了可能更加清晰吧,下面是删除后的代码
    if (isPrototypeCurrentlyInCreation(beanName)) {}
    

    不进if分支,则进入这个else分支,把throw删了 就一句代码;判断当前初始化的bean----X 是不是正在创建原型bean集合当中当中?
    spring源码当中关于这行代码有两行javadoc

    在这里插入图片描述
    比较简单我就不翻译了,一般情况下这里返回false,也就是不会进入if分支抛异常;为什么呢说一般情况下呢?首先这里是判断当前的类是不是正在创建的原型集合当中,即里面只会存原型;一般情况下我们的类不是原型,而是单例的,大家都知道spring默认是单例;所以返回false,再就是即使这个bean是原型也很少会在这里就存在**正在创建的原型集合**当中。因为不管单例还是原型,bean在创建的过程中会add到这个集合当中,但是创建完成之后就会从这个集合remove掉(关于这个文章后面有证明),原型情况第一次创建的时候会add到这个集合,但是不是在这里,而是在后面的创建过程中add,所以这里肯定不会存在,即使后面过程中add到这个集合了,但是创建完成之后也会remove掉,故而下一次实例化同一个原型bean(原型可以实例化无数次)的时候当代码执行到这里也不可能存在集合当中了;除非循环依赖会在bean还没有在这个集合remove之前再次判断一次,才有可能会存在,故而我前面说了一般情况下这里都返回false;那么单例情况我们已经说了一定返回false,原型情况只有循环依赖才会成立,但是只要是正常人就不会对原型对象做循环依赖的;即使你用原型做了循环依赖这里也出抛异常(因为if成立,进入分支 throw exception)。再一次说明原型不支持循环依赖(当然你非得用原型做循环依赖,其实有办法,以后文章说明,本文忽略);画了一幅图说明上面的文字,因为这个集合非常重要,但是读者如果这里不理解也没关系,文章下面我还会结合代码分析一次;

    在这里插入图片描述

    重点来了:说明叫做正在创建的原型集合呢? 还有一个与之对应的叫做正在创建的单例集合
    唯一的区别就是集合里面存的是单例和原型
    故而我们统称正在创建的集合,关于正在创建的集合是什么我下面会解释
    但是需要记住的,这个集合是我的一家之言,说白了这是笔者自己翻译的,叫做正在创建的集合,没有官方支持,至少我也没在书上看到过这个名词

    下面解析doGetBean-4

    else{
          	//doGetBean-4
          	if (mbd.isSingleton()) {
                sharedInstance = getSingleton(beanName, () -> {
                   try {
                      return createBean(beanName, mbd, args);
                   }
                   catch (BeansException ex) {
                      destroySingleton(beanName);
                      throw ex;
                   }
                });
       同样把抛异常的代码删了,如下
       	//doGetBean-4
          	if (mbd.isSingleton()) {
                sharedInstance = getSingleton(beanName, () -> {
                      return createBean(beanName, mbd, args);
                });
    

    代码有点多;if (mbd.isSingleton()) 比较简单,判断当前bean是否单例;本文环境下是成立的;继而

    sharedInstance = getSingleton(beanName, () -> {
                      return createBean(beanName, mbd, args);
                });
    

    这里又调用了一次getSingleton,如果有印象上面也调用了一次getSingleton,这是方法重载,两个getSingleton方法并不是同一个方法,读者自己看参数就行,为了区别我这这里叫做第二次调用getSingleton;上文的叫做第一次调用getSingleton;

    由于这里使用lamda表达式,有些读者看起来不是很理解;笔者改一下吧

    ObjectFactory<?>  singletonFactory = new ObjectFactory(){
    	public Object getObject(){
    		//其实这是个抽象类,不能实例化
    		//createBean是子类实现的,这里就不关心了
    		//你就理解这不是一个抽象类吧
    		AbstractBeanFactory abf = new AbstractBeanFactory();
    		Object bean = abf.createBean(beanName, mbd, args);
    		return bean;
    	};
    };
    //传入 beanName 和singletonFactory 对象
    sharedInstance = getSingleton(beanName,singletonFactory);
    
    这样看是不是明白多了呢?
    

    当然第二次getSingleton就会把我们bean创建出来,换言之整个bean如何被初始化的都是在这个方法里面;至此本文当中笔者例举出来的doGetBean方法的核心代码看起来解析完成了;

    注意我说的是本文当中例举的doGetBean代码,前面我已经说了我删了很多和循环依赖无关的代码,实际spring源码当中这个方法的代码很多,以后文章介绍吧;

    接下来就要研究第二次getSingleton方法的内容了,因为我说了整个bean初始化过程都在里面体现了;

    我先把spring源码贴出来,读者可以忽略这里,因为下面会精简代码;之所以贴出源码就是想告诉读者,为了研究循环依赖,本文中的很代码我是做了删减的;

    spring源码:-----读者可以忽略
    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) {
    				if (this.singletonsCurrentlyInDestruction) {
    					throw new BeanCreationNotAllowedException(beanName,
    							"Singleton bean creation not allowed while singletons of this factory are in destruction " +
    							"(Do not request a bean from a BeanFactory in a destroy method implementation!)");
    				}
    				if (logger.isDebugEnabled()) {
    					logger.debug("Creating shared instance of singleton bean '" + beanName + "'");
    				}
    				beforeSingletonCreation(beanName);
    				boolean newSingleton = false;
    				boolean recordSuppressedExceptions = (this.suppressedExceptions == null);
    				if (recordSuppressedExceptions) {
    					this.suppressedExceptions = new LinkedHashSet<>();
    				}
    				try {
    					singletonObject = singletonFactory.getObject();
    					newSingleton = true;
    				}
    				catch (IllegalStateException ex) {
    					// Has the singleton object implicitly appeared in the meantime ->
    					// if yes, proceed with it since the exception indicates that state.
    					singletonObject = this.singletonObjects.get(beanName);
    					if (singletonObject == null) {
    						throw ex;
    					}
    				}
    				catch (BeanCreationException ex) {
    					if (recordSuppressedExceptions) {
    						for (Exception suppressedException : this.suppressedExceptions) {
    							ex.addRelatedCause(suppressedException);
    						}
    					}
    					throw ex;
    				}
    				finally {
    					if (recordSuppressedExceptions) {
    						this.suppressedExceptions = null;
    					}
    					afterSingletonCreation(beanName);
    				}
    				if (newSingleton) {
    					addSingleton(beanName, singletonObject);
    				}
    			}
    			return singletonObject;
    		}
    	}
    

    下面是我删减后只和循环依赖有关的代码

    public Object getSingleton(String beanName, ObjectFactory<?> 
    singletonFactory) {
    	//getSingleton2 -1
    	Object singletonObject = this.singletonObjects.get(beanName);
    			//getSingleton2 -2
    			if (singletonObject == null) {
    				//getSingleton2 -3
    				if (this.singletonsCurrentlyInDestruction) {
    					throw new Exception(beanName,
    							"excepition");
    				}
    				//getSingleton2 -4
    				beforeSingletonCreation(beanName);
    				//getSingleton2 -5
    				singletonObject = singletonFactory.getObject();	
    			}
    			return singletonObject;
    		}
    		
    

    //getSingleton2 -1 开始解析

    Object singletonObject = this.singletonObjects.get(beanName);
    

    第二次getSingleton上来便调用了this.singletonObjects.get(beanName),直接从单例池当中获取这个对象,由于这里是创建故而一定返回null;singletonObjects是一个map集合,即所谓的单例池;用大白话说spring所有的单例bean实例化好都存放在这个map当中,这也是很多读者以前认为的spring容器,但是笔者想说这种理解是错误的,因为spring容器的概念比较抽象,而单例池只是spring容器的一个组件而已;但是你如果一定要找一个平衡的说法,只能说这个map——singletonObjects仅仅是狭义上的容器;比如你的原型bean便不在这个map当中,所以是狭义的spring容器;下图为这个map在spring源码当中的定义

    在这里插入图片描述

    //getSingleton2 -2 开始解析

    if (singletonObject == null) {
    上面解释了,在spring 初始化bean的时候这里肯定为空,故而成立
    

    //getSingleton2 -3 开始解析

    	if (this.singletonsCurrentlyInDestruction) {
    			throw new Exception(beanName,
    					"excepition");
    		}
    

    这行代码其实比较简单,判断当前实例化的bean是否正在销毁的集合里面;spring不管销毁还是创建一个bean的过程都比较繁琐,都会先把他们放到一个集合当中标识正在创建或者销毁;所以如果你理解了前面那个正在创建集合那么这个正在销毁集合也就理解了;但是不理解也没关系,下面会分析这些集合;

    如果一个bean正在创建,但是有正在销毁那么则会出异常;为什么会有这种情况?其实也很简单,多线程可能会吧;

    //getSingleton2 -4 假设解析

    beforeSingletonCreation(beanName);
    

    这段代码就比较重要了,关于上面说那个正在创建和正在销毁的集合;这段代码就能解释,所以如果上面你没看明白那个集合的意义,笔者这里用spring源码来说明一下;先看看当代码执行到这里的时候语境

    在这里插入图片描述

    当spring觉得可以着手来创建bean的时候首先便是调用beforeSingletonCreation(beanName);判断当前正在实例化的bean是否存在正在创建的集合当中,说白了就是判断当前是否正在被创建;因为spring不管创建原型bean还是单例bean,当他需要正式创建bean的时候他会记录一下这个bean正在创建(add到一个set集合当中);故而当他正式创建之前他要去看看这个bean有没有正在被创建(是否存在集合当中); 为什么spring要去判断是否存在这个集合呢?原因很多除了你们能想到了(你们能想到的基本不会出现,比如并发啊,重复创建什么的,因为他已经做了严格并发处理),其实这个集合主要是为了循环依赖服务的,怎么服务的呢?慢慢看吧,首先我们来看下这行 代码的具体内容

    在这里插入图片描述
    源码:

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

    1、this.inCreationCheckExclusions.contains(beanName)这里是判断当前需要创建的bean是否在Exclusions集合,被排除的bean,程序员可以提供一些bean不被spring初始化(哪怕被扫描到了,也不初始化),那么这些提供的bean便会存在这个集合当中;一般情况下我们不会提供,而且与循环依赖无关;故而所以这里不做深入分析,后面文章如果写到做分析;

    this.singletonsCurrentlyInCreation.add(beanName),如果当前bean不在排除的集合当中那么则这个bean添加到singletonsCurrentlyInCreation(当然这里只是把bean名字添加到集合,为了方便我们直接认为把bean添加到集合吧,因为他能根据名字能找打对应的bean);

    关于singletonsCurrentlyInCreation的定义参考下图

    在这里插入图片描述
    其实就是一个set集合,当运行完this.singletonsCurrentlyInCreation.add(beanName) 之后结果大概如下图这样

    在这里插入图片描述
    我们可以通过debug来调试证明一下上面这幅图

    ** 注意这是张gif,如果你看着不动请参考我上面说的方法**

    在这里插入图片描述
    结果分析:当代码运行完this.singletonsCurrentlyInCreation.add(beanName)之后可以看到singletonsCurrentlyInCreation集合当中只存在一个x,并且后天并没有执行x的构造方法,说明spring仅仅是把x添加到正在创建的集合当中,但是并没有完成bean的创建(因为连构造方法都没调用);

    请一定注意这个集合的数据情况(目前只有一个x);因为这和循环依赖有天大的关系;add完x之后代码接着往下执行;

    //getSingleton2 -5 开始分析

    singletonObject = singletonFactory.getObject();
    可能有读者已经忘记了singletonFactory这个对象怎么来的了;笔者再把代码贴一遍吧

    ObjectFactory<?>  singletonFactory = new ObjectFactory(){
    	public Object getObject(){
    		//其实这是个抽象类,不能实例化
    		//createBean是子类实现的,这里就不关心了
    		//你就理解这不是一个抽象类吧
    		AbstractBeanFactory abf = new AbstractBeanFactory();
    		Object bean = abf.createBean(beanName, mbd, args);
    		return bean;
    	};
    };
    //传入 beanName 和singletonFactory 对象
    sharedInstance = getSingleton(beanName,singletonFactory);
    
    

    singletonFactory.getObject();调用的就是上面代码中getObject方法,换言之调用的是abf.createBean(beanName, mbd, args);把创建好的bean返回出来;至此第二次getSingleton方法结束,bean通过singletonFactory.getObject();调用createBean建完成;接下来分析createBean的源码,继续探讨循环依赖的原理;

    AbstractAutowireCapableBeanFactory#createBean()方法中调用了doCreateBean方法创建bean;下图是dubug流程

    ** 注意这是张gif,如果你看着不动请参考我上面说的方法**

    在这里插入图片描述
    结果分析:因为执行完doCreateBean之后X和Y的构造方法都已经完成了调用,说明这个方法里面对X做了实例化,也就是把bean创建好了,而且完成了循环依赖(因为Y的构造方法也打印说明X在完成属性注入的时候注入了Y,所以Y也实例化了,Y bean也创建好了);接下来重点分析这个doCreateBean方法内容。

    我先给出这个方法的源码全貌;重点我用红色标记了,并且会在进行代码解析;黄色线下面的读者可以不用管,和本文内容没多大关系;

    读者可以好好看看下图:方便你阅读下面的代码解析

    在这里插入图片描述

    //doCreateBean -1

    instanceWrapper = createBeanInstance(beanName, mbd, args);
    

    createBeanInstance 顾名思义就是创建一个实例,注意这里仅仅是创建一个实例对象,还不能称为bean;因为我文章一开头就解释了什么是bean,什么是对象;好吧再啰嗦一下吧,文章比较长,不方便翻阅;

    1、spring bean——受spring容器管理的对象,可能经过了完整的spring bean生命周期(为什么是可能?难道还有bean是没有经过bean生命周期的?答案是有的,具体我们后面文章分析),最终存在spring容器当中;一个bean一定是个对象
    2、对象——任何符合java语法规则实例化出来的对象,但是一个对象并不一定是spring bean;

    同样用dubug来说明一下:

    ** 注意这是张gif,如果你看着不动请参考我上面说的方法**

    在这里插入图片描述
    运行完createBeanInstance之后控制打印了X构造方法的内容,说明X对象已经被创建了,但是这个时候的x不是bean,因为bean的生命周期才刚刚开始;这就好比你跑到天上人间,问了各种你想问的问题之后交了1000块钱,但是这个时候你仅仅是个消费者,还不是渣男,因为一条龙的服务是从交钱开始,接下来的各种服务完成你才是一个名副其实的渣男,不知道这么解释有没有偏差;为了把前面知识串起来,照例画一下当前代码的语境吧

    在这里插入图片描述
    这个createBeanInstance方法是如何把对象创建出来的呢?对应文章开头说的bean的生命周期一共17步,其中的第8步(推断构造方法)和第9步(利用构造方法反射来实例化对象);具体如何推断构造方法我会在后面的博客分析;这里截个图看看代码就行,不做分析;

    推断构造方法的代码运行结果分析——注意这张图比较长,读者可以多看几遍;因为推断构造方法笔者以为是属于spring源码中特别重要和特别难的一块知识;后面会有单独博客来分析,所以读者可以先多看看这张图;

    ** 注意这是张gif,如果你看着不动请参考我上面说的方法**

    在这里插入图片描述
    至此x对象已经实例化出来,代码往下执行到合并beanDefinition,看图吧

    在这里插入图片描述
    但是其实合并beanDefinition和本文讨论的循环依赖无关,故而先跳过;

    //doCreateBean-2 开始解析

    
    	boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences &&
    			isSingletonCurrentlyInCreation(beanName));
    

    这段代码其实比较简单,就是给earlySingletonExposure这个布尔类型的变量赋值;这个变量的意义是——是否支持(开启了)循环依赖;如果返回true则spring会做一些特殊的操作来完成循环依赖;如果返回false,则不会有特殊操作;

    回到天上人间那个问题,好比你去一条龙的时候;人家会分析你是否是雏,如果你是雏则随便给你安排一个技师;当然如果你是笔者这样的资深玩家,可能会安排新亘结衣也说不定;

    那么这个布尔变量的赋值逻辑是怎样的呢?上面代码可知三个条件做&&运算,同时成立才会返回true;
    1、mbd.isSingleton();判断当前实例化的bean是否为单例;再一次说明原型是不支持循环依赖的;因为如果是原型这里就会返回false,由于是&&运算,整个结果都为false;相当于人家判断你是雏;那么新亘结衣什么的就别想了;在本文环境里X是默认单例的,故而整个条件是true。
    2、this.allowCircularReferences;整个全局变量spring 默认为true;当然spring提供了api供程序员修改,这个在本文开头笔者解释过(笔者是通过修改spring源码来改变这个值为false),在没有修改的情况下这里也返回true
    3、isSingletonCurrentlyInCreation(beanName);判断当前正在创建的bean是否在正在创建bean的集合当中;还记得前文笔者已经解释过singletonsCurrentlyInCreation这个集合现在里面存在且只有一个x;故而也会返回true;

    其实这三种情况需要关心的只有第二种;因为第一种是否单例一般都是成立的,因为如果是原型的循环依赖前面代码已经报错了;压根不会执行到这里;第三种情况也一般是成立,因为这个集合是spring操作的,没有提供api给程序员去操作;而正常流程下代码执行到这里,当前正在创建的bean是一定在那个集合里面的;换句话说这三个条件1和3基本恒成立;唯有第二种情况可能会不成立,因为程序员可以通过api来修改第二个条件的结果;

    总结:spring的循环依赖,不支持原型,不支持构造方法注入的bean;默认情况下单例bean是支持循环依赖的,但是也支持关闭,关闭的原理就是设置allowCircularReferences=false;spring提供了api来设置这个值;

    至此我们知道boolean earlySingletonExposure=true,那么代码接着往下执行;判断这个变量;
    在这里插入图片描述
    if成立,进入分支;

    //doCreateBean-3 开始分析

    addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
    这段代码又用了lamda表达式;笔者为了初学者看懂,还是改成传统代码
    ObjectFactory<?> singletonFactory = new ObjectFactory<?>(){
    	public T getObject(){
    		//至于这个getEarlyBeanReference方法的代码,后面再来说
    		// 现在可以理解为就是返回 bean
    		getEarlyBeanReference(beanName, mbd, bean);
    		//getEarlyBeanReference 的代码稍微复杂一点,可以简单理解为下面这样
    		getEarlyBeanReference(beanName, mbd, bean){
    			return bean;
    		}
    	}
    }
    
    也就是singletonFactory.getObject();其实就是返回当前正在实例化的bean
    改完之后的代码可以理解成这样:
    
    addSingletonFactory(beanName,singletonFactory);
    
    

    addSingletonFactory(beanName,singletonFactory);顾名思义添加一个单例工厂;其实这里要非常注意,因为大部分资料里面在说到spring循环依赖的时候都说是提前暴露一个半成品bean;笔者觉得这个不严格;甚至算错误了,所谓的提前暴露就是这里的add,但是我们看到源码并不是add一个bean的,而是add一个工厂对象——singletonFactory;两种说法有什么区别呢?区别可大了,简直天壤之别;我们慢慢分析;这里bean和工厂有什么区别呢?在当前的语境下面bean就是x对象经历完spring生命周期之后;所谓的半成品bean,可能还没有经历完整的生命周期;而工厂对象呢?如果你去ObjectFactory的源码或者直接顾名思义他是一个能够产生对象的工厂,或者叫能够产生bean的工厂;换句话说bean是一个产品,而工厂是产生这些产品的公司;如果还不能理解换成天上人间可能好理解——冰火和全套的区别,冰火是全套里面的一个项目,除了冰火还有其他项目;

    那么spring在这里add的是singletonFactory这个工厂对象(这个工厂可以产生半成品对象),而不是一个半成品对象;相当于这里add的是全套,而不是冰火;将来拿出来的时候是得到工厂,继而通过工厂得到半成品bean;将来拿出来的是全套,你可以在全套里面肆意选择一个项目;不知道我又没有解释清楚这个问题;

    当然说了这么多可能你还是没明白为什么需要在这里add这个工厂对象呢?还有add到哪里去呢?

    我们首先分析bean工厂对象到底add到哪里去了,查看源码
    在这里插入图片描述
    读者可以好好看看上图,笔者在spring源码当中把注释写上了(注释的信息很重要,认真看看),整个方法其实就是对三个map操作,至于这三个map的意义,参考下图
    在这里插入图片描述
    通过代码可以得知singletonFactory主要被add到二级缓存中;至于为什么要add到这个map?主要了循环依赖,提前暴露这个工厂;当然如果你不理解为什么要提前暴露,没关系往下看,看完文章一定会知道的;

    保持好习惯照例画个图,让读者知道现在的情况吧
    在这里插入图片描述
    当然这里还是用一幅图来秒杀一下这个三个map的各种情况吧

    在这里插入图片描述
    一级缓存:可能存在很多bean,比如spring各种内置bean,比如你项目里面其他的已经创建好的bean,但是在X的创建过程中,一级缓存中绝对是没有xbean的,也没用y;因为spring创建bean默认的顺序是根据字母顺序的;

    二级缓存:里面现在仅仅存在一个工厂对象,对应的key为x的beanName,并且这个bean工厂对象的getObect方法能返回现在的这个时候的x(半成品的xbean)
    put完成之后,代码接着往下执行;

    三级缓存:姑且认为里面什么都没有吧

    //doCreateBean-4 开始解析

    populateBean(beanName, mbd, instanceWrapper);
    

    populateBean这个方法可谓大名鼎鼎,主要就是完成属性注入,也就是大家常常说的自动注入;假设本文环境中的代码运行完这行代码那么则会注入y,而y又引用了x,所以注入进来的y对象,也完成了x的注入;什么意思呢?首先看一下没有执行populateBean之前的情况
    在这里插入图片描述
    没有执行populateBean之前只实例化了X,Y并没实例化,那么Y也不能注入了;接下来看看执行完这行代码之后的情况

    在这里插入图片描述
    populateBean里面的代码以后我更新文章来说明,本文先来猜测一下这个方法里面究竟干了什么事;
    x 填充 y (简称 xpy)首先肯定需要获取y,调用getBean(y),getBean的本质上文已经分析过货进入到第一次调用getSingleton,读者可以回顾一下上文我对doGetBean方法名字的解释里说了这个方法是创建bean和获取共用的;

    第一次getSingleton会从单例池获取一下y,如果y没有存在单例池则开始创建y;

    创建y的流程和创建x一模一样,都会走bean的生命周期;比如把y添加到正在创建的bean的集合当中,推断构造方法,实例化y,提前暴露工厂对象(二级缓存里面现在有两个工厂了,分别是x和y)等等。。。。重复x的步骤;

    直到y的生命周期走到填充x的时候ypx,第一次调用getSingletion获取x?这里问个问题,能否获取到x呢?

    在回答这个问题之前我们先把该画的图贴出来,首先那个正在被创建bean的集合已经不在是只有一个x了;(读者可以对比一下上文的图)

    在这里插入图片描述
    然后我们再把xpy到ypx的流程图贴出来,请读者仔细看看

    在这里插入图片描述
    是否能够获取到x呢?首先我们想如果获取失败则又要创建x—>实例化x—填充属性----获取y--------。。。。。。。就无限循环了;所以结果是完成了循环依赖,那么这里肯定能够获取到x;那么获取到x后流程是怎样呢?

    在这里插入图片描述
    那么为什么能够获取到x呢?讲道理联系上文第一次调用getSingleton是无法获取到x的?因为我们上面说过第一次调用getSingleton是从单例池当中获取一个bean,但是x显然没有完成生命周期(x只走到了填充y,还有很多生命周期没走完),所以应该是获取不到的?为了搞清楚这个原因得去查看第一次getSingleton的源码;如果读者有留意的话笔者前面只是凭只管告诉你第一次getSingleton是从单例池当中获取一个bean,并没有去证明,也就是没有去分析第一次getSingleton的源码;而且我在总结第一次getSingleton的时候用了目前这个词;证据如下(图是本文前面的内容,为了翻阅方便我直接贴这里了)

    在这里插入图片描述
    显然这是笔者前面故意挖的坑,所以各位读者在阅读别人的文章或者书籍的时候一定要小心验证;包括笔者的文章如果有错误一定记得告诉我;

    下面来开始对第一次getSIngleton源码做深入分析;首先把源码以及我写的注释贴出来,分为图片和源代码,建议大家看图片,可读性好
    在这里插入图片描述
    源码:如果你仔细看了上面的图片可以跳过这里的源码展示

    protected Object getSingleton(String beanName, boolean allowEarlyReference) {
    		//从单例池当(一级缓存)中直接拿,也就是文章里面'目前'的解释
    		//这也是为什么getBean("xx")能获取一个初始化好bean的根本代码
    		Object singletonObject = this.singletonObjects.get(beanName);
    		//如果这个时候是x注入y,创建y,y注入x,获取x的时候那么x不在容器
    		//第一个singletonObject == null成立
    		//第二个条件判断是否存在正在创建bean的集合当中,前面我们分析过,成立
    		//进入if分支
    		if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
    			synchronized (this.singletonObjects) {
    				//先从三级缓存那x?为什么先从三级缓存拿?下文解释
    				singletonObject = this.earlySingletonObjects.get(beanName);
    				//讲道理是拿不到的,因为这三个map现在只有二级缓存中存了一个工厂对象
    				//回顾一下文章上面的流程讲工厂对象那里,把他存到了二级缓存
    				//所以三级缓存拿到的singletonObject==null  第一个条件成立
    				//第二个条件allowEarlyReference=true,这个前文有解释
    				//就是spring循环依赖的开关,默认为true 进入if分支
    				if (singletonObject == null && allowEarlyReference) {
    					//从二级缓存中获取一个 singletonFactory,回顾前文,能获取到
    					//由于这里的beanName=x,故而获取出来的工厂对象,能产生一个x半成品bean
    					ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
    					//由于获取到了,进入if分支
    					if (singletonFactory != null) {
    						//调用工厂对象的getObject()方法,产生一个x的半成品bean
    						//怎么产生的?下文解释,比较复杂
    						singletonObject = singletonFactory.getObject();
    						//拿到了半成品的xbean之后,把他放到三级缓存;为什么?下文解释
    						this.earlySingletonObjects.put(beanName, singletonObject);
    						//然后从二级缓存清除掉x的工厂对象;?为什么,下文解释
    						this.singletonFactories.remove(beanName);
    					}
    				}
    			}
    		}
    

    针对上面的源码我做一个简单的总结:首先spring从单例池当中获取x,前面说过获取不到,然后判断是否在正在创建bean的集合当中,前面分析过这个集合现在存在x,和y;所以if成立进入分支;进入分支spring直接从三级缓存中获取x,根据前面的分析三级缓存当中现在什么都没有,故而返回nll;进入下一个if分支,从二级缓存中获取一个ObjectFactory工厂对象;根据前面分析,二级缓存中存在x,故而可以获取到;跟着调用singletonFactory.getObject();拿到一个半成品的x bean对象;然后把这个x对象放到三级缓存,同时把二级缓存中x清除(此时二级缓存中只存在一个y了,而三级缓存中多了一个x);

    问题1、为什么首先是从三级缓存中取呢?主要是为了性能,因为三级缓存中存的是一个x对象,如果能取到则不去二级找了;哪有人会问二级有什么用呢?为什么一开始要存工厂呢?为什么一开始不直接存三级缓存?这里稍微有点复杂,如果直接存到三级缓存,只能存一个对象,假设以前存这个对象的时候这对象的状态为xa,但是我们这里y要注入的x为xc状态,那么则无法满足;但是如果存一个工厂,工厂根据情况产生任意xa或者xb或者xc等等情况;比如说aop的情况下x注入y,y也注入x;而y中注入的x需要加代理(aop),但是加代理的逻辑在注入属性之后,也就是x的生命周期周到注入属性的时候x还不是一个代理对象,那么这个时候把x存起来,然后注入y,获取、创建y,y注入x,获取x;拿出来的x是一个没有代理的对象;但是如果存的是个工厂就不一样;首先把一个能产生x的工厂存起来,然后注入y,注入y的时候获取、创建y,y注入x,获取x,先从三级缓存获取,为null,然后从二级缓存拿到一个工厂,调用工厂的getObject();spring在getObject方法中判断这个时候x被aop配置了故而需要返回一个代理的x出来注入给y。当然有的读者会问你不是前面说过getObject会返回一个当前状态的xbean嘛?我说这个的前提是不去计较getObject的具体源码,因为这块东西比较复杂,需要去了解spring的后置处理器功能,这里先不讨论,总之getObject会根据情况返回一个x,但是这个x是什么状态,spring会自己根据情况返回;

    问题2、为什么要从二级缓存remove?因为如果存在比较复杂的循环依赖可以提高性能;比如x,y,z相互循环依赖,那么第一次y注入x的时候从二级缓存通过工厂返回了一个x,放到了三级缓存,而第二次z注入x的时候便不需要再通过工厂去获得x对象了。因为if分支里面首先是访问三级缓存;至于remove则是为了gc吧;

    至此循环依赖的内容讲完,有错误欢迎指正,欢迎留言提问;如果觉得笔者写的对你有帮助可以多多点赞转发吧;

    展开全文
  • spring循环引用

    千次阅读 2012-11-23 12:54:09
    循环依赖就是循环引用,就是两个或多个Bean相互之间的持有对方,比如CircleA引用CircleB,CircleB引用CircleC,CircleC引用CircleA,则它们最终反映为个环。此处不是循环调用,循环调用是方法之间的环调用。  ...

        循环依赖就是循环引用,就是两个或多个Bean相互之间的持有对方,比如CircleA引用CircleB,CircleB引用CircleC,CircleC引用CircleA,则它们最终反映为一个环。此处不是循环调用,循环调用是方法之间的环调用。

        循环调用是无法解决的,除非有终结条件,否则就是死循环,最终导致内存溢出错误。

        Spring容器循环依赖包括构造器循环依赖和setter循环依赖,那Spring容器如何解决循环依赖呢?首先让我们来定义循环引用类:

    package cn.javass.spring.chapter3.bean;
    public class CircleA {
        private CircleB circleB;
        public CircleA() {
        }
        public CircleA(CircleB circleB) {
            this.circleB = circleB;
        }
    public void setCircleB(CircleB circleB)
    {
            this.circleB = circleB;
        }
    public void a() {
       circleB.b();
    }
    }
    package cn.javass.spring.chapter3.bean;
    public class CircleB {
        private CircleC circleC;
        public CircleB() {
        }
        public CircleB(CircleC circleC) {
            this.circleC = circleC;
        }
    public void setCircleC(CircleC circleC)
    {
            this.circleC = circleC;
        }
        public void b() {
            circleC.c();
        }
    }
    package cn.javass.spring.chapter3.bean;
    public class CircleC {
        private CircleA circleA;
        public CircleC() {
        }
        public CircleC(CircleC circleC) {
            this.circleC = circleC;
        }
    public void setCircleC(CircleC circleC)
    {
            this.circleC = circleC;
        }
        public void b() {
            circleC.c();
        }
    }

    <bean id="circleA" class="cn.javass.spring.chapter3.bean.CircleA">
    <constructor-arg index="0" ref="circleB"/>
    </bean>
    <bean id="circleB" class="cn.javass.spring.chapter3.bean.CircleB">
    <constructor-arg index="0" ref="circleC"/>
    </bean>
    <bean id="circleC" class="cn.javass.spring.chapter3.bean.CircleC">
    <constructor-arg index="0" ref="circleA"/>
    </bean>
    写段测试代码(cn.javass.spring.chapter3.CircleTest)测试一下吧:
    @Test(expected = BeanCurrentlyInCreationException.class)
    public void testCircleByConstructor() throws Throwable {
    try {
          new ClassPathXmlApplicationContext("chapter3/circleInjectByConstructor.xml");
        }
        catch (Exception e) {
          //因为要在创建circle3时抛出;
          Throwable e1 = e.getCause().getCause().getCause();
          throw e1;
        }
    }

    让我们分析一下吧:

    1、Spring容器创建“circleA” Bean,首先去“当前创建Bean池”查找是否当前Bean正在创建,如果没发现,则继续准备其需要的构造器参数“circleB”,并将“circleA” 标识符放到“当前创建Bean池”;

    2、Spring容器创建“circleB” Bean,首先去“当前创建Bean池”查找是否当前Bean正在创建,如果没发现,则继续准备其需要的构造器参数“circleC”,并将“circleB” 标识符放到“当前创建Bean池”;

    3、Spring容器创建“circleC” Bean,首先去“当前创建Bean池”查找是否当前Bean正在创建,如果没发现,则继续准备其需要的构造器参数“circleA”,并将“circleC” 标识符放到“当前创建Bean池”;

    4、到此为止Spring容器要去创建“circleA”Bean,发现该Bean 标识符在“当前创建Bean池”中,因为表示循环依赖,抛出BeanCurrentlyInCreationException。

    二、setter循环依赖:表示通过setter注入方式构成的循环依赖。

    对于setter注入造成的依赖是通过Spring容器提前暴露刚完成构造器注入但未完成其他步骤(如setter注入)的Bean来完成的,而且只能解决单例作用域的Bean循环依赖。

    如下代码所示,通过提前暴露一个单例工厂方法,从而使其他Bean能引用到该Bean。
    addSingletonFactory(beanName, new ObjectFactory() {
        public Object getObject() throws BeansException {
            return getEarlyBeanReference(beanName, mbd, bean);
        }
    });
    具体步骤如下:

           1、Spring容器创建单例“circleA” Bean,首先根据无参构造器创建Bean,并暴露一个“ObjectFactory ”用于返回一个提前暴露一个创建中的Bean,并将“circleA” 标识符放到“当前创建Bean池”;然后进行setter注入“circleB”;

           2、Spring容器创建单例“circleB” Bean,首先根据无参构造器创建Bean,并暴露一个“ObjectFactory”用于返回一个提前暴露一个创建中的Bean,并将“circleB” 标识符放到“当前创建Bean池”,然后进行setter注入“circleC”;

           3、Spring容器创建单例“circleC” Bean,首先根据无参构造器创建Bean,并暴露一个“ObjectFactory ”用于返回一个提前暴露一个创建中的Bean,并将“circleC” 标识符放到“当前创建Bean池”,然后进行setter注入“circleA”;进行注入“circleA”时由于提前暴露了“ObjectFactory”工厂从而使用它返回提前暴露一个创建中的Bean;

    4、最后在依赖注入“circleB”和“circleA”,完成setter注入。

     

    对于“prototype”作用域Bean,Spring容器无法完成依赖注入,因为“prototype”作用域的Bean,Spring容器不进行缓存,因此无法提前暴露一个创建中的Bean。
    <!-- 定义Bean配置文件,注意scope都是“prototype”-->
    <bean id="circleA" class="cn.javass.spring.chapter3.bean.CircleA" scope="prototype">
            <property name="circleB" ref="circleB"/>
       </bean>
       <bean id="circleB" class="cn.javass.spring.chapter3.bean.CircleB" scope="prototype">
           <property name="circleC" ref="circleC"/>
       </bean>
       <bean id="circleC" class="cn.javass.spring.chapter3.bean.CircleC" scope="prototype">
           <property name="circleA" ref="circleA"/>
       </bean>

    //测试代码cn.javass.spring.chapter3.CircleTest
    @Test(expected = BeanCurrentlyInCreationException.class)
    public void testCircleBySetterAndPrototype () throws Throwable {
        try {
            ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext(
    "chapter3/circleInjectBySetterAndPrototype.xml");
            System.out.println(ctx.getBean("circleA"));
        }
        catch (Exception e) {
            Throwable e1 = e.getCause().getCause().getCause();
            throw e1;
        }
    }
    对于“singleton”作用域Bean,可以通过“setAllowCircularReferences(false);”来禁用循环引用:

    @Test(expected = BeanCurrentlyInCreationException.class)
    public void testCircleBySetterAndSingleton2() throws Throwable {
        try {
            ClassPathXmlApplicationContext ctx =
    new ClassPathXmlApplicationContext();
            ctx.setConfigLocation("chapter3/circleInjectBySetterAndSingleton.xml");
            ctx.refresh();
        }
        catch (Exception e) {
            Throwable e1 = e.getCause().getCause().getCause();
            throw e1;
        }
    }

    以下是spring创建bean的部分过程源代码,也论证了上述分析

    //创建Bean实例对象 
    protected Object createBean(final String beanName, final RootBeanDefinition mbd, final Object[] args) 
                throws BeanCreationException { 
            if (logger.isDebugEnabled()) { 
                logger.debug("Creating instance of bean '" + beanName + "'"); 
            } 
            //判断需要创建的Bean是否可以实例化,即是否可以通过当前的类加载器加载 
            resolveBeanClass(mbd, beanName); 
            //校验和准备Bean中的方法覆盖 
            try { 
                mbd.prepareMethodOverrides(); 
            } 
            catch (BeanDefinitionValidationException ex) { 
                throw new BeanDefinitionStoreException(mbd.getResourceDescription(), 
                        beanName, "Validation of method overrides failed", ex); 
            } 
            try { 
    //如果Bean配置了初始化前和初始化后的处理器,则试图返回一个需要创建//Bean的代理对象 
                Object bean = resolveBeforeInstantiation(beanName, mbd); 
                if (bean != null) { 
                    return bean; 
                } 
            } 
            catch (Throwable ex) { 
                throw new BeanCreationException(mbd.getResourceDescription(), beanName, 
                        "BeanPostProcessor before instantiation of bean failed", ex); 
            } 
            //创建Bean的入口 
            Object beanInstance = doCreateBean(beanName, mbd, args); 
            if (logger.isDebugEnabled()) { 
                logger.debug("Finished creating instance of bean '" + beanName + "'"); 
            } 
            return beanInstance; 
        } 
    //真正创建Bean的方法 
    protected Object doCreateBean(final String beanName, final RootBeanDefinition mbd, final Object[] args) { 
            //封装被创建的Bean对象 
            BeanWrapper instanceWrapper = null; 
            if (mbd.isSingleton()){//单态模式的Bean,先从容器中缓存中获取同名Bean 
                instanceWrapper = this.factoryBeanInstanceCache.remove(beanName); 
            } 
            if (instanceWrapper == null) { 
                //创建实例对象 
                instanceWrapper = createBeanInstance(beanName, mbd, args); 
            } 
            final Object bean = (instanceWrapper != null ? instanceWrapper.getWrappedInstance() : null); 
            //获取实例化对象的类型 
            Class beanType = (instanceWrapper != null ? instanceWrapper.getWrappedClass() : null); 
            //调用PostProcessor后置处理器 
            synchronized (mbd.postProcessingLock) { 
                if (!mbd.postProcessed) { 
                    applyMergedBeanDefinitionPostProcessors(mbd, beanType, beanName); 
                    mbd.postProcessed = true; 
                } 
            } 
            // Eagerly cache singletons to be able to resolve circular references 
            //向容器中缓存单态模式的Bean对象,以防循环引用 
            boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences && 
                    isSingletonCurrentlyInCreation(beanName)); 
            if (earlySingletonExposure) { 
                if (logger.isDebugEnabled()) { 
                    logger.debug("Eagerly caching bean '" + beanName + 
                            "' to allow for resolving potential circular references"); 
                } 
                //这里是一个匿名内部类,为了防止循环引用,尽早持有对象的引用 
                addSingletonFactory(beanName, new ObjectFactory() { 
                    public Object getObject() throws BeansException { 
                        return getEarlyBeanReference(beanName, mbd, bean); 
                    } 
                }); 
            } 
            //Bean对象的初始化,依赖注入在此触发 
            //这个exposedObject在初始化完成之后返回作为依赖注入完成后的Bean 
            Object exposedObject = bean; 
            try { 
                //将Bean实例对象封装,并且Bean定义中配置的属性值赋值给实例对象 
                populateBean(beanName, mbd, instanceWrapper); 
                if (exposedObject != null) { 
                    //初始化Bean对象 
                    exposedObject = initializeBean(beanName, exposedObject, mbd); 
                } 
            } 
            catch (Throwable ex) { 
                if (ex instanceof BeanCreationException && beanName.equals(((BeanCreationException) ex).getBeanName())) { 
                    throw (BeanCreationException) ex; 
                } 
                else { 
                    throw new BeanCreationException(mbd.getResourceDescription(), beanName, "Initialization of bean failed", ex); 
                } 
            } 
            if (earlySingletonExposure) { 
                //获取指定名称的已注册的单态模式Bean对象 
                Object earlySingletonReference = getSingleton(beanName, false); 
                if (earlySingletonReference != null) { 
                    //根据名称获取的以注册的Bean和正在实例化的Bean是同一个 
                    if (exposedObject == bean) { 
                        //当前实例化的Bean初始化完成 
                        exposedObject = earlySingletonReference; 
                    } 
                    //当前Bean依赖其他Bean,并且当发生循环引用时不允许新创建实例对象 
                    else if (!this.allowRawInjectionDespiteWrapping && hasDependentBean(beanName)) { 
                        String[] dependentBeans = getDependentBeans(beanName); 
                        Set<String> actualDependentBeans = new LinkedHashSet<String>(dependentBeans.length); 
                        //获取当前Bean所依赖的其他Bean 
                        for (String dependentBean : dependentBeans) { 
                            //对依赖Bean进行类型检查 
                            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."); 
                        } 
                    } 
                } 
            } 
            //注册完成依赖注入的Bean 
            try { 
                registerDisposableBeanIfNecessary(beanName, bean, mbd); 
            } 
            catch (BeanDefinitionValidationException ex) { 
                throw new BeanCreationException(mbd.getResourceDescription(), beanName, "Invalid destruction signature", ex); 
            } 
            return exposedObject; 
        } 

    展开全文
  • A中包含一个B的导航集合 IList<B> B中包含一个A的导航实体 A 经过一番配置之后, 在查询A的时候,可以看到A中的导航集合IList<B>已经包含了相应的外键数据 展开这个IList<B>,可以看到里面...
  • Block 循环引用浅谈

    2017-09-19 17:31:44
    循环引用的原因 简单来说就是,【互相持有,造成内存不释放】。 如: self --持有--> block --持有--> self 形成了个环,中间还可能穿插其他对象,反正最后形成了个闭环,造成谁也不撒手,故内存永远不释放...

    Block 的循环引用问题,想必使用过 Block,几乎都遇到过。今天CodeReview时,发现还存在循环引用的问题。故简单记录如下:


    一、循环引用的原因

    简单来说就是,【互相持有,造成内存不释放】。

    如: self --持有--> block --持有--> self  形成了一个环,中间还可能穿插其他对象,反正最后形成了一个闭环,造成谁也不撒手,故内存永远不释放。

    二、解决方案

    也是一句话,砍掉任一一个链,破坏这个互相持有的闭环。

    1、self 不持有 block

        void (^testBlock)() = ^(){
           self.testStr = @"Hello block";
        };
        testBlock(@"");

    2、block 不强持有 self : weakSelf & strongSelf

    __weak __typeof__(self) weakSelf = self;
    
    __strong typeof(weakSelf) strongSelf = weakSelf;
    即 Block 里对 self 为弱引用,不持有 self,即 self 的引用计数不会+1.
    仅引用一次,用weakSelf ,多次引用,则用 strongSelf 。strongSelf 是为了保证在 block 执行过程中,self 不会再变化。
    至于二者区别,可以随便网上查一下,有很多人都对此进行了分享。
         __weak __typeof__(self) weakSelf = self;
        self.block = ^(){
            __strong typeof(weakSelf) strongSelf = weakSelf;
            testStr0 = @"这是 test 字符串";
            NSLog(@"%@",strongSelf->testStr0);
        };
        self.block();
    另,在 block 中对 self 的引用情况,包括使用 self 的属性,调用 self 的方法,以及使用 self 的成员变量。
        __weak __typeof__(self) weakSelf = self;
        self.block = ^(){
            __strong typeof(weakSelf) strongSelf = weakSelf;
            strongSelf->p_testStr = @"这是成员变量";
            NSLog(@"%@",strongSelf->p_testStr);
        };
        self.block();

    3、block 执行完后,置为 nil

        self.testStr = @"Test Str";
        self.block = ^(){
            NSLog(@"%@",self.testStr);
        };
        self.block();
        self.block = nil;

    三、扩展

    在总结循环引用的问题时,突发奇想这样一种情况:
    若 block 中,有用了 GCD , GCD 的 block 里又用到了 self ,那该如何判断是否存在循环引用呢?
    如:
        void (^testBlock)() = ^(){
            dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(5. * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
                self.testStr = @"Hello block";
                NSLog(@"%@>>>gcd执行了",self.testStr);
            });
        };
        testBlock(@"");
    再如:
        self.block = ^(){
            dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(5. * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
                self.testStr = @"Hello block";
                NSLog(@"%@>>>gcd执行了",self.testStr);
            });
        };
        self.block();
    其实,分析其持有关系,看有没有形成互相持有的环,若存在环,那肯定就循环引用了。
    1、testBlock --持有-->  GCD --持有-->  self 【没有形成环,不存在循环引用】
    2、self--持有--> block --持有--> GCD --持有--> self 【形成了环,故存在循环引用】
    解决循环引用的方式还是如上面所述的三点即可。


    附上 demo ,演示上文中提到的 block 中解决循环引用的方式。


    展开全文
  • Fastjson循环地址引用

    2021-03-23 18:14:37
    Fastjson循环地址引用前言、Fastjson循环地址引用产生原因?二、解决方式1.配置SerializerFeature总结 前言 、Fastjson循环地址引用产生原因? 在查看源代码的情况下,发现在每次拼接json数据的时候,...
    
    记录解决FastJson循环地址引用问题。
    
    


    前言


    一、Fastjson循环地址引用产生原因?

    在查看源代码的情况下,发现在每次拼接json数据的时候,fastjson都会记录当前处理的数据是不是完全包含之前出现过的数据。第一次拼接时是不做处理的,但是再第二次做处理时,通过开关SerializerFeature.DisableCircularReferenceDetect来校验是否进行相同数据引用替换?其实fastjson这样做的初衷应该是防止有的对象之间是真正的相互引用,导致死循环。

    二、解决方式

    1.配置SerializerFeature

    代码如下(示例):

    JSON.toJSONString(demoBOList, SerializerFeature.DisableCircularReferenceDetect);
    

    总结

    出现问题之后,应该从源码中找问题,找根本原因而不是直接从BD、Google中查出来结果,要知其所以然。

    展开全文
  • 循环引用问题

    2013-11-20 21:02:58
    // 07-循环引用 // // Created by apple on 13-8-9. // Copyright (c) 2013年 itcast. All rights reserved. // /* 1.@class的作用:仅仅告诉编译器,某个名称是个类 @class Person; // 仅仅告诉编译器,...
  • 解决方法是,在循环引用的字段前面加上段修饰语句 [System.Xml.Serialization.XmlIgnore] 这样在序列化的时候,系统会自动忽略该字段实例的序列化 .net与Java最大的不同在于,XML结构的字段是值传递,而.ne.....
  • 循环引用指的是对象A 中包含一个指向对象B的指针,而对象B中也包含一个指向对象A的引用。比如下面这个例子: function problem(){ var objectA = new Object(); var objectB = new Object(); objectA.someOther...
  • node循环引用简单测试

    千次阅读 2014-01-14 13:20:24
    今天代码遇到方法未定义has no method问题。发现是循环引用的问题...这个结果包括的是循环引用链中的每个。 例子: a.js console.log('a.js'); var b = require('./b'); console.log('a+.js'); console.lo
  • 1、首先讲述一下为什么分为浅拷贝和深拷贝。 js中数据类型分为原始类型和引用类型。原始类型即保存在栈内存中的简单数据段,这种类型值是直接存储在内存中的。而引用类型值则是保存在堆内存中的对象,栈内存中存储...
  • Objective-C - 循环引用问题

    千次阅读 2015-04-21 09:06:49
    循环引用问题/* 1.@class的作用:仅仅告诉编译器,某个名称是一个类 @class Person; // 仅仅告诉编译器,Person是一个类 2.开发中引用一个类的规范 1> 在.h文件中用@class来声明类 2> 在.m文件中用#import来包含...
  • iOS之循环引用

    2015-09-29 10:10:34
    直到某天你的个 app 因内存泄露而闪退,你才突然意识到他们的存在,并且发现循环引用像幽灵一样存在于代码的各个角落。年复一年,你开始学会如何处理循环引用,检测和避免它们,但是这部片子的恐怖结局还是在那里...
  • Objective-c 循环引用

    2015-09-05 20:32:53
    Objective-c 循环引用  OC内存管理中有种特殊的对象管理,那就是循环引用循环引用即是类A中有类B作为成员... 、非ARC情况下的循环引用,Person类中包含Card类,Card类中包含Person类。 1、Person类中对card
  • 11.循环引用

    2014-12-23 09:36:28
    1.@class的作用:仅仅告诉编译器,某个名称是...2.开发中引用一个类的规范 1> 在.h文件中用@class来声明类 2> 在.m文件中用#import来包含类的所有东西 3.两端循环引用解决方案 1> 一端用retain 2> 一端用assign
  • JVM有一个回收算法是引用计数算法,每当对象被引用一次,就+1,释放一个引用就-1,当垃圾回收时,引用计数为0的对象就会被GC掉。但这个方法有个问题,就是无法解决循环引用的问题。 循环引用就是对象A引用了对象B,...
  • 循环引用的处理

    千次阅读 2013-12-09 19:41:10
    C++编程过程中经常遇到循环引用,此时应该怎么解决这类问题呢?本文通过详细的例子介绍循环引用解决方法。
  • 引用计数实现的智能指针循环引用问题 什么情况下会出现循环引用?...循环引用情况下,实例SPA和SPB的引用计数为2,当各自释放的时候,引用计数减为1,并不会释放A、B的对象实例。 // CirCle.h cl...
  • java的循环引用

    千次阅读 2015-07-30 16:04:26
    在C++中使用过智能指针的同学们应该都清楚智能指针对C++中内存... 最近参与到android的项目开发,对java的内存的管理有了个初步的了解,很容易想到了循环引用的问题。比如下面这个例子: public void buidDog() {
  • 聊 JS 中的循环引用 本文主要从 JS 中为什么会出现循环...当对象 1 中的某个属性指向对象 2,对象 2 中的某个属性指向对象 1 就会出现循环引用,(当然不止这种情况,不过原理是一样的)下面通过代码和内存...
  • jpa 循环引用 在上篇文章中 ,我提到我选择通过其主键而不是类型来引用其他聚合。 在处理大型复杂域模型时,我通常使用这种方法(也称为断开域模型)。 在本文中,让我尝试进一步解释如何在JPA中完成它。 请...
  • ARC与非ARC的循环引用

    2015-09-20 16:15:24
    循环引用一般是这么种情况: 我创建了个人的对象和只狗的对象,我在.m文件中实例化了这两个对象,并且在对象里面互相包含对象的属性,互相引用  在ARC中,如果两个修饰符都是使用strong强引用的话,...
  • 思考个问题:不通过 GC ROOT,仍使用引用计数方式,怎么解决它的循环引用问题? 解答此问题前,通过目标驱动法来想象一下,若 Get 了此知识点,可以这样应用到面试中: 面试官: 说一下垃圾回收机制吧 我: …可以...
  • 如何解决引用计数的循环引用问题

    千次阅读 2020-03-28 17:06:43
    循环引用 public class MyObject { public Object ref = null; public static void main(String[] args) { MyObject myObject1 = new MyObject(); MyObject myObject2 = new MyObject(); ...
  • Newtonsoft.Json的循环引用解决方案

    千次阅读 2018-08-31 11:37:22
    网上有很关于Newtonsoft.Json循环引用的解决方案,比如设置循环引用为Ignore,这样在输出JSON时就不会输出。 var setting = new JsonSerializerSettings(); setting.ReferenceLoopHandling = ...
  • 造成循环引用的情况

    千次阅读 2016-08-18 14:37:49
    导致iOS对象无法按预期释放的个无形杀手是——循环引用循环引用可以简单理解为A引用了B,而B又引用了A,双方都同时保持对方的个引用,导致任何时候引用计数都不为0,始终无法释放。若当前对象是个...
  • iOS block之循环引用

    千次阅读 2017-09-10 22:37:56
    此篇博文用到了上篇的一些知识点,有需要了解可以点此链接《iOS block之三种block》前言首先还是从个大家耳熟能详的循环引用的条件说起:有3个对象A、B、C,当A强引用B,B强引用C,C又不小心强引用了A,就出现...
  • 引用计数法的循环引用问题

    千次阅读 2018-10-15 17:13:20
    关于引用计数法,我们可以先看段wiki上的描述:   As a collection algorithm, reference counting tracks, for each object, a count of the number of references to it held by other objects. If an object...
  • 何为循环引用 如果有两个或者以上的对象,它们彼此引用,就会造成循环引用。如下面的例子 class Node { Node next ; } Node a = new Node (); Node b = new Node (); a . next = b ; b . next = a ; 代码...
  • 本文主要从 JS 中为什么会出现循环引用,垃圾回收策略中引用计数为什么有很大的问题,以及循环...当对象 1 中的某个属性指向对象 2,对象 2 中的某个属性指向对象 1 就会出现循环引用,(当然不止这种情况,不过...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 436,597
精华内容 174,638
关键字:

包含一处或多处循环引用