结合关键代码梳理流程
创建原始 bean
BeanWrapper instanceWrapper = createBeanInstance(beanName, mbd, args);
// 原始 bean
finalObject bean = instanceWrapper.getWrappedInstance();
在这一步中,创建的是原始 bean
,因为还没到最后一步属性解析,所以这个类里面没有属性值,可以将它想象成 newClassA
,同时没有构造函数等赋值的操作,这个原始 bean
信息将会在下一步使用到。
addSingleFactory
// 注释 5.2 解决循环依赖 第二个参数是回调接口,实现的功能是将切面动态织入 bean
addSingletonFactory(beanName,()-> getEarlyBeanReference(beanName, mbd, bean));
前面也提到过这个方法,它会将需要提前曝光的单例加入到缓存中,将单例的 beanName
和 beanFactory
加入到缓存,在之后需要用到的时候,直接从缓存中取出来。
populateBean 填充属性
刚才第一步时也说过了,一开始创建的只是初始 bean
,没有属性值,所以在这一步会解析类的属性。在属性解析时,会判断属性的类型,如果判断到是 RuntimeBeanReference
类型,将会解析引用。
就像我们写的例子, CircleA
引用了 CircleB
,在加载 CircleA
时,发现 CircleB
依赖,于是乎就要去加载 CircleB
。
我们来看下代码中的具体流程吧:
protectedvoid populateBean(String beanName,RootBeanDefinition mbd,@NullableBeanWrapper bw){
...
if(pvs !=null){
// 将属性应用到 bean 中,使用深拷贝,将子类的属性一并拷贝
applyPropertyValues(beanName, mbd, bw, pvs);
}
}
protectedvoid applyPropertyValues(String beanName,BeanDefinition mbd,BeanWrapper bw,PropertyValues pvs){
...
String propertyName = pv.getName();
Object originalValue = pv.getValue();
// 注释 5.5 解析参数,如果是引用对象,将会进行提前加载
Object resolvedValue = valueResolver.resolveValueIfNecessary(pv, originalValue);
...
}
publicObject resolveValueIfNecessary(Object argName,@NullableObject value){
// 我们必须检查每个值,看看它是否需要一个运行时引用,然后来解析另一个 bean
if(value instanceofRuntimeBeanReference){
// 注释 5.6 在这一步中,如果判断是引用类型,需要解析引用,加载另一个 bean
RuntimeBeanReference ref =(RuntimeBeanReference) value;
return resolveReference(argName, ref);
}
...
}
跟踪到这里,加载引用的流程比较清晰了,发现是引用类的话,最终会委派 org.springframework.beans.factory.support.BeanDefinitionValueResolver#resolveReference
进行引用处理,核心的两行代码如下:
// 注释 5.7 在这里加载引用的 bean
bean =this.beanFactory.getBean(refName);
this.beanFactory.registerDependentBean(refName,this.beanName);
在这一步进行 CircleB
的加载,但是我们写的例子中, CircleB
依赖了 CircleA
,那它是如何处理的呢,所以这时,我们刚才将 CircleA
放入到缓存中的信息就起到了作用。
getSingleton
还记得之前在类加载时学到的只是么,单例模式每次加载都是取同一个对象,如果在缓存中有,可以直接取出来,在缓存中没有的话才进行加载,所以再来熟悉一下取单例的方法:
protectedObject getSingleton(String beanName,boolean allowEarlyReference){
Object singletonObject =this.singletonObjects.get(beanName);
// 检查缓存中是否存在实例
if(singletonObject ==null&& isSingletonCurrentlyInCreation(beanName)){
// 记住,公共变量都需要加锁操作,避免多线程并发修改
synchronized(this.singletonObjects){
// 如果此 bean 正在加载则不处理
singletonObject =this.earlySingletonObjects.get(beanName);
if(singletonObject ==null&& allowEarlyReference){
// 当某些方法需要提前初始化,调用 addSingletonFactory 方法将对应的
// objectFactory 初始化策略存储在 earlySingletonObjects,并且从 singletonFactories 移除
ObjectFactory<?> singletonFactory =this.singletonFactories.get(beanName);
if(singletonFactory !=null){
singletonObject = singletonFactory.getObject();
this.earlySingletonObjects.put(beanName, singletonObject);
this.singletonFactories.remove(beanName);
}
}
}
}
return singletonObject;
}
虽然 CircleB
引用了 CircleA
,但在之前的方法 addSingletonFactory
时, CircleA
的 beanFactory
就提前暴露。
所以 CircleB
在获取单例 getSingleton()
时,能够拿到 CircleA
的信息,所以 CircleB
顺利加载完成,同时将自己的信息加入到缓存和注册表中,接着返回去继续加载 CircleA
,由于它的依赖已经加载到缓存中,所以 CircleA
也能够顺利完成加载,最终整个加载操作完成~
结合解决场景的流程图和关键代码流程,比较完善的介绍了循环依赖处理方法,下面还有一个 debug
流程图,希望能加深你的理解~
总结
写这篇总结的目的是为了填坑,因为之前在解析类加载的文章中只是简单的过了一下循环依赖的概念,想要将在类加载中留下的坑填掉。
在分析循环依赖的过程中,发现之前对作用域 scope
的不了解,于是补充了一下这个知识点,接着又发现对循环依赖中使用到的缓存和详细处理不熟悉,于是查阅了相关资料,跟踪源码,一步一步进行分析,所以发现越写越多,解决了一个困惑,增加了几个疑问,所以在不断排查和了解中,加深了对 Spring
的理解。
同样,在工作中,经常会遇到与其它团队的合作,也会遇到同时需要对方的新接口支持,例如在 RPC
中遇到循环调用,那我建议还是换一种方案,例如通过消息解耦,避免循环调用,实在没办法要循环调用,要记得在方法中加上退出条件,避免无限循环(>_<)
由于个人技术有限,如果有理解不到位或者错误的地方,请留下评论,我会根据朋友们的建议进行修正
代码和注释都在里面,小伙伴们可以下载我上传的代码,亲测可运行~
Gitee 地址:https://gitee.com/vip-augus/spring-analysis-note.git
Github 地址:https://github.com/Vip-Augus/spring-analysis-note
参考资料
- Spring学习(十五)Spring Bean 的5种作用域介绍
- Spring IOC 容器源码分析 - 循环依赖的解决办法
- Spring 源码深度解析》- 郝佳