沉淀、分享、成長,讓自己和他人都能有所收獲!😄
延遲滿足能給你帶來什么?
大學有四年時間,但幾乎所有人都是臨近畢業(yè)才發(fā)現(xiàn)找一份好工作費勁,尤其是我能非常熟悉的軟件開發(fā)行業(yè),即使是畢業(yè)了還需要額外花錢到培訓機構,在學一遍編程技術才能出去找工作。好像在校這幾年壓根就沒學到什么!
就我個人而言可能是因為上學期間喜歡編程,也從師哥、師姐那里聽到一些關于畢業(yè)后找工作的不容易,也了解了一些社會上對程序員開發(fā)技能的要求級別。也就是得到了這些消息,又加上自己樂于折騰,我給自己定了一個每天都能完成的小目標:
紅塵世界幾個王,我自不服迎頭上。
日敲代碼兩百行,沖進世界五百強。
哈哈哈,就這么每天兩百行代碼,一個月就是6千行,一年就是6萬行,三年后開始實習就有18萬行,一個應屆實習生有將近20萬行代碼的敲擊量,幾乎已經(jīng)可以非常熟練的完成各類簡單的工作,在加上實習中對整個項目流程真正的斷鏈后,找一個正經(jīng)
的開發(fā)工作,還是很容易的。
而這時候找工作的容易,就來自于你一直以來的學習和沉淀,但如果你沒經(jīng)過這些努力,可能等畢業(yè)后就會變得非?;艁y,最后沒辦法只能去一些機構再學習一遍。
謝飛機,小記!
,以前感覺Spring沒啥,看過一篇getBean,我的天!
謝飛機:面試官,最近我看了 Spring 的 getBean 發(fā)現(xiàn)這里好多東西,還有一個是要解決循環(huán)依賴的,這玩意面試有啥要問的嗎?
面試官:有哇,Spring 是如何解決循環(huán)依賴的?
謝飛機:嗯,通過三級緩存提前暴露對象解決的。
面試官:可以哈,那這三個緩存里都存放了什么樣的對象信息呢?
謝飛機:一級緩存存放的是完整對象,也叫成品對象。二級緩存存放的是半成品對象,就是那些屬性還沒賦值的對象。三級緩存存放的是 ObjectFactory<?>
類型的 lambda 表達式,就是這用于處理 AOP 循環(huán)依賴的。
面試官:可以呀,謝飛機有所準備嘛!那如果沒有三級緩存,只有二級或者一級,能解決循環(huán)依賴嗎?
謝飛機:其實我看過資料了,可以解決,只不過 Spring 要保證幾個事情,只有一級緩存處理流程沒法拆分,復雜度也會增加,同時半成品對象可能會有空指針異常。而將半成品與成品對象分開,處理起來也更加優(yōu)雅、簡單、易擴展。另外 Spring 的兩大特性中不僅有 IOC 還有 AOP,也就是基于字節(jié)碼增強后的方法,該存放到哪,而三級緩存最主要,要解決的循環(huán)依賴就是對 AOP 的處理,但如果把 AOP 代理對象的創(chuàng)建提前,那么二級緩存也一樣可以解決。但是,這就違背了 Spring 創(chuàng)建對象的原則,Spring 更喜歡把所有的普通 Bean 都初始化完成,在處理代理對象的初始化。
面試官:飛機,不錯嘛,這次了解了不少。那問個簡單的,你擼過循環(huán)依賴的解決方案?
謝飛機:哦哦,這沒有,沒實踐過!!!確實應該搞一下,試試。
了解問題的本質(zhì)再分析問題,往往更利于對問題有更深入的了解和研究。所以我們在分析 Spring 關于循環(huán)依賴的源碼之前,先要了解下什么是循環(huán)依賴。
public class ABTest {
public static void main(String[] args) {
new ClazzA();
}
}
class ClazzA {
private ClazzB b = new ClazzB();
}
class ClazzB {
private ClazzA a = new ClazzA();
}
java.lang.StackOverflowError
在這部分的代碼中就一個核心目的,我們來自己解決一下循環(huán)依賴,方案如下:
public class CircleTest {
private final static Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);
public static void main(String[] args) throws Exception {
System.out.println(getBean(B.class).getA());
System.out.println(getBean(A.class).getB());
}
private static <T> T getBean(Class<T> beanClass) throws Exception {
String beanName = beanClass.getSimpleName().toLowerCase();
if (singletonObjects.containsKey(beanName)) {
return (T) singletonObjects.get(beanName);
}
// 實例化對象入緩存
Object obj = beanClass.newInstance();
singletonObjects.put(beanName, obj);
// 屬性填充補全對象
Field[] fields = obj.getClass().getDeclaredFields();
for (Field field : fields) {
field.setAccessible(true);
Class<?> fieldClass = field.getType();
String fieldBeanName = fieldClass.getSimpleName().toLowerCase();
field.set(obj, singletonObjects.containsKey(fieldBeanName) ? singletonObjects.get(fieldBeanName) : getBean(fieldClass));
field.setAccessible(false);
}
return (T) obj;
}
}
class A {
private B b;
// ...get/set
}
class B {
private A a;
// ...get/set
}
這段代碼提供了 A、B 兩個類,互相有依賴。但在兩個類中的依賴關系使用的是 setter 的方式進行填充。也就是只有這樣才能避免兩個類在創(chuàng)建之初不非得強依賴于另外一個對象。
getBean
,是整個解決循環(huán)依賴的核心內(nèi)容,A 創(chuàng)建后填充屬性時依賴 B,那么就去創(chuàng)建 B,在創(chuàng)建 B 開始填充時發(fā)現(xiàn)依賴于 A,但此時 A 這個半成品對象已經(jīng)存放在緩存到singletonObjects
中了,所以 B 可以正常創(chuàng)建,在通過遞歸把 A 也創(chuàng)建完整了。
通過上面的例子我們大概了解到,A和B互相依賴時,A創(chuàng)建完后填充屬性B,繼續(xù)創(chuàng)建B,再填充屬性A時就可以從緩存中獲取了,如下:
那這個解決事循環(huán)依賴的事放到 Spring 中是什么樣呢?展開細節(jié)!
雖然,解決循環(huán)依賴的核心原理一樣,但要放到支撐起整個 Spring 中 IOC、AOP 特性時,就會變得復雜一些,整個處理 Spring 循環(huán)依賴的過程如下;
說說細節(jié)
關于本章節(jié)涉及到的案例源碼分析,已更新到 github:https://github.com/fuzhengwei/interview - interview-31
以下是單元測試中對AB依賴的獲取Bean操作,重點在于進入 getBean 的源碼跟進;
@Test
public void test_alias() {
BeanFactory beanFactory = new ClassPathXmlApplicationContext("spring-config.xml");
Bean_A bean_a = beanFactory.getBean("bean_a", Bean_A.class);
logger.info("獲取 Bean 通過別名:{}", bean_a.getBean_b());
}
org.springframework.beans.factory.support.AbstractBeanFactory.java
@Override
public <T> T getBean(String name, Class<T> requiredType) throws BeansException {
return doGetBean(name, requiredType, null, false);
}
doGetBean 方法
protected <T> T doGetBean(
final String name, final Class<T> requiredType, final Object[] args, boolean typeCheckOnly)
throws BeansException {
// 從緩存中獲取 bean 實例
Object sharedInstance = getSingleton(beanName);
// mbd.isSingleton() 用于判斷 bean 是否是單例模式
if (mbd.isSingleton()) {
// 獲取 bean 實例
sharedInstance = getSingleton(beanName, new ObjectFactory<Object>() {
@Override
public Object getObject() throws BeansException {
try {
// 創(chuàng)建 bean 實例,createBean 返回的 bean 實例化好的
return createBean(beanName, mbd, args);
}
catch (BeansException ex) {
destroySingleton(beanName);
throw ex;
}
}
});
// 后續(xù)的處理操作
bean = getObjectForBeanInstance(sharedInstance, name, beanName, mbd);
}
// ...
// 返回 bean 實例
return (T) bean;
}
doCreateBean 方法
protected Object doCreateBean(final String beanName, final RootBeanDefinition mbd, final Object[] args)
throws BeanCreationException {
// 創(chuàng)建 bean 實例,并將 bean 實例包裝到 BeanWrapper 對象中返回
instanceWrapper = createBeanInstance(beanName, mbd, args);
// 添加 bean 工廠對象到 singletonFactories 緩存中
addSingletonFactory(beanName, new ObjectFactory<Object>() {
@Override
public Object getObject() throws BeansException {
// 獲取原始對象的早期引用,在 getEarlyBeanReference 方法中,會執(zhí)行 AOP 相關邏輯。若 bean 未被 AOP 攔截,getEarlyBeanReference 原樣返回 bean。
return getEarlyBeanReference(beanName, mbd, bean);
}
});
try {
// 填充屬性,解析依賴關系
populateBean(beanName, mbd, instanceWrapper);
if (exposedObject != null) {
exposedObject = initializeBean(beanName, exposedObject, mbd);
}
}
// 返回 bean 實例
return exposedObject;
}
createBeanInstance
,創(chuàng)建 bean 實例,并將 bean 實例包裝到 BeanWrapper 對象中返回addSingletonFactory
,添加 bean 工廠對象到 singletonFactories 緩存中getEarlyBeanReference
,獲取原始對象的早期引用,在 getEarlyBeanReference 方法中,會執(zhí)行 AOP 相關邏輯。若 bean 未被 AOP 攔截,getEarlyBeanReference 原樣返回 bean。populateBean
,填充屬性,解析依賴關系。也就是從這開始去找尋 A 實例中屬性 B,緊接著去創(chuàng)建 B 實例,最后在返回回來。getSingleton 三級緩存
protected Object getSingleton(String beanName, boolean allowEarlyReference) {
// 從 singletonObjects 獲取實例,singletonObjects 是成品 bean
Object singletonObject = this.singletonObjects.get(beanName);
// 判斷 beanName ,isSingletonCurrentlyInCreation 對應的 bean 是否正在創(chuàng)建中
if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
synchronized (this.singletonObjects) {
// 從 earlySingletonObjects 中獲取提前曝光未成品的 bean
singletonObject = this.earlySingletonObjects.get(beanName);
if (singletonObject == null && allowEarlyReference) {
// 獲取相應的 bean 工廠
ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
if (singletonFactory != null) {
// 提前曝光 bean 實例,主要用于解決AOP循環(huán)依賴
singletonObject = singletonFactory.getObject();
// 將 singletonObject 放入緩存中,并將 singletonFactory 從緩存中移除
this.earlySingletonObjects.put(beanName, singletonObject);
this.singletonFactories.remove(beanName);
}
}
}
}
return (singletonObject != NULL_OBJECT ? singletonObject : null);
}
singletonObjects.get(beanName)
,從 singletonObjects 獲取實例,singletonObjects 是成品 beanisSingletonCurrentlyInCreation
,判斷 beanName ,isSingletonCurrentlyInCreation 對應的 bean 是否正在創(chuàng)建中allowEarlyReference
,從 earlySingletonObjects 中獲取提前曝光未成品的 beansingletonFactory.getObject()
,提前曝光 bean 實例,主要用于解決AOP循環(huán)依賴綜上,是一個處理循環(huán)依賴的代碼流程,這部分提取出來的內(nèi)容主要為核心內(nèi)容,并沒與長篇大論的全部拆取出來,大家在調(diào)試的時候會涉及的比較多,盡可能要自己根據(jù)流程圖操作調(diào)試幾遍。
綜上從我們自己去嘗試解決循環(huán)依賴,學習了循環(huán)依賴的核心解決原理。又分析了 Spring 解決的循環(huán)依賴的處理過程以及核心源碼的分析。那么接下來我們在總結下三級緩存分別不同的處理過程,算是一個總結,也方便大家理解。
ObjectFactory<?>
類型的 lambda 表達式,而 Spring 的原則又不希望將此類類型的 Bean 前置創(chuàng)建,所以要存放到三級緩存中處理。