zoukankan      html  css  js  c++  java
  • Spring中怎么解决循环依赖?

    前文

    在写Spring之getBean
    的时候提到过在这个过程中要解决循环依赖。

    什么是循环依赖?

    A类依赖B类,B类依赖A类。 这就是循环依赖。
    如下就是一段在Spring中会造成循环依赖的代码

    @Component
    public class A {
        private B b;
    
        @Autowired
        public A(B b) {
            this.b = b;
        }
    }
    @Component
    public class B {
        private A a;
        @Autowired
        public B(A a) {
            this.a = a;
        }
    }
    

    启动后会报错:error creating bean with name 'a': Requested bean is currently in creation: Is there an unresolvable circular reference?

    哪儿在抛错?

    跟踪源码查看一下为何会报错。

    Spring之getBean
    知道了启动的时候会去创建所有非lazy的单实例bean

    主要跟踪getSingleton:
    1.没有已经加载好的实例,那么准备创建了。
    2.创建前先使用beforeSingletonCreation(beanName);去检查是否【正在创建】,抛出循环依赖的异常。

    	public Object getSingleton(String beanName, ObjectFactory<?> singletonFactory) {
    		Assert.notNull(beanName, "Bean name must not be null");
    		synchronized (this.singletonObjects) {
    		//关注:从【一级缓存】中拿【已经加载好的】bean实例
    			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 {
    					//关注:创建实例过程--调用createBean
    					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;
    		}
    	}
    

    但是为何会抛错呢?

    debug跟一下。 发现在第getBean(“a”)的时候,会有如下步骤:
    1.getBean(“a”)。
    2.标记A【正在创建】。
    3.得到A的构造函数,尝试去创建A的实例。
    4.在创建A的实例之前,要得到构造函数的参数B的实例。
    5.getBean(“b”)。
    6.标记B【正在创建】。
    7.重复步骤2-3. 不过此时是得到B的构造函数,并尝试获取A的实例。
    8.getBean(“a”) 。ps:注意,这里是第二次getBean(“a”)了。
    9.再次标记A【正在创建】中。—标记失败,抛出异常。

    具体过程参考下图:
    ps:网图改了一下
    在这里插入图片描述
    由此,知道为何构造注入会引起循环依赖的时候抛错。
    如果对这个注入过程不熟悉的,可以参考我之前的Spring之getBean

    那么如何避免抛错呢?

    1.允许循环依赖(默认是允许的)。①
    2.使用非构造注入,如@Autowired写在成员变量或者setter方法上。
    3.要有无参构造函数,或者是不包含被依赖的成员变量的构造函数(避免在一开始注入的时候就需要一个拥有完整成员变量的B)。

    例如之前的例子:

    开始A实例的创建
    1.getBean(“a”)->标记A【正在创建中】。
    2.创建A的实例的时候,用无参构造创建一个A的实例a。
    3.将半成品(无成员变量)a放到三级缓存中。②
    4.populate的时候,尝试注入成员变量b。


    开始B的实例创建

      5.getBean(“b”)创建一个B的实例>标记B【正在创建中】
      6.B通过createBeanInstance创建实例后,
      7.将半成品(无成员变量)b放到三级缓存中。
      8. populate去注入成员变量a.
      9.getBean(“a”)->getSingleton(“a”, true)->从一级缓存(singletonObjects)中取->取不到则从二级缓存(earlySingletonObjects)中取->取不到则从三级缓存(singletonFactories)中取->从三级缓存中取出ObjectFactory得到A的实例a,将a放入二级缓存,并将ObjectFactory从三级缓存中移出。③
      10.得到了a的实例(半成品),注入到b中。继续走完B创建。
    b实例的创建完成


    继续A实例的创建
    11.将完成品B的实例b,注入到A中。 继续走完A创建。


    非构造注入整个流程
    ps:偷来的图
    在这里插入图片描述

    ①不允许循环依赖当然要报错
    在这里插入图片描述

    ②将半成品(无成员变量)a放到三级缓存中

    将半成品(无成员变量)a放到三级缓存中在这里插入图片描述

    ③从三级缓存中取出ObjectFactory得到A的实例a,将a放入二级缓存,并将ObjectFactory从三级缓存中移出

    在这里插入图片描述

    为何是三级缓存?

    个人认为,第三级缓存的作用, 是留了一个钩子,方便【获取半成品实例】的时候做一些定制。
    如下源码:实现SmartInstantiationAwareBeanPostProcessor#getEarlyBeanReference可以完成该定制。

    	protected Object getEarlyBeanReference(String beanName, RootBeanDefinition mbd, Object bean) {
    		Object exposedObject = bean;
    		if (!mbd.isSynthetic() && hasInstantiationAwareBeanPostProcessors()) {
    			for (BeanPostProcessor bp : getBeanPostProcessors()) {
    				if (bp instanceof SmartInstantiationAwareBeanPostProcessor) {
    					SmartInstantiationAwareBeanPostProcessor ibp = (SmartInstantiationAwareBeanPostProcessor) bp;
    					exposedObject = ibp.getEarlyBeanReference(exposedObject, beanName);
    				}
    			}
    		}
    		return exposedObject;
    	}
    

    参考资料

    https://blog.csdn.net/f641385712/article/details/92801300
    https://blog.csdn.net/weixin_42228338/article/details/97163101

  • 相关阅读:
    Shiro 登录、退出、校验是否登录涉及到的Session和Cookie
    Apache Tomcat 8.0 官方文档
    FastDFS分布式文件系统(主备Tracker、主备Storage)
    PHP文件系统
    PHP 文件包含
    PHP函数
    PHP 全局变量
    PHP7新增知识点
    PHP数据
    PHP常量
  • 原文地址:https://www.cnblogs.com/thewindkee/p/12873099.html
Copyright © 2011-2022 走看看