1. 什么是循環(huán)依賴
Spring核心是由容器完成兩件事:1.創(chuàng)建對象趾代,2.裝配對象的依賴。在創(chuàng)建對象的過程中丰辣,會為其屬性賦值撒强,例如有兩個bean,X和Y笙什,X中有屬性y飘哨,Y中有屬性x。x的創(chuàng)建過程中得湘,設置屬性y杖玲,而此時y不存在,進而去創(chuàng)建y淘正,創(chuàng)建y的過程中,設置屬性x臼闻,發(fā)現(xiàn)x不存在(x正在創(chuàng)建中)…… 如果這樣一直下去鸿吆,互相依賴的對象都不能被創(chuàng)建出來。這就是所謂的循環(huán)依賴述呐。
2. 代碼演示
下面是一個手寫的demo惩淳,演示對容器啟動過程:涵蓋包掃描、bena創(chuàng)建乓搬、bean的循環(huán)依賴的處理思犁。Spring的整個過程比較復雜,比如:bean是在原生對象的基礎做了包裹进肯,創(chuàng)建bean的過程有許許多多其他處理激蹲。demo對這些都做了簡化。
前往github查看源碼 https://github.com/nothingax/spring-circular-references-demo
2.1 容器啟動
模擬容器的啟動過程江掩,主要兩步操作学辱,包掃描、創(chuàng)建bean實例环形,并存入單例池中
/** 單例池策泣,即容器,維護最終生成的單例對象抬吟,{bean的類名:bean實例} */
private Map<String, Object> singletonObjects = new HashMap<>();
/** 存儲【bean獲取函數(shù)】的map:{bean名稱:獲取bean實例的函數(shù)} */
private Map<String, ObjectFactory> singletonFactories = new HashMap<>();
@Test
public void circularReferencesTest() throws Exception {
// 1萨咕、包掃描獲取包下的類
List<String> classNames = this.componentScan("com.demo.circularreferences.object");
// 2、bean創(chuàng)建
for (String className : classNames) {
this.createBean(className);
}
X x = (X) singletonObjects.get(X.class.getName());
Y y = (Y) singletonObjects.get(Y.class.getName());
assert x != null && y != null && x.getY() != null && y.getX() != null;
}
2.2 bean的創(chuàng)建
創(chuàng)建過程中設置bean的屬性火本,在設置屬性之前會將bean的引用(這里是一個獲取gean的函數(shù))放入到singletonFactories中危队,這一步操作是解決循環(huán)依賴的關鍵聪建。
private Object createBean(String className) throws Exception {
// 從單例池中獲取bean對象,如果存在直接返回
Object singleton = singletonObjects.get(className);
if (singleton != null) {
return singleton;
}
// 創(chuàng)建原生對象
Object instance = Class.forName(className).newInstance();
// 暴露方式為存儲一個獲取bean的函數(shù)到map中交掏,
// 將【獲取bean的函數(shù)】放入singletonFactories map中妆偏,這是解決循環(huán)依賴的關鍵。bean是提前暴露盅弛,此時bean仍沒有完成創(chuàng)建钱骂,但拿到它的引用就足夠了
// 用函數(shù)而不是簡單的bean對象,因為函數(shù)更靈活挪鹏,可以插入其他的操作见秽,比如aop
singletonFactories.put(className, () -> {
// TODO 對 instance 的其他操作
return instance;
});
// 依賴裝配:設置instance的屬性
this.handleFieldInject(instance);
// bean創(chuàng)建完成,存入單例池中
singletonObjects.put(className, instance);
return instance;
}
2.3 屬性注入
先從單例池singletonObjects取讨盒,如果取不到則從singletonFactories中取解取,如果存在會取出獲取bean的函數(shù),對該函數(shù)求值返顺,就拿到了先前的bean禀苦。仍然拿不到的話則調(diào)用createBean創(chuàng)建。
private void handleFieldInject(Object instance) throws Exception {
Field[] declaredFields = instance.getClass().getDeclaredFields();
for (Field field : declaredFields) {
// 為標注@CAutowired注解的屬性賦值遂鹊,CAutowired是自定義注解振乏,模擬Spring中@Autowired
if (field.isAnnotationPresent(CAutowired.class)) {
// 獲取屬性類名
String fieldClassName = field.getType().getName();
field.setAccessible(true);
// 從單例池中獲取字段的實例
Object singleton = singletonObjects.get(fieldClassName);
if (singleton != null) {
field.set(instance, singleton);
} else if (singletonFactories.containsKey(fieldClassName)) {
// 單例池中不存在,則從singletonFactories中獲取
ObjectFactory objectFactory = singletonFactories.get(fieldClassName);
// 對函數(shù)求值秉扑,執(zhí)行前面代碼傳入的lambda表達式
Object object = objectFactory.getObject();
field.set(instance, object);
singletonFactories.remove(fieldClassName);
} else {
// singletonFactories也沒有則創(chuàng)建
Object injectObj = this.createBean(fieldClassName);
field.set(instance, injectObj);
}
}
}
}
3. Spring循環(huán)依賴的解決方案
Spring是支持循環(huán)依賴的慧邮,是如何做到的呢?設計主要如下舟陆,三個map误澳,文檔稱為緩存,其中singletonObjects和earlySingletonObjects與上面demo中的含義相同秦躯,earlySingletonObjects的作用是為了在增加一層緩存忆谓,當lambda表達式執(zhí)行過一次后就會將結(jié)果放進去,避免了為多次函數(shù)求值宦赠,提升了性能陪毡。
/** Cache of singleton objects: bean name to bean instance. */
private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);
/** Cache of singleton factories: bean name to ObjectFactory. */
private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16);
/** Cache of early singleton objects: bean name to bean instance. */
private final Map<String, Object> earlySingletonObjects = new HashMap<>(16);
【獲取bean的函數(shù)】存入singletonFactoriesmap中,該函數(shù)在java中的表現(xiàn)是ObjectFactory勾扭,一個函數(shù)式接口
// org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#doCreateBean
// 創(chuàng)建bean的過程中毡琉,添加【獲取bean的函數(shù)】到 singletonFactories中
addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));