• Spring 源码分析(四)——Spring 如何解决循环依赖


    Spring 如何解决循环依赖这是一个非常经典的面试问题,那么 Spring 是如何解决循环依赖问题的呢?又是否能够让其解决循环依赖的方法失效呢?

    一、JAVA 原生的循环依赖

    JAVA 原生中遇到循环依赖时可以通过如下步骤解决。

    实例化 A 对象

    实例化 B 对象

    往 A 对象中设置 B 对象

    往 B 对象中设置 A 对象

    但是有另外一种特殊情况,A 的构造方法参数中包含了 B,B 的构造方法参数中包含了 A,这种情况称为构造方法循环依赖。由于 A 和 B 都需要在实例化对象时提供参数,所以这种循环依赖是无解的。

    二、Spring 中的循环依赖

    如上所述,Spring 也是无法解决构造方法循环依赖的,但是属性循环依赖在实际使用中我们可以看到 Spring 是可以解决的。

    Spring 的解决流程与我们上述的步骤一致:

    getBean——取得 A Bean,在 doCreateBean 方法中开始创建 Bean 操作。

    createBeanInstance——实例化 A Bean。

    populateBean——为 A Bean 设置参数,并调用 getBean 方法创建 B Bean。

    == createBeanInstance——实例化 B Bean。

    == populateBean——为 B Bean 设置参数,并调用 getBean 方法获得未构造完全的 A Bean。

    经过以上流程,Spring 就解决了 Bean 的循环依赖,这里面涉及到一个比较关键的方法 getSingleton(String beanName, boolean allowEarlyReference)

    protected Object getSingleton(String beanName, boolean allowEarlyReference) {
        // Quick check for existing instance without full singleton lock
        Object singletonObject = this.singletonObjects.get(beanName);
        if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
            singletonObject = this.earlySingletonObjects.get(beanName);
            if (singletonObject == null && allowEarlyReference) {
                synchronized (this.singletonObjects) {
                    // Consistent creation of early reference within full singleton lock
                    singletonObject = this.singletonObjects.get(beanName);
                    if (singletonObject == null) {
                        singletonObject = this.earlySingletonObjects.get(beanName);
                        if (singletonObject == null) {
                            ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
                            if (singletonFactory != null) {
                                singletonObject = singletonFactory.getObject();
                                this.earlySingletonObjects.put(beanName, singletonObject);
                                this.singletonFactories.remove(beanName);
                            }
                        }
                    }
                }
            }
        }
        return singletonObject;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25

    以上代码涉及到了三级缓存的概念,singletonObjects 是第一级缓存,存储了创建好的 Bean;earlySingletonObjects 是第二级缓存,存储了未初始化完成的 Bean;singletonFactories 是第三级缓存,存储了 Bean 工厂。

    要解决循环依赖问题,就需要提前暴露未完成属性设值注入的未完成 Bean,否则就无法解决循环依赖。而从上述代码可知,allowEarlyReference 参数是决定是否提前暴露未完成 Bean 的关键参数。

    从上述代码也可知,其实只需要二级缓存 earlySingletonObjects 就可以解决循环依赖问题,并不需要用到第三级缓存。

    其实三级缓存是为了解决一个特殊场景的应用的,那就是在 AOP 代理情况下的循环依赖,详细内容在下文继续介绍。

    三、循环依赖失效

    默认的 Bean 工厂 allowEarlyReference 参数默认是 true,所以可以提前暴露未完成 Bean,进而可以解决循环依赖问题。

    public Object getSingleton(String beanName) {
    	return getSingleton(beanName, true);
    }
    
    • 1
    • 2
    • 3

    如果我们复写这个 Bean 工厂,将 allowEarlyReference 修改为 false,那么 Spring 就无法解决循环依赖问题了。

    AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(new DefaultListableBeanFactory(){
        @Override
        public Object getSingleton(String beanName) {
            return super.getSingleton(beanName, false);
        }
    });
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    还有一种更简单的方法,其实 SpringBeanFactory 提供了关闭循环依赖的接口。

    setAllowCircularReferences 设置为 false 之后,Spring 不会往三级缓存中注入 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.isTraceEnabled()) {
        logger.trace("Eagerly caching bean '" + beanName +
          "' to allow for resolving potential circular references");
      }
      addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    配置方法如下:

    DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();
    beanFactory.setAllowCircularReferences(false);
    AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(beanFactory);
    
    • 1
    • 2
    • 3

    以上修改都将导致循环依赖抛出异常:

    Exception in thread "main" org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'oneBean': Injection of resource dependencies failed; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'twoBean': Injection of resource dependencies failed; nested exception is org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name 'oneBean': Requested bean is currently in creation: Is there an unresolvable circular reference?
    	at org.springframework.context.annotation.CommonAnnotationBeanPostProcessor.postProcessProperties(CommonAnnotationBeanPostProcessor.java:321)
    	at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.populateBean(AbstractAutowireCapableBeanFactory.java:1425)
    	at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:593)
    	at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:516)
    	at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:324)
    	at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:234)
    	at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:322)
    	at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:227)
    	at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveNamedBean(DefaultListableBeanFactory.java:1175)
    	at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveBean(DefaultListableBeanFactory.java:420)
    	at org.springframework.beans.factory.support.DefaultListableBeanFactory.getBean(DefaultListableBeanFactory.java:349)
    	at org.springframework.beans.factory.support.DefaultListableBeanFactory.getBean(DefaultListableBeanFactory.java:342)
    	at org.springframework.context.support.AbstractApplicationContext.getBean(AbstractApplicationContext.java:1127)
    	at com.nineya.spring.bean.cycle.BeanCycleMain.run(BeanCycleMain.java:16)
    	at com.nineya.spring.bean.cycle.BeanCycleMain.main(BeanCycleMain.java:31)
    Caused by: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'twoBean': Injection of resource dependencies failed; nested exception is org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name 'oneBean': Requested bean is currently in creation: Is there an unresolvable circular reference?
    	at org.springframework.context.annotation.CommonAnnotationBeanPostProcessor.postProcessProperties(CommonAnnotationBeanPostProcessor.java:321)
    	at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.populateBean(AbstractAutowireCapableBeanFactory.java:1425)
        ......
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
  • 相关阅读:
    软件测试必备的Linux基础知识
    第7章 C语言的函数指针数组 (四)
    用flask框架flask-sock和websocket创建一个自己的聊天界面
    前缀和学习笔记
    【力扣hot100】day1
    从头开始训练神经网络(Unet)
    VSCode 远程调试C++程序打开/dev/tty设备失败的问题记录
    SpringBoot整合Redis
    HTML5+CSS实现图片3D旋转效果,附音乐
    JavaScript includes() 方法的作用
  • 原文地址:https://blog.csdn.net/nineya_com/article/details/132912982