spring 在依赖注入时,可能会出现相互注入的情况:
@Service
public class Service1 {
@Autowired
private Service2 service2;
}
@Service
public class Service2 {
@Autowired
private Service1 service1;
}
如以上代码,在 Service1 中通过 @Autowird 注入了 Service2,在 Service2 中通过 @Autowird 注入了 Service1,这种相互注入的情况,就叫做循环依赖。
实际上,这种 A持有B对象,B也持有A对象的情况,java 代码是完全支持的:
/**
* 准备service1
*/
public class Service1 {
private Service2 service2;
public void setService2(Service2 service2) {
this.service2 = service2;
}
public Service2 getService2() {
return this.service2;
}
}
/**
* 准备service2
*/
public class Service2 {
private Service1 service1;
public void setService1(Service1 service1) {
this.service1 = service1;
}
public Service1 getService1() {
return this.service1;
}
}
/**
* 主方法中调用
*/
public class Main {
public void main(String[] args) {
// 准备两个对象
Service1 service1 = new Service1();
Service2 service2 = new Service2();
// 相互设置
service1.setService2(service2);
service2.setService1(service1);
}
}
那么,在 spring 中,两个类相互注入对方实例的情况,会有什么问题呢?我们来看 spring bean 的创建过程(注意:这里我们仅分析 bean 的 scope 为 singleton 的情况,也就是 scope 为单例的情况):
这个过程中有几点需要说明下:
- 创建对象:这个其实就是使用 jdk 提供的反射机制创建 java 对象,以第 1 节提到的
Service1为例,可简单理解为Service1 service = new Service1(); - 注入依赖对象:还是以第 1 节提到的
Service1为例,Service1中通过@Autowired自动注入Service2,这一步就是给Service2赋值的过程,可简单理解为service1.setService2(service2); singletonObjects:经过上面两步后,一个 java 对象就变成了一个 spring bean,然后保存到singletonObjects了,这是个map,key是 bean 的名称,value是 bean,它只保存spring bean,不会只在 java 实例。
实际上,java 对象变成 spring bean,不仅仅只是依赖注入,还有初始化、执行 beanPorcessor 方法等,由于本文是分析 spring bean 的循环依赖的,因此我们重点关注与循环依赖相关的步骤。
了解了 spring bean 的产生过程之后,接下来我们就来分析下循环依赖产生的问题,在正式分析前,我们先来明确两个概念:
java对象:实际上,java 中一切对象都可以称之为java对象,为了说明方便,以下提到的java对象仅指实例化完成、但未进行 spring bean 的生命周期对象;spring bean:是一个 java 对象,并且进行了完整的 spring bean 的生命周期对象;
spring bean 的创建过程如下:
对上图说明如下:
- 在
service1对象创建完成后,spring发现service1需要注入service2,然后就去singletonObjects中查找service2,此时是找不到service2,然后就开始了service2的创建过程; - 在
service2对象创建完成后,spring发现service2需要注入service1,然后就去singletonObjects中查找service1,此时是找不到service1,因为第一步中service1并没有创建成功 ,然后就开始了service1的创建过程; - 流程跳回到
1,再次开始了service1的创建、属性注入过程。
到这里,我们惊喜地发现,循环出现了!
我们分析下,循环出现的原因在于,在 service2 获取 service1 时,由于 singletonObjects 中此时并不存在 service1,因此会再走 service1 的创建过程,重新创建 service1,因此,我们有个大胆的想法:如果在 service1 实例化后就把它保存起来,后面再再找 service1 时,就返回这个未进行依赖注入的 service1,像下面这样:
上图中,引入了 earlySingletonObjects,这也是个 map,同 singletonObjects 一样,key 是 bean 的名称,value 是一个未完成依赖注入的对象。
对上图说明如下:
- 在
service1对象创建完成后,先将service1放入earlySingletonObjects,然后进行依赖注入; - 对
service1进行依赖注入时,spring发现service1需要注入service2,然后先去earlySingletonObjects查找service2,未找到;再去singletonObjects中查找service2,还是未找到,于是就开始了service2的创建过程; - 在
service2对象创建完成后,先将service2放入earlySingletonObjects,然后进行依赖注入; - 对
service2进行依赖注入时,spring发现service2需要注入service1,然后就去earlySingletonObjects查找service1,找到了,就将service1注入到service2中,此时service2就是一个spring bean了,将其保存到singletonObjects中; - 经过第 4 步后,我们得到了
service2,然后将其注入到service1中,此时service1也成了一个spring bean,将其保存到singletonObjects中。
经过以上步骤,我们发现,循环依赖得到了解决。
经过上面的分析,我们发现只要额外引入一个 earlySingletonObjects 后,循环依赖就能得到解决。但是,循环依赖真的得到了解决吗?spring 除了 ioc 外,还有另一个重大功能:aop,我们来看看 aop 情况下出现循环依赖会怎样。
在正式介绍 aop 下的循环依赖前,我们先来明确两个个概念:
原始对象:区别于代理对象,指未进行过 aop 的对象,可以是 java 对象,也可以是未进行 aop 的 spring bean;代理对象:进行过 aop 的对象,可以是 java 对象仅进行过 aop 得到的对象 (仅进行过 aop,未进行依赖注入,也未进行初始化),也可以是进行过 aop 的spring bean.
我们先来看看 aop 是如何创建对象的:
相比于 2.1 中的流程,aop 多了 "生成代理对象" 的操作,并且最终保存到 singletonObjects 中的对象也是代理对象。
原始对象与代理对象之间是什么关系呢?用代码示意下,大致如下:
public class ProxyObj extends Obj {
// 原始对象
private Obj obj;
...
}
实际上,两者之间的关系并没有这么简单,但为了说明问题,这里对两者关系做了简化,小伙伴们只需要明白,代理对象持有原始对象的引用即可。
关于原始对象如何变成代理对象的,可以参考 spring aop 之 AnnotationAwareAspectJAutoProxyCreator 分析(下)。
对以上创建过程,用 java 代码模拟如下:
/**
* 准备一个类
*/
public class Obj1 {
}
/**
* 准备一个类,内部有一个属性 Obj1
*/
public class Obj2 {
private Obj1 obj1;
// 省略其他方法
...
}
/**
* 准备Obj2的代理类,内部持有obj2的对象
*/
public class ProxyObj2 extends Obj2 {
private Obj2 obj2;
public ProxyObj2(Obj2 obj2) {
this.obj2 = obj2;
}
// 省略其他方法
...
}
接着,就是模拟 “创建 --> 属性注入 --> 生成代理对象 --> 保存到容器中” 的 流程了:
public static main(String[] args) {
// 准备一个容器,这里保存的是完成上述生命周期的对象
// 1\. 如果元素是原始对象,则该对象已经完成了属性注入
// 2\. 如果元素是代理对象,则该对象持有的原有对象已经完成了属性注入
Collection<?> collection = new ArrayList();
// 开始 Obj2 的创建流程
// 1\. 创建 Obj2 对象
Obj2 obj2 = new Obj2();
// 2\. 往 Obj2 中注入 obj1,但此时并没有obj1,因此先要创建obj1,再将其注入到Obj2中
Obj1 obj1 = new Obj1();
obj2.setObj1(obj1);
// 3\. 生成Obj2的代理对象,代理对象中持有 Obj2的原始对象
ProxyObj2 proxyObj2 = new ProxyObj2(obj2);
// 4\. proxyObj2已经走完了完整的生命周期,因此将代理对象添加到容器时
collection.add(proxyObj2);
}
上述代码中,
- 以
new Obj2()模拟对象的创建 - 以
obj2.setObj1(xxx)模拟依赖注入 - 以
new ProxyObj2(xxx)模拟代理对象的生成 - 以
collection.add(xxx)模拟对象添加到容器中的过程
模拟的流程如下:
- 创建
obj2对象 - 往
Obj2中注入obj1,但此时并没有obj1,因此先要创建obj1,再将其注入到Obj2中 - 生成
Obj2的代理对象proxyObj2,proxyObj2中持有Obj2的原始对象 proxyObj2已经走完了完整的生命周期,因此将代理对象添加到容器时
仔细分析上面的步骤,就会发现,上面的第 2 步与第 3 步完全调换顺序也没问题,代码模拟如下:
public static main(String[] args) {
// 准备一个容器,这里保存的是完成上述生命周期的对象
// 1\. 如果元素是原始对象,则该对象已经完成了属性注入
// 2\. 如果元素是代理对象,则该对象持有的原有对象已经完成了属性注入
Collection<?> collection = new ArrayList();
// 开始 Obj2 的创建流程
// 1\. 创建 Obj2 对象
Obj2 obj2 = new Obj2();
// 2\. 生成Obj2的代理对象,代理对象中持有 Obj2的原始对象
ProxyObj2 proxyObj2 = new ProxyObj2(obj2);
// 3\. 往 obj2 中注入 obj1,但此时并没有obj1,因此先要创建obj1,再将其注入到Obj2中
Obj1 obj1 = new Obj1();
// 这里是注入到原始对象中
obj2.setObj1(obj1);
// 4\. proxyObj2已经走完了完整的生命周期,因此将代理对象添加到容器时
collection.add(proxyObj2);
}
上述代码的流程如下:
- 创建 obj2 对象
- 生成 Obj2 的代理对象,代理对象中持有 Obj2 的原始对象
- 往 Obj2 中注入 obj1,但此时并没有 obj1,因此先要创建 obj1,再将其注入到 Obj2
- proxyObj2 已经走完了完整的生命周期,因此将代理对象添加到容器时
从代码上看,proxyObj2(代理对象) 中持有 ob2(原始对象),生成代理对象后,继续对原始对象进行属性注入,依然能影响代理对象,最终代理对象持有的原始对象也完成了依赖注入,整个过程用图形示意如下:
这里我们再次申明,从 java 对象到 spring bean 的步骤有好多,这里我们仅关注与循环依赖相关的步骤,如果想了解 spring bean 详细的初始化过程,可查看 spring 启动流程之启动流程概览。
到这里,我们探索到代理对象的生命周期可以有两种:
- 创建 --> 属性注入 --> 生成代理对象 --> 将代理对象保存到容器中
- 创建 (原始对象)--> 生成代理对象 (提前进行 aop)--> 对原始对象进行属性注入 --> 将代理对象保存到容器中
这两种都能达到最终目的,即保存到容器中的是代理对象,且代理对象对应的原始对象完成了依赖注入。请牢记这两个创建流程,这是后面解决 aop 下循环依赖问题的核心,说白了,aop 下的循环依赖问题之所以能解决,就是因为对象可以提前进行 aop 操作。
前面我们主要说明了代理对象的创建过程,接下来我们来看看在 aop 下,使用 earlySingletonObjects 来解决循环依赖有什么问题:
我们来分析上图的流程:
- 在
service1对象创建完成后,先将service1放入earlySingletonObjects,然后进行依赖注入; - 对
service1进行依赖注入时,spring发现service1需要注入service2,然后先去earlySingletonObjects查找service2,未找到;再去singletonObjects中查找service2,还是未找到,于是就开始了service2的创建过程; - 在
service2对象创建完成后,先将service2放入earlySingletonObjects,然后进行依赖注入; - 对
service2进行依赖注入时,spring发现service2需要注入service1,然后就去earlySingletonObjects查找service1,找到了,就将service1注入到service2中,然后再进行 aop,此时service2是一个代理对象,将其保存到singletonObjects中; - 经过第 4 步后,我们得到了
service2的代理对象,然后将其注入到service1中,接着再对service1进行 aop,此时service1也成了一个spring bean,将其保存到singletonObjects中。
上述步骤有什么问题呢?仔细看第 4 步,就会发现,注入到 service2 的 service1 并不是代理对象!纵观全局,最终得到的 service1 与 service2 都是代理对象,注入到 service2 的 service1 应该也是代理对象才对。因此,在 aop 下,循环依赖的问题又出现了!
前面我们提到,在 aop 下,引入 earlySingletonObjects 并不能解决循环依赖的问题,那 spring 是怎么解决的呢?spring 再次引入了一个 map 来解决这个问题,这也是人们常说的 spring 三级缓存,对这三个 map 说明如下:
- 一级缓存
singletonObjects:类型为ConcurrentHashMap<String, Object>,位于DefaultSingletonBeanRegistry类中,key为beanName,value是完整的spring bean,即完成属性注入、初始化的 bean,如果 bean 需要 aop,存储的就是代理对象; - 二级缓存
earlySingletonObjects:类型为HashMap<String, Object>,位于DefaultSingletonBeanRegistry类中,key为beanName,value是实例化完成,但未进行依赖注入的bean,如果bean需要aop,这里存储的就是代理对象,只不过代理对象所持有的原始对象并未进行依赖注入; - 三级缓存
singletonFactories:类型为HashMap<String, ObjectFactory>,位于DefaultSingletonBeanRegistry类中,key为beanName,value存储的是一个lambda表达式:() -> getEarlyBeanReference(beanName, mbd, bean),getEarlyBeanReference中的bean是刚创建完成的java bean,没有进行 spring 依赖注入,也没进行 aop (关于这个lambda表达式,后面会继续分析)。
为了说明方便,下面对 singletonObjects、earlySingletonObjects 和 singletonFactories 分别称为一级缓存、二级缓存和三级缓存。
spring 解决 aop 下的循环依赖流程如下:
这个图看着比较复杂,其实分开来看就比较简单了,上述操作中,1~8 是获取 service1 的流程,5.1~5.8 是获取 service2 的流程,5.5.1 是再次获取 service1 的流程,只不过在处理 service1 的初始化过程中,会触发 service2 的初始化过程,而 service2 的初始化时,又会依赖到 service1,因此才看着像是连在一起,比较复杂。
对上图的过程,这里说明如下(建议:如果觉得流程比较复杂,可以先看 1~8 的操作,再看 5.1~5.8 的操作,最后两者联合起来看,这样会清晰很多):
-
service1:获取service1,从一级缓存中获取,此时是获取不到的;
-
service1:创建service1的实例;
-
service1:获取需要注入的属性与方法(在原始对象上进行获取);
-
service1:如果开启了支持循环依赖的配置,就将service1放到三级缓存中(是否支持循环依赖,是可以配置的);
-
service1:对service1进行依赖注入,需要service2,然后就开始了service2的获取流程;
- 5.1
service2:获取service2,从一级缓存中获取,此时是获取不到的; - 5.2
service2:创建service2的实例; - 5.3
service2:获取需要注入的属性与方法(在原始对象上进行获取); - 5.4
service2:如果开启了支持循环依赖的配置,就将service2放到三级缓存中(是否支持循环依赖,是可以配置的); - 5.5
service2:对service2进行依赖注入,需要service1,然后就开始了service1的获取流程; - 5.5.1
service1: 获取service1,从一级缓存中获取,获取不到;此时发现service1正在创建中,于是继续从二、三级缓存中获取,最终从三级缓存中获取到了,将其放入二级缓存。从三级缓存获取的过程中,会判断service1是否需要进行 aop,然后开始 aop 操作,因此放入二级缓存中的是service1代理代理,提前进行 aop 是解决循环依赖的关键; - 5.6
service2:得到了service1后(这里的service1是代理对象),将其注入到service2中,接着对service2进行 aop,得到service2的代理对象; - 5.7
service2:如果支持循环依赖,先从一、二级缓存中再次获取service2,都未获取到,就使用当前service2(当前service2是代理对象); - 5.8
service2:将 service2 的代理对象放入一级缓存中,删除二、三级缓存,至此,service2初始化完成,注入的service1是代理对象,一级缓存中的service2也是代理对象; -
service1:回到service1的生命周期,拿到service2(这里的service2是代理对象)后,将其注入到service1,service1的依赖注入完成,进行初始化,这里会判断service1是否需要进行 aop,虽然service1是需要进行 aop 的,但由于在5.5.1已经进行过 aop 了,因此,这里直接返回(到这一步,service1还是原始对象);
-
service1:如果支持循环依赖,先从一级缓存中获取service1,获取不到;再从二缓存中获取service1,可以获取到(从5.5.1可知,二级缓存里是service1代理对象),返回;
-
service1:将二级缓存中获取的对象注册到一级缓存中,删除二、三级缓存,至此,service1初始化完成,注入的service2是代理对象,一级缓存中的service1也是代理对象。
以上流程,虽然步骤较多,但 service1 与 service2 的获取步骤是相同的,只要弄清了其中之一的获取流程,另一个 bean 的获取流程就很雷同了。
在上述流程中,还有两个数据结构需要说明下:
singletonsCurrentlyInCreation:类型为SetFromMap<String>,位于DefaultSingletonBeanRegistry,创建方式为Collections.newSetFromMap(new ConcurrentHashMap<>(16)),表明这是个由ConcurrentHashMap实现的 set,存储的是正在创建中的对象,判断当前对象是否在创建中就是通过查找当前对象是否在这个 set 中做到的;earlyProxyReferences:类型为ConcurrentHashMap<Object, Object>,位于AbstractAutoProxyCreator,存储的是提前进行 aop 的对象,如果一个对象提前进行了 aop,在后面再次 aop 时,会通过判断对象是否在earlyProxyReferences中而确定要不要进行 aop,以此来保证每个对象只进行一次 aop。
至此,spring 一共提供了 5 个数据结构来辅助解决循环依赖问题,总结如下:
| 结构 | 说明 |
|---|---|
singletonObjects |
一级缓存,类型为 ConcurrentHashMap<String, Object>,位于 DefaultSingletonBeanRegistry 类中,key 为 beanName,value 是完整的 spring bean,即完成属性注入、初始化的 bean,如果 bean 需要 aop,存储的就是代理对象 |
earlySingletonObjects |
二级缓存,类型为 HashMap<String, Object>,位于 DefaultSingletonBeanRegistry 类中,key 为 beanName,value 是实例化完成,但未进行依赖注入的 bean,如果 bean 需要 aop,这里存储的就是代理对象,只不过代理对象所持有的原始对象并未进行依赖注入 |
singletonFactories |
三级缓存,类型为 HashMap<String, ObjectFactory>,位于 DefaultSingletonBeanRegistry 类中,key 为 beanName,value 存储的是一个 lambda 表达式:() -> getEarlyBeanReference(beanName, mbd, bean),getEarlyBeanReference(xxx) 中的 bean 是刚创建完成的 java bean,没有进行 spring 依赖注入,也没进行 aop |
singletonsCurrentlyInCreation |
类型为 SetFromMap<String>,位于 DefaultSingletonBeanRegistry,创建方式为 Collections.newSetFromMap(new ConcurrentHashMap<>(16)),表明这是个由 ConcurrentHashMap 实现的 set,存储的是正在创建中的对象,可以用来判断当前对象是否在创建中 |
earlyProxyReferences |
类型为 ConcurrentHashMap<Object, Object>,位于 AbstractAutoProxyCreator,存储的是提前进行 aop 的对象,可以用来判断 bean 是否进行过 aop,保证每个对象只进行一次 aop |
以上就是 spring 解决循环依赖的完整流程了。
在正式分析源码前,我们首先模拟循环下依赖解决的过程,代码如下:
/**
* 准备一个类,内部有一个属性 Obj2
*/
public class Obj1 {
// 需要注入 obj2
private Obj2 obj2;
// 省略其他方法
...
}
/**
* 准备一个类,内部有一个属性 Obj1
*/
public class Obj2 {
// 需要注入 ob1
private Obj1 obj1;
// 省略其他方法
...
}
/**
* 准备Obj2的代理类,内部持有obj2的对象
*/
public class ProxyObj2 extends Obj2 {
// obj2代理类内部持有obj2的原始对象
private Obj2 obj2;
public ProxyObj2(Obj2 obj2) {
this.obj2 = obj2;
}
// 省略其他方法
...
}
/**
* 准备Obj1的代理类,内部持有obj1的对象
*/
public class ProxyObj1 extends Obj1 {
// obj2代理类内部持有obj1的原始对象
private Obj1 obj1;
public ProxyObj1(Obj1 obj1) {
this.obj1 = obj1;
}
// 省略其他方法
...
}
- 首先准备了两个类:
Obj1与Obj2, 其中Obj1有个属性为Obj2,Obj2中有个属性为Obj1; - 接着准备了
Obj1与Obj2的代理类ProxyObj1、ProxyObj2,并且ProxyObj1、ProxyObj2分别有一个属性:Obj1、Obj2; - 我们依旧以
new ObjX()模拟对象的创建; - 我们依旧以
objX.setObjX(xxx)模拟依赖注入; - 我们依旧以
new ProxyObjX(xxx)模拟代理对象的生成; - 我们依旧以
collection.add(xxx)模拟对象添加到容器中的过程;
我们模拟最终得到的结果为:
- 最终放入容器的对象分别是
proxyObj1,proxyObj2 - 注入到
obj1中的是proxyObj2,注入到obj2中的是proxyObj2
准备工作已经完成了,接下来我们就开始进行模拟了。
要求:
- Obj1 与 Obj2 必须严格按照 “创建 --> 属性注入 --> 生成代理对象 --> 保存到容器中” 的流程创建
- 两个对象的创建流程可以交替进行
目标:
- 最终放入容器的对象分别是
proxyObj1,proxyObj2 - 注入到
obj1中的是proxyObj2,注入到obj2中的是proxyObj2
代码如下:
public static main(String[] args) {
// 准备一个容器,这里保存的是完成上述生命周期的对象
// 1\. 如果元素是原始对象,则该对象已经完成了属性注入
// 2\. 如果元素是代理对象,则该对象持有的原有对象已经完成了属性注入
Collection<?> collection = new ArrayList();
// 1\. 创建 Obj1 对象
Obj1 obj1 = new Obj1();
// 接下来需要将obj2的代理对象注入到obj1中,但此时容器中并没有obj2的代理对象,于是切换到obj2的创建流程
// 一. 创建 Obj2 对象
Obj2 obj2 = new Obj2();
// 到这里,obj2需要注入obj1的代理对象,但此时容器中并没有obj2的代理对象,于是又要切到obj1的创建流程
}
在执行以上流程中 ,发现创建 Obj2 对象后,流程就进行不下去了:
obj1需要注入obj2的代理对象,但找不到,于是切换到obj2的创建流程;obj2需要注入obj1的代理对象,但找不到,于是切换到obj1的创建流程;obj1需要注入obj2的代理对象,但找不到,于是切换到obj2的创建流程;- ...
如此循环往复。
模拟结果:未达到预期目标,本次模拟宣告失败。
要求:
- Obj1 与 Obj2 必须以下两种流程之一创建:
- “创建 --> 属性注入 --> 生成代理对象 --> 保存到容器中” 的流程创建
- “创建 (原始对象)--> 生成代理对象 --> 对原始对象进行属性注入 --> 将代理对象保存到容器中” 的流程创建
- 两个对象的创建流程可以交替进行
目标:
- 最终放入容器的对象分别是
proxyObj1,proxyObj2 - 注入到
obj1中的是proxyObj2,注入到obj2中的是proxyObj2### 1. 什么是循环依赖?
spring 在依赖注入时,可能会出现相互注入的情况:
@Service
public class Service1 {
@Autowired
private Service2 service2;
}
@Service
public class Service2 {
@Autowired
private Service1 service1;
}
如以上代码,在 Service1 中通过 @Autowird 注入了 Service2,在 Service2 中通过 @Autowird 注入了 Service1,这种相互注入的情况,就叫做循环依赖。
实际上,这种 A持有B对象,B也持有A对象的情况,java 代码是完全支持的:
/**
* 准备service1
*/
public class Service1 {
private Service2 service2;
public void setService2(Service2 service2) {
this.service2 = service2;
}
public Service2 getService2() {
return this.service2;
}
}
/**
* 准备service2
*/
public class Service2 {
private Service1 service1;
public void setService1(Service1 service1) {
this.service1 = service1;
}
public Service1 getService1() {
return this.service1;
}
}
/**
* 主方法中调用
*/
public class Main {
public void main(String[] args) {
// 准备两个对象
Service1 service1 = new Service1();
Service2 service2 = new Service2();
// 相互设置
service1.setService2(service2);
service2.setService1(service1);
}
}
那么,在 spring 中,两个类相互注入对方实例的情况,会有什么问题呢?我们来看 spring bean 的创建过程(注意:这里我们仅分析 bean 的 scope 为 singleton 的情况,也就是 scope 为单例的情况):
这个过程中有几点需要说明下:
- 创建对象:这个其实就是使用 jdk 提供的反射机制创建 java 对象,以第 1 节提到的
Service1为例,可简单理解为Service1 service = new Service1(); - 注入依赖对象:还是以第 1 节提到的
Service1为例,Service1中通过@Autowired自动注入Service2,这一步就是给Service2赋值的过程,可简单理解为service1.setService2(service2); singletonObjects:经过上面两步后,一个 java 对象就变成了一个 spring bean,然后保存到singletonObjects了,这是个map,key是 bean 的名称,value是 bean,它只保存spring bean,不会只在 java 实例。
实际上,java 对象变成 spring bean,不仅仅只是依赖注入,还有初始化、执行 beanPorcessor 方法等,由于本文是分析 spring bean 的循环依赖的,因此我们重点关注与循环依赖相关的步骤。
了解了 spring bean 的产生过程之后,接下来我们就来分析下循环依赖产生的问题,在正式分析前,我们先来明确两个概念:
java对象:实际上,java 中一切对象都可以称之为java对象,为了说明方便,以下提到的java对象仅指实例化完成、但未进行 spring bean 的生命周期对象;spring bean:是一个 java 对象,并且进行了完整的 spring bean 的生命周期对象;
spring bean 的创建过程如下:
对上图说明如下:
- 在
service1对象创建完成后,spring发现service1需要注入service2,然后就去singletonObjects中查找service2,此时是找不到service2,然后就开始了service2的创建过程; - 在
service2对象创建完成后,spring发现service2需要注入service1,然后就去singletonObjects中查找service1,此时是找不到service1,因为第一步中service1并没有创建成功 ,然后就开始了service1的创建过程; - 流程跳回到
1,再次开始了service1的创建、属性注入过程。
到这里,我们惊喜地发现,循环出现了!
我们分析下,循环出现的原因在于,在 service2 获取 service1 时,由于 singletonObjects 中此时并不存在 service1,因此会再走 service1 的创建过程,重新创建 service1,因此,我们有个大胆的想法:如果在 service1 实例化后就把它保存起来,后面再再找 service1 时,就返回这个未进行依赖注入的 service1,像下面这样:
上图中,引入了 earlySingletonObjects,这也是个 map,同 singletonObjects 一样,key 是 bean 的名称,value 是一个未完成依赖注入的对象。
对上图说明如下:
- 在
service1对象创建完成后,先将service1放入earlySingletonObjects,然后进行依赖注入; - 对
service1进行依赖注入时,spring发现service1需要注入service2,然后先去earlySingletonObjects查找service2,未找到;再去singletonObjects中查找service2,还是未找到,于是就开始了service2的创建过程; - 在
service2对象创建完成后,先将service2放入earlySingletonObjects,然后进行依赖注入; - 对
service2进行依赖注入时,spring发现service2需要注入service1,然后就去earlySingletonObjects查找service1,找到了,就将service1注入到service2中,此时service2就是一个spring bean了,将其保存到singletonObjects中; - 经过第 4 步后,我们得到了
service2,然后将其注入到service1中,此时service1也成了一个spring bean,将其保存到singletonObjects中。
经过以上步骤,我们发现,循环依赖得到了解决。
经过上面的分析,我们发现只要额外引入一个 earlySingletonObjects 后,循环依赖就能得到解决。但是,循环依赖真的得到了解决吗?spring 除了 ioc 外,还有另一个重大功能:aop,我们来看看 aop 情况下出现循环依赖会怎样。
在正式介绍 aop 下的循环依赖前,我们先来明确两个个概念:
原始对象:区别于代理对象,指未进行过 aop 的对象,可以是 java 对象,也可以是未进行 aop 的 spring bean;代理对象:进行过 aop 的对象,可以是 java 对象仅进行过 aop 得到的对象 (仅进行过 aop,未进行依赖注入,也未进行初始化),也可以是进行过 aop 的spring bean.
我们先来看看 aop 是如何创建对象的:
相比于 2.1 中的流程,aop 多了 "生成代理对象" 的操作,并且最终保存到 singletonObjects 中的对象也是代理对象。
原始对象与代理对象之间是什么关系呢?用代码示意下,大致如下:
public class ProxyObj extends Obj {
// 原始对象
private Obj obj;
...
}
实际上,两者之间的关系并没有这么简单,但为了说明问题,这里对两者关系做了简化,小伙伴们只需要明白,代理对象持有原始对象的引用即可。
关于原始对象如何变成代理对象的,可以参考 spring aop 之 AnnotationAwareAspectJAutoProxyCreator 分析(下)。
对以上创建过程,用 java 代码模拟如下:
/**
* 准备一个类
*/
public class Obj1 {
}
/**
* 准备一个类,内部有一个属性 Obj1
*/
public class Obj2 {
private Obj1 obj1;
// 省略其他方法
...
}
/**
* 准备Obj2的代理类,内部持有obj2的对象
*/
public class ProxyObj2 extends Obj2 {
private Obj2 obj2;
public ProxyObj2(Obj2 obj2) {
this.obj2 = obj2;
}
// 省略其他方法
...
}
接着,就是模拟 “创建 --> 属性注入 --> 生成代理对象 --> 保存到容器中” 的 流程了:
public static main(String[] args) {
// 准备一个容器,这里保存的是完成上述生命周期的对象
// 1\. 如果元素是原始对象,则该对象已经完成了属性注入
// 2\. 如果元素是代理对象,则该对象持有的原有对象已经完成了属性注入
Collection<?> collection = new ArrayList();
// 开始 Obj2 的创建流程
// 1\. 创建 Obj2 对象
Obj2 obj2 = new Obj2();
// 2\. 往 Obj2 中注入 obj1,但此时并没有obj1,因此先要创建obj1,再将其注入到Obj2中
Obj1 obj1 = new Obj1();
obj2.setObj1(obj1);
// 3\. 生成Obj2的代理对象,代理对象中持有 Obj2的原始对象
ProxyObj2 proxyObj2 = new ProxyObj2(obj2);
// 4\. proxyObj2已经走完了完整的生命周期,因此将代理对象添加到容器时
collection.add(proxyObj2);
}
上述代码中,
- 以
new Obj2()模拟对象的创建 - 以
obj2.setObj1(xxx)模拟依赖注入 - 以
new ProxyObj2(xxx)模拟代理对象的生成 - 以
collection.add(xxx)模拟对象添加到容器中的过程
模拟的流程如下:
- 创建
obj2对象 - 往
Obj2中注入obj1,但此时并没有obj1,因此先要创建obj1,再将其注入到Obj2中 - 生成
Obj2的代理对象proxyObj2,proxyObj2中持有Obj2的原始对象 proxyObj2已经走完了完整的生命周期,因此将代理对象添加到容器时
仔细分析上面的步骤,就会发现,上面的第 2 步与第 3 步完全调换顺序也没问题,代码模拟如下:
public static main(String[] args) {
// 准备一个容器,这里保存的是完成上述生命周期的对象
// 1\. 如果元素是原始对象,则该对象已经完成了属性注入
// 2\. 如果元素是代理对象,则该对象持有的原有对象已经完成了属性注入
Collection<?> collection = new ArrayList();
// 开始 Obj2 的创建流程
// 1\. 创建 Obj2 对象
Obj2 obj2 = new Obj2();
// 2\. 生成Obj2的代理对象,代理对象中持有 Obj2的原始对象
ProxyObj2 proxyObj2 = new ProxyObj2(obj2);
// 3\. 往 obj2 中注入 obj1,但此时并没有obj1,因此先要创建obj1,再将其注入到Obj2中
Obj1 obj1 = new Obj1();
// 这里是注入到原始对象中
obj2.setObj1(obj1);
// 4\. proxyObj2已经走完了完整的生命周期,因此将代理对象添加到容器时
collection.add(proxyObj2);
}
上述代码的流程如下:
- 创建 obj2 对象
- 生成 Obj2 的代理对象,代理对象中持有 Obj2 的原始对象
- 往 Obj2 中注入 obj1,但此时并没有 obj1,因此先要创建 obj1,再将其注入到 Obj2
- proxyObj2 已经走完了完整的生命周期,因此将代理对象添加到容器时
从代码上看,proxyObj2(代理对象) 中持有 ob2(原始对象),生成代理对象后,继续对原始对象进行属性注入,依然能影响代理对象,最终代理对象持有的原始对象也完成了依赖注入,整个过程用图形示意如下:
这里我们再次申明,从 java 对象到 spring bean 的步骤有好多,这里我们仅关注与循环依赖相关的步骤,如果想了解 spring bean 详细的初始化过程,可查看 spring 启动流程之启动流程概览。
到这里,我们探索到代理对象的生命周期可以有两种:
- 创建 --> 属性注入 --> 生成代理对象 --> 将代理对象保存到容器中
- 创建 (原始对象)--> 生成代理对象 (提前进行 aop)--> 对原始对象进行属性注入 --> 将代理对象保存到容器中
这两种都能达到最终目的,即保存到容器中的是代理对象,且代理对象对应的原始对象完成了依赖注入。请牢记这两个创建流程,这是后面解决 aop 下循环依赖问题的核心,说白了,aop 下的循环依赖问题之所以能解决,就是因为对象可以提前进行 aop 操作。
前面我们主要说明了代理对象的创建过程,接下来我们来看看在 aop 下,使用 earlySingletonObjects 来解决循环依赖有什么问题:
我们来分析上图的流程:
- 在
service1对象创建完成后,先将service1放入earlySingletonObjects,然后进行依赖注入; - 对
service1进行依赖注入时,spring发现service1需要注入service2,然后先去earlySingletonObjects查找service2,未找到;再去singletonObjects中查找service2,还是未找到,于是就开始了service2的创建过程; - 在
service2对象创建完成后,先将service2放入earlySingletonObjects,然后进行依赖注入; - 对
service2进行依赖注入时,spring发现service2需要注入service1,然后就去earlySingletonObjects查找service1,找到了,就将service1注入到service2中,然后再进行 aop,此时service2是一个代理对象,将其保存到singletonObjects中; - 经过第 4 步后,我们得到了
service2的代理对象,然后将其注入到service1中,接着再对service1进行 aop,此时service1也成了一个spring bean,将其保存到singletonObjects中。
上述步骤有什么问题呢?仔细看第 4 步,就会发现,注入到 service2 的 service1 并不是代理对象!纵观全局,最终得到的 service1 与 service2 都是代理对象,注入到 service2 的 service1 应该也是代理对象才对。因此,在 aop 下,循环依赖的问题又出现了!
前面我们提到,在 aop 下,引入 earlySingletonObjects 并不能解决循环依赖的问题,那 spring 是怎么解决的呢?spring 再次引入了一个 map 来解决这个问题,这也是人们常说的 spring 三级缓存,对这三个 map 说明如下:
- 一级缓存
singletonObjects:类型为ConcurrentHashMap<String, Object>,位于DefaultSingletonBeanRegistry类中,key为beanName,value是完整的spring bean,即完成属性注入、初始化的 bean,如果 bean 需要 aop,存储的就是代理对象; - 二级缓存
earlySingletonObjects:类型为HashMap<String, Object>,位于DefaultSingletonBeanRegistry类中,key为beanName,value是实例化完成,但未进行依赖注入的bean,如果bean需要aop,这里存储的就是代理对象,只不过代理对象所持有的原始对象并未进行依赖注入; - 三级缓存
singletonFactories:类型为HashMap<String, ObjectFactory>,位于DefaultSingletonBeanRegistry类中,key为beanName,value存储的是一个lambda表达式:() -> getEarlyBeanReference(beanName, mbd, bean),getEarlyBeanReference中的bean是刚创建完成的java bean,没有进行 spring 依赖注入,也没进行 aop (关于这个lambda表达式,后面会继续分析)。
为了说明方便,下面对 singletonObjects、earlySingletonObjects 和 singletonFactories 分别称为一级缓存、二级缓存和三级缓存。
spring 解决 aop 下的循环依赖流程如下:
这个图看着比较复杂,其实分开来看就比较简单了,上述操作中,1~8 是获取 service1 的流程,5.1~5.8 是获取 service2 的流程,5.5.1 是再次获取 service1 的流程,只不过在处理 service1 的初始化过程中,会触发 service2 的初始化过程,而 service2 的初始化时,又会依赖到 service1,因此才看着像是连在一起,比较复杂。
对上图的过程,这里说明如下(建议:如果觉得流程比较复杂,可以先看 1~8 的操作,再看 5.1~5.8 的操作,最后两者联合起来看,这样会清晰很多):
-
service1:获取service1,从一级缓存中获取,此时是获取不到的;
-
service1:创建service1的实例;
-
service1:获取需要注入的属性与方法(在原始对象上进行获取);
-
service1:如果开启了支持循环依赖的配置,就将service1放到三级缓存中(是否支持循环依赖,是可以配置的);
-
service1:对service1进行依赖注入,需要service2,然后就开始了service2的获取流程;
- 5.1
service2:获取service2,从一级缓存中获取,此时是获取不到的; - 5.2
service2:创建service2的实例; - 5.3
service2:获取需要注入的属性与方法(在原始对象上进行获取); - 5.4
service2:如果开启了支持循环依赖的配置,就将service2放到三级缓存中(是否支持循环依赖,是可以配置的); - 5.5
service2:对service2进行依赖注入,需要service1,然后就开始了service1的获取流程; - 5.5.1
service1: 获取service1,从一级缓存中获取,获取不到;此时发现service1正在创建中,于是继续从二、三级缓存中获取,最终从三级缓存中获取到了,将其放入二级缓存。从三级缓存获取的过程中,会判断service1是否需要进行 aop,然后开始 aop 操作,因此放入二级缓存中的是service1代理代理,提前进行 aop 是解决循环依赖的关键; - 5.6
service2:得到了service1后(这里的service1是代理对象),将其注入到service2中,接着对service2进行 aop,得到service2的代理对象; - 5.7
service2:如果支持循环依赖,先从一、二级缓存中再次获取service2,都未获取到,就使用当前service2(当前service2是代理对象); - 5.8
service2:将 service2 的代理对象放入一级缓存中,删除二、三级缓存,至此,service2初始化完成,注入的service1是代理对象,一级缓存中的service2也是代理对象; -
service1:回到service1的生命周期,拿到service2(这里的service2是代理对象)后,将其注入到service1,service1的依赖注入完成,进行初始化,这里会判断service1是否需要进行 aop,虽然service1是需要进行 aop 的,但由于在5.5.1已经进行过 aop 了,因此,这里直接返回(到这一步,service1还是原始对象);
-
service1:如果支持循环依赖,先从一级缓存中获取service1,获取不到;再从二缓存中获取service1,可以获取到(从5.5.1可知,二级缓存里是service1代理对象),返回;
-
service1:将二级缓存中获取的对象注册到一级缓存中,删除二、三级缓存,至此,service1初始化完成,注入的service2是代理对象,一级缓存中的service1也是代理对象。
以上流程,虽然步骤较多,但 service1 与 service2 的获取步骤是相同的,只要弄清了其中之一的获取流程,另一个 bean 的获取流程就很雷同了。
在上述流程中,还有两个数据结构需要说明下:
singletonsCurrentlyInCreation:类型为SetFromMap<String>,位于DefaultSingletonBeanRegistry,创建方式为Collections.newSetFromMap(new ConcurrentHashMap<>(16)),表明这是个由ConcurrentHashMap实现的 set,存储的是正在创建中的对象,判断当前对象是否在创建中就是通过查找当前对象是否在这个 set 中做到的;earlyProxyReferences:类型为ConcurrentHashMap<Object, Object>,位于AbstractAutoProxyCreator,存储的是提前进行 aop 的对象,如果一个对象提前进行了 aop,在后面再次 aop 时,会通过判断对象是否在earlyProxyReferences中而确定要不要进行 aop,以此来保证每个对象只进行一次 aop。
至此,spring 一共提供了 5 个数据结构来辅助解决循环依赖问题,总结如下:
| 结构 | 说明 |
|---|---|
singletonObjects |
一级缓存,类型为 ConcurrentHashMap<String, Object>,位于 DefaultSingletonBeanRegistry 类中,key 为 beanName,value 是完整的 spring bean,即完成属性注入、初始化的 bean,如果 bean 需要 aop,存储的就是代理对象 |
earlySingletonObjects |
二级缓存,类型为 HashMap<String, Object>,位于 DefaultSingletonBeanRegistry 类中,key 为 beanName,value 是实例化完成,但未进行依赖注入的 bean,如果 bean 需要 aop,这里存储的就是代理对象,只不过代理对象所持有的原始对象并未进行依赖注入 |
singletonFactories |
三级缓存,类型为 HashMap<String, ObjectFactory>,位于 DefaultSingletonBeanRegistry 类中,key 为 beanName,value 存储的是一个 lambda 表达式:() -> getEarlyBeanReference(beanName, mbd, bean),getEarlyBeanReference(xxx) 中的 bean 是刚创建完成的 java bean,没有进行 spring 依赖注入,也没进行 aop |
singletonsCurrentlyInCreation |
类型为 SetFromMap<String>,位于 DefaultSingletonBeanRegistry,创建方式为 Collections.newSetFromMap(new ConcurrentHashMap<>(16)),表明这是个由 ConcurrentHashMap 实现的 set,存储的是正在创建中的对象,可以用来判断当前对象是否在创建中 |
earlyProxyReferences |
类型为 ConcurrentHashMap<Object, Object>,位于 AbstractAutoProxyCreator,存储的是提前进行 aop 的对象,可以用来判断 bean 是否进行过 aop,保证每个对象只进行一次 aop |
以上就是 spring 解决循环依赖的完整流程了。
在正式分析源码前,我们首先模拟循环下依赖解决的过程,代码如下:
/**
* 准备一个类,内部有一个属性 Obj2
*/
public class Obj1 {
// 需要注入 obj2
private Obj2 obj2;
// 省略其他方法
...
}
/**
* 准备一个类,内部有一个属性 Obj1
*/
public class Obj2 {
// 需要注入 ob1
private Obj1 obj1;
// 省略其他方法
...
}
/**
* 准备Obj2的代理类,内部持有obj2的对象
*/
public class ProxyObj2 extends Obj2 {
// obj2代理类内部持有obj2的原始对象
private Obj2 obj2;
public ProxyObj2(Obj2 obj2) {
this.obj2 = obj2;
}
// 省略其他方法
...
}
/**
* 准备Obj1的代理类,内部持有obj1的对象
*/
public class ProxyObj1 extends Obj1 {
// obj2代理类内部持有obj1的原始对象
private Obj1 obj1;
public ProxyObj1(Obj1 obj1) {
this.obj1 = obj1;
}
// 省略其他方法
...
}
- 首先准备了两个类:
Obj1与Obj2, 其中Obj1有个属性为Obj2,Obj2中有个属性为Obj1; - 接着准备了
Obj1与Obj2的代理类ProxyObj1、ProxyObj2,并且ProxyObj1、ProxyObj2分别有一个属性:Obj1、Obj2; - 我们依旧以
new ObjX()模拟对象的创建; - 我们依旧以
objX.setObjX(xxx)模拟依赖注入; - 我们依旧以
new ProxyObjX(xxx)模拟代理对象的生成; - 我们依旧以
collection.add(xxx)模拟对象添加到容器中的过程;
我们模拟最终得到的结果为:
- 最终放入容器的对象分别是
proxyObj1,proxyObj2 - 注入到
obj1中的是proxyObj2,注入到obj2中的是proxyObj2
准备工作已经完成了,接下来我们就开始进行模拟了。
要求:
- Obj1 与 Obj2 必须严格按照 “创建 --> 属性注入 --> 生成代理对象 --> 保存到容器中” 的流程创建
- 两个对象的创建流程可以交替进行
目标:
- 最终放入容器的对象分别是
proxyObj1,proxyObj2 - 注入到
obj1中的是proxyObj2,注入到obj2中的是proxyObj2
代码如下:
public static main(String[] args) {
// 准备一个容器,这里保存的是完成上述生命周期的对象
// 1\. 如果元素是原始对象,则该对象已经完成了属性注入
// 2\. 如果元素是代理对象,则该对象持有的原有对象已经完成了属性注入
Collection<?> collection = new ArrayList();
// 1\. 创建 Obj1 对象
Obj1 obj1 = new Obj1();
// 接下来需要将obj2的代理对象注入到obj1中,但此时容器中并没有obj2的代理对象,于是切换到obj2的创建流程
// 一. 创建 Obj2 对象
Obj2 obj2 = new Obj2();
// 到这里,obj2需要注入obj1的代理对象,但此时容器中并没有obj2的代理对象,于是又要切到obj1的创建流程
}
在执行以上流程中 ,发现创建 Obj2 对象后,流程就进行不下去了:
obj1需要注入obj2的代理对象,但找不到,于是切换到obj2的创建流程;obj2需要注入obj1的代理对象,但找不到,于是切换到obj1的创建流程;obj1需要注入obj2的代理对象,但找不到,于是切换到obj2的创建流程;- ...
如此循环往复。
模拟结果:未达到预期目标,本次模拟宣告失败。
要求:
- Obj1 与 Obj2 必须以下两种流程之一创建:
- “创建 --> 属性注入 --> 生成代理对象 --> 保存到容器中” 的流程创建
- “创建 (原始对象)--> 生成代理对象 --> 对原始对象进行属性注入 --> 将代理对象保存到容器中” 的流程创建
- 两个对象的创建流程可以交替进行
目标:
- 最终放入容器的对象分别是
proxyObj1,proxyObj2 - 注入到
obj1中的是proxyObj2,注入到obj2中的是proxyObj2
示例代码如下:
public static main(String[] args) {
// 准备一个容器,这里保存的是完成上述生命周期的对象
// 1\. 如果元素是原始对象,则该对象已经完成了属性注入
// 2\. 如果元素是代理对象,则该对象持有的原有对象已经完成了属性注入
Collection<?> collection = new ArrayList();
// 1\. 创建 Obj1 对象
Obj1 obj1 = new Obj1();
// 接下来需要将obj2的代理对象注入到obj1中,但此时容器中并没有obj2的代理对象,于是切换到obj2的创建流程
// 一. 创建 Obj2 对象
Obj2 obj2 = new Obj2();
// 2\. 对 Obj1 提前代理
ProxyObj1 proxyObj1 = new ProxyObj1(obj1);
// 二. 将 proxyObj1 注入到 obj2 中
obj2.setObj1(proxyObj1);
// 三. 生成 obj2的代理对象
ProxyObj2 proxyObj2 = new ProxyObj2(obj2);
// 四. proxyObj2 已经走完了完整的生命周期,将代理对象添加到容器时
collection.add(proxyObj2);
// 此时容器中已经有 obj2 的代理对象了,继续obj1的生命周期
// 3\. 将 proxyObj2 注入到 obj1 中
obj1.setObj2(proxyObj2);
// 4\. proxyObj1 已经走完了完整的生命周期,将代理对象添加到容器时
collection.add(proxyObj1);
}
上面的代码中,obj1 的流程用 “1,2,3,4” 标识,obj2 的流程用 “一,二,三,四” 标识,两者流程如下:
- obj1:“创建 (原始对象)--> 生成代理对象 --> 对原始对象进行属性注入 --> 将代理对象保存到容器中”
- obj2:“创建 --> 属性注入 --> 生成代理对象 --> 保存到容器中”
最终两者都存入了容器中,达到了预期的目标。
对比上面两个模拟代码,发现模拟 2 之 所以能达到预期目标,主要是因为在注入 obj2 的 obj1 属性时,提前生成了 obj1 的代理对象 proxyObj1,使得 obj2 能完成整个创建流程。这里再次证明,提供进行 aop 对循环依赖的解决起到至关重要的作用!
限于篇幅,本文就先到这里了,本文主要分析了循环依赖的产生,介绍了 spring 解决循环依赖的步骤,最后通过两段代码模拟了循环依赖的解决,下一篇文章我们将从 spring 源码分析 spring 是如何解决循环依赖的。
本文原文链接:https://my.oschina.net/funcy/blog/4659555 ,限于作者个人水平,文中难免有错误之处,欢迎指正!原创不易,商业转载请联系作者获得授权,非商业转载请注明出处。
示例代码如下:
public static main(String[] args) {
// 准备一个容器,这里保存的是完成上述生命周期的对象
// 1\. 如果元素是原始对象,则该对象已经完成了属性注入
// 2\. 如果元素是代理对象,则该对象持有的原有对象已经完成了属性注入
Collection<?> collection = new ArrayList();
// 1\. 创建 Obj1 对象
Obj1 obj1 = new Obj1();
// 接下来需要将obj2的代理对象注入到obj1中,但此时容器中并没有obj2的代理对象,于是切换到obj2的创建流程
// 一. 创建 Obj2 对象
Obj2 obj2 = new Obj2();
// 2\. 对 Obj1 提前代理
ProxyObj1 proxyObj1 = new ProxyObj1(obj1);
// 二. 将 proxyObj1 注入到 obj2 中
obj2.setObj1(proxyObj1);
// 三. 生成 obj2的代理对象
ProxyObj2 proxyObj2 = new ProxyObj2(obj2);
// 四. proxyObj2 已经走完了完整的生命周期,将代理对象添加到容器时
collection.add(proxyObj2);
// 此时容器中已经有 obj2 的代理对象了,继续obj1的生命周期
// 3\. 将 proxyObj2 注入到 obj1 中
obj1.setObj2(proxyObj2);
// 4\. proxyObj1 已经走完了完整的生命周期,将代理对象添加到容器时
collection.add(proxyObj1);
}
上面的代码中,obj1 的流程用 “1,2,3,4” 标识,obj2 的流程用 “一,二,三,四” 标识,两者流程如下:
- obj1:“创建 (原始对象)--> 生成代理对象 --> 对原始对象进行属性注入 --> 将代理对象保存到容器中”
- obj2:“创建 --> 属性注入 --> 生成代理对象 --> 保存到容器中”
最终两者都存入了容器中,达到了预期的目标。
对比上面两个模拟代码,发现模拟 2 之 所以能达到预期目标,主要是因为在注入 obj2 的 obj1 属性时,提前生成了 obj1 的代理对象 proxyObj1,使得 obj2 能完成整个创建流程。这里再次证明,提供进行 aop 对循环依赖的解决起到至关重要的作用!
限于篇幅,本文就先到这里了,本文主要分析了循环依赖的产生,介绍了 spring 解决循环依赖的步骤,最后通过两段代码模拟了循环依赖的解决,下一篇文章我们将从 spring 源码分析 spring 是如何解决循环依赖的。
本文原文链接:https://my.oschina.net/funcy/blog/4659555 ,限于作者个人水平,文中难免有错误之处,欢迎指正!原创不易,商业转载请联系作者获得授权,非商业转载请注明出处。






