Spring源码-循环依赖问题1


Spring流程图


DefaultSingletonBeanRegistry

在这里插入图片描述

Spring中解决循环依赖问题

Spring循环依赖包括构造器循环依赖setter循环依赖

创建3个互相引用的类

public class TestA {
    
    private TestB testB;

     public TestA(TestB testB) {
        this.testB=testB;
    }
    public TestB getTestB() {
        return testB;
    }

    public void setTestB(TestB testB) {
        this.testB = testB;
    }
}


public class TestB {
    private TestC testC;

     public TestB(TestC testC) {
        this.testC=testC;
    }
    public TestC getTestC() {
        return testC;
    }

    public void setTestC(TestC testC) {
        this.testC = testC;
    }
}

public class TestC {
    private TestA testA;

    public TestC(TestA testA) {
        this.testA=testA;
    }
    public TestA getTestA() {
        return testA;
    }

    public void setTestC(TestA testA) {
        this.testA = testA;
    }

}

Spring循环依赖的3中情况

  1. 构造器循环引用:无法解决

    表示通过构造器注入构成循环依赖,这种依赖无法解决,只能抛出
    BeanCurrentlyInCreationException异常表示循环依赖
    
    Spring容器将每一个正在创建的bean标识符放在一个“当前创建bean池”中,bean标识符在创建过程中
    将一直保持这个状态,此时如果在创建bean的过程中发现自己已经在“当前创建bean池”里是时。将会抛
    出BeanCurrentlyInCreationException异常表示循环依赖,而对创建完成的bean将从“当前创建
    bean池”中清除掉
    

实例演示

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

在这里插入图片描述

  • Spring容器在创建“testA”bean,首先去“当前创建bean池”查找是否当前bean正在创建,如果没发现,则继续准备其需要的构造器参数”testB“,并将“testA”放到“当前创建bean池”中,去创建testB
  • 然后Spring容器在创建“testB”bean,首先去“当前创建bean池”查找是否当前bean正在创建,如果没发现,则继续准备其需要的构造器参数”testC“,并将“testB”放到“当前创建bean池”中,去创建testC。
  • 在然后Spring容器在创建“testC”bean,首先去“当前创建bean池”查找是否当前bean正在创建,如果没发现,则继续准备其需要的构造器参数”testA“,并将“testC”放到“当前创建bean池”中,去创建”testA“。
    -到此位置在此回到创建”testA“,发现该bean标识符在”当前创建bean池“中,因为循环依赖,抛出
    BeanCurrentlyInCreationException

2.setter循环依赖

表示通过setter注入方式构成的循环依赖。对于setter注入造成的依赖时通过Spring容器提前暴露刚完成
构造器注入但还未完成其他步骤(如setter注入)的bean来完成的,而是只能解决单例作用域的bean循环依赖
。通过提前暴露一个单例工厂方法,从而使其它bean能引用到该bean

这个方法将完成构造器调用的bean提前暴露封装成ObjectFactory到三级缓存中,之后可以直接三级缓存中通过传入beanName到ObjectFactory#getBean()中获取bean
在这里插入图片描述
在这里插入图片描述

  • Spring容器创建单例”testA“bean,首先根据无参构造器创建bean,并暴露一个”ObjectFactory“用于返回一个提前暴露一个正在创建中的bean,并将”testA“标识符放到”当前创建bean池“,然后进行setter注入”testB“。
  • Spring容器创建单例”testB“bean,首先根据无参构造器创建bean,并暴露一个”ObjectFactory“用于返回一个提前暴露一个正在创建中的bean,并将”testB“标识符放到”当前创建bean池“,然后进行setter注入”testC“。
  • Spring容器创建单例”testC“bean,首先根据无参构造器创建bean,并暴露一个”ObjectFactory“用于返回一个提前暴露一个正在创建中的bean,并将”testC“标识符放到”当前创建bean池“,然后进行setter注入”testA“,进行注入”testA“时由于提前暴露了”ObjectFactory“工厂,从而使用它返回提前暴露一个创建中的bean。
  • 最后在依赖注入”testB“,”testC“。
  1. 对于prototype范围的依赖处理

    对于”prototype“作用域bean,Spring容器无法完成依赖注入,因为Spring容器不进行缓存”prototype“
    作用域的bean,因此无法提前暴露一个正在创建中的bean。
    

对于”singleton“作用域bean,可以通过”setAllowCircularReferences(false);“来禁止循环引用

画图解决

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

三级缓存

在这里插入图片描述

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

debug测试

public class A {
    private B b;


    public void setB(B b) {
        this.b = b;
    }
}

public class B {
    private A a;


    public void setA(A a) {
        this.a = a;
    }
}

<bean id="a" class="com.lv.circulardependency.A">
        <property name="b" ref="b"></property>
    </bean>

    <bean id="b" class="com.lv.circulardependency.B">
        <property name="a " ref="a"></property>
    </bean>


        ApplicationContext applicationContext=new ClassPathXmlApplicationContext("cd.xml");

开始debug,进入实例化bean的方法

在这里插入图片描述
F7进入refresh(),一直f8,到下面方法,f7进入,这是实例化和初始化bean的方法。
在这里插入图片描述
进入后f8到**preInstantiateSingletons()**方法f7进入

在这里插入图片描述

循环每一个bean,根据bean的类型是否进入if条件

在这里插入图片描述

A对象没有实现FactoryBean,跳出循环

在这里插入图片描述

跳出循环后,调用getBean()和doGetBean()获取实例

f7进入
在这里插入图片描述
继续f7,执行doGetBean
在这里插入图片描述

检测缓存中有没有A对象,有则直接使用,没有就创建调用

f7进入
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

缓存中没有A对象,执行createBean()和doCreateBean()创建A的实例

在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

T getObject() {
		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;
			}
}

f7进入
在这里插入图片描述

T getObject() {
		try {
			//上面调用这个createBean
			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;
			}
}

f7进入
在这里插入图片描述

找到doCreateBean。

f7进入
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
通过反射创建bean的过程
在这里插入图片描述
在这里插入图片描述

已经获取到实例化的A了,将半成品A提前添加到三级缓存中,给其他对象注入使用

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

提前暴露到三级缓存中,供循环依赖使用后,可以开始初始化A实例了(populateBean和initializeBean)

1.populateBean属性赋值,给A实例中的B属性填充属性
在这里插入图片描述
在这里插入图片描述
f7进入,f8到这个位置
在这里插入图片描述
在这里插入图片描述
f7进入
在这里插入图片描述
f7进入

在工厂中解决对另一个bean的引用

在这里插入图片描述
f7进入发现,又到了doGetBean方法
在这里插入图片描述

去缓存获取A中属性B的实例,如果没有创建B的实例(现在还没有,所以创建)

同样先去缓存中获取B实例
在这里插入图片描述

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

缓存中没有B实例,创建B实例完成,初始化B对象之前,将B的半成品放入三级缓存

在这里插入图片描述
在这里插入图片描述
此时的三级缓存

在这里插入图片描述

给A中属性B的实例开始初始化

1.populateBean赋值
在这里插入图片描述

在这里插入图片描述

给A实例中的B属性赋值,这时候发现B属性中引用了A实例,则取缓存中先获取A实例将B属性初始化(如果没有则创建,此时三级缓存中有A实例)

同理也是个引用对象,这是B中的属性A实例。
在这里插入图片描述
f7进入
在这里插入图片描述
开始解析
在这里插入图片描述

去缓存中获取A实例
在这里插入图片描述
在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

意思就是

ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
singletonObject = singletonFactory.getObject();

//其中执行getObject相当于执行了getEarlyBeanReference方法
Object getObject(){
	Object exposedObject=this.getEarlyBeanReference(beanName, mbd, bean);
	return exposedObject;
}

在这里插入图片描述
在这里插入图片描述

在这里插入图片描述

获取到半成品A并将半成品A放入二级缓存后,开始初始化B实例,给B实例中的A属性赋值

在这里插入图片描述
解析RuntimeBeanReference在这里插入图片描述
在这里插入图片描述

去缓存中获取A的半成品实例,发现在二级缓存中有A实例的半成品,直接使用

在这里插入图片描述
在这里插入图片描述
f8
在这里插入图片描述
继续f8
在这里插入图片描述
继续f8
在这里插入图片描述
返回到初始化B完成的方法
在这里插入图片描述

将完成品B放入一级缓存,并删除二级和三级缓存中B的信息

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

获得完整的B对象后,继续初始化A,A实例已经可以在一级缓存中获取B,直接使用

在这里插入图片描述

将A的完整实例添加到一级缓存,并删除二级和三级缓存中数据

在这里插入图片描述

在这里插入图片描述

整个缓存的流程

首先在一级缓存中查找,然后二级缓存,最后三级缓存。
在这里插入图片描述

1.首先在缓存中获取A对象,发现没有,则实例化A,并将A未初始化的实例添加到三级缓存,开始初始化A,发现其中有B的引用,然后去缓存中获取B实例,没有,则开始实例化B,并将B未初始化的实例添加到三级缓存,开始初始化B实例,结果又发现B中有A的引用,则去缓存中获取A实例,此时三级缓存中有半成品A实例。

在这里插入图片描述
2.找到半成品A后,获取A的半成品,并将A的半成品放入二级缓存,然后删除三级缓存中的A。

在这里插入图片描述
在这里插入图片描述
3.然后,B实例可以在二级缓存拿到半成品A继续初始化,B初始化完成,将B的完成品加入一级缓存,并删除关于B的二级和三级缓存的信息

虽然三级缓存中有B的信息,但是还没有使用就被删除了
在这里插入图片描述
在这里插入图片描述

4.B实例化初始化完成后,A还是半成品,还未初始化完,继续初始化A实例。
①此时一级缓存中一级有了B的实例,A初始化可以直接使用
在这里插入图片描述

②初始化完成A后,将A加入一级缓存,并删除关于A的二级和三级缓存的信息。此时的A是成品,B中的A引用也变成了成品

在这里插入图片描述

清除二级缓存中A的属性。
在这里插入图片描述
到此为止,此时,只有一级缓存中有A和B的完整的实例

引出的问题

1.三级缓存解决循环依赖的关键什么,为什么可以通过提前暴露对象可以解决

关键: 实例化和初始化分开处理,在中间给其他对象赋值时并不是完整的对象,将未初始化(半成品)的对象暴露出去,给其他对象使用

半成品:完成实例化但是没有初始化

2.只使用一级缓存可以解决循环依赖。

不能,在整个处理过程中存在半成品和成品,如果只有一级缓存,那么半成品和成品都会放到一级缓存中,这时候获取对象时,会先从一级缓存中获取,因为成品和半成品的key相同的,可能取出半成品对象。因此要把半成品和成品的存放分开。

②既然,key相同,可以给key或value做标记来分辨半成品和成品吗?

不能,因为你遍历出来的半成品和成品key相同,你不知道哪个是半成品,哪个成品,所以无法加标记来分辨半成品和成品。而根据value标记,每次都要获取value的值在标记,明显很麻烦,不如直在定义一个缓存,来区分半成品和成品

3.只使用二级缓存,不使用三级缓存可以吗?

二级缓存和三级缓存的区别,三级调用了这个lamaba表达式
在这里插入图片描述
①我们可以发现,只有A对象调用了这个lamaba表达式生成了二级缓存中的半成品A,而B对象没有调用。

也就是当我们确保所有的bean都不调用这个表达式时,只使用二级缓存即可解决循环依赖。

②()->getEarlyBeanReference这个方法的作用是什么?
直译:获取早期的bean引用。


//获取用于早期访问指定bean的参考,*通常是为了解决循环参考。 * @param beanName bean的名称(用于错误处理)
protected Object getEarlyBeanReference(String beanName, RootBeanDefinition mbd, Object bean) {
		Object exposedObject = bean;
		if (!mbd.isSynthetic() && hasInstantiationAwareBeanPostProcessors()) {
			for (SmartInstantiationAwareBeanPostProcessor bp : getBeanPostProcessorCache().smartInstantiationAware) {
				exposedObject = bp.getEarlyBeanReference(exposedObject, beanName);
			}
		}
		return exposedObject;
	}
@Override
	public Object getEarlyBeanReference(Object bean, String beanName) {
		Object cacheKey = getCacheKey(bean.getClass(), beanName);
		this.earlyProxyReferences.put(cacheKey, bean);
		return wrapIfNecessary(bean, beanName, cacheKey);
	}

在这里插入图片描述

**
	 * Wrap the given bean if necessary, i.e. if it is eligible for being proxied.
	 * 必要时包装给定的bean,即是否有资格被代理
	 * @param bean the raw bean instance
	 * @param beanName the name of the bean
	 * @param cacheKey the cache key for metadata access
	 * @return a proxy wrapping the bean, or the raw bean instance as-is
	 */
	protected Object wrapIfNecessary(Object bean, String beanName, Object cacheKey) {
		if (StringUtils.hasLength(beanName) && this.targetSourcedBeans.contains(beanName)) {
			return bean;
		}
		if (Boolean.FALSE.equals(this.advisedBeans.get(cacheKey))) {
			return bean;
		}
		if (isInfrastructureClass(bean.getClass()) || shouldSkip(bean.getClass(), beanName)) {
			this.advisedBeans.put(cacheKey, Boolean.FALSE);
			return bean;
		}

		// Create proxy if we have advice.
		Object[] specificInterceptors = getAdvicesAndAdvisorsForBean(bean.getClass(), beanName, null);
		if (specificInterceptors != DO_NOT_PROXY) {
			this.advisedBeans.put(cacheKey, Boolean.TRUE);
			Object proxy = createProxy(
					bean.getClass(), beanName, specificInterceptors, new SingletonTargetSource(bean));
			this.proxyTypes.put(cacheKey, proxy.getClass());
			return proxy;
		}

		this.advisedBeans.put(cacheKey, Boolean.FALSE);
		return bean;
	}

所以使用三级缓存本质,解决aop代理问题。

4.如果某个bean对象代理对象,那么会不会创建普通对象?

在这里插入图片描述
所以提前创建这个对象,需要代理则用代理对象覆盖这个对象,不进行代理则使用原对象。

5 为什么使用三级缓存就可以解决aop代理的问题

当一个对象需要被代理的时候,在整个创建过程中时包含两个对象的,一个普通对象,一个代理对象,bean默认是单例的,那么整个生命周期的处理环节中,一个beanName能对应两个bean对象吗?,既然不能的吗,保证我们在使用的时候加一层判断,是否需要代理。

知道什么时候使用代理对象?

不知道什么时候回调,所以我们通过匿名内部类的形式,在使用的时候直接对普通对象覆盖,保证全局唯一性!

三级缓存都存什么

第一级缓存存的是对外暴露的对象,也就是我们应用需要用到的
第二级缓存的作用是为了处理循环依赖的对象创建问题,里面存的是半成品对象或半成品对象的代理对象
第三级缓存的作用处理存在 AOP + 循环依赖的对象创建问题,能将代理对象提前创建

  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值