spring源码剖析之如何处理循环引用

前言

何为循环引用?一个类A引用类B,而B又引用A,导致两个类互相引用。spring有多种依赖注入方式,最主要的就是setter和构造注入。针对singleton的setter注入,spring为我们解决了循环引用的问题。但是针对构造注入,spring也无能为力,只能抛出BeanCurrentlyInCreationException,下面来分析一下源码。

构造注入

分别有两个类User2、Address2分别持有对方的引用:

public class Address2 implements Serializable {

    public Address2(User2 user2) {
        System.out.println("address2 begin");
    }

    private User user;

    public User getUser() {
        return user;
    }

    public void setUser(User user) {
        this.user = user;
    }
}

public class User2 implements Serializable{
    public User2(Address2 address2) {
        System.out.println("User2 begin");
    }

    private Address address;

    public Address getAddress() {
        return address;
    }

    public void setAddress(Address address) {
        this.address = address;
    }
}

applicationContext.xml的配置:

    <bean id="user" class="com.pingan.instance.User2">
        <constructor-arg index="0" ref="address"/>
    </bean>

    <bean id="address" class="com.pingan.instance.Address2">
        <constructor-arg index="0" ref="user"/>
    </bean>

启动spring,立马抛出BeanCurrentlyInCreationException异常:

Caused by: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'address' defined in class path resource [applicationContext.xml]: Cannot resolve reference to bean 'user' while setting constructor argument; nested exception is org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name 'user': Requested bean is currently in creation: Is there an unresolvable circular reference?
  at org.springframework.beans.factory.support.BeanDefinitionValueResolver.resolveReference(BeanDefinitionValueResolver.java:359)
  at org.springframework.beans.factory.support.BeanDefinitionValueResolver.resolveValueIfNecessary(BeanDefinitionValueResolver.java:108)
  at org.springframework.beans.factory.support.ConstructorResolver.resolveConstructorArguments(ConstructorResolver.java:634)
  at org.springframework.beans.factory.support.ConstructorResolver.autowireConstructor(ConstructorResolver.java:140)
  at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.autowireConstructor(AbstractAutowireCapableBeanFactory.java:1139)
  at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBeanInstance(AbstractAutowireCapableBeanFactory.java:1042)
  at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:504)
  at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:476)
  at org.springframework.beans.factory.support.AbstractBeanFactory$1.getObject(AbstractBeanFactory.java:303)
  at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:230)
  at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:299)
  at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:194)
  at org.springframework.beans.factory.support.BeanDefinitionValueResolver.resolveReference(BeanDefinitionValueResolver.java:351)
  ... 17 more
Caused by: org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name 'user': Requested bean is currently in creation: Is there an unresolvable circular reference?
  at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.beforeSingletonCreation(DefaultSingletonBeanRegistry.java:347)
  at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:223)
  at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:299)
  at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:194)
  at org.springframework.beans.factory.support.BeanDefinitionValueResolver.resolveReference(BeanDefinitionValueResolver.java:351)
  ... 29 more

仔细看下错误堆栈,发现就是在resolveReference的时候去解析构造函数引发的报错。下面开始分析代码,直接定位到AbstractApplicationContext->finishBeanFactoryInitialization->preInstantiateSingletons->getBean方法,开始实例化对象,在getBean中有一段专门针对singleton实例化的代码(不清楚spring整个加载机制的,可以自行查找资料,这里就不展开了);

if (mbd.isSingleton()) {
    sharedInstance = getSingleton(beanName, new ObjectFactory<Object>() {
            @Override
            public Object getObject() throws BeansException {
                try {
                    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);
}

如果这个类是singleton(默认scope=singleton),则通过getSingleton获取实例,里面用到了一个回调方法,也就是说当getSingleton()->getObject()的时候会触发createBean操作。下面跟进createBean->doCreateBean方法,正式开始Bean的初始化操作,里面有几个核心方法:

        BeanWrapper instanceWrapper = null;
        if (mbd.isSingleton()) {
            instanceWrapper = this.factoryBeanInstanceCache.remove(beanName);
        }
        if (instanceWrapper == null) {
            instanceWrapper = createBeanInstance(beanName, mbd, args);
        }
        ...
        // Initialize the bean instance.
        Object exposedObject = bean;
        try {
            populateBean(beanName, mbd, instanceWrapper);
            if (exposedObject != null) {
                exposedObject = initializeBean(beanName, exposedObject, mbd);
            }
        }

里面的createBeanInstance是专门通过构造函数实例化对象的,没错,这里就是解析构造注入的地方,直接定位到autowireConstructor->ConstructorResolver.autowireConstructor->resolveConstructorArguments->resolveValueIfNecessary->resolveReference针对构造函数注入,首先要解析构造函数参数,然后解析注入对象:

     private Object resolveReference(Object argName, RuntimeBeanReference ref) {
        try {
            String refName = ref.getBeanName();
            refName = String.valueOf(doEvaluate(refName));
            if (ref.isToParent()) {
                if (this.beanFactory.getParentBeanFactory() == null) {
                    throw new BeanCreationException(
                            this.beanDefinition.getResourceDescription(), this.beanName,
                            "Can't resolve reference to bean '" + refName +
                            "' in parent factory: no parent factory available");
                }
                return this.beanFactory.getParentBeanFactory().getBean(refName);
            }
            else {
                Object bean = this.beanFactory.getBean(refName);
                this.beanFactory.registerDependentBean(refName, this.beanName);
                return bean;
            }
        }
        catch (BeansException ex) {
            throw new BeanCreationException(
                    this.beanDefinition.getResourceDescription(), this.beanName,
                    "Cannot resolve reference to bean '" + ref.getBeanName() + "' while setting " + argName, ex);
        }
    }

resolveReference中最终还是通过beanFactory.getBean去获取bean的实例,那到底是在哪里抛出异常的呢?回到getSingleton方法:

public Object getSingleton(String beanName, ObjectFactory<?> singletonFactory) {
        Assert.notNull(beanName, "'beanName' must not be null");
        synchronized (this.singletonObjects) {
            Object singletonObject = this.singletonObjects.get(beanName);
            if (singletonObject == null) {
                if (this.singletonsCurrentlyInDestruction) {
                    ...
                }
                beforeSingletonCreation(beanName);
                boolean newSingleton = false;
                boolean recordSuppressedExceptions = (this.suppressedExceptions == null);
                if (recordSuppressedExceptions) {
                    this.suppressedExceptions = new LinkedHashSet<Exception>();
                }
                try {
                    //回调方法
                    singletonObject = singletonFactory.getObject();
                    newSingleton = true;
                }
                catch (IllegalStateException ex) {
                    ...
                }
                catch (BeanCreationException ex) {
                    ...
                }
                finally {
                    if (recordSuppressedExceptions) {
                        this.suppressedExceptions = null;
                    }
                    afterSingletonCreation(beanName);
                }
                if (newSingleton) {
                    addSingleton(beanName, singletonObject);
                }
            }
            return (singletonObject != NULL_OBJECT ? singletonObject : null);
        }
    }

singletonFactory.getObject之前执行了一个beforeSingletonCreation方法:

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

原来是singletonsCurrentlyInCreation中已经存在该beanName了,大致清楚了。
结论:当我们去实例化User2的时候,先把User2加入到singletonsCurrentlyInCreation中,然后去解析构造函数的时候发现Address2没有实例化,然后通过getBean去实例化,并把它加入到singletonsCurrentlyInCreation中,解析Address2的时候发现User2又没有实例完成,去实例的时候发现User2已经在实例化过程中了,只能抛出异常,出现循环引用,此时的spring也无能为力,只能叹息一声:"为何要用构造注入,你不知道它有循环注入的问题吗?"。既然如此,那setter注入是怎么解决循环引用的问题?

setter注入
public class User implements Serializable{
    public User() {
        System.out.println("User begin");
    }

    private Address address;

    public Address getAddress() {
        return address;
    }

    public void setAddress(Address address) {
        this.address = address;
    }
}
public class Address implements Serializable {

    public Address() {
        System.out.println("address begin");
    }

    private User user;

    public User getUser() {
        return user;
    }

    public void setUser(User user) {
        this.user = user;
    }
}

applicationContext.xml:

    <bean id="user" class="com.pingan.instance.User">
        <property name="address" ref="address"/>
    </bean>

    <bean id="address" class="com.pingan.instance.Address">
        <property name="user" ref="user"/>
    </bean>

大体流程和构造注入一致,只是一个是解析构造函数,一个是通过setter.定位到AbstractAutowireCapableBeanFactory->doCreateBean中的populate方法:

      try {
            populateBean(beanName, mbd, instanceWrapper);
            if (exposedObject != null) {
                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);
            }
        }

跟进去populateBean,里面针对了不同的注入类型进行了解析AUTOWIRE_BY_NAME、AUTOWIRE_BY_NAME、AUTOWIRE_BY_TYPE.我们这里是通过setter注入的,所以重点关注最下面的applyPropertyValues方法:

        BeanDefinitionValueResolver valueResolver = new BeanDefinitionValueResolver(this, beanName, mbd, converter);
        ...
        String propertyName = pv.getName();
        Object originalValue = pv.getValue();

        //核心方法
        Object resolvedValue = valueResolver.resolveValueIfNecessary(pv, originalValue);
        Object convertedValue = resolvedValue;
        boolean convertible = bw.isWritableProperty(propertyName) &&
                !PropertyAccessorUtils.isNestedOrIndexedProperty(propertyName);
        if (convertible) {
            convertedValue = convertForProperty(resolvedValue, propertyName, bw, converter);
        }
        // Possibly store converted value in merged bean definition,
        // in order to avoid re-conversion for every created bean instance.
        if (resolvedValue == originalValue) {
            if (convertible) {
                pv.setConvertedValue(convertedValue);
            }
            deepCopy.add(pv);
        }
        else if (convertible && originalValue instanceof TypedStringValue &&
                !((TypedStringValue) originalValue).isDynamic() &&
                !(convertedValue instanceof Collection || ObjectUtils.isArray(convertedValue))) {
            pv.setConvertedValue(convertedValue);
            deepCopy.add(pv);
        }
        else {
            resolveNecessary = true;
            deepCopy.add(new PropertyValue(pv, convertedValue));
        }

BeanDefinitionValueResolver就是解析器,负责解析注入的参数,valueResolver.resolveValueIfNecessary(pv, originalValue)和构造注入的一样,正式开始解析参数,最后调用了resolveReference,最终又会通过getSingleton来获取实例。
重点就是上面的getSingleton这个方法,首先会读取singletonObjects缓存中的实例,如果存在则直接返回。因为当我们实例化完成的时候,会通过addSingleton加入到缓存,所以Address注入User的时候不会重新加载一遍,只是从缓存中直接读取,所以不会有循环引用的问题。

总结

对spring循环引用的问题有了个基本的认识,如果项目中出现这种问题也能快速的定位,不至于惊慌失措.

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