spring 循环依赖 分享笔记


一、什么是循环依赖?
A类的属性指向了B类的对象,B类的属性又指向了A类的对象。这就产生了循环依赖。

// A依赖了B
class A{
public B b;
}

// B依赖了A
class B{
public A a;
}

 


二、我们自己写代码是否会产生循环依赖?
当然会呀。 我们是如何解决的。

但是,在Spring中循环依赖就是一个问题了,为什么?

因为,在Spring中,一个对象并不是简单new出来了,而是会经过一系列的Bean的生命周期,就是因为Bean的生命周期所以才会出现循环依赖问题。当然,在Spring中,出现循环依赖的场景很多,有的场景Spring自动帮我们解决了,而有的场景则需要程序员来解决,下文详细来说。

 


看一下代码的演示吧。
CircularDependencyTest 类 main方法,屏蔽掉spring容器相关的代码。

我们再来看一下,spring循环依赖运行的结果
CircularDependencyTest 类 main方法,屏蔽掉手动new类的代码,启动spring相关代码。

 

三、要明白Spring中的循环依赖,得先明白Spring中Bean的生命周期。
1、要理解bean的生命周期首先记住两个概念
请读者一定记住两个概念——spring bean(一下简称bean)和对象;
1、spring bean——受spring容器管理的对象,可能经过了完整的spring bean生命周期(为什么是可能?难道还有bean是没有经过bean生命周期的?答案是有的,具体我们后面文章分析),最终存在spring容器当中;一个bean一定是个对象
2、对象——任何符合java语法规则实例化出来的对象,但是一个对象并不一定是spring bean;

2、接下来我们简单过一下spring中Bean的生命周期:
之前咱们一起手写过一个springBean创建过程给大家。
看一下spring bean 创建的流程图,因为这不是我今天将的重点,我们就简单过一下。

 

 

四、我把Bean的生命周期简化一下,因为此这五步骤与循环依赖相关
CircularDependencyA的生命周期
1、实例化CircularDependencyA(new CircularDependencyA()),原始对象
2、填充circularDependencyB属性;
3、填充其他属性;
4、初始化后;
5、添加单例池;

五、那么如何产生循环依赖呢?循环依赖的产生如下:
当CircularDependencyA的生命周期执行到第2步需要注入circularDependencyB 属性时,
1、此时去单例池中找circularDependencyB 所对应的 bean。肯定是找不到的。
2、spring会去创建 CircularDependencyB 对应的Bean,
3、执行CircularDependencyB的生命周期

所以我们接下来的流程如下:
ps:
CircularDependencyA的生命周期 第二步需要调整 增加箭头后面的内容;
画出CircularDependencyB的生命周期 流程;

CircularDependencyA的生命周期
1、实例化CircularDependencyA(new CircularDependencyA()),原始对象
2、填充circularDependencyB属性;--->从单例池中去找circularDependencyB对应的Bean-->找不到--->创建circularDependencyB对应的Bean
3、填充其他属性;
4、初始化后;
5、添加单例池;

CircularDependencyB的生命周期
1、实例化CircularDependencyB(new CircularDependencyB()),原始对象
2、填充circularDependencyA属性;
3、填充其他属性;
4、初始化后;
5、添加单例池;

 

六、那么在执行 CircularDependencyB的生命周期的第2步时,需要注入CircularDependencyA属性时,
1、此时去单例池中找CircularDependencyA 所对应的 bean。肯定是找不到的。
2、spring会去创建 CircularDependencyA 对应的Bean,
3、执行CircularDependencyA的生命周期
4、我们一起来看一下 A B 类的虚幻依赖图。 A创建时-->需要B-->B去创建-->需要A,从而产生了循环依赖。

PS:
CircularDependencyB的生命周期的第二步调整,增加箭头后面的内容;

CircularDependencyB的生命周期
1、实例化CircularDependencyB(new CircularDependencyB()),原始对象
2、填充circularDependencyA属性;--->从单例池中去找circularDependencyA对应的Bean-->找不到--->创建circularDependencyA对应的Bean
3、填充其他属性;
4、初始化后;
5、添加单例池;

 

七、现在看见问题产生了,如果各位是spring框架的开发者,那么应该如何打破循环依赖呢?
我们可以通过增加一个Map 二级缓存 ;
我们来看一下流程图:

 

八、如果我这样来修改CircularDependencyA的创建过程,
1、增加一个map arzMap<BeanName,原始对象>;
2、当CircularDependencyA实例化后 将原始对象 添加arzMap中;
3、继续执行CircularDependencyA的生命周期,当指定到第二步的时候,发现需要填充circularDependencyB属性,
4、此时去单例池中找circularDependencyB 所对应的 bean。肯定是找不到的。
5、spring会去创建 CircularDependencyB 对应的Bean,
6、执行CircularDependencyB的生命周期

ps:
CircularDependencyA的生命周期第一步,添加原始对象箭头后面的内容。

CircularDependencyA的生命周期
1、实例化CircularDependencyA(new CircularDependencyA()),CircularDependencyA原始对象-->ArzMap<BeanName,CircularDependencyA原始对象>
2、填充circularDependencyB属性;--->从单例池中去找circularDependencyB对应的Bean-->找不到--->创建circularDependencyB对应的Bean
3、填充其他属性;
4、初始化后;
5、添加单例池;

 

九、CircularDependencyB的新创建过程介绍,
1、增加一个map arzMap<BeanName,CircularDependencyB原始对象>;
2、当B 实例化后 添加 arzMap中;
3、继续执行CircularDependencyB的生命周期,当指定到第二步的时候,发现需要填充circularDependencyA属性,
4、此时去单例池中找circularDependencA 所对应的 bean。肯定是找不到的。
5、此时不去创建、而是去ArzMap中去找,找到了就不用创建circularDependencA 的bean。
6、ArzMap找不到就需要创建circularDependencA 的bean了。

ps:
CircularDependencyB的生命周期 第二步 在”找不到“后添加 从”从ArzMap中查找----> 找不到“内容。

CircularDependencyB的生命周期
1、实例化CircularDependencyB(new CircularDependencyB()),CircularDependencyB原始对象-->ArzMap<BeanName,CircularDependencyB原始对象>
2、填充circularDependencyA属性;--->从单例池中去找circularDependencyA对应的Bean-->找不到--->从ArzMap中查找----> 找不到-->创建circularDependencyA对应的Bean
3、填充其他属性;
4、初始化后;
5、添加单例池;


十,同样我们要调整一下CircularDependencyA的创建过程:

ps:
CircularDependencyA的生命周期 第二步 在”找不到“后添加”从ArzMap中查找----> 找不到-->“

CircularDependencyA的生命周期
1、实例化CircularDependencyA(new CircularDependencyA()),CircularDependencyA原始对象-->ArzMap<BeanName,CircularDependencyA原始对象>
2、填充circularDependencyB 属性;--->从单例池中去找circularDependencyB 对应的Bean-->找不到--->从ArzMap中查找----> 找不到-->创建circularDependencyB 对应的Bean
3、填充其他属性;
4、初始化后;
5、添加单例池;


十一、目前增加了二级缓存后,A、B创建的过程就如下:
1、重新过一遍A 和 B的生命周期流程;
2、再看一下流程图;

CircularDependencyA的生命周期
1、实例化CircularDependencyA(new CircularDependencyA()),CircularDependencyA原始对象-->ArzMap<BeanName,CircularDependencyA原始对象>
2、填充circularDependencyB 属性;--->从单例池中去找circularDependencyB 对应的Bean-->找不到--->从ArzMap中查找----> 找不到-->创建circularDependencyB 对应的Bean
3、填充其他属性;
4、初始化后;
5、添加单例池;

CircularDependencyB的生命周期
1、实例化CircularDependencyB(new CircularDependencyB()),CircularDependencyB原始对象-->ArzMap<BeanName,CircularDependencyB原始对象>
2、填充circularDependencyA属性;--->从单例池中去找circularDependencyA对应的Bean-->找不到--->从ArzMap中查找----> 找不到-->创建circularDependencyA对应的Bean
3、填充其他属性;
4、初始化后;
5、添加单例池;


十一、我们思考一个问题;
我在将A对象赋值给B对象a属性时,A对象是一个原始的对象,是没有进行过属性填充的,这样对后面使用B对象的a属性是否会有问题?
不会有问题。
例如我们常写的的代码。

A a = new A();
B b = new B();
b.a = a;

a.b = b;
a.x = 1;
a.y = 2;

 

十二、那么现在明明二级缓存就可以打破整个循环依赖,spring什么会用到三级缓存呢? 有谁知道吗?
其实这和我们生命周期的第四步 初始化后有关系,因为在这步会产生代理对象。
如果A的原始对象注入给B的属性之后,A的原始对象进行了AOP产生了一个代理对象,此时就会出现,对于A而言,它的Bean对象其实应该是AOP之后的代理对象,而B的a属性对应的并不是AOP之后的代理对象,这就产生了冲突。
B依赖的A和最终的A不是同一个对象。
我们来调整一下CircularDependencyA的生命周期。 在生命周期的第四步 初始化后 会执行AOP 产生代理对象。
ps:
CircularDependencyA的生命周期 第四步 增加((AOP)---产生CircularDependencyA的代理对象;)


CircularDependencyA的生命周期
1、实例化CircularDependencyA(new CircularDependencyA()),CircularDependencyA原始对象-->ArzMap<BeanName,CircularDependencyA原始对象>
2、填充circularDependencyB 属性;--->从单例池中去找circularDependencyB 对应的Bean-->找不到--->从ArzMap中查找----> 找不到-->创建circularDependencyB 对应的Bean
3、填充其他属性;
4、初始化后(AOP)---产生CircularDependencyA的代理对象;
5、添加单例池;

十三、我们来看一下源码:
源码:
org.springframework.beans.factory.support.AbstractBeanFactory#createBean 创建Bean
org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#createBean(java.lang.String, org.springframework.beans.factory.support.RootBeanDefinition, java.lang.Object[])----doCreateBean

org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#doCreateBean

 

通过源码 我们可以看到 spring 是先产生原始对象、在去对原始对象进行属性填充、再进行AOP的。
spring AOP
1、先有原始对象;
2、对原始对象填充属性;
3、AOP 原始对象---》代理对象(代理对象方式是可以用到原始对象的)
target:原始对象


十四、我们一来跑一下代码AOP的代码吧:
演示CircularDependencyA AOP的产生情况:
0、开启com.luban.app.CircularDependencyConfig @EnableAspectJAutoProxy 注解
1、为了更方便大家看明白,我先简单一点,把循环依赖去掉,将circularDependencyB类的属性的代码全部注掉;。
2、看 org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#doCreateBean 方法中 代码exposedObject = initializeBean(beanName, exposedObject, mbd); 执行的结果; 可以看出是代理对象。

 


十五:这里有一个知识点: 为什么代理对象的属性为null:
因为:
1、属性注入是注入到原始对象上,
2、而代理对象通过target指向原始对象,来使用属性,因此代理对象的属性不用赋值;
3、代理对象真正关心的是方法 而不是属性,


十六:好接下来我们重新梳理一下CircularDependencyA流程。
CircularDependencyA的生命周期
1、实例化CircularDependencyA(new CircularDependencyA()),CircularDependencyA原始对象-->ArzMap<BeanName,CircularDependencyA原始对象>
2、填充circularDependencyB 属性;--->从单例池中去找circularDependencyB 对应的Bean-->找不到--->从ArzMap中查找----> 找不到-->创建circularDependencyB 对应的Bean
3、填充其他属性;
4、初始化后(AOP)---产生CircularDependencyA的代理对象;
5、添加单例池;

我们会发现 在第一步产生了 一个原始对象、在第四步又产生了一个代理对象, 那么放入单例池中的是哪个对象呢?
对,是代理对象。


十七、如果说我们把CircularDependencyA 的代理对象放入单例池中,那么也就是别的bean要用的CircularDependencyA属性都应该是代理对象。但是我们在刚刚2、填充circularDependencyB类中赋值A属性时,给的确实原始对象,这样是有问题的。这个可以理解吧?
正确的做法应该是什么呢? A的代理对象添加到单例池、A的代理对象赋值给B类的a属性上。


十八、我们该如何解决 这个问题呢??
这个问题很好解决,我们可以思考一下步骤
1、如果我们在创建A原始对象后,先进行AOP,然后将A的代理对象放入ArzMapmap中
2、这样B生命周期第2步,从ArzMap中获取的就是A的代理对象了。进行属性赋值
3、A生命周期完成后,把代理对方放入到单例池中

调整后的 CircularDependencyA的生命周期
1、实例化CircularDependencyA(new CircularDependencyA()),CircularDependencyA原始对象-->AOP ---->CircularDependencyA代理对象 --->ArzMap<BeanName,CircularDependencyA代理对象>
2、填充circularDependencyB 属性;--->从单例池中去找circularDependencyB 对应的Bean-->找不到--->从ArzMap中查找----> 找不到-->创建circularDependencyB 对应的Bean
3、填充其他属性;
4、初始化后(AOP)---产生CircularDependencyA的代理对象;
5、添加单例池;

CircularDependencyB的生命周期
1、实例化CircularDependencyB(new CircularDependencyB()),CircularDependencyB原始对象-->-->AOP ---->CircularDependencyB代理对象 ArzMap<BeanName,CircularDependencyB代理对象>
2、填充circularDependencyA属性;--->从单例池中去找circularDependencyA对应的Bean-->找不到--->从ArzMap中查找----> 找不到-->创建circularDependencyA对应的Bean
3、填充其他属性;
4、初始化后;
5、添加单例池;

十九、这里有问题了?
1、如果在生命周期的第一步进行AOP了,在第四步就不要进行AOP了,Spring源码是怎么判断的?
我来看一下,spring正常的源码流程是在第四步进行AOP,在什么情况下,会进行在第一步提前AOP呢? 这是一种特殊情况。
当出现什么情况下???----------》CircularDependencyA 提前进行AOP
结论:当CircularDependencyA出现了循环依赖的时候,才需要对CircularDependencyA提前进行AOP。
那么我们就发现这个问题,转变成了如何判断CircularDependencyA是否出现了循环依赖的问题,并且是在 CircularDependencyA的生命周期第一步,就要进行判断,我们发现这个很难判断。
A和B的声明周期的十步中,有一步特别容易判断,
是在CircularDependencyB的生命周期的第二步的时候,就特别容易判断出来。
我们这样考虑一下,
如果B的第二步是,在单例池中找不到A的bean时,我们可以做一件事情, 去发现A是不是正在创建中,我们就可以认为A出现了循环依赖。
这是我们不应该在A的第一步进行AOP。
我们再来看一下调整一下生命周期的流程。


PS:
CircularDependencyB的生命周期 第一步去掉 生成代理对象 “-->AOP ---->CircularDependencyA代理对象 ”,ArzMapMAP中存储 A原始对象。
CircularDependencyB的生命周期 第一步去掉 生成代理对象 “-->AOP ---->CircularDependencyB代理对象 ”,ArzMapMAP中存储 B原始对象。
CircularDependencyB的生命周期 第二步”好不到“ 后添加 ”CircularDependencyA创建中-->代表CircularDependencyA出现了循环依赖“。

调整后的 CircularDependencyA的生命周期
1、实例化CircularDependencyA(new CircularDependencyA()),CircularDependencyA原始对象 --->ArzMap<BeanName,CircularDependencyA原始对象>
2、填充circularDependencyB 属性;--->从单例池中去找circularDependencyB 对应的Bean-->找不到--->从ArzMap中查找----> 找不到-->创建circularDependencyB 对应的Bean
3、填充其他属性;
4、初始化后(AOP)---产生CircularDependencyA的代理对象;
5、添加单例池;

CircularDependencyB的生命周期
1、实例化CircularDependencyB(new CircularDependencyB()),CircularDependencyB原始对象-->ArzMap<BeanName,CircularDependency原始对象>
2、填充circularDependencyA属性;--->从单例池中去找circularDependencyA对应的Bean-->找不到--->CircularDependencyA创建中-->代表CircularDependencyA出现了循环依赖--->从ArzMap中查找---->找不到-->创建circularDependencyA对应的Bean
3、填充其他属性;
4、初始化后;
5、添加单例池;


二十、那么我们如何发现CircularDependencyA是否在创建中呢?
在第一步实例化之间,先将类加入到一个createingSet(singletonsCurrentlyInCreation)中,
这样我们再B的第二步中,在单例找不到bean后,直接去createingSet中看看是不是有这个类,就知道是否在创建中。

代码见:补充材料一;

ps: 分别在A和B的生命周期中添加第0步。

调整后的 CircularDependencyA的生命周期
0、createingSet.add(CircularDependencyA)
1、实例化CircularDependencyA(new CircularDependencyA()),CircularDependencyA原始对象 --->ArzMap<BeanName,CircularDependencyA原始对象>
2、填充circularDependencyB 属性;--->从单例池中去找circularDependencyB 对应的Bean-->找不到--->从ArzMap中查找----> 找不到-->创建circularDependencyB 对应的Bean
3、填充其他属性;
4、初始化后(AOP)---产生CircularDependencyA的代理对象;
5、添加单例池;

CircularDependencyB的生命周期
0、createingSet.add(CircularDependencyB)
1、实例化CircularDependencyB(new CircularDependencyB()),CircularDependencyB原始对象-->ArzMap<BeanName,CircularDependency原始对象>
2、填充circularDependencyA属性;--->从单例池中去找circularDependencyA对应的Bean-->找不到--->CircularDependencyA创建中-->代表CircularDependencyA出现了循环依赖--->从ArzMap中查找----> 找不到-->创建circularDependencyA对应的Bean
3、填充其他属性;
4、初始化后;
5、添加单例池;

 

二十一、我们来进一步看一下,在CircularDependencyB的生命周期第二步中的”CircularDependencyA创建中“就可以转化成”判断“createingSet是否包含CircularDependencyA”,如果存在,就代表发生了循环依赖,在去执行CircularDependencyA的AOP,产生CircularDependencyA的代理对象,赋值给CircularDependencyB中的a属性。
PS:
CircularDependencyB的生命周期 第二步 “CircularDependencyA创建中” 变成 “createing包含CircularDependencyA”,
CircularDependencyB的生命周期 第二步 “代表CircularDependencyA出现了循环依赖” 后添加 “--->CircularDependencyA AOP----CircularDependencyA代理对象”,

CircularDependencyB的生命周期
0、createingSet.add(CircularDependencyB)
1、实例化CircularDependencyB(new CircularDependencyB()),CircularDependencyB原始对象-->ArzMap<BeanName,CircularDependency原始对象>
2、填充circularDependencyA属性;--->从单例池中去找circularDependencyA对应的Bean-->找不到--->createing包含CircularDependencyA-->代表CircularDependencyA出现了循环依赖--->CircularDependencyA AOP----CircularDependencyA代理对象
3、填充其他属性;
4、初始化后;
5、添加单例池;

 

二十二、我们再来看一下改后的A、B创建的十步, 我们可以知道CircularDependencyA的AOP不应该是在CircularDependencyA生命周期的第一步,
把A第一步后面的都去掉,对A的第一步而言,还是只产生一个CircularDependencyA的原始对象。
然后执行第二步,去填充circularDependencyB的属性,在填充属性时,创建 CircularDependencyB。
在执行CircularDependencyB生命周期第二步,填充CircularDependencyA的时候,发现CircularDependencyA出现了循环依赖,
就针对CircularDependencyA进行AOP,得到一个CircularDependencyA的代理对象。
赋值给CircularDependencyB的a属性。

调整后的 CircularDependencyA的生命周期
0、createingSet.add(CircularDependencyA)
1、实例化CircularDependencyA(new CircularDependencyA()),CircularDependencyA原始对象
2、填充circularDependencyB 属性;--->从单例池中去找circularDependencyB 对应的Bean-->找不到--->从ArzMap中查找----> 找不到-->创建circularDependencyB 对应的Bean
3、填充其他属性;
4、初始化后(AOP)---产生CircularDependencyA的代理对象;
5、添加单例池;


CircularDependencyB的生命周期
0、createingSet.add(CircularDependencyB)
1、实例化CircularDependencyB(new CircularDependencyB()),CircularDependencyB原始对象-->AOP ---->CircularDependencyB代理对象->ArzMap<BeanName,CircularDependencyB代理对象>
2、填充circularDependencyA属性;--->从单例池中去找circularDependencyA对应的Bean-->找不到--->createing包含CircularDependencyA-->代表CircularDependencyA出现了循环依赖--->CircularDependencyA AOP----CircularDependencyA代理对象
3、填充其他属性;
4、初始化后;
5、添加单例池;


二十三、但是我们现在又遇到一个问题,我在B的生命周期第二步创建A的代理对象,如何放入到单例池中呢?
我是否可以在B生命周期第二步产生完A的代理对象后,把他放入单例池中呢?
(不能,代理对象中的原始对象的属性还没有填充值,不是一个完整的Bean,不能把没有进行完依赖注入的bean放入单例池中)


二十四、接下来我们再思考,如果我们同样在有一个C类也同样注入A的属性,而A类又注入了C类作为属性,那么就会产生如下问题。
1、在执行A 生命周期第二步的时候,我们会需要B类,在B类的第二步会产生创建一个A类的代理对象;
2、在执行A 生命周期第三部的时候,我们需要C类,在C类生命周期的第二步同样会产生一个A类的代理对象,
3、而B类产生的A类代理对象和C类产生的A代理对象有不是同一个了。
4、而A类是单例的,必须保证给B类和给C类的是同一个代理对象。所以这有问题

PS:
在CircularDependencyA 生命周期 第二步后,添加“2、填充circularDependencyC 属性;--->从单例池中去找circularDependencyC 对应的Bean-->找不到--->从ArzMap中查找----> 找不到-->创建circularDependencyC 对应的Bean”
增加一个CircularDependencyC的生命周期 创建流程。


调整后的 CircularDependencyA的生命周期
0、createingSet.add(CircularDependencyA)
1、实例化CircularDependencyA(new CircularDependencyA()),CircularDependencyA原始对象-->ArzMap<BeanName,CircularDependencyA原始对象>
2、填充circularDependencyB 属性;--->从单例池中去找circularDependencyB 对应的Bean-->找不到--->从ArzMap中查找----> 找不到-->创建circularDependencyB 对应的Bean
2、填充circularDependencyC 属性;--->从单例池中去找circularDependencyC 对应的Bean-->找不到--->从ArzMap中查找----> 找不到-->创建circularDependencyC 对应的Bean
3、填充其他属性;
4、初始化后(AOP)---产生CircularDependencyA的代理对象;
5、添加单例池;


CircularDependencyB的生命周期
0、createingSet.add(CircularDependencyB)
1、实例化CircularDependencyB(new CircularDependencyB()),CircularDependencyB原始对象-->ArzMap<BeanName,CircularDependencyB原始对象>
2、填充circularDependencyA属性;--->从单例池中去找circularDependencyA对应的Bean-->找不到--->createing包含CircularDependencyA-->代表CircularDependencyA出现了循环依赖--->CircularDependencyA AOP----CircularDependencyA代理对象
3、填充其他属性;
4、初始化后;
5、添加单例池;


CircularDependencyC的生命周期
0、createingSet.add(CircularDependencyC)
1、实例化CircularDependencyC(new CircularDependencyC()),CircularDependencyC原始对象-->ArzMap<BeanName,CircularDependencyC原始对象>
2、填充circularDependencyA属性;--->从单例池中去找circularDependencyA对应的Bean-->找不到--->createing包含CircularDependencyA-->代表CircularDependencyA出现了循环依赖--->CircularDependencyA AOP----CircularDependencyA代理对象
3、填充其他属性;
4、初始化后;
5、添加单例池;

===================================

二十四、如何解决我上面说的问题呢?
这里我们需要一个map,而这个map才是 SPRING 第二级缓存earlySingletonObjects, 我们先暂定里面存储的是AOP后的代理对象。
1、在B生命周期第二步,我们将生成的A的代理对象,放入二级缓存中。
2、在C生命周期第二步,我们可以让其从单例中没有获取到时,先从2级缓存中获取,如果没有再去创建代理对象。 这样就保证了A只有一个代理对象。
3、二级缓存解决了一个类同时和另外两个类循环依赖的问题。

ps:
CircularDependencyB的生命周期 第二步
第一个“找不到”后添加 “第二级缓存 --》找不到--》”。
“CircularDependencyA代理对象-” 后添加 “第二级缓存 <circularDependencyA,CircularDependencyA代理对象>”
CircularDependencyC的生命周期 第二步 可以简化为:
第一个“找不到” 后添加 “--->第二级缓存----CircularDependencyA代理对象”


调整后的 CircularDependencyA的生命周期
0、createingSet.add(CircularDependencyA)
1、实例化CircularDependencyA(new CircularDependencyA()),CircularDependencyA原始对象-->ArzMap<BeanName,CircularDependencyA原始对象>
2、填充circularDependencyB 属性;--->从单例池中去找circularDependencyB 对应的Bean-->找不到--->从ArzMap中查找----> 找不到-->创建circularDependencyB 对应的Bean
2、填充circularDependencyC 属性;--->从单例池中去找circularDependencyB 对应的Bean-->找不到--->从ArzMap中查找----> 找不到-->创建circularDependencyB 对应的Bean
3、填充其他属性;
4、初始化后(AOP)---产生CircularDependencyA的代理对象;
5、添加单例池;


CircularDependencyB的生命周期
0、createingSet.add(CircularDependencyB)
1、实例化CircularDependencyB(new CircularDependencyB()),CircularDependencyB原始对象-->ArzMap<BeanName,CircularDependencyB原始对象>
2、填充circularDependencyA属性;--->从单例池中去找circularDependencyA对应的Bean-->找不到--->第二级缓存 --》找不到--》createing包含CircularDependencyA-->代表CircularDependencyA出现了循环依赖--->CircularDependencyA AOP----CircularDependencyA代理对象--- >第二级缓存 <circularDependencyA,CircularDependencyA代理对象>
3、填充其他属性;
4、初始化后;
5、添加单例池;


CircularDependencyC的生命周期
0、createingSet.add(CircularDependencyC)
1、实例化CircularDependencyC(new CircularDependencyC()),CircularDependencyC原始对象-->ArzMap<BeanName,CircularDependencyC原始对象>
2、填充circularDependencyA属性;--->从单例池中去找circularDependencyA对应的Bean-->找不到--->第二级缓存----CircularDependencyA代理对象
3、填充其他属性;
4、初始化后;
5、添加单例池;

 

二十五、有了第二级缓存后,A的声明周期可以调整如下
因为A的声明周期会在第四步进行AOP,那么可以再AOP之前先从二级缓存中获取一下是否有代理对象,如果获取到,就给到单例池。
如果没有就进行CircularDependencyA的AOP,我们先给CircularDependencyA的生命周期,添加一个4.5步骤。

跑一下代码:
分别是 只有A类么有产生循环依赖时 需要AOP的情况;
A,B类产生循环依赖后时,需要AOP的情况;

ps:
调整后的 CircularDependencyA的生命周期 第四步后,增加4.5步。


调整后的 CircularDependencyA的生命周期
0、createingSet.add(CircularDependencyA)
1、实例化CircularDependencyA(new CircularDependencyA()),CircularDependencyA原始对象-->ArzMap<BeanName,CircularDependencyA原始对象>
2、填充circularDependencyB 属性;--->从单例池中去找circularDependencyB 对应的Bean-->找不到--->从ArzMap中查找----> 找不到-->创建circularDependencyB 对应的Bean
2、填充circularDependencyC 属性;--->从单例池中去找circularDependencyB 对应的Bean-->找不到--->从ArzMap中查找----> 找不到-->创建circularDependencyB 对应的Bean
3、填充其他属性;
4、初始化后(AOP)---产生CircularDependencyA的代理对象;
4.5从第二级缓存中,获取CircularDependencyA的代理对象。
5、添加单例池;

 

二十六、我们还有两个问题没有解解决?
1、在A类执行到第四步的时候,如何知道A类已经提前进行了AOP?
源码中在执行到了第四步一定会判断,A类是不是进行了AOP?因为毕竟咋B类生命周期的第二步已经对A类进行了AOP。
这个4.5步 我们后面分析。

调整后的 CircularDependencyA的生命周期
0、createingSet.add(CircularDependencyA)
1、实例化CircularDependencyA(new CircularDependencyA()),CircularDependencyA原始对象-->ArzMap<BeanName,CircularDependencyA原始对象>
2、填充circularDependencyB 属性;--->从单例池中去找circularDependencyB 对应的Bean-->找不到--->从ArzMap中查找----> 找不到-->创建circularDependencyB 对应的Bean
2、填充circularDependencyC 属性;--->从单例池中去找circularDependencyB 对应的Bean-->找不到--->从ArzMap中查找----> 找不到-->创建circularDependencyB 对应的Bean
3、填充其他属性;
4、初始化后(AOP)---产生CircularDependencyA的代理对象(判断:CircularDependencyA是否进行了AOP?);
4.5从第二级缓存中,获取CircularDependencyA的代理对象。
5、添加单例池;

 

 

2、在B类的第二步要进行A对象的AOP,是需要A的原始对象的。 那么该如何获得A的原始对象呢?
这就引出了我们的第三级缓存,也就是一开始我们的加 ArzMap<BeanName,CircularDependencyA原始对象> ,但是真实名字叫singletonFactories,我们暂定 里面的key是beanName, value 存储的是 原始对象。
1、首先在A进行第一步时,创建了A原始对象后,把他放入到第三级缓存中,这和我们一开始假设的ArzMap是很像的。 key是 beanName,Value 是原始对象;
2、那么当B发现 A产生了循环依赖,需要进行AOP的时候,就可以从第三级缓存中获取原始对象,
3、有了A的原始对象后,就可以进行对A进行AOP了,
4、所以第三级缓存 主要存的就是我们的类的原始对象。

PS:A的生命周期第一步调整; 把“arzMap” 变成了 “第三级缓存Map”
B的生命周期第二步调整: 在“代表CircularDependencyA出现了循环依赖” 后增加 “从第三级缓存中获取”

调整后的 CircularDependencyA的生命周期
0、createingSet.add(CircularDependencyA)
1、实例化CircularDependencyA(new CircularDependencyA()),CircularDependencyA原始对象-->第三级缓存<BeanName,CircularDependencyA原始对象>
2、填充circularDependencyB 属性;--->从单例池中去找circularDependencyB 对应的Bean-->找不到--->从ArzMap中查找----> 找不到-->创建circularDependencyB 对应的Bean
2、填充circularDependencyC 属性;--->从单例池中去找circularDependencyB 对应的Bean-->找不到--->从ArzMap中查找----> 找不到-->创建circularDependencyB 对应的Bean
3、填充其他属性;
4、初始化后(AOP)---产生CircularDependencyA的代理对象;
4.5从第二级缓存中,获取CircularDependencyA的代理对象。
5、添加单例池;

CircularDependencyB的生命周期
0、createingSet.add(CircularDependencyB)
1、实例化CircularDependencyB(new CircularDependencyB()),CircularDependencyB原始对象-->ArzMap<BeanName,CircularDependencyB原始对象>
2、填充circularDependencyA属性;--->从单例池中去找circularDependencyA对应的Bean-->找不到--->第二级缓存 --》找不到--》createing包含CircularDependencyA-->代表CircularDependencyA出现了循环依赖--->从第三级缓存中获取--->CircularDependencyA AOP----CircularDependencyA代理对象--- >第二级缓存 <circularDependencyA,CircularDependencyA代理对象>
3、填充其他属性;
4、初始化后;
5、添加单例池;


二十七、看源码
看一下源码流程:
创建一个Bean
1、org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#ObjectFactory
首先实例化,得到一个原始对象 bean;
// 实例化
if (instanceWrapper == null) {
// 创建bean实例,
instanceWrapper = createBeanInstance(beanName, mbd, args);
}
//原始对象
final Object bean = instanceWrapper.getWrappedInstance();
在得到原始对象后,之后 607行 会有一个addSingletonFactory方法,这里就是我们说的第三级缓存;
addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
传入的参数,有beanName,和原始对象bean,
通过此方法 我们发现 传入的value,不仅仅是bean的原始对象, 还需要beanName,BeanDefinition。
因为map的value 需要多个值,那么spring 是通过 这三个值以一个lambda表达式的方式传入的。 () -> getEarlyBeanReference(beanName, mbd, bean);

进入addSingletonFactory方法后,我们会发现以下代码:
if (!this.singletonObjects.containsKey(beanName)) {
this.singletonFactories.put(beanName, singletonFactory);
this.earlySingletonObjects.remove(beanName);
this.registeredSingletons.add(beanName);
}

这个代码我需要关注的只要this.singletonFactories.put(beanName, singletonFactory);把bean的名字和 lambda表达式存入singletonFactories这个缓存中。


二十八、这里问大家一个问题,在这个this.singletonFactories.put(beanName, singletonFactory); put方法执行时,lambda表达式是否会执行?
不会的。
这里仅仅只是,把beanName 和lambda表达式存入到一个hashMap,是不会调用lambda表达式中的方法。


二十九、我们再来看一下这两个流程:
1、A生命周期的第一步,我们不管什么情况,都直接将A的原始对象封装成一个lambda表达式,存入到三级缓存中singletonFactories Map<beanName,ObjectFactory<?>>
所以现在我们就有了 三级缓存的结构 key是beanName,value是一个ObjectFactory,ObjectFactory就是给功能接口。也就是lambda表达式传了过去。

ps:
CircularDependencyA的生命周期 第一步 “CircularDependencyA原始对象” 后变成了 “第三级缓存<BeanName:lambda(CircularDependencyA原始对象,beanName,BeanDefinition)>”

调整后的 CircularDependencyA的生命周期
0、createingSet.add(CircularDependencyA)
1、实例化CircularDependencyA(new CircularDependencyA()),CircularDependencyA原始对象-->第三级缓存<BeanName:lambda(CircularDependencyA原始对象,beanName,BeanDefinition)>
2、填充circularDependencyB 属性;--->从单例池中去找circularDependencyB 对应的Bean-->找不到--->从ArzMap中查找----> 找不到-->创建circularDependencyB 对应的Bean
2、填充circularDependencyC 属性;--->从单例池中去找circularDependencyB 对应的Bean-->找不到--->从ArzMap中查找----> 找不到-->创建circularDependencyB 对应的Bean
3、填充其他属性;
4、初始化后(AOP)---产生CircularDependencyA的代理对象;
4.5从第二级缓存中,获取CircularDependencyA的代理对象。
5、添加单例池;


三十、那么在什么时候执行addSingletonFactory这个添加呢? 我们来看一下源码:
boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences &&
isSingletonCurrentlyInCreation(beanName));

1、mbd.isSingleton() 是否是单例的bean
2、this.allowCircularReferences 是否支持循环引用,默认是支持的
3、isSingletonCurrentlyInCreation(beanName) 判断bean是否正在创建中,
4、一般情况 我们进到这里这三个都为true,所以就会进入addSingletonFactory方法,就会向三级缓存中加值。

我们看一下第2个 isSingletonCurrentlyInCreation 这个方法。 会判断 是否在singletonsCurrentlyInCreation中。

这个在doCreateBean之前已经添加过了。 如果要看参见 补充材料一。

public boolean isSingletonCurrentlyInCreation(String beanName) {
return this.singletonsCurrentlyInCreation.contains(beanName);
}

// singletonsCurrentlyInCreation 这是一个set 就是在调用doCreateBean之前已经赋值了
private final Set<String> singletonsCurrentlyInCreation =
Collections.newSetFromMap(new ConcurrentHashMap<>(16));

5、这个singletonsCurrentlyInCreation就是生命周期第0步的createingSet ,“正在创建的单例集合”,其实系统中还有正在创建的“正在创建的原型集合“。


6、再看流程图:
我们发现,在我们调整了三级缓存的存储内容后,在B的生命周期的第二步中,从三级缓存中获得就不再A的原始对象,而是一个lambda表达式。

ps:
调整了B生命周期的第二步 从”三级缓存中获取“后添加”lambda(CircularDependencyA原始对象,beanName,BeanDefinition)-->执行lambda表达式“。

CircularDependencyB的生命周期
0、createingSet.add(CircularDependencyB)
1、实例化CircularDependencyB(new CircularDependencyB()),CircularDependencyB原始对象-->ArzMap<BeanName,CircularDependencyB原始对象>
2、填充circularDependencyA属性;--->从单例池中去找circularDependencyA对应的Bean-->找不到--->第二级缓存 --》找不到--》createing包含CircularDependencyA-->代表CircularDependencyA出现了循环依赖--->从第三级缓存中获取--->lambda(CircularDependencyA原始对象,beanName,BeanDefinition)-->执行lambda表达式----CircularDependencyA代理对象--- >第二级缓存 <circularDependencyA,CircularDependencyA代理对象>
3、填充其他属性;
4、初始化后;
5、添加单例池;


三十一、那么我们再来看一下lambda表达式的执行情况:
我们来看一下源码:
1、org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#doCreateBean 方法中
612行代码 addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean)); 代码中的 getEarlyBeanReference方法。这个就是一个lambda表达式,我们点进去看一下

2、org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#getEarlyBeanReference方法中,我们看一下此方法

这个方法具体在做什么,这个方法有就是在执行后置处理器,后置处理器的类型是SmartInstantiationAwareBeanPostProcessor,我们看一下这个后置处理器,发现是一个接口,这个后置处理器接口有很多实现类,我们用的最多的是
org.springframework.aop.framework.autoproxy.AbstractAdvisorAutoProxyCreator这个实现类,就是跟我们AOP有关系的。
这个方法里最主要的就是执行
978行的
exposedObject = ibp.getEarlyBeanReference(exposedObject, beanName); 代码的 getEarlyBeanReference 这个方法

3、进入getEarlyBeanReference 方法
org.springframework.beans.factory.config.SmartInstantiationAwareBeanPostProcessor#getEarlyBeanReference
这是一个接口类,

4、看一下具体实现类
org.springframework.aop.framework.autoproxy.AbstractAutoProxyCreator#getEarlyBeanReference。
只有这个方法 写了具体逻辑。
方法中:wrapIfNecessary(bean, beanName, cacheKey); 这个方法 就是很重要的,这个方法 就是根据情况来判断是否需要执行AOP了 。

5、我们来看一下wrapIfNecessary这个方法:
在这个方法中会判断bean是否需要aop,具体的判断。
354行代码 Object[] specificInterceptors = getAdvicesAndAdvisorsForBean(bean.getClass(), beanName, null);
会返回类所对应的advisors,可以认为是否有对应的切面。

6、如果specificInterceptors 不为空,进入到if中,
执行 360 行代码 创建一个代理对象,并返回
Object proxy = createProxy(
bean.getClass(), beanName, specificInterceptors, new SingletonTargetSource(bean));

PS :
在B的生命周期第二行,执行lambda表达式后,增加”AOP“,执行lambda表达式,就是去执行AOP。

CircularDependencyB的生命周期
0、createingSet.add(CircularDependencyB)
1、实例化CircularDependencyB(new CircularDependencyB()),CircularDependencyB原始对象-->ArzMap<BeanName,CircularDependencyB原始对象>
2、填充circularDependencyA属性;--->从单例池中去找circularDependencyA对应的Bean-->找不到--->第二级缓存 --》找不到--》createing包含CircularDependencyA-->代表CircularDependencyA出现了循环依赖--->从第三级缓存中获取--->lambda(CircularDependencyA原始对象,beanName,BeanDefinition)-->执行lambda表达式-->AOP -->CircularDependencyA代理对象--- >第二级缓存 <circularDependencyA,CircularDependencyA代理对象>
3、填充其他属性;
4、初始化后;
5、添加单例池;


三十二、我们再来梳理一下,当我们在执行任何一个bean的生命周期的第二步的时候, 我们以CircularDependencyB为例。
1、首先在填充A属性时,会去单例此中获取A的bean,
2、如果获取不到,会去二级缓存中查找,
3、如果在二级缓存中找不到,再去createingSet(singletonsCurrentlyInCreation)中找,
4、在createingSet(singletonsCurrentlyInCreation)中找到了,则代表发生了循环依赖。
5、再从第三级缓存中获取,出lambda表达式,
6、然后去执行lambda表达式,进行AOP,产生代理对象,放入二级缓存中。


CircularDependencyB的生命周期
0、createingSet.add(CircularDependencyB)
1、实例化CircularDependencyB(new CircularDependencyB()),CircularDependencyB原始对象-->ArzMap<BeanName,CircularDependencyB原始对象>
2、填充circularDependencyA属性;--->从单例池中去找circularDependencyA对应的Bean-->找不到--->第二级缓存 --》找不到--》createing包含CircularDependencyA-->代表CircularDependencyA出现了循环依赖--->从第三级缓存中获取--->lambda(CircularDependencyA原始对象,beanName,BeanDefinition)-->执行lambda表达式-->AOP -->CircularDependencyA代理对象--- >第二级缓存 <circularDependencyA,CircularDependencyA代理对象>
3、填充其他属性;
4、初始化后;
5、添加单例池;


三十三、这是我们一步一步提到出来的结论,接下来我们看一下getSingleton方法:
org.springframework.beans.factory.support.DefaultSingletonBeanRegistry#getSingleton(java.lang.String, boolean)
在spring 填充属性是,回去执行getSingleton,通过beanName获取对应的一个bean
1、 首先会从一级缓存中获取:
Object singletonObject = this.singletonObjects.get(beanName);
2、如果一级缓存找不到: 并且 bean是正在创建中。
if (singletonObject == null && isSingletonCurrentlyInCreation(beanName))
3、在从二级缓存中获取,
singletonObject = this.earlySingletonObjects.get(beanName);
4、如果二级缓存中没有获取到, 在从三级缓存获取
ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
5、基本三级缓存中都活获取到的,因为我们再之前的代码任何一个bean都会向三级缓存中添加。
在org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#doCreateBean 方法中。
通过addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));向三级缓存中添加 一个lambda表达式。
6、从三级缓存中获取后,直接去执行
singletonObject = singletonFactory.getObject();代码, 其实就是就在执行lamb表达式。
7、在执行lambda表达式是,就会判断是否需要AOP,
如果需要AOP就会产生一个代理对象,把代理对象放入二级缓存,
从三级缓存中移除。

代码:
org.springframework.beans.factory.support.DefaultSingletonBeanRegistry#getSingleton(java.lang.String, boolean)
189行
//执行lambda AOP
singletonObject = singletonFactory.getObject();
this.earlySingletonObjects.put(beanName, singletonObject);
this.singletonFactories.remove(beanName);

 

三十三、这里看完后,我们知道了,
1、同一个bean 在其实二级缓存和三级缓存中,只能有一个中存。
2、二级缓存中,存储的是三级缓存中lambda表达式的执行结果。
现在我们的三个缓存都有了,也知道里面存储的是什么了 ,我们一起来看一下。 见三级缓存的内容

 

 

三十四、我们再这里总结一下流程图:
1、我刚刚一直在分析产生循环依赖的情况,
1、1 A需要AOP时
1、2 A不需要AOP时

2、因为是同一段代码逻辑,我们再来看一下如果不产生循环,会不会有问题。

2、1 A需要AOP时
2、2 A不需要AOP时

 

 

三十五、最后,我们就剩下看看 第4步和第4.5步代码分析:

调整后的 CircularDependencyA的生命周期
0、createingSet.add(CircularDependencyA)
1、实例化CircularDependencyA(new CircularDependencyA()),CircularDependencyA原始对象-->第三级缓存<BeanName:lambda(CircularDependencyA原始对象,beanName,BeanDefinition)>
2、填充circularDependencyB 属性;--->从单例池中去找circularDependencyB 对应的Bean-->找不到--->从ArzMap中查找----> 找不到-->创建circularDependencyB 对应的Bean
2、填充circularDependencyC 属性;--->从单例池中去找circularDependencyB 对应的Bean-->找不到--->从ArzMap中查找----> 找不到-->创建circularDependencyB 对应的Bean
3、填充其他属性;
4、初始化后(AOP)---产生CircularDependencyA的代理对象;
4.5从第二级缓存中,获取CircularDependencyA的代理对象。
5、添加单例池

代码如下
在org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#doCreateBean方法中:
第640行代码:
Object earlySingletonReference = getSingleton(beanName, false);
执行此行代码:
1、在解决循环依赖时,当属性注入完后,从getSingleton中的得到执行完lambda表达式的对象,不为空。就将返回的对象放入单例池中。
2、如果不存在循环依赖是,则返回null。 就将第4步产生的对象放入单例池。


三十六、A的生命周期第四步如何判断,A是否已经被AOP过?
看代码:
org.springframework.aop.framework.autoproxy.AbstractAutoProxyCreator#postProcessAfterInitialization 方法

org.springframework.aop.framework.autoproxy.AbstractAutoProxyCreator#getEarlyBeanReference 方法
1、getEarlyBeanReference 是lambda表达式执行的方法。
2、此方法中 this.earlyProxyReferences.put(cacheKey, bean); 代码 ,将所有提前进行过lambda执行的对象的原始对象记录了到earlyProxyReferences中。
3、在看org.springframework.aop.framework.autoproxy.AbstractAutoProxyCreator#postProcessAfterInitialization方法中。this.earlyProxyReferences.remove(cacheKey) 代码会从earlyProxyReferences获取某个对象。
4、如果获取到,对象并且与现有原始对象相等,则说明已经提前进行过lambda表示式执行,不需要再执行 wrapIfNecessary方法了。
5、所以产生了循环依赖的对象,不会在此方法中 执行 wrapIfNecessary方法了。
6、并将原始bean返回。

ps 查找代码流程:
org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#doCreateBean 632行 initializeBean(beanName, exposedObject, mbd)
》org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#initializeBean(java.lang.String, java.lang.Object, org.springframework.beans.factory.support.RootBeanDefinition) 1897行 wrappedBean = applyBeanPostProcessorsAfterInitialization(wrappedBean, beanName);
》org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#applyBeanPostProcessorsAfterInitialization 425行 Object current = processor.postProcessAfterInitialization(result, beanName);
》org.springframework.beans.factory.config.BeanPostProcessor#postProcessAfterInitialization 接口类
》org.springframework.aop.framework.autoproxy.AbstractAutoProxyCreator#postProcessAfterInitialization 实现类

 

三十七、是否所有的循环依赖都解决了吗?
1、A是原型的,B是单例的 是否可以? 可以的
2、A是单例的,B是原型的 是否可以? 可以的
3、两个原型的对象产生了循环依赖。解决不了。
4、构造方法注入产生循环依赖,解决不了。
5、dependsOn产生循环依赖,解决不了。

 


##############

三级缓存 :
1、第一次缓存:单例池,singletonObjects Map<beanName,Object>
存储执行了完整生命周期的bean
key beanName,value 存储执行了完整生命周期的bean
2、第二级缓存:代理对象(三级缓存lambda表达式执行结果) earlySingletonObjects Map<beanName,Object>
存储因循环依赖而执行了lambda表达式的bean的执行结果 存放半成品的bean
key beanName,value 三级缓存中lambda表达式 执行的结果的半成品的bean
3、第三级缓存:实例化对象封装的lambda表达式 singletonFactories Map<beanName,ObjectFactory<?>>
存储bean的lambda表达式的ObjectFactory类型的工厂对象
key beanName, value 存储bean的lambda表达式的ObjectFactory类型的工厂对象

另外两个:
1、Set<String> singletonsCurrentlyInCreation =
Collections.newSetFromMap(new ConcurrentHashMap<>(16));
存储正在创建的bean对象
2、Map<Object, Object> earlyProxyReferences = new ConcurrentHashMap<>(16);
存储提前执行了lambda表达式的bean对象

 

 

//资料:
https://blog.csdn.net/java_lyvee/article/details/101793774

 


// 补充材料:
一、 单例bean什么时候添加到singletonsCurrentlyInCreation集合中?

org.springframework.beans.factory.support.DefaultSingletonBeanRegistry#getSingleton(java.lang.String, org.springframework.beans.factory.ObjectFactory<?>) 方法中
230行代码
beforeSingletonCreation(beanName);进行添加的。

 

二、演示CircularDependencyA AOP的产生情况:(可以演示四种情况。)
0、开启com.luban.app.CircularDependencyConfig @EnableAspectJAutoProxy 注解
1、为了更方便大家看明白,我先简单一点,把循环依赖去掉,将circularDependencyB类的属性的代码全部注掉;。
2、看 org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#doCreateBean 方法中 代码exposedObject = initializeBean(beanName, exposedObject, mbd); 执行的结果; 可以看出是代理对象。

 

posted on 2020-12-25 10:24  AlexGeng  阅读(124)  评论(0编辑  收藏  举报

导航