Spring bean的循环依赖问题

1. 什么是循环依赖?

譬如,A对象依赖B对象,B对象依赖A对象

public class A{
	//依赖B
	private B b;
}

public class B{
	//依赖A
	private A a;
}

如果不考虑spring,循环依赖并不是问题,对象之间相互依赖是很正常的事情

 A a = new A();
 B b = new B();

但是在Spring中循环依赖就是一个问题,在Spring中,一个对象并不是new出来这么简单,而是会经过一系列的Bean生命周期,就是因为bean的生命周期所有才会出现循环依赖问题。要想明白Spring的循环依赖问题,首先要知道Spring Bean的生命周期。

2. Spring Bean的生命周期

2.1. 生命周期的回顾

大家可以看SpringBean的生命周期

2.2. Bean的生成步骤

被Spring管理的对象叫做bean,bean的生成步骤如下:

  1. Spring扫描指定路径下的配置文件或者配置类得到BeanDefinition
  2. 根据得到的BeanDefinition生成bean,这个bean只是被实例化了,并没有属性赋值
  3. 填充bean对象的属性,也就是依赖注入
  4. 如果bean对象的某一个方法被AOP了,那么需要根据bean对象生成一个代理对象
  5. 把最终生成的代理对象放入到单例池中(源码叫singletonObjects),下次getBean时就直接从单例池中获取

Spring Bean的生成过程步骤很多,上面只是写了一部分,各种后置处理器就不写出来了,不是本文的重要内容。

从上面的步骤中,可以看到Spring需要给对象中的属性进行依赖注入,这个注入过程是啥样的呢?

比如上文中的A类, A类中存在一个B类的b属性,所以当A类生成一个bean对象之后,就会给b属性去赋值,此时会根据b属性的类型和属性名去BeanFactory中去获取B类所对应的单例bean。

  1. 如果此时BeanFactory中存在B对应的bean,那么直接拿来赋值给b属性
  2. 如果此时BeanFactory中不存在B对应的bean,那么需要生成一个B对应的bean,然后赋值给b属性

在第二种的情况下,问题就出现了,如果此时BeanFactory中还没有生成对应的bean,那么就需要去生成,就会经历B的bean生命周期。

那么在创建B类的bean的过程中,如果B类中依赖一个A类的a属性,那么在创建B的bean的过程中就需要A类对应的bean,但是触发B类的

bean创建的条件是A类bean在创建过程中的依赖注入,所以这里就出现了循环依赖;

A Bean创建–>依赖了 B 属性–>触发 B Bean创建—>B 依赖了 A 属性—>需要 A Bean(但A Bean还在创建过程中)

从而导致A 的bean创建不出来,B的bean也创建不出来。

这是循环依赖的场景,但是Spring通过某种机制解决了部分循环依赖的问题,这个机制就是三级缓存

3. 三级缓存

  • 一级缓存为:singletonObjects;
  • 二级缓存为:earlySingletonObjects;
  • 三级缓存为:singletonFactories;
/** Cache of singleton objects: bean name –> bean instance */
private final Map singletonObjects = new ConcurrentHashMap(256);

/** Cache of singleton factories: bean name –> ObjectFactory */
private final Map> singletonFactories = new HashMap>(16);

/** Cache of early singleton objects: bean name –> bean instance */
private final Map earlySingletonObjects = new HashMap(16);
3.1. 三个缓存分别有什么作用
  1. singletonObjects:缓存的是已经经历了完整生命周期的bean对象
  2. earlySingletonObjects:比singletonObjects多了一个early,表示缓存的是早期的bean对象,早期指的是生命周期还没走完就把这个bean放入到了earlySingletonObjects中。
  3. singletonFactories:缓存的是ObjectFactory,表示对象工厂,用来创建某个对象。

4. 思路分析

上面分析到,之所以产生依赖,是因为A创建时,需要B去创建,而B创建时需要A,从而产生了依赖。

为了打破这个循环,Spring决定加个缓存。

A的bean在创建过程中,在进行依赖注入之前,先把A的原始bean放入缓存(提早暴露,放到缓存中,其他bean需要时直接从缓存中拿),放入

缓存后,再进行依赖注入,此时A的bean依赖了B的bean。

如果B的bean不存在,则需要创建B的bean,而创建B的bean的过程和A的一样,先是创建一个B的原始bean,放入到缓存中,然后B的bean

进行依赖注入A,此时能从缓冲中拿到A的原始bean,B的原始bean依赖注入完成之后,B的生命周期结束,那么A的生命周期也结束。

4.1. 为什么需要三级缓存?

从上面的分析过程可以看出,只需要一个缓存好像就能解决循环依赖的问题了,为什么Spring中还需要三级缓存呢?

联想一个场景:

如果A的原始对象注入给B的属性之后,A的原始对象进行了AOP产生了一个代理对象,此时就会出现,对于A而言,它的bean对象其实应该

是AOP之后的代理对象,而B的a属性对应的并不是AOP之后的代理对象,这就产生了冲突。

B依赖的A和最终的A不是同一个对象。

那么这个问题怎么解决呢?

这个问题可以说是没有办法解决的,因为在一个bean的生命周期最后,Spring会提供BeanPostProcessor可以对bean进行加工,这个加工不

仅仅只是能修改bean的属性值,也可以替换掉当前bean。

而BeanPostProcessor的执行在bean的生命周期中是出于属性注入之后的,循环依赖是发生在属性注入过程中的,所以很有可能导致注入

给B对象的A对象和经历过完整生命周期之后的A对象不是一个对象。

所以这种情况下的循环依赖,Spring是解决不了的,因为在属性注入时,Spring也不知道A对象后会经历过哪些BeanPostProcessor以及会对

A对象做什么处理。

5. Spring解决了哪种情况下的循环依赖

AOP就是通过一个BeanPostProcessor来实现的,在Spring中AOP利用的要么是JDK动态代理,要么是CGLib的动态代理,所以如果给一个

类中的某个方法设置了切面,那么这个类最终就需要生成一个代理对象。

一般的过程就是:A类->生成一个bean->属性注入->基于切面生成一个代理对象->把代理对象放入singltonObjects单例池中。

而 AOP 可以说是 Spring 中除开 IOC 的另外一大功能,而循环依赖又是属于 IOC 范畴的,所以这两大功能想要并存,Spring 需要特殊处理。

如何处理的,就是利用了第三级缓存singletonFactories。

首先,singletonFactories中存的是某个beanName对应的ObjectFactory,在bean的生命周期中,生成完原始对象之后,就会构造一个ObjectFactory存入singletonFactories中。

5.1. ObjectFactory

这个ObjectFactory是一个函数式接口,支持Lambda表达式:() ->getEarlyBeanReference(beanName, mbd, bean)

执行该lambda表达式会执行getEarlyBeanReference方法:

在这里插入图片描述
该方法会执行SmartInstantiationAwareBeanPostProcessor接口中的getEarlyBeanReference方法,而这个接口中的实现类只有两个类,一个是AbstractAutoProxyCreator,一个是InstantiationAwareBeanPostProcessorAdapter

AbstractAutoProxyCreator

	@Override
	public Object getEarlyBeanReference(Object bean, String beanName) throws BeansException {
		Object cacheKey = getCacheKey(bean.getClass(), beanName);
		if (!this.earlyProxyReferences.contains(cacheKey)) {
			this.earlyProxyReferences.add(cacheKey);
		}
		return wrapIfNecessary(bean, beanName, cacheKey);
	}

InstantiationAwareBeanPostProcessorAdapter

	@Override
	public Object getEarlyBeanReference(Object bean, String beanName) throws BeansException {
		return bean;
	}

可以看到,默认只有AbstractAutoProxyCreator真正意义上实现了getEarlyBeanReference方法,而该类就是用来进行AOP的。

5.2. getEarlyBeanReference() 方法
	@Override
	public Object getEarlyBeanReference(Object bean, String beanName) throws BeansException {
		Object cacheKey = getCacheKey(bean.getClass(), beanName);
		if (!this.earlyProxyReferences.contains(cacheKey)) {
			//	cachekey就是beanName
			this.earlyProxyReferences.add(cacheKey);
		}
		return wrapIfNecessary(bean, beanName, cacheKey);
	}

首先得到一个cachekey,cachekey就是beanName。然后把beanName和bean(这是原始对象)存入 earlyProxyReferences 中。调用 wrapIfNecessary 进行AOP,得到一个代理对象。

那么什么时候会调用 getEarlyBeanReference 方法呢?

在这里插入图片描述

图中的 ObjectFactory 就是上文说的 labmda 表达式,中间有 getEarlyBeanReference 方法。

注意存入 singletonFactories 时并不会执行 lambda 表达式,也就是不会执行getEarlyBeanReference 方法。

从 singletonFactories 根据 beanName 得到一个 ObjectFactory ,然后执行 ObjectFactory ,也就是执行 getEarlyBeanReference 方法,此时会得到一个 A 原始对象经过 AOP 之后的代理对象,然后把该代理对象放入 earlySingletonObjects 中。

此时并没有把代理对象放入 singletonObjects 中,那什么时候放入到 singletonObjects 中呢?

此时,我们只得到了 A 原始对象的代理对象,这个对象还不完整,因为 A 原始对象还没有进行属性填充,所以此时不能直接把A的代理对象放入 singletonObjects 中,所以只能把代理对象放入earlySingletonObjects 。

假设现在有其他对象依赖了 A,那么则可以从 earlySingletonObjects 中得到 A 原始对象的代理对象了,并且是A的同一个代理对象。

当 B 创建完了之后,A 继续进行生命周期,而 A 在完成属性注入后,会按照它本身的逻辑去进行AOP,而此时我们知道 A 原始对象已经经历过了 AOP ,所以对于 A 本身而言,不会再去进行 AOP了,那么怎么判断一个对象是否经历过了 AOP 呢?

会利用上文提到的 earlyProxyReferences,在 AbstractAutoProxyCreator 的 postProcessAfterInitialization 方法中,会去判断当前

beanName 是否在 earlyProxyReferences,如果在则表示已经提前进行过 AOP了,无需再次进行 AOP。

对于 A 而言,进行了 AOP 的判断后,以及 BeanPostProcessor 的执行之后,就需要把 A 对应的对象放入 singletonObjects 中了,但是我们知道,应该是要 A 的代理对象放入 singletonObjects 中,所以此时需要从 earlySingletonObjects 中得到代理对象,然后入 singletonObjects 中。

6. 总结

A对象实例化之后,在属性注入之前,会将A对象放入到三级缓存中,key是beanName,value是ObjectFactory,等到A对象属性注入时,发

现依赖B,又去实例化B,B在属性注入时需要去获取A对象,这里就从三级缓存拿出ObjectFactory,从ObjectFactory等到对应的bean(就是

对象A),把三级缓存的A删除,放到二级缓存中,二级缓存中存的key是beanName,value是bean(这时候的bean还不是完整的,没有属性注

入),等到实例化完成之后,就把二级缓存给删除,放到一级缓存中,我们自己去getBean的时候,实际上拿到的是一级缓存的。

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值