Spring第二篇 Spring IOC
目錄
作為一個(gè)后端開(kāi)發(fā)妄荔,我們的日常離不開(kāi)Spring,尤其是Spring的IoC泳挥,但是你真的了解Spring IoC其中的細(xì)節(jié)嗎?
Spring的Bean是怎么創(chuàng)建的呢至朗?bean的生命周期是怎么樣的屉符?其中有什么容易踩坑的點(diǎn)嗎?讓我們帶著疑問(wèn)一起來(lái)看看锹引。
一矗钟、概念梳理
1.什么是IoC和DI
IoC:控制反轉(zhuǎn)(Inversion of Control)容器,這不是什么技術(shù)嫌变,而是一種設(shè)計(jì)思想吨艇。在Java開(kāi)發(fā)中,Ioc意味著將你設(shè)計(jì)好的對(duì)象交給容器控制初澎,而不是傳統(tǒng)的在你的對(duì)象內(nèi)部直接控制秸应。
IoC一些解釋:
●誰(shuí)控制誰(shuí),控制什么:傳統(tǒng)Java SE程序設(shè)計(jì)碑宴,我們直接在對(duì)象內(nèi)部通過(guò)new進(jìn)行創(chuàng)建對(duì)象软啼,是程序主動(dòng)去創(chuàng)建依賴對(duì)象;而IoC是有專門一個(gè)容器來(lái)創(chuàng)建這些對(duì)象延柠,即由Ioc容器來(lái)控制對(duì)象的創(chuàng)建祸挪;誰(shuí)控制誰(shuí)?當(dāng)然是IoC 容器控制了對(duì)象贞间;控制什么贿条?那就是主要控制了外部資源獲取(不只是對(duì)象包括比如文件等)增热。
●為何是反轉(zhuǎn)整以,哪些方面反轉(zhuǎn)了:有反轉(zhuǎn)就有正轉(zhuǎn),傳統(tǒng)應(yīng)用程序是由我們自己在對(duì)象中主動(dòng)控制去直接獲取依賴對(duì)象峻仇,也就是正轉(zhuǎn)公黑;而反轉(zhuǎn)則是由容器來(lái)幫忙創(chuàng)建及注入依賴對(duì)象;為何是反轉(zhuǎn)摄咆?因?yàn)橛扇萜鲙臀覀儾檎壹白⑷胍蕾噷?duì)象凡蚜,對(duì)象只是被動(dòng)的接受依賴對(duì)象,所以是反轉(zhuǎn)吭从;哪些方面反轉(zhuǎn)了朝蜘?依賴對(duì)象的獲取被反轉(zhuǎn)了。
DI:依賴注入(Dependency Injection)涩金,組件之間依賴關(guān)系由容器在運(yùn)行期決定谱醇,形象的說(shuō)暇仲,即由容器動(dòng)態(tài)的將某個(gè)依賴關(guān)系注入到組件之中。依賴注入的目的并非為軟件系統(tǒng)帶來(lái)更多功能枣抱,而是為了提升組件重用的頻率熔吗,并為系統(tǒng)搭建一個(gè)靈活、可擴(kuò)展的平臺(tái)佳晶。IoC和DI其實(shí)是同一個(gè)概念的不同角度描述桅狠。
傳統(tǒng)獲取對(duì)象的方式與IoC方式對(duì)比:
二、Bean的創(chuàng)建
1.IoC容器的初始化
Spring IOC容器初始化的核心過(guò)程可以簡(jiǎn)單的如下圖表示轿秧,主要有四個(gè)步驟(還有一些如:后置加載器中跌,國(guó)際化,事件廣播器等一些過(guò)程不展開(kāi)):
Bean定義的定位菇篡,Bean 可能定義在XML中漩符,或者一個(gè)注解,或者其他形式驱还。這些都被用Resource來(lái)定位嗜暴,讀取Resource獲取BeanDefinition 注冊(cè)到 Bean定義注冊(cè)表中。
第一次向容器getBean操作會(huì)觸發(fā)Bean的創(chuàng)建過(guò)程,實(shí)列化一個(gè)Bean時(shí) ,根據(jù)BeanDefinition中類信息等實(shí)列化Bean议蟆。
將實(shí)列化的Bean放到單列Bean緩存內(nèi)闷沥。
此后再次獲取向容器getBean就會(huì)從緩存中獲取。
擴(kuò)展閱讀:IoC容器初始化源碼解析Spring IOC容器初始化
2.Bean的注入方式
a.常用的注入方式
bean的注入方式是老生常談的話題了咐容,這里不展開(kāi)細(xì)說(shuō)舆逃。
目前主要有五種注入方式:SET注入,構(gòu)造器注入戳粒,靜態(tài)工廠路狮,實(shí)例工廠,注解方式
b.常用的Bean的配置參數(shù)
屬性含義說(shuō)明
idbean的唯一標(biāo)識(shí)
class類的完全限定名
parent父類bean定義的名字如果沒(méi)有任何聲明蔚约,會(huì)使用父bean奄妨,但是也可以重寫父類。重寫父類時(shí)苹祟,子bean 必須與父bean 兼容砸抛,也就是說(shuō),接受父類的屬性值和構(gòu)造器聲明的值苔咪。
子bean會(huì)繼承父bean的構(gòu)造器聲明的值锰悼,屬性值柳骄,并且重寫父bean的方法团赏。如果init方法,destroy方法已聲明耐薯,他們會(huì)覆蓋父bean相應(yīng)的設(shè)置舔清。保留的設(shè)置會(huì)直接從子bean獲取丝里,包括depends on,auto wire mode体谒,scope杯聚,lazy init.
abstract聲明bean是否是抽象的該屬性決定該類是否會(huì)實(shí)例化。默認(rèn)是false抒痒。
注意:abstract屬性不會(huì)被子bean繼承幌绍,所以,abstract為true時(shí)需要對(duì)每個(gè)bean顯示聲明故响。
lazy-init決定是否延遲實(shí)例化如果為false,則啟動(dòng)時(shí)會(huì)立即實(shí)例化單例模式的bean傀广。默認(rèn)是false。
注意:lazy-init屬性不會(huì)被子bean繼承彩届。
autowire決定是否自動(dòng)裝配bean的屬性autowire有4中模式(該屬性不會(huì)被子bean繼承伪冰。):
1."no":Spring默認(rèn)的模式。bean的引用必須在XML文件中通過(guò)<ref/>元素或ref屬性顯示定義樟蠕。
2."byName":通過(guò)屬性名使用自動(dòng)裝配贮聂。如果一個(gè)Cat類擁有一個(gè)dog屬性,那么Spring會(huì)根據(jù)名字dog去尋找bean,如果沒(méi)有找到bean,則不會(huì)自動(dòng)裝配寨辩。
3."byType":如果Spring容器只有該屬性類型的一個(gè)bean吓懈,會(huì)自動(dòng)裝配。當(dāng)有多個(gè)該屬性類型的bean時(shí)會(huì)報(bào)錯(cuò)捣染。如果沒(méi)有骄瓣,則不會(huì)自動(dòng)裝配。
4."constructor":針對(duì)構(gòu)造器引用耍攘,和byType類似榕栏。
depends-on該bean初始化時(shí)依賴的其他beanbean工廠確保其他bean在該bean之前完成初始化。
注意:依賴項(xiàng)一般通過(guò)bean屬性或構(gòu)造器聲明蕾各,這個(gè)屬性對(duì)其他依賴(如靜態(tài)類或啟動(dòng)階段數(shù)據(jù)庫(kù)的準(zhǔn)備)是必要的扒磁。
注意:depends-on屬性不會(huì)被子bean繼承。
scopebean的作用域singleton:單例模式式曲,默認(rèn)選項(xiàng)
prototype:非單例模式
request:對(duì)于web應(yīng)用妨托,每一個(gè)請(qǐng)求產(chǎn)生一個(gè)新的實(shí)例
session:對(duì)于web應(yīng)用,一個(gè)session產(chǎn)生一個(gè)實(shí)例
init-method初始化方法bean創(chuàng)建時(shí)的初始化方法
destroy-method銷毀方法bean銷毀時(shí)調(diào)用的方法吝羞,僅僅在singleton模式下起作用
擴(kuò)展閱讀:自定義命名空間:2. xml自定義命名空間解析
c.常用的注解:Spring常用注解
3.Bean解析注冊(cè)過(guò)程
這個(gè)過(guò)程完成Bean的從配置文件到解析注冊(cè)成bean工廠的過(guò)程(對(duì)應(yīng)代碼在AbstactApplicationContext.obtainFreshBeanFactory)
XML
Resource
BeanDefinition
BeanFactory
讀取
解析
注冊(cè)
通過(guò)讀取XML配置文件獲取 Resource 資源兰伤,獲取這個(gè)資源包含了路徑config/spring/local/appcontext-client.xml 文件下定義的BeanDefinition信息。
創(chuàng)建一個(gè) BeanFactory钧排,這里使用 DefaultListableBeanFactory敦腔。
創(chuàng)建一個(gè)載入 BeanDefinition 的解讀器,這里使用 XmlBeanDefinitionReader 來(lái)載入 XML 文件形式 BeanDefinition恨溜,通過(guò)一個(gè)回調(diào)配置給 factory符衔。
從定義好的資源位置讀入配置信息找前,具體的解析過(guò)程由 XmlBeanDefinitionReader 的 loadBeanDefinitions() 方法來(lái)完成。完成整個(gè)載入和注冊(cè) Bean 定義之后判族,需要的 IoC 容器就建立起來(lái)可以直接使用了躺盛。
4.Bean的創(chuàng)建過(guò)程
這個(gè)過(guò)程完成Bean的實(shí)例化,如果是singleton類型的Bean還會(huì)放入緩存池中形帮。(對(duì)應(yīng)源碼:AbstactApplicationContext.finishBeanFactoryInitialization)
start
轉(zhuǎn)換對(duì)應(yīng)的beanName
嘗試從緩存中加載單例
N
Y
緩存中加載的單例為空
doGetBean
transformedBeanName
getSingleton
Bean實(shí)例化
getObjectForBeanInstance
類型轉(zhuǎn)換
end
原型模式依賴檢查
檢測(cè)是否到父類工廠加載bean
存在循環(huán)依賴且原型bean槽惫,拋異常中斷加載
isPrototypeCurrentlyInCreation
parentBeanFactory != null && !containsBeanDefinition(beanName)
轉(zhuǎn)化為RootBeanDefinition
getMergedLocalBeanDefinition
尋找Bean的依賴
getDependsOn
Y
N
存在依賴的bean
優(yōu)先加載依賴的bean
針對(duì)不同的scope創(chuàng)建bean
單例模式
原型模式
bean的模式
getBean
getSingleton
根據(jù)單例模式創(chuàng)建bean
創(chuàng)建bean
bean實(shí)例化
類型轉(zhuǎn)換
指定的scope上創(chuàng)建bean
創(chuàng)建bean
bean實(shí)例化
createBean
getObjectForBeanInstance
getObjectForBeanInstance
createBean
bean實(shí)例化
end
getObjectForBeanInstance
name參數(shù)可能是別名或者FactoryBean,需要一系列解析
單例的bean先從緩存中加載
加載失敗再嘗試從singletonFactory中加載
為避免循環(huán)依賴辩撑,先將bean的ObjectFactory加入緩存
獲取的bean如果
符合requireType指定類型則直接返回
否則轉(zhuǎn)化為指定類型
這里得到的是工廠bean的初始狀態(tài)躯枢,我們真正想要的是工廠bean中定義的factory-method方法中返回的bean
遞歸調(diào)用,
優(yōu)先加載當(dāng)前bean的依賴bean
擴(kuò)展閱讀:Spring創(chuàng)建Bean過(guò)程源碼解析:Spring-IOC源碼解讀Spring源碼深度解析
三槐臀、Bean的生命周期
1.Bean生命周期
Bean生命周期是IOC中非常核心的內(nèi)容锄蹂,Spring為我們預(yù)留了很多的接口,讓我們?cè)赽ean實(shí)例化前后水慨、初始化前后可以寫入一些自己的邏輯得糜。
實(shí)例化Bean對(duì)象
設(shè)置對(duì)象屬性
檢查Aware相關(guān)接口并設(shè)置相關(guān)依賴
BeanPostProcessor前置處理
檢查是否是InitializingBean以決定是否調(diào)用afterPropertiesSet方法
檢查是否配有自定義的init-method
BeanPostProcessor后置處理
注冊(cè)必要的Destruction相關(guān)回調(diào)接口
使用中
是否實(shí)現(xiàn)DisposableBean接口
是否配置有自定義的DisposableBean接口
*Bean的生命周期
1. 根據(jù)BeanDefinition信息,實(shí)例化對(duì)象晰洒,Constructor構(gòu)造方法;
2. 根據(jù)BeanDefinition信息朝抖,配置Bean的所有屬性(將bean的引用注入到bean對(duì)應(yīng)的屬性,*可能存在循環(huán)依賴問(wèn)題);
3. 如果Bean實(shí)現(xiàn)了BeanNameAware接口谍珊,工廠調(diào)用Bean的setBeanName治宣,參數(shù)為Bean的Id;
4. 如果Bean實(shí)現(xiàn)BeanFactoryAware接口,Spring將調(diào)用setBeanFactory()方法砌滞,將BeanFactory容器實(shí)例傳入侮邀;
5. 如果Bean實(shí)現(xiàn)ApplicationContextAware接口,Spring將調(diào)用setApplicationContext()方法贝润,將bean所在的應(yīng)用上下文的引用傳入進(jìn)來(lái)绊茧;
5. 如果存在類實(shí)現(xiàn)了BeanPostProcessor接口,執(zhí)行這些實(shí)現(xiàn)類的postProcessBeforeInitialization方法打掘,這相當(dāng)于在Bean初始化之前插入邏輯 华畏;
6. 如果Bean實(shí)現(xiàn)InitializingBean接口, 執(zhí)行afterPropertiesSet方法尊蚁;
7. 如果Bean指定了init-method方法亡笑,就會(huì)調(diào)用該方法。例:\<bean init-method="init"> 横朋;
8. 如果存在類實(shí)現(xiàn)了BeanPostProcessor接口仑乌,執(zhí)行這些實(shí)現(xiàn)類的postProcessAfterInitialization方法,這相當(dāng)于在Bean初始化之后插入邏輯 ;
9. 這個(gè)階段Bean已經(jīng)可以使用了绝骚,scope為singleton的Bean會(huì)被緩存在IOC容器中
10. 如果Bean實(shí)現(xiàn)了DisposableBean接口, 在容器銷毀的時(shí)候執(zhí)行destroy方法祠够。
11. 如果配置了destory-method方法压汪,就調(diào)用該方法。例:\<bean destroy-method="customerDestroy">
擴(kuò)展閱讀:Spring的擴(kuò)展接口 :Spring 擴(kuò)展接口
2.Bean實(shí)例化順序
1.解析內(nèi)部類古瓤,查看內(nèi)部類是否應(yīng)該被定義成一個(gè)Bean止剖,如果是,遞歸解析落君。
2.解析@PropertySource穿香,也就是解析被引入的Properties文件。
3.解析配置類上是否有@ComponentScan注解绎速,如果有則執(zhí)行掃描動(dòng)作皮获,通過(guò)掃描得到的Bean Class會(huì)被立即解析成BeanDefinition,添加進(jìn)beanDefinitionNames屬性中纹冤。之后查看掃描到的Bean Class是否是一個(gè)配置類(大部分情況是洒宝,因?yàn)闃?biāo)識(shí)@Component注解),如果是則遞歸解析這個(gè)Bean Class萌京。
4.解析@Import引入的類雁歌,如果這個(gè)類是一個(gè)配置類,則遞歸解析知残。
5.解析@Bean標(biāo)識(shí)的方法靠瞎,此種形式定義的Bean Class不會(huì)被遞歸解析
6.解析父類上的@ComponentScan,@Import求妹,@Bean乏盐,父類不會(huì)被再次實(shí)例化,因?yàn)槠渥宇惸軌蜃龈割惖墓ぷ髦苹校恍枰~外的Bean了丑勤。
Spring 解決bean的循環(huán)依賴::05、spring ioc-bean的循環(huán)依賴
Spring 的bean加載順序:Spring Bean加載順序
四吧趣、避坑指南
相關(guān)case:
對(duì)bean生命周期了解不充分法竞,在static方法中使用getBean方法,此時(shí)bean還未初始化强挫。
2018-04-08 account-server上線大量NoClassDefFoundError錯(cuò)誤
2.Spring默認(rèn)同id名的bean會(huì)被覆蓋岔霸,一個(gè)解決辦法是不允許同id的bean覆蓋 參考:spring 同名bean問(wèn)題 分析和解決
CaseStudy-20170321-paynotice上線導(dǎo)致未發(fā)券
CaseStudy-20150113-支付成功后與指紋相關(guān)的數(shù)據(jù)沒(méi)有發(fā)送給aegis
CaseStudy-20190108 [API研發(fā)組][bug] -indexapi讀取MCC失敗
3.Spring的bean加載順序。
忽視Spring bean加載順序俯渤,造成多次執(zhí)行程序呆细,結(jié)果會(huì)有所不同
五、參考文獻(xiàn)