前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >第04天 Spring是如何解决循环依赖的

第04天 Spring是如何解决循环依赖的

作者头像
程序员Leo
发布2023-08-16 08:30:16
2770
发布2023-08-16 08:30:16
举报
文章被收录于专栏:Java知识点Java知识点

# 1. 什么是循环依赖

就是 A 对象依赖了 B 对象,B 对象依赖了 A 对象。

说白是一个或多个对象实例之间存在直接或间接的依赖关系,这种依赖关系构成了构成一个环形调用。

代码语言:javascript
复制
// A依赖了B
class A{
	public B b;
}

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

那么循环依赖是个问题吗?

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

比如

代码语言:javascript
复制
A a = new A();
B b = new B();

a.b = b;
b.a = a;

这样,A,B 就依赖上了。

但是,在 Spring 中循环依赖就是一个问题了,为什么?

因为,在 Spring 中,一个对象并不是简单 new 出来了,而是会经过一系列的 Bean 的生命周期,就是因为 Bean 的生命周期所以才会出现循环依赖问题。当然,在 Spring 中,出现循环依赖的场景很多,有的场景 Spring 自动帮我们解决了,而有的场景则需要程序员来解决,下文详细来说。

要明白 Spring 中的循环依赖,得先明白 Spring 中 Bean 的生命周期

# 2. Bean 的生命周期

Bean 的生命周期指的就是:在 Spring 中,Bean 是如何生成的?

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

  1. Spring 扫描 class 得到 BeanDefinition
  2. 根据得到的 BeanDefinition 去生成 bean
  3. 首先根据 class 推断构造方法
  4. 根据推断出来的构造方法,反射,得到一个对象(暂时叫做原始对象)
  5. 填充原始对象中的属性(依赖注入)
  6. 如果原始对象中的某个方法被 AOP 了,那么则需要根据原始对象生成一个代理对象
  7. 把最终生成的代理对象放入单例池(源码中叫做 singletonObjects)中,下次 getBean 时就直接从单例池拿即可

可以看到,对于 Spring 中的 Bean 的生成过程,步骤还是很多的,并且不仅仅只有上面的 7 步,还有很多很多,比如 Aware 回调、初始化等等,这里不详细讨论。

可以发现,在 Spring 中,构造一个 Bean,包括了 new 这个步骤(第 4 步构造方法反射)。得到一个原始对象后,Spring 需要给对象中的属性进行依赖注入,那么这个注入过程是怎样的?

比如上文说的 A 类,A 类中存在一个 B 类的 b 属性,所以,当 A 类生成了一个原始对象之后,就会去给 b 属性去赋值,此时就会根据 b 属性的类型和属性名去 BeanFactory 中去获取 B 类所对应的单例 bean。如果此时 BeanFactory 中存在 B 对应的 Bean,那么直接拿来赋值给 b 属性;如果此时 BeanFactory 中不存在 B 对应的 Bean,则需要生成一个 B 对应的 Bean,然后赋值给 b 属性。

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

那么在创建 B 类的 Bean 的过程中,如果 B 类中存在一个 A 类的 a 属性,那么在创建 B 的 Bean 的过程中就需要 A 类对应的 Bean,但是,触发 B 类 Bean 的创建的条件是 A 类 Bean 在创建过程中的依赖注入,所以这里就出现了循环依赖:

ABean 创建–> 依赖了 B 属性–> 触发 BBean 创建 —>B 依赖了 A 属性 —> 需要 ABean(但 ABean 还在创建过程中)从而导致 ABean 创建不出来,BBean 也创建不出来。这是循环依赖的场景,但是上文说了,在 Spring 中,通过某些机制帮开发者解决了部分循环依赖的问题,这个机制就是三级缓存

# 3. 三级缓存

三级缓存是通用的叫法。

一级缓存为:singletonObjects 二级缓存为:earlySingletonObjects 三级缓存为:singletonFactories

先稍微解释一下这三个缓存的作用:

singletonObjects 中缓存的是已经经历了完整生命周期的 bean 对象。

earlySingletonObjects 比 singletonObjects 多了一个 early,表示缓存的是早期的 bean 对象。早期是什么意思?表示 Bean 的生命周期还没走完就把这个 Bean 放入 earlySingletonObjects。

singletonFactories 中缓存的是 ObjectFactory,表示对象工厂,用来创建某个对象的。

# 4. 分析

先来分析为什么缓存能解决循环依赖。

上文分析得到,之所以产生循环依赖的问题,主要是:

A 创建时 —> 需要 B---->B 去创建 —> 需要 A,从而产生了循环

image-20230811100835778
image-20230811100835778

下面用一张图告诉你,spring 是如何解决循环依赖的:

image-20230811095252357
image-20230811095252357

A 的 Bean 在创建过程中,在进行依赖注入之前,先把 A 的原始 Bean 放入缓存(提早暴露,只要放到缓存了,其他 Bean 需要时就可以从缓存中拿了),放入缓存后,再进行依赖注入,此时 A 的 Bean 依赖了 B 的 Bean,如果 B 的 Bean 不存在,则需要创建 B 的 Bean,而创建 B 的 Bean 的过程和 A 一样,也是先创建一个 B 的原始对象,然后把 B 的原始对象提早暴露出来放入缓存中,然后在对 B 的原始对象进行依赖注入 A,此时能从缓存中拿到 A 的原始对象(虽然是 A 的原始对象,还不是最终的 Bean),B 的原始对象依赖注入完了之后,B 的生命周期结束,那么 A 的生命周期也能结束。

因为整个过程中,都只有一个 A 原始对象,所以对于 B 而言,就算在属性注入时,注入的是 A 原始对象,也没有关系,因为 A 原始对象在后续的生命周期中在堆中没有发生变化。

从上面这个分析过程中可以得出,只需要一个缓存就能解决循环依赖了,那么为什么 Spring 中还需要 singletonFactories 呢?

这是难点,基于上面的场景想一个问题:如果 A 的原始对象注入给 B 的属性之后,A 的原始对象进行了 AOP 产生了一个代理对象,此时就会出现,对于 A 而言,它的 Bean 对象其实应该是 AOP 之后的代理对象,而 B 的 a 属性对应的并不是 AOP 之后的代理对象,这就产生了冲突。

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

**

那么如何解决这个问题?这个问题可以说没有办法解决。

因为在一个 Bean 的生命周期最后,Spring 提供了 BeanPostProcessor 可以去对 Bean 进行加工,这个加工不仅仅只是能修改 Bean 的属性值,也可以替换掉当前 Bean。

举个例子:

代码语言:javascript
复制
@Component
public class User {
}

@Component
public class TestBeanPostProcessor implements BeanPostProcessor {

	@Override
	public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {

        // 生成了一个新的User对象
		if (beanName.equals("user")) {
			System.out.println(bean);
			User user = new User();
			return user;
		}

		return bean;
	}
}

public class Test {
	public static void main(String[] args) {

		AnnotationConfigApplicationContext context =
				new AnnotationConfigApplicationContext(AppConfig.class);
		
		User user = context.getBean("user", User.class);
		System.out.println(user);

	}
}

运行 main 方法,得到的打印如下:

代码语言:javascript
复制
com.test.service.User@67gh4rt3
com.test.service.User@56tr75b3

所以在 BeanPostProcessor 中可以完全替换掉某个 beanName 对应的 bean 对象。

而 BeanPostProcessor 的执行在 Bean 的生命周期中是处于属性注入之后的,循环依赖是发生在属性注入过程中的,所以很有可能导致,注入给 B 对象的 A 对象和经历过完整生命周期之后的 A 对象,不是一个对象。这就是有问题的。

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

# 5. Spring 解决了哪种循环依赖

虽然上面的情况可能发生,但是肯定发生得很少,我们通常在开发过程中,不会这样去做,但是,某个 beanName 对应的最终对象和原始对象不是一个对象却会经常出现,这就是 AOP。

AOP 就是通过一个 BeanPostProcessor 来实现的,这个 BeanPostProcessor 就是 AnnotationAwareAspectJAutoProxyCreator,它的父类是 AbstractAutoProxyCreator,而在 Spring 中 AOP 利用的要么是 JDK 动态代理,要么 CGLib 的动态代理,所以如果给一个类中的某个方法设置了切面,那么这个类最终就需要生成一个代理对象。

一般过程就是:A 类 —> 生成一个普通对象–> 属性注入–> 基于切面生成一个代理对象–> 把代理对象放入 singletonObjects 单例池中。

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

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

首先,singletonFactories 中存的是某个 beanName 对应的 ObjectFactory,在 bean 的生命周期中,生成完原始对象之后,就会构造一个 ObjectFactory 存入 singletonFactories 中。这个 ObjectFactory 是一个函数式接口,所以支持 Lambda 表达式:() -> getEarlyBeanReference(beanName, mbd, bean)

上面的 Lambda 表达式就是一个 ObjectFactory,执行该 Lambda 表达式就会去执行 getEarlyBeanReference 方法,而该方法如下:

代码语言:javascript
复制
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;
}

该方法会去执行 SmartInstantiationAwareBeanPostProcessor 中的 getEarlyBeanReference 方法,而这个接口下的实现类中只有两个类实现了这个方法,一个是 AbstractAutoProxyCreator,一个是 InstantiationAwareBeanPostProcessorAdapter,它的实现如下:

代码语言:javascript
复制
// InstantiationAwareBeanPostProcessorAdapter
@Override
public Object getEarlyBeanReference(Object bean, String beanName) throws BeansException {
	return bean;
}

// AbstractAutoProxyCreator
@Override
public Object getEarlyBeanReference(Object bean, String beanName) {
	Object cacheKey = getCacheKey(bean.getClass(), beanName);
	this.earlyProxyReferences.put(cacheKey, bean);
	return wrapIfNecessary(bean, beanName, cacheKey);
}

所以很明显,在整个 Spring 中,默认就只有 AbstractAutoProxyCreator 真正意义上实现了 getEarlyBeanReference 方法,而该类就是用来进行 AOP 的。上文提到的 AnnotationAwareAspectJAutoProxyCreator 的父类就是 AbstractAutoProxyCreator。

那么 getEarlyBeanReference 方法到底在干什么?

首先得到一个 cachekey,cachekey 就是 beanName。

然后把 beanName 和 bean(这是原始对象)存入 earlyProxyReferences 中

调用 wrapIfNecessary 进行 AOP,得到一个代理对象。

那么,什么时候会调用 getEarlyBeanReference 方法呢?回到循环依赖的场景中

image-20230811101249564
image-20230811101249564
  1. 三级缓存放的并不是代理对象;是 ObjectFactory 对象,且传递了函数式接口 getEarlyBeanReference 方法作为该对象 getObject 方法的内部实现,getEarlyBeanReference 方法是一处 Bean 后置处理器 SmartInstantiationAwareBeanPostProcessor 的使用,接口方法 getEarlyBeanReference 可以拓展代理对象的创建,这种情况我感觉是为了避免两个代理对象的循环依赖问题,例如 A、B 相互依赖且都需要创建 AOP 代理对象,此时如果按照不做处理,getBean (A),填充 B 即 getBean (B),B 填充 A,此时 B 填充了 A 的真实对象,然后 B 完成 getBean 周期,初始化后创建了代理对象 PB 并且返回,A 填充的 B 是 B 的代理对象 PB,但是 B 填充的确是普通的 A;所以才会在后置处理器 SmartInstantiationAwareBeanPostProcessor 的实现类里提供了 getEarlyBeanReference 方法来提前创建代理对象;这样一来 AB 填充的都是 BA 的代理对象;当然代理对象创建后会放入缓存,避免初始化后重复创建代理对象;
代码语言:javascript
复制
//处理AOP的后置处理器实现的抽象类AbstractAutoProxyCreator
public Object getEarlyBeanReference(Object bean, String beanName) {
   Object cacheKey = getCacheKey(bean.getClass(), beanName);
   this.earlyProxyReferences.put(cacheKey, bean);
    //提前触发代理对象的创建
   return wrapIfNecessary(bean, beanName, cacheKey);
}
  1. 考虑到如果有代理对象的创建,需要放到一级缓存的就应该是代理对象而非 target 对象;所以在初始化完成后需要判断下是否创建了代理对象,从而确定向一级缓存是代理对象(如果创建了的话);如下代码块 getSingleton 方法会拿到二级缓存里的 Bean 对象,正常情况下就是 targetBean,如果创建了代理对象二级缓存里就是代理对象了,如果不涉及循环依赖,放到一级里的 exposedObject 就是正确的 Bean(作为初始化的返回值,在初始化方法里创建了代理对象,那么也没影响,代理对象会被正常放入一级),如果同时涉及到 AOP 和循环代理,就会触发如下条件,由于提前创建代理对象,初始化方法并不会重复创建,所以满足 exposedObject == bean,所以此时二级缓存里通过 getEarlyBeanReference 创建的代理对象需要放入一级而非 exposedObject,所以执行 exposedObject = earlySingletonReference;
代码语言:javascript
复制
Object earlySingletonReference = getSingleton(beanName, false);
if (earlySingletonReference != null) {
    //正常相等
    //只涉及AOP不等,不需要处理
    //同时循环和AOP,需要纠正为getEarlyBeanReference创建的代理对象
   if (exposedObject == bean) {
      //如果循环依赖创建提前创建了,那么此时初始化后不会重复创建,所以exposedObject和bean是相等的
      exposedObject = earlySingletonReference;//因此直接替换为提前创建的代理对象,作为放入一级缓存的对象
   }
本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2023-08-15,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • # 1. 什么是循环依赖
  • # 2. Bean 的生命周期
  • # 3. 三级缓存
  • # 4. 分析
  • # 5. Spring 解决了哪种循环依赖
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档