Java的循环引用

在使用spring的场景中,有时会碰到如下的一种情况,即bean之间的循环引用。并且,在一般情况下,这个配置在现有的spring3.0中是可以正常工作的,前提是没有对beanA和beanB进行增强。但是,如果任意一方进行了增强,比如通过spring的代理对beanA进行了增强,即实际返回的对象和原始对象不一致的情况,在这种情况下,就会报如下错误:org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name 'chuguanService': Bean with name 'chuguanService' has been injected into other beans [orderFinishService] 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,其所引用的对象并不是由spring容器最终生成的对象,而只是一个原始对象,而spring不允许这种情况出现,即持有过程中间对象。从上面的异常中很清楚的看到有两个service,chuguanService以及orderFinishService,这两个bean互相引用,就出现了循环引用的问题。正常情況下,spring对这种scope单例循环引用是支持的,但是这里报错是因为我的项目中多了一些其他的配置。在項目的aop配置文件中配置了

<bean class="com.jd.oa.supplier.edw.ump.UmpMonitorProcessor">

    <property name="filterPath">

                <value>classpath:/ump/filter.xml</value>

    </property>

    <property name="umpAppName" value="vps-edw"/>

</bean>

在filter.xml配置文件中又配置了一些bean,其中包括chuguanService,最终结果是配置文件中的bean会再做一层动态代理。目的是采用动态代理的形式对项目中的代码统一增加UMP的监控。项目中的UmpMonitorProcessor.class会实现BeanPostProcessor,并且重写了postProcessAfterInitialization方法,代码如下:

public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {

             ...........................................

            //如果实现了接口的bean,使用java动态代理来生成代理。

           if(beaninstanceof BaseTaskTemplate || (!S.isNull(beanProxyStrategy.get(beanName)) && !beanProxyStrategy.get(beanName))){

                    //如果没有实现任何接口,那么使用cglib来生成代理

                    UmpMonitorCglibProxy proxy =new UmpMonitorCglibProxy();

                     proxy.setBeanName(beanName);

                     proxy.setAppName(umpAppName);

                     Object proxy0 = proxy.getInstance(bean);

                     proxy.setMethods(beanMethodNames.get(beanName));

                     return proxy0;}

            return bean;

}

那么spring是如何处理bean间引用,以及使用何种策略处理循环引用的呢?简单说一下其实就是利用三级缓存的形式。Spring的单例对象的初始化主要分为三步:


图1

(1)createBeanInstance:实例化,其实也就是调用对象的构造方法实例化对象

(2)populateBean:填充属性,这一步主要是多bean的依赖属性进行填充

(3)initializeBean:调用spring xml中的init 方法。

那么我们要解决循环引用也应该从初始化过程着手,对于单例来说,在Spring容器整个生命周期内,有且只有一个对象,Spring为了解决单例的循环依赖问题,使用了三级缓存。这三级缓存分别指:

singletonFactories : 三级缓存,单例对象工厂的cache

earlySingletonObjects :二级缓存,提前曝光的单例对象的Cache

singletonObjects:一级缓存,单例对象的cache

Spring首先从一级缓存singletonObjects中获取。如果获取不到,并且对象正在创建中,就再从二级缓存earlySingletonObjects中获取。如果还是获取不到且允许singletonFactories通过getObject()获取,就从三级缓存singletonFactory.getObject()(三级缓存)获取。当发生循环引用时,beanA首先完成了初始化的第一步,并且将自己提前曝光到singletonFactories中,此时进行初始化的第二步,发现自己依赖对象beanB,此时就尝试去get(B),发现B还没有被创建,所以走beanB的创建流程,beanB在初始化第一步的时候发现自己依赖了对象beanA,于是尝试get(A),尝试一级缓存singletonObjects,发现没有,因为beanA还没初始化完全,尝试二级缓存earlySingletonObjects也没有,尝试三级缓存singletonFactories,由于beanA通过ObjectFactory将自己提前曝光了,所以beanB能够通过ObjectFactory.getObject拿到beanA对象,beanB拿到beanA对象后顺利完成了初始化阶段1、2、3,完全初始化之后将自己放入到一级缓存singletonObjects中。此时返回beanA中,beanA此时能拿到beanB的对象顺利完成自己的初始化阶段2、3,最终beanA也完成了初始化,放入到一级缓存singletonObjects中,这是正常情况下spring解决单例bean循环引用的方案。但是,当出现项目中的配置之后会出现无法解决循环引用的问题,

下面再看一下创建bean的方法。其中,AbstractAutowireCapableBeanFactory类中的的doCreateBean方法。其中一段代码:


图2

在initializeBean方法中,会调用applyBeanPostProcessorsAfterInitialization方法,方法中会执行postProcessAfterInitialization方法


图3

也就是会调用我们UmpMonitorProcessor的postProcessAfterInitialization方法。继续回到doCreateBean方法中,接下来会执行一段校验。


图4

上面有4个判断点

判断点1,首先确定这个对象能从earlySingletonObjects中取出对象来,经过上面的分析,我们知道,在正常情况下,此对象为null,即不存在循环检测。而在循环引用中,此对象能够被取出来。

判断点2,再判断这个对象和当前通过beanPostProcessor处理过的对象是否相同,如果相同,表示对象没有经过修改,即A=A-,那么循环引用成立。无需处理

判断点3,判断当前对象A是否被其他对象所依赖,在循环引用中,已经处理了A和B,那么在依赖表中,即在属性dependentBeanMap和dependenciesForBeanMap中。其中A->B表示A依赖于B,B->A表示B依赖于A。那么在dependentBeanMap中就会出现两个entry,分别为A->B和B->A。这里A依赖于B,那么表示A已经被依赖,则进入进一步检测中。在检测中,将取得一个A的被依赖列表中的bean已经被创建的对象列表值。

判断点4,如果被依赖对象列表不为空,则表示出现循环引用。因为按照创建规则,如果A->B,则必须先创建B,而B->A,则必须先创建A。在这里,A被B依赖,就要求A必须在B之前被创建,而B又被A依赖,又要求A必须在B之前被创建。这创建的两个对象必须满足一致才可以。即在A->B中的两个对象,必须和B->A的两个对象,互相一致才可以,否则就不是循环引用。

在上面这个校验过程中,在对A进行验证时,就会从earlySingletonObjects中取得一个A,但是这个A和后面的A-可能不是同一个对象,这是因为有了beanPostProcessor存在,它可以改变bean的最终值,比如对原始bean进行封装,代理等。在这个过程中,出现了3个对象A,A-,B,而B中所持有的A对象为原始的A。如果这里的A和A-不是同一个对象,即产生了beanA有了beanB的引用,但beanB并没有beanA的引用,而是另一个beanA的引用。这肯定不满足循环引用的条件,所以项目启动会报上面的异常日志。

解决方案是,可以采用AOP切面的形式,对需要加UMP监控的类以及方法增加配置。

文章参考地址:https://www.iflym.com/index.php/code/201208280001.html

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,884评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,755评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 158,369评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,799评论 1 285
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,910评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,096评论 1 291
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,159评论 3 411
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,917评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,360评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,673评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,814评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,509评论 4 334
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,156评论 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,882评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,123评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,641评论 2 362
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,728评论 2 351

推荐阅读更多精彩内容

  • 文章作者:Tyan博客:noahsnail.com 3.4 Dependencies A typical ente...
    SnailTyan阅读 4,135评论 2 7
  • 一. Java基础部分.................................................
    wy_sure阅读 3,807评论 0 11
  • 昏鸦枯藤伴树老,人家流水望小桥。瘦马西风卷古道,西下斜阳,天涯人已断肠。
    十里街阅读 408评论 0 1
  • 时隔多年未曾联系,一不小心在居然在知乎上遇见。有一搭没一搭地聊了一阵,竟发现人生差点少了一个知己。 说起LL,这时...
    目分目分目分阅读 893评论 0 1
  • 今天初六了,明天大家都要回到各自的岗位好好工作了,祝大家都有一个更美好的前程吧! 前几天无意打开当当网,首页有个选...
    冰天落落阅读 276评论 0 0