Spring bean循环依赖解决方案

前言

开工了,大家在一个春节过后是否可以快速的适应假期之后的工作呢,小编开工第一天就要加班,想起来苦兮兮。因为还调休了一天导致错过红包,心理感觉错过了一个亿,难受啊,只能化悲愤为动力,写个文章安慰自己一下。
今天小编来说一下spring bean的循环依赖,其实网上这个资料超级多,各位大佬也已经总结的相当清楚,那小编就从几个方面切入再次理解一下spring bean的循环依赖方案。咱们从为什么会产生,怎么解决,spring为什么使用三级缓存以及对应流程图以及源码来分析一下,废话不多说,咱们开始。

产生循环依赖的原因

为什么会产生循环依赖,这其实很简单就是一个类与另一个类相互依赖导致。咱们用java代码表示一下。

@Service
public class XServiceImpl implements X{
    @Resource
    private Y y;
}

@Service
public class YServiceImpl implements Y{
    @Resource
    private X x;
}

假设我们有X和Y接口的 service实现的类,这也是我们业务代码中常常写的一种方式。这是X和Y是相互依赖的,假如我们不用spring管理那我们的代码可以这样写

X x = new XServiceImpl();
Y y = new YServiceImpl();
x.setY(y);
y.setX(x);

这样就简单的解决了循环依赖的问题。那为什么交由spring容器管理,循环依赖就成问题了呢,这和spring生命周期有关,他在实例化一个bean后会填充属性,比方说X在实例化后去填充Y,填充完毕后X的spring bean才算彻底创建完毕。这样在创建过程中会循环往复导致了bean创建不出来,所以就引入了spring bean循环依赖的问题。

如何解决spring bean循环依赖

在解决循环依赖之前,首先必须有几个前提:

  1. spring bean创建过程必须要经过反射创建对象这一步,如果你不使用属性注入,转而使用构造参数注入就会出问题,因为Spring都没有办法实例化对象,就更不要谈属性注入了。
  2. 循环依赖的bean必须是单例的,如果不是单例的,就会出现我们上面说的无限循环的问题!
    这样的代码就会错误了:
@Service
public class XServiceImpl implements X{
    
    public XServiceImpl(Y y){

    }
}

解决方案:我们创建两个容器,一个为singletonObjects,另一个earlySingletonObjects,这两个类型都是Map类型,这里名称是不是很熟悉。singletonObjects为单例池,将创建并属性注入完成的对象放入其中,而earlySingletonObjects为存放已经创建完成,但是属性没有注入好的对象!然后我们重新梳理创建XServiceImpl 和YServiceImpl的过程

  • 创建 XServiceImpl 完成后,把自己存到earlySingletonObjects里面去,然后发现依赖YServiceImpl
  • 先从singletonObjects查找是否有YServiceImpl,如果没有则从earlySingletonObjects查找依然没有YServiceImpl
  • 创建YServiceImpl,创建完成后把自己存入earlySingletonObjects,然后发现依赖XServiceImpl
  • 同理 先从singletonObjects查找是否有XServiceImpl ,显然还没有,那么则从earlySingletonObjects查找,发现存在了。
  • 然后将XServiceImpl 注入到YServiceImpl,这样YServiceImpl创建完成了,放入singletonObjects,然后将YServiceImpl从earlySingletonObjects删除。之后返回完成的YServiceImpl
  • 回到XServiceImpl 创建过程发现YServiceImpl创建完毕然后注入属性,这样也创建完毕了
  • 将XServiceImpl 放入singletonObjects然后从earlySingletonObjects移除即可。

spring为何引入三级缓存

小编上面所写的方案,似乎完全可以解决循环依赖的问题,那spring为什么要引入三级缓存。三级缓存有分别是什么?咱们先来看一下。

  • singletonObjects:单例池,我们存放已经创建完成,并且属性也注入完毕的对象.
  • earlySingletonObjects:提前暴露的对象,存放已经创建完成,但是没有注入好的对象.
  • singletonFactories:提前暴露的对象,存放已经创建完成,但是还没有注入好的对象的工厂对象,通过这个工厂可以返回这个对象.
    咱们思考为什么要使用三级缓存,他的意义

我们创建的bean所依赖的对象是一个需要被Aop代理的对象,怎么办?遇到这种情况,我们肯定不能够直接把创建完成的对象放到缓存中去的!为什么,因为我们期望的注入的是一个被代理后的对象,而不是一个原始对象! 所以这里并不能够直接将一个原始对象放置到缓存中,我们可以直接进行判断,如果需要Aop的话进行代理之后放入缓存!但是,请大家想一下,Aop的操作是在哪里做的?是在Spring声明周期的最后一步来做的!但是,如果我们进行判断创建的话,Aop的代理逻辑就会在创建实例的时候就进行Aop的代理了,这明显是不符合Spring对于Bean生命周期的定义的!所以,Spring有重新定义了一个缓存【singletonFactories】用来存放一个Bean的工厂对象,创建的对象之后,填充属性之前会吧创建好的对象放置到【singletonFactories】缓存中去,并不进行实例化,只有在发生了循环引用,或者有对象依赖他的时候,他才会调用工厂方法返回一个代理对象,从而保证了Spring对于Bean生命周期的定义!

我们看一下三级缓存的定义,以及他的工厂方法实现

/** Cache of singleton factories: bean name to ObjectFactory. */
	private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16);
//创建实例化之后将自己放入singletonFactories 
protected void addSingletonFactory(String beanName, ObjectFactory<?> singletonFactory) {
		Assert.notNull(singletonFactory, "Singleton factory must not be null");
		synchronized (this.singletonObjects) {
			if (!this.singletonObjects.containsKey(beanName)) {
				this.singletonFactories.put(beanName, singletonFactory);
				this.earlySingletonObjects.remove(beanName);
				this.registeredSingletons.add(beanName);
			}
		}
	}
	//ObjectFactory的具体方法
	protected Object getEarlyBeanReference(String beanName, RootBeanDefinition mbd, Object bean) {
		Object exposedObject = bean;
		if (!mbd.isSynthetic() && hasInstantiationAwareBeanPostProcessors()) {
			//对BeanPostProcessor遍历找到SmartInstantiationAwareBeanPostProcessor
			//如果存在则返回一个处理后的对象,比方说Aop处理后的对象
			for (SmartInstantiationAwareBeanPostProcessor bp : getBeanPostProcessorCache().smartInstantiationAware) {
				exposedObject = bp.getEarlyBeanReference(exposedObject, beanName);
			}
		}
		//没有直接返回
		return exposedObject;
	}

看到这儿大家是否已经理解了为什么需要三级缓存的真正含义。

spring 循环依赖的流程图

在这里插入图片描述

spring 循环依赖源码分析

首先,我们会先创建对象【XServiceImpl】的时候会先从缓存中获取一下,获取到直接返回,获取不到在创建
在这里插入图片描述
第一次获取肯定为null,因为还没放入缓存数据,获取到空对象之后,开始创建对象!
在这里插入图片描述
创建对象完成之后,把这个对象包装成工厂对象,然后放到三级缓存!
在这里插入图片描述
然后,开始进行属性的填充【YServiceImpl】
在这里插入图片描述
填充过程中,调用getBean 查询缓存中是否存在需要注入的对象
在这里插入图片描述
这里会发现,此时又回到了第一步的逻辑,也是获取不到任何对象!于是往下走,开始创建对象,然后将创建好的对象【YServiceImpl】放置到三级缓存!然后再次开始属性注入,发现依赖【XServiceImpl】,于是再次开始尝试用bean容器里面获取【XServiceImpl】,于是再次走到第一步!但是这一次和以往不同,在获取【XSercieImpl】的时候,因为在创建的时候已经放置到了三级缓存中去,此时是能够获取到数据的!
在这里插入图片描述
于是这个对象就被返回,注入到对应的属性,一路返回到,注入完成,初始化完成,走完整个【YServiceImpl】Bean的生命周期! 然后一路返回到,创建bean的调用处!将【YServiceImpl】放到一级缓存里面
在这里插入图片描述
然后再次返回这个对象,到【XServciceImpl】的注入逻辑,最终【YServiceImpl】被注入到【XServciceImpl】,然后【XServciceImpl】也返回到创建Bean的调用处,放置到一级缓存,最终整个循环引用彻底完成!

总结

以上就是小编总结的spring bean的循环依赖。希望小编说清楚了,最后祝大家开工顺利,牛年大吉!

  • 3
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

木兮君

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值