栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Java

spring源码之循环依赖

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

spring源码之循环依赖

什么是循环依赖?
就是现在有两个类 M N,其中在M中注入了N,N中注入了M,这就是循环依赖。
其实在java原本的代码逻辑上,循环依赖是没有问题的,注入的时候new一个新的不就行了吗。、所以循环依赖只在spring会有问题,因为spring中有bean生命周期的流程,在属性注入的时候如果M类依赖了N,那么就要去单例池中取N。但是N现在还没有实例化,所以在N的生命周期中,在属性注入的时候发现N也依赖了M。这时候你等我实例化,我等你实例化,这不就死锁了吗。
样例代码如下:

@Component
public class M {
	@Autowired
	N n;

	public void print(){
		System.out.println("print-M");
	}
}

@Component
public class N {

	@Autowired
	M m;

	public void print(){
		System.out.println("print-N");
	}
}

	@Test
	public void defaultCycle(){
		AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(LifeCycleConfig.class);
		context.getBean(M.class).print();
		context.getBean(N.class).print();
	}

//打印结果
print-M
print-N

代码分析
循环依赖的代码在这个位置
AnnotationConfigApplicationContext.refresh() -> finishBeanFactoryInitialization() -> beanFactory.preInstantiateSingletons() -> getBean()
按照我们刚才的代码,这里是先拿到M类来进行实例化,也就是说getBean(m)

doGetBean() -> getSingleton()
这个方法判断M是否在单例池中


回到doGetBean()方法继续往下看,终于有一天到了这个方法

这里是个lambda表达式,我们先看getSingleton()方法

beforeSingletonCreation()
就是在这里,将M放入了正在创建集合

然后回到getSingleton()方法继续往下,有个singletonFactory.getObject()方法,因为进入这个方法的时候有个lambdm表达式,所以这个方法其实调用的就是createBean()

createBean() -> doCreateBean()方法中,把M给new出来了

这个方法继续往下走,走到这个地方就是循环依赖的主要逻辑之一
判断了一下是否需要循环依赖,操作了几个集合
这几个集合就是三级缓存的内容,后面我们再分析

然后代码继续往下走,进入这个方法中,这个方法就比较复杂了。
可以理解为,这里就是要注入依赖的N,然后发现N不在单例池中,于是就要去实例化N

然后N也被放入到创建集合中

然后下面的逻辑也是一样的,N同样被放到那几个集合中

然后N也操作了这个方法进行属性填充,这时候发现需要注入M

那就也调用doGetBean来获取M

这次再进行判断,M已经在被创建集合中了,所以这里会返回值。
那么重点来了,这里就是所谓的三级缓存,其实并没有spring官方文档说哪个是一级哪个是二级、三级,就是看我们自己的理解。
而我在这里就把
单例池Map集合singletonObjects称为一级缓存
Map集合earlySingletonObjects称为称为二级缓存,因为是第二个出现的。
Map> 集合singletonFactories称为三级缓存

还记得上面的判断是否需要开启循环依赖处理的逻辑中 addSingletonFactory()方法吗?
那里操作了三个集合,在这里就用到了。
singletonObjects中找不到M,earlySingletonObjects二级缓存也找不到,singletonFactories这个里面可以找到,并且是m和ObjectFactory工厂。

这里有几个问题,一个是三级缓存为什么一定要放一个工厂,而不是直接放半成品的bean呢?循环依赖的时候,N的属性注入M,为什么不放一个半成品的M,最后M实例化完成不就可以了吗。为什么非要换成工厂对象放进去。
另一个是,二级缓存不就可以存储半成品的bean了吗,为什么一定要搞一个三级缓存?

第三个问题:为什么不把所有的类都提前进行AOP代理,全都放进二级缓存中,那不就不需要三级缓存了吗?


解答:
一、为什么不放一个半成品的bean,因为在N的实例化链路中,有可能在回调方法中直接使用m这个类的方法,这时候的半成品就有问题了。

二、singletonFactories三级缓存中存储的是个m和工厂,因为不能在N的属性中注入半成品的m,所以在这里要通过这个三级缓存把它先实例化出来,注入N的属性中。
earlySingletonObjects二级缓存是存储这个M,当三级缓存通过工厂方法实例化出来之后,就会放入这个二级缓存,那么如果有三个类循环依赖的话,就不用重复实例化M了。并且在M后续的链路中,如果有AOP代理这种扩展的话,在上面已经执行了,在后续就不会再执行aop扩展了。

三、首先我们来看aop在正常情况下是什么时候完成的,是在bean创建完全之后通过annotationawareaspectjautoproxycreator这个后置处理器来完成的。在这个后置处理postprocessafterinitialization()方法中对初始化后的bean进行aop代理。
spring设计的原则就是先初始化bean,最后进行代理对象的创建。设计之初就是然bean在生命周期的最后一步来完成代理。而循环依赖严格来说并不是一个正常的流程,那么如果为了解决循环依赖而把所有bean的代理动作放在前面,就违背了spring的设计原则。
那么三级缓存的目的就为了在没有循环依赖的情况下,让bean的代理对象在最后一步进行创建,使得bean符合spring的生命周期原则。

面试回答三级缓存:
大概说一下bean的生命周期,把需要三级缓存的场景说一下,然后再解释循环依赖

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/877866.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号