1.循环依赖问题
a.依赖分组
图源:小傅哥
- 循环依赖主要分为这三种,自身依赖于自身、互相循环依赖、多组循环依赖。
- 但无论循环依赖的数量有多少,循环依赖的本质是一样的。就是你的完整创建依赖于我,而我的完整创建也依赖于你,但我们互相没法解耦,最终导致依赖创建失败。
- 所以需要 Spring 提供了除了构造函数注入和原型注入外的,setter 循环依赖注入解决方案
b.使用一级缓存粗糙地解决问题
- 如果仅以一级缓存解决循环依赖,那么在实现上可以通过在A对象 newInstance 创建且未填充属性后,直接放入缓存中。
- 在
A对象
的属性填充B对象
时,如果缓存中不能获取到B对象
,则开始创建B对象
,同样创建完成后,把B对象
填充到缓存中去。 - 接下来就开始对
B对象
的属性进行填充,恰好这会可以从缓存中拿到半成品的A对象
,那么这个时候B对象
的属性就填充完了。 - 最后返回来继续完成
A对象
的属性填充,把实例化后并填充了属性的B对象
赋值给A对象的b属性
,这样就完成了一个循环依赖操作。
// 定义一个线程安全的存储单例对象的 Map
private final static Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);
// 泛型方法,用于获取指定类的 Bean 实例
private static <T> T getBean(Class<T> beanClass) throws Exception {
// 获取 Bean 的名称(类名的小写形式)
String beanName = beanClass.getSimpleName().toLowerCase();
// 检查 Map 中是否已经存在该 Bean 的实例
if (singletonObjects.containsKey(beanName)) {
// 如果存在,则直接返回该实例
return (T) singletonObjects.get(beanName);
}
// 如果不存在,则创建一个新的 Bean 实例
Object obj = beanClass.newInstance();
// 将新创建的 Bean 实例放入缓存中
//创建Bean马上放入Map中,不等待填充就放入.
singletonObjects.put(beanName, obj);
// 获取该对象的所有声明字段(属性)
Field[] fields = obj.getClass().getDeclaredFields();
// 遍历每个字段进行依赖注入
//我们是先:将实例放入Map中,再进行注入。
for (Field field : fields) {
// 设置字段可访问,即使它是私有的
field.setAccessible(true);
// 获取字段的类型
Class<?> fieldClass = field.getType();
// 为字段类型生成对应的 Bean 名称(小写形式)
String fieldBeanName = fieldClass.getSimpleName().toLowerCase();
// 判断 singletonObjects 中是否已有此字段类型的 Bean 实例
field.set(obj, singletonObjects.containsKey(fieldBeanName)
? singletonObjects.get(fieldBeanName) // 如果有,则直接注入
: getBean(fieldClass)); // 如果没有,则递归调用 getBean 方法创建并注入
// 还原字段的可访问性
field.setAccessible(false);
}
// 返回创建好的 Bean 实例
return (T) obj;
}
public static void main(String[] args) throws Exception {
System.out.println(getBean(B.class).getA());
System.out.println(getBean(A.class).getB());
}
cn.ceshi.springframework.test.A@49476842
cn.ceshi.springframework.test.B@78308db1
Process finished with exit code 0
- 从测试效果和截图依赖过程中可以看到,一级缓存也可以解决简单场景的循环依赖问题。
- 其实
getBean
,是整个解决循环依赖的核心内容,A 创建后填充属性时依赖 B,那么就去创建 B,在创建 B 开始填充时发现依赖于 A,但此时 A 这个半成品对象已经存放在缓存到singletonObjects
中了,所以 B 可以正常创建,在通过递归把 A 也创建完整了。
c.使用三级缓存
要理解 DefaultSingletonBeanRegistry
中的三级缓存,可以通过以下几个关键点和注解来帮助你理清思路。
1. 一级缓存(singletonObjects)
- 定义:用于存放完全初始化的单例对象。
- 作用:当你请求一个 bean 时,首先会在这里查找。如果存在,直接返回。
// 一级缓存,普通对象
private Map<String, Object> singletonObjects = new ConcurrentHashMap<>();
2. 二级缓存(earlySingletonObjects)
- 定义:存放那些已经被实例化但尚未完全初始化的对象,通常是因为它们被依赖的其他对象还未完成初始化。
- 作用:当一级缓存中找不到时,会检查这个缓存。此时,只有在代理对象的情况下,才会出现在这个缓存中。
javaCopy Code// 二级缓存,提前暴漏对象,没有完全实例化的对象
protected final Map<String, Object> earlySingletonObjects = new HashMap<>();
3. 三级缓存(singletonFactories)
- 定义:存放能够创建对象的工厂(ObjectFactory),这些工厂在初始化的过程中提供 bean 的实例。
- 作用:当一级和二级缓存都没有找到相应的 bean 时,将会尝试从这个缓存中获取对应的工厂方法,并使用它来创建对象。
javaCopy Code// 三级缓存,存放代理对象
private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<String, ObjectFactory<?>>();
4. 工作流程
-
当调用
getSingleton(String beanName)
方法时,首先在一级缓存中查找该 bean。 -
如果找不到,则检查二级缓存是否有提前曝光的对象。
-
如果仍然找不到,则查看三级缓存中是否存在相应的对象工厂(ObjectFactory)。
- 如果存在,则调用工厂的
getObject()
方法创建该 bean,并将其放入二级缓存中,随后移除三级缓存中的工厂。
- 如果存在,则调用工厂的
5. 示例代码解析
以下是 getSingleton
方法的简化逻辑分析:
/**
* DefaultSingletonBeanRegistry 是一个实现 SingletonBeanRegistry 接口的类,
* 用于管理 Spring 应用程序中的单例 bean。
* 它支持三级缓存机制,以提高单例 bean 的获取性能。
*/
public class DefaultSingletonBeanRegistry implements SingletonBeanRegistry {
// 一级缓存,存储完全实例化的单例对象
private Map<String, Object> singletonObjects = new ConcurrentHashMap<>();
// 二级缓存,存储提前暴露的对象,这些对象可能还未完全实例化
protected final Map<String, Object> earlySingletonObjects = new HashMap<>();
// 三级缓存,存放代理对象的工厂
private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>();
// 存放可被销毁的 bean 的映射
private final Map<String, DisposableBean> disposableBeans = new LinkedHashMap<>();
/**
* 获取指定名称的单例对象。如果对象不存在,将尝试从早期缓存或工厂中创建。
*
* @param beanName 需要获取的单例对象的名称
* @return 单例对象,如果不存在则返回 null
*/
@Override
public Object getSingleton(String beanName) {
// 从一级缓存中获取对象
Object singletonObject = singletonObjects.get(beanName);
if (singletonObject == null) {
// 从二级缓存中获取对象
singletonObject = earlySingletonObjects.get(beanName);
// 判断二级缓存中是否有对象
if (singletonObject == null) {
// 从三级缓存中获取工厂并尝试创建对象
ObjectFactory<?> singletonFactory = singletonFactories.get(beanName);
if (singletonFactory != null) {
singletonObject = singletonFactory.getObject();
// 将创建的对象放入二级缓存
earlySingletonObjects.put(beanName, singletonObject);
// 移除三级缓存中的工厂
singletonFactories.remove(beanName);
}
}
}
return singletonObject;
}
/**
* 注册一个单例对象,将其放入一级缓存。
*
* @param beanName 要注册的单例对象的名称
* @param singletonObject 注册的单例对象
*/
public void registerSingleton(String beanName, Object singletonObject) {
singletonObjects.put(beanName, singletonObject);
// 清理早期缓存和三级缓存
earlySingletonObjects.remove(beanName);
singletonFactories.remove(beanName);
}
/**
* 添加一个单例工厂,如果该 bean 尚未在一级缓存中存在。
*
* @param beanName 要注册的 bean 名称
* @param singletonFactory 生成单例对象的工厂
*/
protected void addSingletonFactory(String beanName, ObjectFactory<?> singletonFactory) {
if (!this.singletonObjects.containsKey(beanName)) {
this.singletonFactories.put(beanName, singletonFactory);
// 清理早期缓存
earlySingletonObjects.remove(beanName);
}
}
/**
* 注册一个可被销毁的 bean,当容器关闭时自动调用其销毁方法。
*
* @param beanName bean 名称
* @param bean 可被销毁的 bean 实例
*/
public void registerDisposableBean(String beanName, DisposableBean bean) {
disposableBeans.put(beanName, bean);
}
}
6. 总结
- 一级缓存:完全初始化的对象,用于直接返回。
- 二级缓存:提前曝光的对象,主要用于解决循环依赖的问题。
- 三级缓存:对象工厂,提供创建对象的能力。
这种设计模式使得 Spring 能够高效地管理 bean 的生命周期,同时解决了循环依赖的问题。希望这些注解能帮助你更好地理解三级缓存的运作!
d.业务的循环依赖问题
1.示例代码
假设我们有两个类 UserService
和 OrderService
,它们相互依赖。
jimport org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
@Component
class UserService {
private OrderService orderService;
@Autowired
public void setOrderService(OrderService orderService) {
this.orderService = orderService;
}
public void print() {
System.out.println("UserService is using OrderService");
}
}
@Component
class OrderService {
private UserService userService;
@Autowired
public void setUserService(UserService userService) {
this.userService = userService;
}
public void print() {
System.out.println("OrderService is using UserService");
}
}
2.循环依赖分析
在这个例子中:
UserService
依赖于OrderService
。OrderService
又依赖于UserService
。
这种情况就形成了一个循环依赖。
3.Spring 处理流程
-
实例化
UserService
:- Spring 首先创建
UserService
的实例,并发现它需要OrderService
。
- Spring 首先创建
-
存储到三级缓存:
- 在创建
UserService
时,Spring 会将其的ObjectFactory
存放到三级缓存中。
- 在创建
-
实例化
OrderService
:- 然后,Spring 会创建
OrderService
的实例,并发现它需要UserService
(此时UserService
还没有完全初始化)。
- 然后,Spring 会创建
-
从二级缓存获取:
- 因为
UserService
在三级缓存中,Spring 会通过ObjectFactory
获取到UserService
的引用,而不是完全初始化的 Bean。这使得OrderService
能够完成依赖注入。
- 因为
-
完成初始化:
- 当
OrderService
完成初始化后,Spring 会将其放入一级缓存中。 - 接着,Spring 会返回到
UserService
,并将OrderService
注入到UserService
中,完成其初始化并放入一级缓存。
- 当
4.结果
通过三级缓存,Spring 能够成功解决这个循环依赖问题,确保 UserService
和 OrderService
都能够被正确地实例化并注入。
5.使用建议
- 在实际应用中,应尽量避免循环依赖,因为它可能导致设计不清晰。可以考虑重构代码,将依赖关系解耦,或者使用设计模式(如观察者模式、策略模式等)来减少直接依赖。