Spring 循环依赖解析

Spring 循环依赖解析

什么是循环依赖

循环依赖就是两个或两个以上的对象相互依赖,形成了环状的依赖关系。

class A{
	public B b;
}

class B{
	public A a;
}

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

上述代码描述了两个对象之间的相互依赖关系。
由于在 Spring 中,Bean 的生命周期是由 Spring 进行管理,所以这种依赖关系在 Bean 的创建过程中,就会出现循环依赖的问题。在 Spring 中出现循环依赖的场景由多种,其中 Spring 会解决部分循环依赖问题,而 Spring 无法解决的,就需要在开发中避免或自己去解决。

Bean 的生命周期

Bean 的生命周期可以简化为以下几点:

  1. Spring 扫描 class 得到 BeanDefinition
  2. 根据 BeanDefinition ,通过反射调用构造方法,生成 bean 的原始对象(可能会发生构造器循环依赖)。
  3. 填充原始对象中的属性,也就是依赖注入(可能会发生属性循环依赖)
  4. 初始化回调方法
  5. 进行初始化前、初始化、初始化后操作
  6. 把最终生成的代理对象放入单例池 singletonObjects

构造器循环依赖

构造器循环依赖 :两个或多个 Bean 通过构造函数相互依赖时,就会发生构造器循环依赖。例如,BeanA 的构造函数参数需要 BeanB,而 BeanB 的构造函数参数需要 BeanA。这种循环依赖是无法解决的,因为在创建 Bean 实例时需要提供所有的构造函数参数,而循环依赖导致无法满足这个条件。
代码实例如下:

@Component
public class AService {

    public BService bService;

    public AService(BService bService) {
        this.bService = bService;
    }

    public void test() {
        System.out.println("bService = " + bService);
    }
}

@Component
public class BService {

    public AService aService;

    public BService(AService aService) {
        this.aService = aService;
    }

    public void test() {
        System.out.println("aService = " + aService);
    }
}

上述代码是存在构造器循环依赖的。

产生原因:

  1. 当 Spring 实例化 AService 对象时,会推断使用 AService 中的哪一个构造方法,因为重写了该类的构造方法,该类的空参构造被覆盖掉了,因此 Spring 只能选择该构造方法;
  2. 使用该构造方法时,发现形参为 BService,Spring 首先会调用 getBean() 方法,在容器内寻找BService;
  3. 此时容器内还未实例化 BService,因此无法在容器内找到 BService,然后 Spring 会调用 doCreateBean() 方法,去实例化 BService;
  4. 当实例化 BService时,又会重复上述过程,出现报错。

解决方法:
在 AService 中的构造方法上,添加 @Lazy 注解。
当 AService 中的构造方法,添加该注解后,在调用 AService 的构造方法时,Spring 后创建一个 BService 的代理对象,给 AService 的构造方法去使用,以此来实例化 AService,并完成后续的生命周期。

属性循环依赖

代码示例

@Component
public class AService {

    public BService bService;

    public void test() {
        System.out.println("bService = " + bService);
    }
}

@Component
public class BService {

    public AService aService;

    public void test() {
        System.out.println("aService = " + aService);
    }
}

上述代码存在属性方法间的循环依赖,该类循环依赖,由 Spring 通过三级缓存方式,进行了解决。

产生原因(假设不存在三级缓存):

  1. AService 并没有新增构造方法,因此 Spring 采用默认的无参构造去进行对象的实例化,实例化对象后,进入属性注入环节。
  2. AService 中 有 BService 属性,Spring 会在容器中寻找 BService 对象。
  3. 由于 Spring 中不存在 BServce 对象,会去执行创建 BService 的流程。
  4. 在创建 BSerivce 的流程中,发现 AService 并没有存在,会去进行创建 AService,从而循环上述问题,出现循环依赖。

解决思路:
添加一层缓存进行解决:
在对象进行实例化后,将实例化好的对象,提前暴露,放到一个缓存中,后续有对象需要该对象,去缓存中寻找。
如下图所示:
在这里插入图片描述

为什么需要三级缓存

通过上述流程可以看出,二级缓存就能够解决 Spring 的属性循环依赖,为什么需要三级缓存呢?
在这里插入图片描述
假设A的原始对象为@00a1,A并且需要进行AOP,得到的代理对象为@00a2。
由于B对象中的A对象,得到的是A的原始对象,并非代理对象。
此时就会出现不一致问题,即 B.A = @00a1,通过容器获取的A对象为@00a2。
因此需要三级缓存。

三级缓存

一级缓存:singletonObjects,也就是平时所说的单例池,存放的经过完整的生命周期的 bean 对象。
二级缓存:earlySingletonObjects,存放的是早期的 bean 对象,是没有经过完整生命周期的 bean 对象。
三级缓存:singletonFactories,对象工厂,用于创建早期 bean 对象的工厂。

三级缓存执行流程

  1. 首先进行实例化,然后判断该对象是否满足条件,若满足,则加入三级缓存中。
// 1.单例bean;2.允许循环依赖;3.正在创建的bean
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");
			}
			// 循环依赖-添加到三级缓存,此处缓存的val是lamda表达式,当需要从三级缓存中获取bean时,才会执行该表达式
			addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
		}
  1. 填充 BService 属性,在容器中查询该对象,若找不到,则进行创建
    (2.1) 实例化 BService 对象,判断是否满足条件,若满足,则加入到三级缓存中。
    (2.2) 填充 AService 属性,流程见下方 getSingleton() 方法解析
    (2.3) 填充其他属性
    (2.4) 执行初始化流程
    (2.5) 放入单例池中
	/*
		该方法主要流程:
			1.首先查询一级缓存,若存在,则返回,否则去查询二级缓存
			2.查询二级缓存,若存在,则返回,否则通过三级缓存去创建
			3.创建完毕,将不完整的bean,放入二级缓存,删除三级缓存中相应信息,并返回
	*/
	protected Object getSingleton(String beanName, boolean allowEarlyReference) {
		// 首先去一级缓存查询,判断是否可以查询到
		Object singletonObject = this.singletonObjects.get(beanName);
		if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
			// 若查询不到,则去二级查询去找
			singletonObject = this.earlySingletonObjects.get(beanName);
			if (singletonObject == null && allowEarlyReference) {
				synchronized (this.singletonObjects) {
					// 加锁,重复上述流程,保证原子性
					singletonObject = this.singletonObjects.get(beanName);
					if (singletonObject == null) {
						singletonObject = this.earlySingletonObjects.get(beanName);
						if (singletonObject == null) {
							ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
							// 若一二级缓存中都没有该bean,则通过三级缓存创建bean
							if (singletonFactory != null) {
								singletonObject = singletonFactory.getObject();
								// 放入二级缓存,此bean没有经过完整的生命周期,不能放入一级缓存中
								this.earlySingletonObjects.put(beanName, singletonObject);
								// 从三级缓存中移除该bean的相关信息
								this.singletonFactories.remove(beanName);
							}
						}
					}
				}
			}
		}
		return singletonObject;
	}
  1. 填充其他属性
  2. 执行初始化流程
  3. 放入单例池中

此时便通过三级缓存机制,解决了 Spring 的属性循环依赖问题。

  • 39
    点赞
  • 23
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值