Spring第二篇 Spring IOC
目錄
作為一個后端開發,我們的日常離不開Spring,尤其是Spring的IoC,但是你真的了解Spring IoC其中的細節嗎?
Spring的Bean是怎么創建的呢?bean的生命周期是怎么樣的?其中有什么容易踩坑的點嗎?讓我們帶著疑問一起來看看。
一、概念梳理
1.什么是IoC和DI
IoC:控制反轉(Inversion of Control)容器,這不是什么技術,而是一種設計思想。在Java開發中,Ioc意味著將你設計好的對象交給容器控制,而不是傳統的在你的對象內部直接控制。
IoC一些解釋:
●誰控制誰,控制什么:傳統Java SE程序設計,我們直接在對象內部通過new進行創建對象,是程序主動去創建依賴對象;而IoC是有專門一個容器來創建這些對象,即由Ioc容器來控制對象的創建;誰控制誰?當然是IoC 容器控制了對象;控制什么?那就是主要控制了外部資源獲取(不只是對象包括比如文件等)。
●為何是反轉,哪些方面反轉了:有反轉就有正轉,傳統應用程序是由我們自己在對象中主動控制去直接獲取依賴對象,也就是正轉;而反轉則是由容器來幫忙創建及注入依賴對象;為何是反轉?因為由容器幫我們查找及注入依賴對象,對象只是被動的接受依賴對象,所以是反轉;哪些方面反轉了?依賴對象的獲取被反轉了。
DI:依賴注入(Dependency Injection),組件之間依賴關系由容器在運行期決定,形象的說,即由容器動態的將某個依賴關系注入到組件之中。依賴注入的目的并非為軟件系統帶來更多功能,而是為了提升組件重用的頻率,并為系統搭建一個靈活、可擴展的平臺。IoC和DI其實是同一個概念的不同角度描述。
傳統獲取對象的方式與IoC方式對比:
二、Bean的創建
1.IoC容器的初始化
Spring IOC容器初始化的核心過程可以簡單的如下圖表示,主要有四個步驟(還有一些如:后置加載器,國際化,事件廣播器等一些過程不展開):
Bean定義的定位,Bean 可能定義在XML中,或者一個注解,或者其他形式。這些都被用Resource來定位,讀取Resource獲取BeanDefinition 注冊到 Bean定義注冊表中。
第一次向容器getBean操作會觸發Bean的創建過程,實列化一個Bean時 ,根據BeanDefinition中類信息等實列化Bean。
將實列化的Bean放到單列Bean緩存內。
此后再次獲取向容器getBean就會從緩存中獲取。
擴展閱讀:IoC容器初始化源碼解析Spring IOC容器初始化
2.Bean的注入方式
a.常用的注入方式
bean的注入方式是老生常談的話題了,這里不展開細說。
目前主要有五種注入方式:SET注入,構造器注入,靜態工廠,實例工廠,注解方式
b.常用的Bean的配置參數
屬性含義說明
idbean的唯一標識
class類的完全限定名
parent父類bean定義的名字如果沒有任何聲明,會使用父bean,但是也可以重寫父類。重寫父類時,子bean 必須與父bean 兼容,也就是說,接受父類的屬性值和構造器聲明的值。
子bean會繼承父bean的構造器聲明的值,屬性值,并且重寫父bean的方法。如果init方法,destroy方法已聲明,他們會覆蓋父bean相應的設置。保留的設置會直接從子bean獲取,包括depends on,auto wire mode,scope,lazy init.
abstract聲明bean是否是抽象的該屬性決定該類是否會實例化。默認是false。
注意:abstract屬性不會被子bean繼承,所以,abstract為true時需要對每個bean顯示聲明。
lazy-init決定是否延遲實例化如果為false,則啟動時會立即實例化單例模式的bean。默認是false。
注意:lazy-init屬性不會被子bean繼承。
autowire決定是否自動裝配bean的屬性autowire有4中模式(該屬性不會被子bean繼承。):
1."no":Spring默認的模式。bean的引用必須在XML文件中通過<ref/>元素或ref屬性顯示定義。
2."byName":通過屬性名使用自動裝配。如果一個Cat類擁有一個dog屬性,那么Spring會根據名字dog去尋找bean,如果沒有找到bean,則不會自動裝配。
3."byType":如果Spring容器只有該屬性類型的一個bean,會自動裝配。當有多個該屬性類型的bean時會報錯。如果沒有,則不會自動裝配。
4."constructor":針對構造器引用,和byType類似。
參考:Spring中的自動裝配
depends-on該bean初始化時依賴的其他beanbean工廠確保其他bean在該bean之前完成初始化。
注意:依賴項一般通過bean屬性或構造器聲明,這個屬性對其他依賴(如靜態類或啟動階段數據庫的準備)是必要的。
注意:depends-on屬性不會被子bean繼承。
scopebean的作用域singleton:單例模式,默認選項
prototype:非單例模式
request:對于web應用,每一個請求產生一個新的實例
session:對于web應用,一個session產生一個實例
init-method初始化方法bean創建時的初始化方法
destroy-method銷毀方法bean銷毀時調用的方法,僅僅在singleton模式下起作用
擴展閱讀:自定義命名空間:2. xml自定義命名空間解析
c.常用的注解:Spring常用注解
3.Bean解析注冊過程
這個過程完成Bean的從配置文件到解析注冊成bean工廠的過程(對應代碼在AbstactApplicationContext.obtainFreshBeanFactory)
XML
Resource
BeanDefinition
BeanFactory
讀取
解析
注冊
通過讀取XML配置文件獲取 Resource 資源,獲取這個資源包含了路徑config/spring/local/appcontext-client.xml 文件下定義的BeanDefinition信息。
創建一個 BeanFactory,這里使用 DefaultListableBeanFactory。
創建一個載入 BeanDefinition 的解讀器,這里使用 XmlBeanDefinitionReader 來載入 XML 文件形式 BeanDefinition,通過一個回調配置給 factory。
從定義好的資源位置讀入配置信息,具體的解析過程由 XmlBeanDefinitionReader 的 loadBeanDefinitions() 方法來完成。完成整個載入和注冊 Bean 定義之后,需要的 IoC 容器就建立起來可以直接使用了。
4.Bean的創建過程
這個過程完成Bean的實例化,如果是singleton類型的Bean還會放入緩存池中。(對應源碼:AbstactApplicationContext.finishBeanFactoryInitialization)
start
轉換對應的beanName
嘗試從緩存中加載單例
N
Y
緩存中加載的單例為空
doGetBean
transformedBeanName
getSingleton
Bean實例化
getObjectForBeanInstance
類型轉換
end
原型模式依賴檢查
檢測是否到父類工廠加載bean
存在循環依賴且原型bean,拋異常中斷加載
isPrototypeCurrentlyInCreation
parentBeanFactory != null && !containsBeanDefinition(beanName)
轉化為RootBeanDefinition
getMergedLocalBeanDefinition
尋找Bean的依賴
getDependsOn
Y
N
存在依賴的bean
優先加載依賴的bean
針對不同的scope創建bean
單例模式
原型模式
bean的模式
getBean
getSingleton
根據單例模式創建bean
創建bean
bean實例化
類型轉換
指定的scope上創建bean
創建bean
bean實例化
createBean
getObjectForBeanInstance
getObjectForBeanInstance
createBean
bean實例化
end
getObjectForBeanInstance
name參數可能是別名或者FactoryBean,需要一系列解析
單例的bean先從緩存中加載
加載失敗再嘗試從singletonFactory中加載
為避免循環依賴,先將bean的ObjectFactory加入緩存
獲取的bean如果
符合requireType指定類型則直接返回
否則轉化為指定類型
這里得到的是工廠bean的初始狀態,我們真正想要的是工廠bean中定義的factory-method方法中返回的bean
遞歸調用,
優先加載當前bean的依賴bean
擴展閱讀:Spring創建Bean過程源碼解析:Spring-IOC源碼解讀Spring源碼深度解析
三、Bean的生命周期
1.Bean生命周期
Bean生命周期是IOC中非常核心的內容,Spring為我們預留了很多的接口,讓我們在bean實例化前后、初始化前后可以寫入一些自己的邏輯。
實例化Bean對象
設置對象屬性
檢查Aware相關接口并設置相關依賴
BeanPostProcessor前置處理
檢查是否是InitializingBean以決定是否調用afterPropertiesSet方法
檢查是否配有自定義的init-method
BeanPostProcessor后置處理
注冊必要的Destruction相關回調接口
使用中
是否實現DisposableBean接口
是否配置有自定義的DisposableBean接口
*Bean的生命周期
1. 根據BeanDefinition信息,實例化對象,Constructor構造方法;
2. 根據BeanDefinition信息,配置Bean的所有屬性(將bean的引用注入到bean對應的屬性,*可能存在循環依賴問題);
3. 如果Bean實現了BeanNameAware接口,工廠調用Bean的setBeanName,參數為Bean的Id;
4. 如果Bean實現BeanFactoryAware接口,Spring將調用setBeanFactory()方法,將BeanFactory容器實例傳入;
5. 如果Bean實現ApplicationContextAware接口,Spring將調用setApplicationContext()方法,將bean所在的應用上下文的引用傳入進來;
5. 如果存在類實現了BeanPostProcessor接口,執行這些實現類的postProcessBeforeInitialization方法,這相當于在Bean初始化之前插入邏輯 ;
6. 如果Bean實現InitializingBean接口, 執行afterPropertiesSet方法;
7. 如果Bean指定了init-method方法,就會調用該方法。例:\<bean init-method="init"> ;
8. 如果存在類實現了BeanPostProcessor接口,執行這些實現類的postProcessAfterInitialization方法,這相當于在Bean初始化之后插入邏輯 ;
9. 這個階段Bean已經可以使用了,scope為singleton的Bean會被緩存在IOC容器中
10. 如果Bean實現了DisposableBean接口, 在容器銷毀的時候執行destroy方法。
11. 如果配置了destory-method方法,就調用該方法。例:\<bean destroy-method="customerDestroy">
擴展閱讀:Spring的擴展接口 :Spring 擴展接口
2.Bean實例化順序
1.解析內部類,查看內部類是否應該被定義成一個Bean,如果是,遞歸解析。
2.解析@PropertySource,也就是解析被引入的Properties文件。
3.解析配置類上是否有@ComponentScan注解,如果有則執行掃描動作,通過掃描得到的Bean Class會被立即解析成BeanDefinition,添加進beanDefinitionNames屬性中。之后查看掃描到的Bean Class是否是一個配置類(大部分情況是,因為標識@Component注解),如果是則遞歸解析這個Bean Class。
4.解析@Import引入的類,如果這個類是一個配置類,則遞歸解析。
5.解析@Bean標識的方法,此種形式定義的Bean Class不會被遞歸解析
6.解析父類上的@ComponentScan,@Import,@Bean,父類不會被再次實例化,因為其子類能夠做父類的工作,不需要額外的Bean了。
Spring 解決bean的循環依賴::05、spring ioc-bean的循環依賴
Spring 的bean加載順序:Spring Bean加載順序
四、避坑指南
相關case:
對bean生命周期了解不充分,在static方法中使用getBean方法,此時bean還未初始化。
2018-04-08 account-server上線大量NoClassDefFoundError錯誤
2.Spring默認同id名的bean會被覆蓋,一個解決辦法是不允許同id的bean覆蓋 參考:spring 同名bean問題 分析和解決
CaseStudy-20170321-paynotice上線導致未發券
CaseStudy-20150113-支付成功后與指紋相關的數據沒有發送給aegis
2018年Q4-CaseStudy-20181110-促銷組-活動創建引入新事務管理器導致促銷活動數據不完整或未正常開始
CaseStudy-20190108 [API研發組][bug] -indexapi讀取MCC失敗
3.Spring的bean加載順序。
忽視Spring bean加載順序,造成多次執行程序,結果會有所不同
五、參考文獻