Spring源码分析3-对Bean循环依赖的处理

  1. 构造器注入循环依赖
  2. setter注入循环依赖
  3. prototype范围的循环依赖

本文主要是分析Spring如何处理循环依赖,分别从构造器注入循环依赖、setter注入循环依赖、prototype范围的循环依赖三个方面进行详细分析。

正式开始前先废话一下:

之前写了两篇近似于流水账的Spring源码分析,写到后边越来越多看不懂的地方(所以分析1分析2大家就当是流水账看看罢了,没什么营养)。之后回头细细一想,感觉精力都花在了Spring的上下文环节,至于面向切面编程AOP、控制反转IOC、依赖注入DI等等机制的实现原理都一窍不通。后来我看了一本书《Spring源码深度解析》,终于开始恍然大悟。

我明白直接研究Spring的核心机制,不应该从RestfultWeb入手,因为那样子其实是从Spring boot层面开始阅读源码了。

可以阅读这篇文章《Spring,Spring MVC及Spring Boot区别》大致了解。

所以直接按照书上介绍的内容去啃源码就对了。另外提一下就是书上的Spring版本要比现在的版本老了,但是核心思想一样的,底层的源码改动不大,可以正常阅读。

读到《Spring源码深度解析》5章6节的循环依赖的时候,感觉有挺多不明白的地方,后来逐个调试后,发现了很多有趣的东西。所以通过这篇文章记录下来。

首先要知道Spring框架允许setter注入循环依赖,但对于构造器注入循环依赖、prototype范围的循环依赖,框架都没法很好解决,
然后直接抛异常终止程序。我们尝试从源码层面去分析这三者的不同。

我们先定义两个类:

@Data
@AllArgsConstructor
@NoArgsConstructor
public class TestA {
    private TestB testB;

    @Override
    public String toString() {
        return "TestA";
    }
}

@Data
@AllArgsConstructor
@NoArgsConstructor
public class TestB {
    private TestA testA;

    @Override
    public String toString() {
        return "TestB";
    }
}

这里我采用了lombock的注解@Data来自动生成getter/setter、构造器、toString等方法,例外地我特地重写了toString方法。因为lombock自动生成的toString方法会导致栈溢出,后文我会提及这个。

构造器注入循环依赖

在xml中注入这两个bean,使其出现循环依赖。scope默认是singleton的。

    <bean id="testA" class="com.example.circle.TestA">
        <constructor-arg index="0" ref="testB"></constructor-arg>
    </bean>
    <bean id="testB" class="com.example.circle.TestB">
        <constructor-arg index="0" ref="testA"></constructor-arg>
    </bean>

在main方法里尝试去加载和获取这些bean(后文的类似,不再重复)

    //测试构造器注入的依赖处理
    context = new ClassPathXmlApplicationContext("classpath:circle/constructor/circle.xml");
    testA = (TestA) context.getBean("testA");
    System.out.println(testA.toString());

运行后,可以注意到报错如下:

Exception in thread "main" org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'testA' defined in class path resource [circle/constructor/circle.xml]: Cannot resolve reference to bean 'testB' while setting constructor argument; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'testB' defined in class path resource [circle/constructor/circle.xml]: Cannot resolve reference to bean 'testA' while setting constructor argument; nested exception is org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name 'testA': Requested bean is currently in creation: Is there an unresolvable circular reference?
    at org.springframework.beans.factory.support.BeanDefinitionValueResolver.resolveReference(BeanDefinitionValueResolver.java:378)
    at org.springframework.beans.factory.support.BeanDefinitionValueResolver.resolveValueIfNecessary(BeanDefinitionValueResolver.java:110)
    at org.springframework.beans.factory.support.ConstructorResolver.resolveConstructorArguments(ConstructorResolver.java:625)
    at org.springframework.beans.factory.support.ConstructorResolver.autowireConstructor(ConstructorResolver.java:153)
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.autowireConstructor(AbstractAutowireCapableBeanFactory.java:1267)
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBeanInstance(AbstractAutowireCapableBeanFactory.java:1124)
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:535)
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:495)
    at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:317)
    at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:222)
    at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:315)
    at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:199)
    at org.springframework.beans.factory.support.DefaultListableBeanFactory.preInstantiateSingletons(DefaultListableBeanFactory.java:759)
    at org.springframework.context.support.AbstractApplicationContext.finishBeanFactoryInitialization(AbstractApplicationContext.java:869)
    at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:550)
    at org.springframework.context.support.ClassPathXmlApplicationContext.<init>(ClassPathXmlApplicationContext.java:144)
    at org.springframework.context.support.ClassPathXmlApplicationContext.<init>(ClassPathXmlApplicationContext.java:85)
    at com.example.circle.CircleMain.main(CircleMain.java:29)
Caused by: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'testB' defined in class path resource [circle/constructor/circle.xml]: Cannot resolve reference to bean 'testA' while setting constructor argument; nested exception is org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name 'testA': Requested bean is currently in creation: Is there an unresolvable circular reference?
    at org.springframework.beans.factory.support.BeanDefinitionValueResolver.resolveReference(BeanDefinitionValueResolver.java:378)
    at org.springframework.beans.factory.support.BeanDefinitionValueResolver.resolveValueIfNecessary(BeanDefinitionValueResolver.java:110)
    at org.springframework.beans.factory.support.ConstructorResolver.resolveConstructorArguments(ConstructorResolver.java:625)
    at org.springframework.beans.factory.support.ConstructorResolver.autowireConstructor(ConstructorResolver.java:153)
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.autowireConstructor(AbstractAutowireCapableBeanFactory.java:1267)
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBeanInstance(AbstractAutowireCapableBeanFactory.java:1124)
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:535)
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:495)
    at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:317)
    at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:222)
    at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:315)
    at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:199)
    at org.springframework.beans.factory.support.BeanDefinitionValueResolver.resolveReference(BeanDefinitionValueResolver.java:367)
    ... 17 more
Caused by: org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name 'testA': Requested bean is currently in creation: Is there an unresolvable circular reference?
    at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.beforeSingletonCreation(DefaultSingletonBeanRegistry.java:339)
    at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:215)
    at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:315)
    at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:199)
    at org.springframework.beans.factory.support.BeanDefinitionValueResolver.resolveReference(BeanDefinitionValueResolver.java:367)
    ... 29 more

里边的异常堆栈是我们分析源码得好帮手,顺着堆栈一步步进去看看吧。

看到最开始新建上下文的时候,上下文主动刷新,代码逻辑进入了finishBeanFactoryInitialization(beanFactory),这个方法的作用是实例化所有的剩余的非懒加载的单例。

继续深究,我们看到进入了下边这段代码里。因为默认的scope是singleton的,所以会进入这段逻辑里去。这里的关键是getSingleton方法,这个方法的第二个入参是个函数ObjectFactory<?> singletonFactory,函数调用的createBean是个实例化Bean的动作。所有的bean实例化都会经过createBean这个方法,prototype范围的bean也不例外,区别就在于它不需要经过。getSingleton的逻辑。

// Create bean instance.
if (mbd.isSingleton()) {
    sharedInstance = getSingleton(beanName, () -> {
        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);
}

对于getSingleton,他其实最关键是做了三个事情,第一是通过beforeSingletonCreation(beanName)标注这个bean正在创建中,第二是通过第二个入参函数获得实例化后的bean singletonFactory.getObject(),第三是将步骤一的标注删除。

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 {
                    //调用工厂方法获得实例化后的bean
                    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;
        }
    }

着重留意这个标注单例bean正在创建的过程,其实就是在singletonsCurrentlyInCreation添加一个集合元素。这个动作在循环依赖检测中起到关键作用。

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

当这个bean被标注正在创建中后,就会进入创建阶段,我们深究下去,代码到了AbstractAutowireCapableBeanFactory#doCreateBean里,这里有个关键的动作:

if (instanceWrapper == null) {
    instanceWrapper = createBeanInstance(beanName, mbd, args);
}

展开createBeanInstance方法。我们看到最后,注意到构造BeanWrapper的时候,会根据bean的注入方式选择构造方法。像现在所调试的构造器注入方式,会去bean里找到相应的构造器来对bean进行实例化TestA,但因为TestA通过构造器注入了TestB,就会触发一个新的doGetBean动作,去实例化这个TestB。恩,正是这个地方会引发构造器循环依赖。后文会说到的setter注入,因为使用的是默认的构造函数,所以在这个地方不存在循环依赖的问题。

protected BeanWrapper createBeanInstance(String beanName, RootBeanDefinition mbd, @Nullable Object[] args) {
        // Make sure bean class is actually resolved at this point.
        Class<?> beanClass = resolveBeanClass(mbd, beanName);

        if (beanClass != null && !Modifier.isPublic(beanClass.getModifiers()) && !mbd.isNonPublicAccessAllowed()) {
            throw new BeanCreationException(mbd.getResourceDescription(), beanName,
                    "Bean class isn't public, and non-public access not allowed: " + beanClass.getName());
        }

        Supplier<?> instanceSupplier = mbd.getInstanceSupplier();
        if (instanceSupplier != null) {
            return obtainFromSupplier(instanceSupplier, beanName);
        }

        if (mbd.getFactoryMethodName() != null)  {
            return instantiateUsingFactoryMethod(beanName, mbd, args);
        }

        // Shortcut when re-creating the same bean...
        boolean resolved = false;
        boolean autowireNecessary = false;
        if (args == null) {
            synchronized (mbd.constructorArgumentLock) {
                if (mbd.resolvedConstructorOrFactoryMethod != null) {
                    resolved = true;
                    autowireNecessary = mbd.constructorArgumentsResolved;
                }
            }
        }
        if (resolved) {
            if (autowireNecessary) {
                return autowireConstructor(beanName, mbd, null, null);
            }
            else {
                return instantiateBean(beanName, mbd);
            }
        }

        // Need to determine the constructor...
        //根据入参情况(是否为构造器注入)选择对应的构造器来实例化这个bean
        Constructor<?>[] ctors = determineConstructorsFromBeanPostProcessors(beanClass, beanName);
        if (ctors != null ||
                mbd.getResolvedAutowireMode() == RootBeanDefinition.AUTOWIRE_CONSTRUCTOR ||
                mbd.hasConstructorArgumentValues() || !ObjectUtils.isEmpty(args))  {
            return autowireConstructor(beanName, mbd, ctors, args);
        }

        // No special handling: simply use no-arg constructor.
        //使用默认构造函数实例化这个bean
        return instantiateBean(beanName, mbd);
    }

OK,上文已经因为实例化TestA,然后触发了TestB的实例化。其实TestB的实例化过程和TestA是一模一样的,同样到了createBeanInstance也会尝试实例化TestA。好的,现在又到了实例化TestA的过程,我们来看看这一次有啥不一样。我们发现又回到了这里:

// Create bean instance.
if (mbd.isSingleton()) {
    sharedInstance = getSingleton(beanName, () -> {
        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);
}
protected void beforeSingletonCreation(String beanName) {
    if (!this.inCreationCheckExclusions.contains(beanName) && !this.singletonsCurrentlyInCreation.add(beanName)) {
        throw new BeanCurrentlyInCreationException(beanName);
    }
}

当再一次因为TestA来到这个beforeSingletonCreation的时候,会发现他已经处于单例创建中。那就糟了,直接抛异常终止了。

这就是构造器循环依赖注入的处理逻辑。

setter注入循环依赖

我们知道setter单例注入循环依赖是Spring框架允许的,为什么会有这个差异呢,我们先来了解DefaultSingletonBeanRegistry里的几个重要的缓存数据。

    /** Cache of singleton objects: bean name --> bean instance */
    /**已经实例化完毕的单例缓存**/
    private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);

    /** Cache of singleton factories: bean name --> ObjectFactory */
    /**单例工厂类缓存**/
    private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16);

    /** Cache of early singleton objects: bean name --> bean instance */
    /**提前曝光的单例实例对象缓存**/
    private final Map<String, Object> earlySingletonObjects = new HashMap<>(16);

    /** Names of beans that are currently in creation */
    /**正在创建中的单例名字集合**/
    private final Set<String> singletonsCurrentlyInCreation =
            Collections.newSetFromMap(new ConcurrentHashMap<>(16));

从上边我们可以大致推测,已经创建好的Bean存放在singletonObjects,正在创建中还没注入完毕的bean暂存在earlySingletonObjects,那些创建中的单例bean的工厂类放在singletonFactories,正在创建中的bean的名字放在singletonsCurrentlyInCreation;

下边我们看看这几个东西怎么发挥作用。

先看看这个常用的getSingleton方法,第二个入参是限定是否需要获取那些为了解决循环依赖而被提前曝光的半成品bean(部分属性还没注入,属性为null)。可以看到其实这个方法是一个三级缓存机制,如果有成品的bean在singletonObjects,直接返回;如果有提前曝光的bean在earlySingletonObjects,获取并返回;如果有半成品bean的工厂方法在singletonFactories,那就用工厂类生成实例,放进earlySingletonObjects,并且删除工厂方法.

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;
}

我们再回到AbstractAutowireCapableBeanFactory#doCreateBean,setter注入的循环依赖也会经过

if (instanceWrapper == null) {
    instanceWrapper = createBeanInstance(beanName, mbd, args);
}

上文我们说到,因为使用的是默认构造函数,不存在递归调用获取bean的问题,所以程序可以继续往下执行到:

// Eagerly cache singletons to be able to resolve circular references
// even when triggered by lifecycle interfaces like BeanFactoryAware.
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, () -> getEarlyBeanReference(beanName, mbd, bean));
}

这个地方很有意思,注解也写到提前缓存这个单例bean以解决循环依赖。这里通过addSingletonFactory提前暴露了这个bean的一个工厂方法,以及bean自身。这里要注意也暴露的bean本身,因为getEarlyBeanReference(beanName, mbd, bean)的第三个入参。

protected void addSingletonFactory(String beanName, ObjectFactory<?> singletonFactory) {
    Assert.notNull(singletonFactory, "Singleton factory must not be null");
    synchronized (this.singletonObjects) {
        if (!this.singletonObjects.containsKey(beanName)) {
            this.singletonFactories.put(beanName, singletonFactory);
            this.earlySingletonObjects.remove(beanName);
            this.registeredSingletons.add(beanName);
        }
    }
}

正式因为这个地方提前暴露了这个构造中的bean,当别的bean构造过程中依赖他的时候,能够获取到一个半成品的bean,并且在属性注入的时候,注入这个半成品。从而避免了循环引用。举个例子:经过createBeanInstance方法得到一个半成品TestA后,并且提前曝光TestA这个半成品,然后尝试通过populateBean对这个半成品进行属性TestB的注入,发现TestB还没创建,从而调用GetBean获取TestB的实例。接着TestB进入创建环节,同样是经过createBeanInstance方法得到一个半成品TestB,并提前曝光自己,然后尝试通过populateBean对这个半成品进行属性TestA的注入,找到一个创建中的半成品TestA,成功注入返回。返回后,TestA半成品得到TestB后也成功注入,得到全部返回。

这样子就成功解决了循环依赖的问题。

prototype范围的循环依赖

prototype范围的bean在创建时候的待遇不太一样,看最关键的代码AbstractBeanFactory#doGetBean:


if (mbd.isPrototype()) {
    // It's a prototype -> create a new instance.
    Object prototypeInstance = null;
    try {
        beforePrototypeCreation(beanName);
        prototypeInstance = createBean(beanName, mbd, args);
    }
    finally {
        afterPrototypeCreation(beanName);
    }
    bean = getObjectForBeanInstance(prototypeInstance, name, beanName, mbd);
}

这里边直接调用了createBean,而不像单例情况下,前后分别做标记的添加和删除。那样子上边说到的三级缓存就没有效果啦,因为第一个if判断条件不成立,直接返回了,只剩下一级缓存。而且创建前还做了一个特别的动作:

beforePrototypeCreation(beanName);

这其实也是一个打标记,也是用作循环检测的。这个标记在前文用到了,请看:

// Fail if we're already creating this bean instance:
// We're assumably within a circular reference.
if (isPrototypeCurrentlyInCreation(beanName)) {
    throw new BeanCurrentlyInCreationException(beanName);
}

这里看到检测出循环依赖就马上短路了。

测试demo请移步:https://github.com/duvalCC/SpringDemo

参考资料
1.Spring中循环引用的处理-1
2.Spring中循环引用的处理-2
3.Spring中循环引用的处理-3


转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。可以在下面评论区评论,也可以邮件至 duval1024@gmail.com

×

喜欢就点赞,疼爱就打赏