springIoc依賴注入循環(huán)依賴三級(jí)緩存實(shí)踐
springIoc的理解,原理和實(shí)現(xiàn)
控制反轉(zhuǎn)
理論思想,原來的對(duì)象是由使用者來進(jìn)行控制,有了spring之后,可以把整個(gè)對(duì)象交給spring來幫我們進(jìn)行管理
依賴注入DI
依賴注入,把對(duì)應(yīng)的屬性的值注入到具體的對(duì)象中,@autowired,populateBean完成屬性的注入
容器
beanFactory,存儲(chǔ)對(duì)象,使用map結(jié)構(gòu)來存儲(chǔ),在spring中一般存在三級(jí)緩存,singletonObjects存放完整的bean對(duì)象,earylySingletonObject,factoryObject,整個(gè)bean的生命周期,從創(chuàng)建初始化使用到銷毀的過程都是容器來管理的
分:
- 一般ioc容器的時(shí)候涉及到容器的創(chuàng)建過程(beanFactory,DefaultListableBeanFactory),向bean工廠中設(shè)置一些參數(shù)BeanPostProcessor,Aware接口的子類等等屬性
- 加載解析bean對(duì)象,準(zhǔn)備要?jiǎng)?chuàng)建的bean對(duì)象的定義對(duì)象 beanDefinition(xml或者注解的解析過程)
- beanFactoryPostProcessor的處理,此處是擴(kuò)展點(diǎn),PlaceHolder ConfigurSupport(占位符),ConfigurationClassProcessor.
- BeanPostProcessor的注冊功能,方便后續(xù)bean對(duì)象完成具體的擴(kuò)展對(duì)象。
- 通過反射的方式將BeanDefinition對(duì)象實(shí)例化成具體的bean對(duì)象。
- bean對(duì)象的初始化過程(填充屬性,調(diào)用aware的子類方法,調(diào)用BeanPostProcessor前置處理方法,調(diào)用init-method方法,BeanPostProcessor的后置處理方法)
- 生成完整的bean對(duì)象,通過getBean方法可以直接獲取。
createBeanFactory,getBean,doGetBean,createBean,doCreateBean,createBeanInstance,populateBean
- 先通過createBeanFactory創(chuàng)建出一個(gè)Bean工廠(DeafaultListableBeanFactory)
- 開始循環(huán)創(chuàng)建對(duì)象,因?yàn)槿萜髦械腷ean默認(rèn)都是單例的,所以通過getBean,doGetBean從容器中查找,找不到的話,通過createBean,doCreateBean方法,以反射的方式創(chuàng)建對(duì)象,一般情況下使用無參的構(gòu)造方法(getDeclaredConstruce,newInstance)
- 進(jìn)行對(duì)象的屬性填充populateBean
- 進(jìn)行其他的初始化操作initializingBean
描述一下bean的生命周期
- 實(shí)例化bean,反射的方式生成對(duì)象
- 填充bean的屬性:populateBean(),循環(huán)依賴的問題(三級(jí)緩存)
- 調(diào)用aware接口相關(guān)的方法:invokeAwareMethod(完成beanName可以獲取容器bean的名稱,BeanFactory獲取當(dāng)前bean factory這也可以調(diào)用容器的服務(wù),BeanClassLoader對(duì)象的屬性設(shè)置)
- 調(diào)用BeanPostProcessor中的前置方法:使用比較多的ApplicationContextPostProcessor設(shè)置ApplicationContext,Environment,ResourceLoader,EmbeddValueResolver等對(duì)象。
- 調(diào)用initmethod方法:invokeInitmethod(),判斷是否實(shí)現(xiàn)了IntialzingBean接口,如果有,調(diào)用afterPropertiesSet方法。
- 調(diào)用BeanPostProceessor的后置處理方法:spring的aop就是在此實(shí)現(xiàn)的,AbstractAutoProxyCreator注冊Destuction相關(guān)的回調(diào)接口。
- 通過getbean獲取完整對(duì)象
- 銷毀 判斷是否實(shí)現(xiàn)了DisposableBean接口 destroyMethod方法



循環(huán)依賴
什么是循環(huán)依賴
第一種互相依賴:A 依賴 B,B 又依賴 A,它們之間形成了循環(huán)依賴

第二種三者之間的依賴 A依賴B,B依賴C,C依賴A

第三種是自我依賴:A依賴A形成了循環(huán)依賴

三級(jí)緩存
- singletonObjects:緩存某個(gè)beanName對(duì)應(yīng)的經(jīng)過了完整生命周期的bean
- earlySingletonObjects:緩存提前拿原始對(duì)象進(jìn)行了AOP之后得到的代理對(duì)象,原始對(duì)象還沒有進(jìn)行屬性注入和后續(xù)的BeanPostProcessor等生命周期
- singletonFactories:緩存的是一個(gè)ObjectFactory,主要用來去生成原始對(duì)象進(jìn)行了AOP之后得到的代理對(duì)象,在每個(gè)Bean的生成過程中,都會(huì)提前暴露一個(gè)工廠,這個(gè)工廠可能用到,也可能用不到,如果沒有出現(xiàn)循環(huán)依賴依賴本bean,那么這個(gè)工廠無用,本bean按照自己的生命周期執(zhí)行,執(zhí)行完后直接把本bean放入singletonObjects中即可,如果出現(xiàn)了循環(huán)依賴依賴了本bean,則另外那個(gè)bean執(zhí)行ObjectFactory提交得到一個(gè)AOP之后的代理對(duì)象(如果有AOP的話,如果無需AOP,則直接得到一個(gè)原始對(duì)象)。
public class DefaultSingletonBeanRegistry extends SimpleAliasRegistry implements SingletonBeanRegistry {
/** 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);
}
能否把一級(jí)緩存和二級(jí)緩存合并,只保留一個(gè)map結(jié)構(gòu)來完成類似的功能?
理論上是可行的,一級(jí)緩存和二級(jí)緩存的value類型都是Object,都可以用來存放對(duì)象,只是對(duì)象的狀態(tài)不同而已,所以當(dāng)使用一個(gè)map的時(shí)候,只要給定具體的標(biāo)識(shí)就可以解決這個(gè)問題,也就是說設(shè)置對(duì)象的時(shí)候,在value中不再直接存放對(duì)象,而是對(duì)象加標(biāo)識(shí)位,比如0代表成品,1代表半成品,那么一個(gè)map就可以存儲(chǔ)成品對(duì)象和半成品對(duì)象了,但是雖然可以這么干,沒人會(huì)這么做,代碼不優(yōu)雅,而且每次在進(jìn)行對(duì)象的存或者放的時(shí)候都要判斷是否是成品對(duì)象,比較麻煩,直接用兩個(gè)map結(jié)構(gòu)解決即可
如果只有一級(jí)緩存和二級(jí)緩存,能否解決循環(huán)依賴的問題?
可以,但是有前提條件:沒有aop的配置或者不需要?jiǎng)?chuàng)建代理對(duì)象的時(shí)候,兩個(gè)map就可以解決循環(huán)依賴的問題
為什么三級(jí)緩存可以解決aop下的循環(huán)依賴問題?三級(jí)緩存到底有什么作用?
1,在同一個(gè)容器中,能否出現(xiàn)同名的不同對(duì)象?
不能,id是唯一標(biāo)識(shí)
2,在同一個(gè)容器中,按照標(biāo)準(zhǔn)的生命周期,先創(chuàng)建了原始對(duì)象,后續(xù)又創(chuàng)建了代理對(duì)象,那時(shí)會(huì)怎么辦?
當(dāng)創(chuàng)建了代理對(duì)象之后,應(yīng)該要使用代理對(duì)象的,但是原始對(duì)象已經(jīng)存在,那么應(yīng)該將原始對(duì)象給覆蓋掉。getEarlyBeanReference()方法執(zhí)行的邏輯是一樣的
3,三級(jí)緩存到底有什么作用,為什么存在代理對(duì)象的時(shí)候就要使用三級(jí)緩存呢?
在標(biāo)準(zhǔn)的bean的生命周期中,要先創(chuàng)建出原始對(duì)象,創(chuàng)建出原始對(duì)象之后要使用populateBean方法來完成屬性的賦值,此時(shí)賦值的對(duì)象是原始對(duì)象,因?yàn)榇韺?duì)象還沒有創(chuàng)建,代理對(duì)象的創(chuàng)建步驟是在BeanPostProcessor的后置處理方法中,也就是說已經(jīng)完成賦值之后代理對(duì)象才創(chuàng)建出來,所以會(huì)報(bào)錯(cuò)(this means that said other beans do not use the final version of the bean),如何解決這個(gè)問題呢?
需要將代理對(duì)象的創(chuàng)建前置,也就是說在對(duì)象賦值的那一刻,要唯一性的確定出到底是原始對(duì)象還是代理對(duì)象,所以會(huì)優(yōu)先把所有的bean都放到三級(jí)緩存中,在需要進(jìn)行對(duì)象賦值的時(shí)候,從三級(jí)緩存中取出lambda表達(dá)式,lambda表達(dá)式的執(zhí)行邏輯就是確定原始對(duì)象還是代理對(duì)象。如果是原始對(duì)象就賦值原始對(duì)象,如果是代理對(duì)象就賦值代理對(duì)象。
Spring中哪些情況下,不能解決循環(huán)依賴問題?
1,多例Bean通過setter注入的情況,不能解決循環(huán)依賴的問題。當(dāng)兩個(gè)多例Bean相互依賴并且使用 Setter 方法注入時(shí),Spring容器無法解決循環(huán)依賴。這是因?yàn)樵诙嗬J较?,每次請求獲取一個(gè)新的Bean實(shí)例,Spring容器無法在創(chuàng)建Bean之前就確定依賴關(guān)系。
2,構(gòu)造器注入的bean情況,不能解決循環(huán)依賴的問題。循環(huán)依賴的產(chǎn)生:循環(huán)依賴通常在Bean的創(chuàng)建階段發(fā)生,而構(gòu)造器注入是在Bean創(chuàng)建之前發(fā)生的,因此無法通過構(gòu)造器注入的方式解決已經(jīng)發(fā)生的循環(huán)依賴。
3,設(shè)置了@DependsOn的Bean情況,不能解決循環(huán)依賴的問題。
總結(jié)
以上為個(gè)人經(jīng)驗(yàn),希望能給大家一個(gè)參考,也希望大家多多支持腳本之家。
相關(guān)文章
springboot 中異步任務(wù),定時(shí)任務(wù),郵件任務(wù)詳解
這篇文章主要介紹了springboot 與異步任務(wù),定時(shí)任務(wù),郵件任務(wù),本文通過實(shí)例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2023-09-09
詳細(xì)講解Java中==與equals的區(qū)別對(duì)比
這篇文章主要為大家詳細(xì)介紹了Java中==與equals的區(qū)別對(duì)比,文中有詳細(xì)的代碼示例供大家參考,具有一定的參考價(jià)值,感興趣的同學(xué)可以參考閱讀下2023-09-09
SpringBoot?使用?spring.profiles.active?來區(qū)分不同環(huán)境配置實(shí)現(xiàn)
很多時(shí)候,我們項(xiàng)目在開發(fā)環(huán)境和生產(chǎn)環(huán)境的配置是不一樣的,例如,數(shù)據(jù)庫配置,在開發(fā)的時(shí)候,我們一般用測試數(shù)據(jù)庫,而在生產(chǎn)環(huán)境,我們要用生產(chǎn)數(shù)據(jù)庫,這時(shí)候,我們可以利用?profile?在不同的環(huán)境下配置用不同的配置文件或者不同的配置,感興趣的可以了解一下2026-04-04
Java自動(dòng)化實(shí)現(xiàn)將HTML轉(zhuǎn)換為Word
在日常的軟件開發(fā)和企業(yè)應(yīng)用中,我們經(jīng)常會(huì)遇到需要將動(dòng)態(tài)生成的 HTML 內(nèi)容轉(zhuǎn)化為規(guī)范的 Word 文檔,本文將介紹如何利用 Java 輕松實(shí)現(xiàn) HTML 到 Word 的高效轉(zhuǎn)換,需要的可以了解下2025-10-10
深入講解spring boot中servlet的啟動(dòng)過程與原理
這篇文章主要給大家介紹了關(guān)于spring boot中servlet啟動(dòng)過程與原理的相關(guān)資料,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2018-07-07
mybatis 中 foreach collection的用法小結(jié)(三種)
這篇文章主要介紹了mybatis 中 foreach collection的用法小結(jié)(三種),需要的朋友可以參考下2017-10-10

