2020-09-25

Spring第二篇 Spring IOC

目錄

一、概念梳理

1.什么是IoC和DI

二、Bean的創建

1.IoC容器的初始化

2.Bean的注入方式

3.Bean解析注冊過程

4.Bean的創建過程

三、Bean的生命周期

1.Bean生命周期

2.Bean實例化順序

四、避坑指南

五、參考文獻

作為一個后端開發,我們的日常離不開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注入,構造器注入,靜態工廠,實例工廠,注解方式

參考:Spring Bean注入

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產生一個實例

參考:Spring Bean的scope

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失敗

門票客訴排序不生效case原因分析

3.Spring的bean加載順序。

Spring中Bean加載順序引起的坑

忽視Spring bean加載順序,造成多次執行程序,結果會有所不同

五、參考文獻

Spring官網IOC文檔

Spring-IOC源碼解讀

Spring源碼淺析及引申

spring ioc源碼學習

SpringBean生命周期詳解

https://blog.csdn.net/levena/article/details/52268472

http://www.lxweimin.com/p/f9d18f495635

?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。