spring如何解决循环依赖

上次我们说到了springbean的生命周期,详见我的另外一篇博客
springbean的生命周期源码解析

我们提了几个问题:
为什么要默认支持单例模式

为什么在第5步的时候在前面就要调用getSingleton方法,而后在后面的重载的getSingleton方法中对类进行实例化?

为什么要设置三级缓存?

这些问题都将在spring解决循环依赖的时候解决,我们用上一篇博客的例子模拟一下循环依赖:

@Component
public class TestService {
	@Autowired
	private UserService us;
	TestService(){
		System.out.println("调用testservice构造函数");
	}
}
@Component
public class UserService {
	@Autowired
	private TestService ts;
	public UserService(){
		System.out.println("调用userservice构造函数");
	}
}

其余的配置类和测试类的代码见上一篇博客,我们可以看到,A类中有B类的引用,B类中有A类的引用,这就是循环依赖,spring怎么解决它呢。

我们回忆上一篇文章,我们有两个非常重要的函数,一个是第8步调用过的:
addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean))
这个函数是调用在属性注入之前!!!
我们看看这个函数干了什么:

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);//bean加入三级缓存
				this.earlySingletonObjects.remove(beanName);
				this.registeredSingletons.add(beanName);
			}
		}
	}

走到这一步spring认为这个bean虽然还没有执行属性注入但是已经可以用了,所以将其加入三级缓存。这是很重要的一步,因为下次就直接从三级缓存中取。

上篇文章的第5步:getSingleton函数

我们当时遗留的问题是为什么要在这里getSingleton获取,一个bean进来第一次一定是取不到的,为什么还去getSingleton,这里就和循环依赖有关系了。

假设spring先去实例化TestService,执行了addSingletonFactory后TestService也就加入了三级缓存,接着属性注入发现自己依赖了UserService,然后实例化UserService,在UseService实例化的过程中,也会调用上面的addSingletonFactory方法,将UseService加入三级缓存,这时候三级缓存中有两个值:TestService和UserService,然后识别到有TestService的引用,这时候再进入getSingleton这个方法,这时候第一个条件就满足了,单例池拿不到&&bean正在创建当中,条件满足,那么就去earlySingletonObjects(二级缓存)拿,拿不到并且allowEarlyReference(允许循环依赖),那么接着往下走,二级缓存拿不到,那么就去三级缓存拿,三级缓存是一个工厂对象,它要先拿到工厂对象,再从工厂对象中拿bean,然后移除三级缓存中的bean,所以这会儿拿到了TestService,就会给UseService注入,UseService的初始化就完成了,结束之后,然后TestService继续执行属性注入的代码,这时候可以拿到完整的UserService,TestService实例化完成,循环依赖结束。

protected Object getSingleton(String beanName, boolean allowEarlyReference) {
		//从单例池中拿
		Object singletonObject = this.singletonObjects.get(beanName);
		if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {//如果单例池取不到,并且bean正在创建中
			synchronized (this.singletonObjects) {
				singletonObject = this.earlySingletonObjects.get(beanName);//尝试去二级缓存拿
				if (singletonObject == null && allowEarlyReference) {//如果二级缓存拿不到并且程序允许循环依赖
					ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);//从三级缓存拿
					if (singletonFactory != null) {//如果拿到了
						singletonObject = singletonFactory.getObject();//三级缓存中取出来的工厂对象中拿出bean
						this.earlySingletonObjects.put(beanName, singletonObject);//二级缓存中加入这个bean
						this.singletonFactories.remove(beanName);//从三级缓存移除
					}
				}
			}
		}
		return singletonObject;
	}

下面是解决的循环依赖的流程图:
在这里插入图片描述
题外话,为什么spring的bean默认是单例的:
单例有什么好处呢,就是省空间,减少JVM垃圾回收的负担,取的话效率也很高,直接从池子中拿就完了会很快。
缺点就是:某些情况下线程不安全,因为内存当中只有你这一份实例,多线程操作的话会不安全,原型模式可能会好一些,但也不是绝对的安全,因为如果原型模式下以来了一个单例的bean,那么也会不安全。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值