4.2 循环依赖:Spring的三级缓存恋爱指南
引言
想象一下,A对象依赖B对象,B对象又依赖A对象,这种"你中有我,我中有你"的关系,像极了恋爱中的情侣。Spring是如何处理这种"循环依赖"的呢?今天我们就来揭秘Spring的三级缓存恋爱指南!
1. 循环依赖的"恋爱困境"
场景描述:
- A对象需要B对象才能完成初始化。
- B对象也需要A对象才能完成初始化。
- 结果:A和B陷入了"你等我,我等你"的死循环。
Spring的解决方案:三级缓存机制。
2. 三级缓存机制详解
Spring通过三级缓存(singletonObjects
、earlySingletonObjects
、singletonFactories
)来解决循环依赖问题。我们可以把这三层缓存比作恋爱的三个阶段:
缓存层级 | 比喻 | 作用 |
---|---|---|
singletonObjects |
正式结婚 | 存放完全初始化好的Bean,可以直接使用。 |
earlySingletonObjects |
订婚 | 存放提前暴露的Bean(半成品),用于解决循环依赖。 |
singletonFactories |
相亲 | 存放Bean的工厂对象,用于创建Bean的早期引用(代理对象或原始对象)。 |
3. 代码实战:循环依赖的完整示例
以下是一个完整的Spring Boot项目示例,展示循环依赖的处理过程。
项目结构
css
src/main/java/com/example/demo/
├── DemoApplication.java
├── config/
│ └── AppConfig.java
├── service/
│ ├── ServiceA.java
│ └── ServiceB.java
代码实现
ServiceA.java
java
package com.example.demo.service;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service
public class ServiceA {
private final ServiceB serviceB;
@Autowired
public ServiceA(ServiceB serviceB) {
this.serviceB = serviceB;
System.out.println("ServiceA initialized!");
}
public void sayHello() {
System.out.println("Hello from ServiceA!");
}
}
ServiceB.java
java
package com.example.demo.service;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service
public class ServiceB {
private final ServiceA serviceA;
@Autowired
public ServiceB(ServiceA serviceA) {
this.serviceA = serviceA;
System.out.println("ServiceB initialized!");
}
public void sayHello() {
System.out.println("Hello from ServiceB!");
}
}
AppConfig.java
java
package com.example.demo.config;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
@Configuration
@ComponentScan(basePackages = "com.example.demo.service")
public class AppConfig {
}
DemoApplication.java
java
package com.example.demo;
import com.example.demo.service.ServiceA;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ApplicationContext;
@SpringBootApplication
public class DemoApplication {
public static void main(String[] args) {
ApplicationContext context = SpringApplication.run(DemoApplication.class, args);
ServiceA serviceA = context.getBean(ServiceA.class);
serviceA.sayHello();
}
}
运行结果
csharp
ServiceA initialized!
ServiceB initialized!
Hello from ServiceA!
4. 三级缓存的工作流程
-
创建A对象:
- Spring调用
ServiceA
的构造函数,发现需要注入ServiceB
。 - 将A对象的工厂(
singletonFactories
)放入缓存。
- Spring调用
-
创建B对象:
- Spring调用
ServiceB
的构造函数,发现需要注入ServiceA
。 - 从
singletonFactories
中获取A对象的早期引用(半成品),并放入earlySingletonObjects
。
- Spring调用
-
完成A对象的初始化:
- 将B对象注入A对象。
- 将A对象放入
singletonObjects
,完成初始化。
-
完成B对象的初始化:
- 将A对象注入B对象。
- 将B对象放入
singletonObjects
,完成初始化。
5. 图文解析
以下是一个简单的流程图,展示三级缓存的工作过程:
css
+-------------------+ +-------------------+ +-------------------+
| singletonFactories | ----> | earlySingletonObjects | ----> | singletonObjects |
+-------------------+ +-------------------+ +-------------------+
| | |
| 1. 创建A对象 | 2. 创建B对象 | 3. 完成初始化
| 放入工厂缓存 | 获取A的早期引用 | 放入正式缓存
v v v
6. 常见面试题
-
Spring如何解决循环依赖?
- 通过三级缓存机制,提前暴露Bean的早期引用。
-
三级缓存分别是什么?
singletonObjects
:存放完全初始化的Bean。earlySingletonObjects
:存放提前暴露的Bean(半成品)。singletonFactories
:存放Bean的工厂对象。
-
循环依赖的局限性是什么?
- 只支持单例Bean的循环依赖。
- 不支持原型(Prototype)Bean的循环依赖。
7. 总结
Spring的三级缓存机制就像一场精心策划的恋爱:
- 相亲阶段 :通过
singletonFactories
找到合适的对象。 - 订婚阶段 :通过
earlySingletonObjects
确定关系。 - 结婚阶段 :通过
singletonObjects
完成最终绑定。
通过这种方式,Spring成功解决了循环依赖的"恋爱困境",让Bean们能够和谐共存!
彩蛋:如果你在面试中被问到循环依赖,可以幽默地说:"Spring的三级缓存就像恋爱中的三个阶段,从相亲到结婚,每一步都不能少!"