diff --git a/source/_posts/2023-11-22-circular-dependencies-in-Spring.md b/source/_posts/2023-11-22-circular-dependencies-in-Spring.md
index 97e3b410..58b93460 100644
--- a/source/_posts/2023-11-22-circular-dependencies-in-Spring.md
+++ b/source/_posts/2023-11-22-circular-dependencies-in-Spring.md
@@ -1,62 +1,68 @@
---
title: Spring 中的循环依赖
date: 2023-11-22 08:28:26
-tags:
- - java
- - spring
+tags: [java, spring]
---
+`Spring` 中的循环依赖是一个“大名鼎鼎”的问题,本文从原始的问题出发分析应当如何正确地看待和处理循环依赖现象,同时也会回归到源码详细介绍 `Spring` 的具体处理过程,并在最后给出笔者的个人思考。
+
+
+
## 循环依赖的介绍和讨论
### 什么是循环依赖?
-当 bean A 依赖另一个 bean B,bean B 也依赖了 bean A,我们称之为循环依赖:
+当 `Bean A` 依赖另一个 `Bean B`,`Bean B` 也依赖了 `Bean A`,我们就称之为循环依赖:
```console
-Bean A -> bean B -> bean A
+Bean A -> Bean B -> Bean A
```
-首先,我们应该将循环依赖和 Spring 中的循环依赖问题分开看待。循环依赖是一个正常的现象,一个 employee 依赖他的 department,department 拥有许多 employee。先实例化 employee 后实例化 department,然后为它们设置依赖,或者颠倒顺序进行操作,并不会发生什么问题。
+首先,我们应该将循环依赖和 “`Spring` 中的循环依赖问题”分开看待。循环依赖是一个正常的现象,一个 employee 依赖他的 department,department 拥有许多 employee。先实例化 employee 后实例化 department,然后先后为它们设置依赖,这样并不会发生什么问题。
### Spring 中的循环依赖问题
-当 Spring 加载所有的 beans 时,会进行**依赖注入**处理。Spring 并不是先将所有的 bean 实例化,再去进行依赖注入,而是实例化一个 bean 后,立即对它进行依赖注入,为此它会递归地实例化 bean 的依赖。
-实际上,以上的过程同样并不会产生什么大问题,在**实例化和依赖注入分成两个阶段**的情况下,你可以**轻而易举地保存和获取已经实例化的 bean**。唯一的问题是,获取的已经实例化的 bean 可能尚未初始化完毕(比如依赖尚未全部注入),那么你只需要确保它在初始化完毕前不被使用即可。
-这样你通过两个 map,一个保存已经初始化完毕、可用的完成品 bean,一个保存尚未初始化完毕、不可用的半成品 bean,就能解决问题了。
+#### 常规的循环依赖问题
-> 在一些资料中,你会看到有人强调如果只是想要解决常规的循环引用,那么只需要两个缓存。
+当 `Spring` 加载所有的 `Bean` 时,会进行**依赖注入**处理。`Spring` 并不是先将所有的 `Bean` 实例化,再去进行依赖注入,而是实例化一个 `Bean` 后,立即对它进行依赖注入,为此它会递归地实例化 `Bean` 的依赖。仔细思考,即使在存在循环依赖问题的时候,以上的过程同样并不会产生什么大问题,在**实例化和依赖注入分成两个阶段**的情况下,你可以**轻而易举地保存和获取已经实例化的 `Bean`** 。唯一的问题是,获取的已经实例化的 `Bean` 可能尚未初始化完毕(比如它的依赖尚未全部注入),那么你只需要确保它在初始化完毕前不被使用即可。
+按照上述思路,你可以使用两个 `map`,一个保存已经初始化完毕、可以使用的完成品 `Bean`,一个保存尚未初始化完毕、不可以被使用的半成品 `Bean`。
+
+> 在一些资料中,你会看到有人特地强调如果只是解决常规的循环引用问题,那么只需要两个缓存。
{% asset_img "Pasted image 20231122210105.png" 循环引用-常规情况的解决方案 %}
但是问题并不总是那么简单,如果实例化和依赖注入不能分为两个阶段,如果 B 依赖的不再是简单的 A 对象,而是 A 的代理,那么上述方案就不再适用了。
-#### 构造器方法形成的循环依赖
+#### 构造器方法的循环依赖
-如果 A 的构造器方法需要 B,B 的构造器方法需要 A,那么在 A 的实例化阶段就需要 B 的实例,B 的实例化阶段又需要 A,这就陷入了死循环。
+如果 A 的构造器方法需要 B,B 的构造器方法需要 A,那么在 A 的实例化阶段就需要 B 的实例,B 的实例化阶段又需要 A,这就陷入了死循环。虽然我们常说 `Spring` 解决了循环依赖问题,但实际上,`Spring` 并没有解决所有情形的循环依赖问题。
-> 虽然我们常说 Spring 解决了循环依赖问题,但实际上,Spring 并没有解决所有情形的循环依赖问题。
-在构造器方法形成的循环依赖中,需要人为地介入,使用 @Lazy 注解告诉 Spring,延迟初始化 Bean 来解决。
-如果是 prototype 类型的 bean 发生循环依赖,Spring 也会抛出异常。显然每次都创建新的 bean 必然会导致无限循环。
+- 要应对构造器方法的循环依赖,需要人为地介入,使用 `@Lazy` 注解告诉 `Spring`,延迟 `Bean` 的初始化。在这时候,被标注的参数注入的不是一个立即创建的实例,而是一个代理对象。
+- 此外,如果是 `prototype` 类型的 `Bean` 发生循环依赖,`Spring` 会抛出异常,因为每次都创建新的 `Bean` 必然会导致无限循环。
#### 循环依赖中出现代理
-Spring 鼎鼎大名的核心功能,除了 IOC,还有 AOP。在 AOP 的场景中,bean A 的完成品不是简单的 A 对象,而是一个 A 的代理。
+`Spring` 鼎鼎大名的核心功能,除了 `IOC`,还有 `AOP`。在 `AOP` 的场景中,`Bean A` 的完成品不是简单的 A 对象,而是一个 A 的代理。这时候又该如何应对呢?似乎不能再简单地将保存的 A 的实例交给 B,否则 B 持有的就不是最终的 A 的代理。
-思路其实并不难,既然需要 A 的代理,那么在获取 B 依赖的 A 时,直接根据已有的半成品 A 创建代理就好了。
+如果你没有被 `Spring` 影响思路的话,其实并不难。既然需要 A 的代理,那么在获取 B 依赖的 A 时,直接根据已有的半成品 A 创建代理就好了。
#### 解决方案的思路小结
-当我们脱离 Spring 具体的代码讨论循环依赖问题,我们会发现解决的思路是简单、清晰和理所当然的。事实上 Spring 的解决方案也是如此。
+当我们脱离 `Spring` 的具体方案和代码讨论循环依赖问题,我们会发现解决的思路是简单、清晰和理所当然的。事实上 `Spring` 的解决方案也是如此,当然其中会有很多值得深思的细节。回顾循环依赖问题的解决思路,你会发现:
+
+1. 循环依赖本身是普通的,一个手动可解决的问题
+2. `Spring` 依赖注入时,虽然 `Bean B` 依赖的 `Bean A` 尚未初始化完毕,但是已经实例化,可以用来赋值
+3. 在 `Spring AOP` 中,既然 `Bean B` 依赖的 `Bean A` 需要是 A 对象的代理,那么就在那时候创建代理,用来赋值即可
-1. 循环依赖本身是普通的,手动可解决的。
-2. Spring 依赖注入时,虽然 bean B 依赖的 bean A 尚未初始化完毕,但是已经实例化,可以用来赋值
-3. Spring AOP 中,既然 bean B 依赖的 bean A 需要是 A 对象的代理,那么就创建代理,再用来赋值
+## 流程图和测试用例
-## 测试用例和流程图预览
+在开始之前我们先放一张循环引用的处理流程图,用于在后续分析过程中进行对照。
-{% asset_img "Pasted image 20231122233750.png" 循环引用-流程图 %}
+
{% asset_img "Pasted image 20231122233750.png" 循环引用-流程图 %}
-- CircularA
+以下是测试用例的代码:
+
+- `CircularA`
```java
public class CircularA {
private CircularB circularB;
@@ -68,7 +74,7 @@ public class CircularA {
}
}
```
-- CircularB
+- `CircularB`
```java
public class CircularB {
private CircularA circularA;
@@ -80,7 +86,7 @@ public class CircularB {
}
}
```
-- circular-reference-test.xml
+- `circular-reference-test.xml`
```xml
@@ -104,19 +110,19 @@ public class CircularReferenceTest {
## 第一次获取 circularA
-### doGetBean(circularA)
+调用 `doGetBean(circularA)` 方法第一次获取:
-1. 从缓存中获取 circularA(先不看具体代码)
-2. 因缓存中不存在,就创建 circularA
+1. 从缓存中获取 `circularA`(先不看方法内的具体代码,在第一次进入该方法时,必定返回 `null`)
+2. 因缓存中不存在,就创建 `circularA`
```java
protected T doGetBean(
final String name, final Class requiredType, final Object[] args, boolean typeCheckOnly)
throws BeansException {
- // 尝试从缓存中获取 circularA,结果为 null
+ // 尝试从缓存中获取 circularA,第一次结果必定为 null
Object sharedInstance = getSingleton(beanName);
// ...
- // 再次从缓存中获取 circularA,如果为 null,就创建
+ // 再次从缓存中获取 circularA(双重校验),如果为 null,就创建
sharedInstance = getSingleton(beanName, new ObjectFactory