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的配置:

    
        
    

    
        
    

启动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() {
            @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();
                }
                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:

    
        
    

    
        
    

大体流程和构造注入一致,只是一个是解析构造函数,一个是通过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循环引用的问题有了个基本的认识,如果项目中出现这种问题也能快速的定位,不至于惊慌失措.

你可能感兴趣的:(spring源码剖析之如何处理循环引用)