Spring Bean 循環依賴為什么需要三級緩存
這里指的是單例的、非構造依賴的循環引用。很多人都知道Spring用了三層緩存來解決循環依賴,但是不知道其原因,為什么是三級緩存?二級緩存不行嗎?一級緩存不可以 ?
三級緩存
Spring 解決循環依賴的核心就是提前暴露對象,而提前暴露的對象就是放置於第二級緩存中。緩存的底層都是Map,至於它們屬於第幾層是由Spring獲取數據順序以及其作用來表現的。
三級緩存的說明:
名稱 | 作用 |
---|---|
singletonObjects |
一級緩存,存放完整的 Bean。 |
earlySingletonObjects |
二級緩存,存放提前暴露的Bean,Bean 是不完整的,未完成屬性注入和執行 初始化(init) 方法。 |
singletonFactories |
三級緩存,存放的是 Bean 工廠,主要是生產 Bean,存放到二級緩存中。 |
在DefaultSingletonBeanRegistry
類中:
/** 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 ConcurrentHashMap<>(16);
為什么使用三級緩存?
第一 先說說第一級緩存singletonObjects
1.先說一級緩存singletonObjects
。實際上,一級依賴已經可以解決循環依賴的問題,假設兩個beanA和beanB相互依賴,beanA被實例化后,放入一級緩存,即使沒有進行初始化,但是beanA的引用已經創建(棧到堆的引用已經確定),其他依賴beanB已經可以持有beanA的引用,但是這個bean在沒有初始化完成前,其內存(堆)里的字段、方法等還不能正常使用,but,這並不影響對象之間引用持有;這個時候beanA依賴的beanB實例化,beanB可以順利拿到beanA的引用,完成beanB的實例化與初始化,並放入一級緩存,在beanB完成創建后,beanA通過緩存順利拿到beanB的引用,至此,循環依賴只需一層緩存就能完成。
2.一級緩存的關鍵點在與:bean實例化與初始化的分離。從JVM的角度,實例化后,對象已經存在,其內的屬性都是初始默認值,只有在初始化后才會賦值,以及持有其他對象的引用。通過這個特性,在實例化后,我們就可以將對象的引用放入緩存交給需要引用依賴的其他對象,這個過程就是提前暴露。
第二 說說第三級緩存singletonFactories
1.上述我們通過一級緩存已經拿到的對象有什么問題?
根本問題就是,我們拿到的是bean的原始引用,如果我們需要的是bean的代理對象怎么辦?Spring里充斥了大量的動態代理模式的架構,典型的AOP就是動態代理模式實現的,再比如我們經常使用的配置類注解@Configuration
在缺省情況下(full mode),其內的所有@Bean都是處於動態代理模式,除非手動指定proxyBeanMethods = false
將配置轉成簡略模式(lite mode)。
2.所以,Spring在bean實例化后,將原始bean放入第三級緩存singletonFactories中,第三級緩存里實際存入的是ObjectFactory接口簽名的回調實現。
# 函數簽名
addSingletonFactory(String beanName, ObjectFactory<?> singletonFactory)
# 具體實現由回調決定
addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
那么如果有動態代理的需求,里面可以埋點進行處理,將原始bean包裝后返回。
protected Object getEarlyBeanReference(String beanName, RootBeanDefinition mbd, Object bean) {
Object exposedObject = bean;
if (!mbd.isSynthetic() && hasInstantiationAwareBeanPostProcessors()) {
for (BeanPostProcessor bp : getBeanPostProcessors()) {
if (bp instanceof SmartInstantiationAwareBeanPostProcessor) {
SmartInstantiationAwareBeanPostProcessor ibp = (SmartInstantiationAwareBeanPostProcessor) bp;
exposedObject = ibp.getEarlyBeanReference(exposedObject, beanName);
}
}
}
return exposedObject;
}
3.通過第三級緩存我們可以拿到可能經過包裝的對象,解決對象代理封裝的問題。
第三 說說第二級緩存earlySingletonObjects
1.為什么需要earlySingletonObjects
這個二級緩存?並且,如果只有一個緩存的情況下,為什么不直接使用singletonFactories
這個緩存,即可實現代理又可以緩存數據。
2.從軟件設計角度考慮,三個緩存代表三種不同的職責,根據單一職責原理,從設計角度就需分離三種職責的緩存,所以形成三級緩存的狀態。
3、再次說說三級緩存的划分及其作用。
一級緩存singletonObjects
是完整的bean,它可以被外界任意使用,並且不會有歧義。
二級緩存earlySingletonObjects
是不完整的bean,沒有完成初始化,它與singletonObjects
的分離主要是職責的分離以及邊界划分,可以試想一個Map緩存里既有完整可使用的bean,也有不完整的,只能持有引用的bean,在復雜度很高的架構中,很容易出現歧義,並帶來一些不可預知的錯誤。
三級緩存singletonFactories
,其職責就是包裝一個bean,有回調邏輯,所以它的作用非常清晰,並且只能處於第三層。
在實際使用中,要獲取一個bean,先從一級緩存一直查找到三級緩存,緩存bean的時候是從三級到一級的順序保存,並且緩存bean的過程中,三個緩存都是互斥的,只會保持bean在一個緩存中,而且,最終都會在一級緩存中。
總結
還不到總結的時候... ...