1. 没有代理对象时的处理
解决该问题的关键在于何时将实例化后的bean放进容器中,设置属性前还是设置属性后。现有的执行流程,bean实例化后并且设置属性后会被放进singletonObjects单例缓存中。如果我们调整一下顺序,当bean实例化后就放进singletonObjects单例缓存中,提前暴露引用,然后再设置属性,就能解决上面的循环依赖问题,执行流程变为:
- 步骤一:getBean(a),检查singletonObjects是否包含a,singletonObjects不包含a,实例化A放进singletonObjects,设置属性b,发现依赖B,尝试getBean(b)
- 步骤二:getBean(b),检查singletonObjects是否包含b,singletonObjects不包含b,实例化B放进singletonObjects,设置属性a,发现依赖A,尝试getBean(a)
- 步骤三:getBean(a),检查singletonObjects是否包含a,singletonObjects包含a,返回a
- 步骤四:步骤二中的b拿到a,设置属性a,然后返回b
- 步骤五:步骤一中的a拿到b,设置属性b,然后返回a
可见调整bean放进singletonObjects(人称一级缓存)的时机到bean实例化后即可解决循环依赖问题。但为了和spring保持一致,我们增加一个二级缓存earlySingletonObjects,在bean实例化后将bean放进earlySingletonObjects中(见AbstractAutowireCapableBeanFactory#doCreateBean方法第6行),getBean()时检查一级缓存singletonObjects和二级缓存earlySingletonObjects中是否包含该bean,包含则直接返回(见AbstractBeanFactory#getBean第1行)。
增加二级缓存,不能解决有代理对象时的循环依赖。原因是放进二级缓存earlySingletonObjects中的bean是实例化后的bean,而放进一级缓存singletonObjects中的bean是代理对象(代理对象在BeanPostProcessor#postProcessAfterInitialization中返回),两个缓存中的bean不一致。比如上面的例子,如果A被代理,那么B拿到的a是实例化后的A,而a是被代理后的对象,即b.getA() != a。
Mini-Spring容器
import java.util.HashMap;
import java.util.Map;
public class MiniSpringContainer {
// 一级缓存:存放完全初始化的单例bean
private final Map<String, Object> singletonObjects = new HashMap<>();
// 二级缓存:存放早期曝光的单例bean(未完成属性设置)
private final Map<String, Object> earlySingletonObjects = new HashMap<>();
// 模拟getBean方法
public Object getBean(String beanName) {
// 1. 先从一级缓存中获取已完成初始化的bean
if (singletonObjects.containsKey(beanName)) {
return singletonObjects.get(beanName);
}
// 2. 再从二级缓存中获取早期曝光的bean
if (earlySingletonObjects.containsKey(beanName)) {
return earlySingletonObjects.get(beanName);
}
// 3. 如果bean未被创建,则创建bean
Object bean = createBean(beanName);
// 4. 将创建的bean放入一级缓存
singletonObjects.put(beanName, bean);
return bean;
}
// 模拟bean创建过程
private Object createBean(String beanName) {
Object bean = null;
if ("A".equals(beanName)) {
// 5. 创建A的早期对象,并放入二级缓存
bean = new A();
earlySingletonObjects.put(beanName, bean);
// 6. 设置A的依赖B
((A) bean).setB((B) getBean("B"));
} else if ("B".equals(beanName)) {
// 5. 创建B的早期对象,并放入二级缓存
bean = new B();
earlySingletonObjects.put(beanName, bean);
// 6. 设置B的依赖A
((B) bean).setA((A) getBean("A"));
}
// 7. 从二级缓存移除,放入一级缓存中
earlySingletonObjects.remove(beanName);
return bean;
}
}
// A类和B类相互依赖
class A {
private B b;
public void setB(B b) {
this.b = b;
}
}
class B {
private A a;
public void setA(A a) {
this.a = a;
}
}
说明:
- 当
getBean("A")
被调用时,Spring会首先检查一级缓存singletonObjects
是否存在A。如果不存在,进入createBean
过程,开始创建A,并立即将A的早期对象(未初始化完全)放入二级缓存earlySingletonObjects
中。 - A依赖B,故而
createBean("B")
被调用,B也会被放入二级缓存中。此时B依赖A,但由于A已被放入二级缓存,B可以拿到A的早期对象。 - 最后,A和B的依赖关系设置完成后,它们会被从二级缓存中移入一级缓存。
通过这种方式,Spring可以在实例化过程中打破循环依赖,因为依赖项是从二级缓存中取的早期对象。
2. 有代理对象时的处理
在有代理对象的情况下,Spring采用了三级缓存机制。在这种情况下,代理对象不能直接放到二级缓存,而是通过工厂方法来生成并放入三级缓存。这里简化代码来展示如何处理代理对象的循环依赖。
Mini-Spring容器(带代理对象)
import java.util.HashMap;
import java.util.Map;
import java.util.function.Supplier;
public class MiniSpringContainerWithProxy {
// 一级缓存:存放完全初始化的单例bean(包括代理对象)
private final Map<String, Object> singletonObjects = new HashMap<>();
// 二级缓存:存放早期曝光的单例bean(未完成属性设置,未代理)
private final Map<String, Object> earlySingletonObjects = new HashMap<>();
// 三级缓存:存放bean工厂,生成代理对象的工厂
private final Map<String, Supplier<Object>> singletonFactories = new HashMap<>();
// 模拟getBean方法
public Object getBean(String beanName) {
// 1. 先从一级缓存中获取已完成初始化的bean
if (singletonObjects.containsKey(beanName)) {
return singletonObjects.get(beanName);
}
// 2. 再从二级缓存中获取早期曝光的bean
if (earlySingletonObjects.containsKey(beanName)) {
return earlySingletonObjects.get(beanName);
}
// 3. 从三级缓存中获取bean工厂,生成代理对象
if (singletonFactories.containsKey(beanName)) {
Object proxyBean = singletonFactories.get(beanName).get();
earlySingletonObjects.put(beanName, proxyBean);
return proxyBean;
}
// 4. 如果bean未被创建,则创建bean
Object bean = createBean(beanName);
// 5. 将创建的bean放入一级缓存
singletonObjects.put(beanName, bean);
return bean;
}
// 模拟bean创建过程
private Object createBean(String beanName) {
Object bean = null;
if ("A".equals(beanName)) {
// 创建A的实例,并放入三级缓存中,通过代理工厂暴露代理对象
bean = new A();
singletonFactories.put(beanName, () -> createProxy(bean));
// 设置A的依赖B
((A) bean).setB((B) getBean("B"));
} else if ("B".equals(beanName)) {
// 创建B的实例,并放入三级缓存中,通过代理工厂暴露代理对象
bean = new B();
singletonFactories.put(beanName, () -> createProxy(bean));
// 设置B的依赖A
((B) bean).setA((A) getBean("A"));
}
// 初始化完成后,从三级缓存移除,将bean放入一级缓存
singletonFactories.remove(beanName);
return bean;
}
// 模拟创建代理对象
private Object createProxy(Object bean) {
// 简化代理对象创建,实际中可能会用动态代理等方式
return new ProxyBean(bean);
}
}
// A类和B类相互依赖
class A {
private B b;
public void setB(B b) {
this.b = b;
}
}
class B {
private A a;
public void setA(A a) {
this.a = a;
}
}
// 简化的代理对象类
class ProxyBean {
private final Object target;
public ProxyBean(Object target) {
this.target = target;
}
}
说明:
- 当
getBean("A")
被调用时,Spring会创建A,并将A的代理工厂(Supplier<Object>
)放入三级缓存中singletonFactories
。这样,当B依赖A时,可以从三级缓存中获取A的代理对象。 - 同样,当B被创建时,其代理工厂也被放入三级缓存。
- 一旦代理对象被生成并注入到依赖链中,它会被移到二级缓存中供后续使用,最终完成初始化并移入一级缓存。
总结
- 没有代理对象时:使用一级和二级缓存,通过提前曝光未完全初始化的bean来解决循环依赖。
- 有代理对象时:通过三级缓存暴露代理对象工厂,以确保依赖链中获取到的是代理对象,解决代理bean的循环依赖。
整体流程
import java.util.HashMap;
import java.util.Map;
import java.util.function.Supplier;
public class MiniSpringContainerWithObjectFactory {
// 一级缓存:完全初始化的单例bean
private final Map<String, Object> singletonObjects = new HashMap<>();
// 二级缓存:提前曝光的单例bean,尚未完全初始化
private final Map<String, Object> earlySingletonObjects = new HashMap<>();
// 三级缓存:ObjectFactory,用来生成bean实例(包括代理对象)
private final Map<String, Supplier<Object>> singletonFactories = new HashMap<>();
// 模拟getBean方法
public Object getBean(String beanName) {
// 1. 检查一级缓存
if (singletonObjects.containsKey(beanName)) {
return singletonObjects.get(beanName);
}
// 2. 检查二级缓存(已曝光但未完全初始化的bean)
if (earlySingletonObjects.containsKey(beanName)) {
return earlySingletonObjects.get(beanName);
}
// 3. 检查三级缓存,通过ObjectFactory来生成代理或者实例化bean
if (singletonFactories.containsKey(beanName)) {
Object bean = singletonFactories.get(beanName).get();
earlySingletonObjects.put(beanName, bean); // 将从三级缓存中取出的bean放入二级缓存
return bean;
}
// 4. 如果没有缓存中的bean,则创建
return createBean(beanName);
}
// 模拟bean的创建过程
private Object createBean(String beanName) {
Object bean;
if ("A".equals(beanName)) {
// 通过构造函数创建bean A,并将其ObjectFactory放入三级缓存中
bean = new A();
// 提前曝光bean A
singletonFactories.put(beanName, () -> bean);
// 开始填充A的依赖,依赖于B
((A) bean).setB((B) getBean("B"));
} else if ("B".equals(beanName)) {
// 通过构造函数创建bean B,并将其ObjectFactory放入三级缓存中
bean = new B();
// 提前曝光bean B
singletonFactories.put(beanName, () -> bean);
// 开始填充B的依赖,依赖于A
((B) bean).setA((A) getBean("A"));
} else {
throw new RuntimeException("Unknown bean: " + beanName);
}
// 完成bean的初始化,移除三级缓存中的ObjectFactory
singletonFactories.remove(beanName);
// 从二级缓存移到一级缓存
earlySingletonObjects.remove(beanName);
singletonObjects.put(beanName, bean);
return bean;
}
}
// 模拟相互依赖的A类和B类
class A {
private B b;
public void setB(B b) {
this.b = b;
}
}
class B {
private A a;
public void setA(A a) {
this.a = a;
}
}
流程解释
- 检查 A 是否在缓存中:
getBean("A")
时,首先检查一级缓存singletonObjects
,如果不存在,则继续查找。 - 通过构造函数创建 A:如果A不存在,则调用
createBean("A")
方法开始创建A。- 在这个过程中,将A的ObjectFactory提前曝光到三级缓存中
singletonFactories
,为后续可能的依赖解决提供支持。
- 在这个过程中,将A的ObjectFactory提前曝光到三级缓存中
- A 开始属性填充,依赖 B:A发现自己依赖B,于是开始
getBean("B")
。 - 检查 B 是否在缓存中:同样的,首先检查B是否在缓存中(一级、二级和三级)。
- 通过构造函数创建 B:如果B不存在,则调用
createBean("B")
开始创建B,并将B的ObjectFactory提前曝光。 - B 依赖 A,检查 A 是否在缓存中:B的依赖A已经在三级缓存中被曝光,通过
ObjectFactory
获取到A的早期对象,避免了重复创建。 - 返回 A 并完成 B 的创建:B获得A之后继续其创建流程,最终完成并返回给A。
- A 完成创建:最终A的依赖B被成功设置,A也完成了初始化,至此A和B的创建完毕。
关键点
- 提前曝光:通过
ObjectFactory
将未完全初始化的bean提前放入三级缓存,避免重复创建。 - 三级缓存的使用:如果依赖的bean在初始化过程中被再次请求,Spring会从三级缓存中获取已经曝光的早期对象,而不会重复创建bean。
- 依赖填充:在创建bean时,Spring会自动检测依赖项并使用
getBean
方法解决这些依赖,确保每个bean的依赖都能被正确填充。