Spring IOC Container原理解析

Spring Framework 之 IOC

IOC、DI基礎概念

關於IOC和DI大家都不陌生,我們直接上martin fowler的原文,裏面已經有DI的例子和spring的使用示例

《Inversion of Control Containers and the Dependency Injection pattern》

//www.martinfowler.com/articles/injection.html

 

我們這裡只關注一些重點概念做為思考,摘一部分原文:

「As a result I think we need a more specific name for this pattern. Inversion of Control is too generic a term, and thus people find it confusing. As a result with a lot of discussion with various IoC advocates we settled on the name Dependency Injection.」

「我想我們需要給這個模式起一個更能說明其特點的名字。控制反轉太寬泛了,因而常常讓人迷惑。通過和一些IOC愛好者商討之後我們把該模式叫做依賴注入」

所以說IOC是抽象的概念,常用於形容一個框架,DI則是具體實現的模式,其實可以去spring官網,看到在使用這兩個詞時也很講究。

 

「When these containers talk about how they are so useful because they implement “Inversion of Control” I end up very puzzled. Inversion of control is a common characteristic of frameworks, so saying that these lightweight containers are special because they use inversion of control is like saying my car is special because it has wheels.」

「當這些容器的設計者說這些容器是如此的有用,因為他們實現了「控制反轉」。而我卻深感迷惑,控制反轉是IOC框架的共有特徵,如果說一個框架以實現了控制反轉為特點相當於說我的汽車有輪子。」

 

如果說依賴注入其實只是框架最基本的功能,那麼什麼才是spring高級、核心功能呢?

我們去看看spring官網:

//docs.spring.io/spring-framework/docs/current/reference/html/overview.html#overview-philosophy

關於設計理念的第一點:

「Provide choice at every level. Spring lets you defer design decisions as late as possible. For example, you can switch persistence providers through configuration without changing your code. The same is true for many other infrastructure concerns and integration with third-party APIs」

「在各個級別提供選擇。Spring允許您儘可能推遲設計決策。例如,您可以通過修改配置切換持久層提供程序,而無需更改代碼。許多其他基礎設施以及與第三方API的集成也是如此。」

 

所以說spring的核心在於基於IOC為基礎理念來做各種基礎設施的提供者,這裡的「基礎設施」也可以理解為對各種中間件的抽象整合。

DI是最基礎的一個功能,所有其他功能模塊的地基。所以說基於DI的 各式各樣的應用全家桶才是spring的核心競爭力。

 

IOC在平時可能看不到什麼作用,但是在關鍵性的對接或者架構層面作用就大了:

例如事務的管理,不管jdbc還是oracle的事務實現代碼如何,我們統一使用spring transaction(當然也結合了AOP),只需要修改相關的數據庫配置就可以。

例如註冊中心依賴的eureka,想要切換到nacos或者consul,代碼同樣不用改,修改相關的包引用,修改配置文件相關的配置就好了,代碼都不用動。

 

結論:看完上面的描述還是感覺很虛,就像martin fowler說的IOC容器實現依賴注入並沒什麼特殊的,spring通過依賴注入為我們提供哪些支撐,以及我們如何運用依賴注入將各種服務組裝成我們的系統才是更關鍵的,這也正是本文的關注點。

 

 

為了讓下文更好的被理解,我們這裡還是簡單的貼一些martin fowler文章里的代碼

沒有IOC框架時:

需要自己進行對象的初始化,依賴對象的設置

class MovieLister...
    private MovieFinder finder;
    public MovieLister()
    {
        finder = new ColonDelimitedMovieFinder("movies1.txt");
    }

如果需要修改finder實現,則需要直接修改MovieLister(高層)的代碼,這樣其實就是我們常說的高層依賴底層,底層實現換了,高層代碼就要變。

 

有IOC框架時:

我們直接站在巨人肩膀,例如服務定位器一樣可以實現DI,但是我們這裡不去關心

我們舉例習以為常的依賴注入的編碼步驟:

1、  編寫配置(xml文件、註解、java config)

2、  加載配置,通過配置生產對象(即時或者懶加載都可以)

3、  獲取注入好的對象

Java config 配置類:

@Configuration
public class MovieConfig{
    @Bean
    MovieLister movieLister(MovieFinder movieFinder){
        return new MovieLister(movieFinder);
    }

    @Bean
    MovieFinder movieFinder(){
        return new ColonDelimitedMovieFinder (「movies1.txt」)
    }
}

代碼:

public void testWithSpring() throws Exception {
    ApplicationContext ctx = new AnnotationConfigApplicationContext(MovieConfig.class);
    MovieLister lister = (MovieLister) ctx.getBean("movieLister");
}

關鍵對象

為了更好的方便理解,我們嘗試着將現實世界的對象一一映射到虛擬世界

現實世界

這裡我們以飯店舉例(右鍵-新標籤頁打開 可查看大圖)

幾個關鍵的對象:

海鮮加工飯店

有自己的招牌和特色,不過注意該飯店所有食材均需要客戶自帶。

出單系統

客戶點菜下單(到前台或者找服務員都行,因為自己帶的菜還要交給飯店嘛)後自動將客戶的訂單打印出來,打印出來的訂單除了菜名還會加上:序號、桌位號、菜品的口味等。職責就是簡潔清晰的打印出訂單信息,供廚師或其他人使用。

訂單複核員

我們這個餐廳必須要做一個步驟,就是訂單出單後再去找客戶確認訂單,訂單確認了才能交給廚房去做,這樣做的目的一來是為了避免客戶誤點或溝通失誤,二來通過確認的溝通也提升了用戶體驗。

為什麼我們這裡這個角色叫訂單複核員,而不叫服務員呢,因為我們的印象里服務員能幹很多其他的事情,這樣的話反而弱化了訂單確認的這個關鍵動作。

後廚

根據送過來的食材和訂單做菜,後廚關注的是如何根據訂單和食材來把菜做好。

虛擬世界

(右鍵-新標籤頁打開 可查看大圖)

 直接看這個圖可能會有一點懵,我們後面再詳細一一進行說明和講解,請注意,我們本章節後文都是圍繞該圖進行講解,此圖非常重要。

現實世界

虛擬世界

說明

海鮮加工飯店

ApplicationContext

對標海鮮加工飯店,廚房裡的廚子都是他的打工仔,他制定出精美的菜單來吸引食客,@Component、@Configuration、@Bean都是它的金字招牌菜,其實就是對廚子進行了包裝,且有門面吸引客源。

海鮮菜單

Java config

Spring IOC獨有的招牌菜:@Component

@ComponentScan @Bean @Import等美味

後廚

BeanFactory

根據食材和訂單做菜;沒有飯店實體店則基本只能做點路邊攤小買賣。

出單系統

BeanDefinitionRegistryPostProcessor

相當於出單系統,把客戶想要的菜給轉化到訂單上,例如ConfigurationClassPostProcessor將@Component @Configuration註解類轉化為BeanDefinition;基於java config就離不開他

也是BeanFactoryPostProcessor的子類

訂單複核員

BeanFactoryPostProcessor

確認訂單信息時菜還沒做,例如可以允許客戶對訂單做一些信息修改

訂單

BeanDefinition

即訂單信息,後廚要看着訂單來做菜

成品

Bean

最終的產物

從上面的例子我們大致能夠區分出了BeanFactory和ApplicationContext的區別。

Spring可以讓我們參與到任意一個角色中:客戶、海鮮加工飯店老闆、出單系統、訂單複核員、後廚,可以參與到其中任意環節中。

 

那我們可以做些什麼有趣事情呢?

例如可以制定我們特色的菜單,像mybatis的@Mapper特色菜。

確認訂單時給所有訂單信息里加贈飲料(xml聲明的bean的屬性里${xx}佔位符的替換)等

 

初步理解了這些關鍵對象之後,我們再深入到各個環節,看看各個環節都是怎麼乾的

BeanFactory

給我提供訂單信息和原材料我就做,訂單和食材缺一不可

讓我們先聚焦後廚,因為後廚是飯店的核心。

 

在spring framework中,Bean的生命周期在Beanfactory里就已經閉環了

ApplicationContext只是加一些料,例如掃描java config轉義成BeanDefinition給到BeanFactory,然後再添加一些BeanPostProcessor等。

注意本文重點關注的是基於主流java config配置的實現,其實xml文件的配置原理也類似,不是本文重點不做探討。

BeanFactory的生命周期是什麼,其實就是用BeanDefinitionRegistryPostProcessor、BeanFactoryPostProcessor 這兩個接口的實現類,往BeanFactory註冊BeanDefinition和修改BeanFactory里的BeanDefinition及其他信息。

BeanDefinition

訂單信息在虛擬世界長什麼樣的?它起到了承上啟下非常關鍵的作用。

為什麼一開始要先講BeanDefinition呢,不管基於什麼配置方式都需要生成BeanDefinition,在Bean的生命周期中,第一步也是getMergedBeanDefinition。

 

我們先來看一下BeanDefinition的類圖繼承關係

 BeanDefinition實現類與各個場景的對應,這裡我們只關注java config場景的

配置場景

BeanDefinition實現類

說明

@Component及其子類,

例如:

@Configuration

@Service

AnnotatedGenericBeanDefinition

@Bean

ConfigurationClassBeanDefinition

@ComponentScan掃描到的類:

ScannedGenericBeanDefinition

RootBeanDefinition

將parentName的bean進行合併整合的結果

常用於xml配置文件聲明的bean

GenericBeanDefinition

RootBeanDefinition(merged bean definition)

Bean生命周期中最重要的BeanDefinition實現類

 

因為不管中間過程中是什麼BeanDefinition,不管玩出什麼花樣

最終在bean生命周期中都會變成 RootBeanDefinition

當整合成RootBeanDefinition 後,故而spring 會給我們留了一個統一的後置處理器:MergedBeanDefinitionPostProcessor

 

BeanDefinition的屬性:

屬性

類型

說明

parentName

ChildBeanDefinition

String

相當於java的繼承父類,spring最後會把所有類型的bean都重新揉到一起形成一個新的RootBeanDefinition;

只有ChildBeanDefinition才有該屬性值

beanClass

AbstractBeanDefinition

Object

可以為string 也可以為 class<?>

也可以為空,為空時基本都是要通過factoryBeanName、factoryMethodName去實例化對象

scope

String

值:

singleton

prototype

refresh (spring cloud)

默認為空,為空時就當單例處理]

singleton

詳見@Scope註解;

@Scope註解的使用還是有點竅門的,例如@RefreshScope是什麼原理?

lzyInit

Boolean

是否延遲加載

ApplicationContext.refresh()時不加載,getBean()時才去加載

詳見@Lazy

autowireMode

AbstractBeanDefinition

int

這裡其實就是DI的幾種注入方式了,目前java config已經很靈活,直接註解的形式去定義就行了

AUTOWIRE_NO(默認值)

 默認裝配模式, 目前非xml配置都是使用這種方式,然後程序員使用註解手動注入

AUTOWIRE_BY_NAME

通過set方法,並且 set方法的名稱需要和bean的name一致 

AUTOWIRE_BY_TYPE

通過set方法,並且再根據bean的類型,注入屬性,是通過類型配置

AUTOWIRE_CONSTRUCTOR

通過構造函數注入

 

Spring @Bean註解的autowire屬性可以給我們去設置該屬性,也能通過BeanFactoryPostProcessor去修改BeanDefinition的autowireMode;不過一般也用不到去修改這個。

@Bean因為是註解在方法上,所以是AUTOWIRE_CONSTRUCTOR

@Autowired 是在AutowiredAnnotationBeanPostProcessor里默認通過類型判斷去找對應的bean,類似於AUTOWIRE_BY_TYPE,也是在這裡處理@Qualifier註解的bean name查找。因為@Autowired只是一個屬性值不是BeanDefinition,所有沒有autowireMode屬性一說

dependsOn

String[]

依賴了哪些bean,加載該bean時會先去加載依賴的bean,再來加載該bean

詳見@DependsOn()

autowireCandidate

Boolean

設置當前bean在被其他對象作為自動注入對象的時候,是否作為候選bean,默認true

如果設為false則別的bean引用不到該bean

@Scoped(「」)值不為空時,bean原始的BeanDefinition會被設置為autowireCandidate =false,會新生成一個新的beanClass為代理類class的BeanDefinition設置autowireCandidate=true,即替換了掉原來的beanDefinition

primary

Boolean

設置是不是最優先的候選bean

只對使用者產生影響,對原始對象的構造不起任何影響,原始對象該生成bean還是生成;只是後面被其他地方用於時,BeanFactory去判斷選擇

@Primary

qualifiers

AbstractBeanDefinition

Map<String, AutowireCandidateQualifier>

 

Register a qualifier to be used for autowire candidate resolution, keyed by the qualifier’s type name

只對使用者產生影響,對原始對象的構造不起任何影響,原始對象該生成bean還是生成;只是後面被其他地方用於時,BeanFactory去判斷選擇

@Qualifier 需和@Autowired一起使用

instanceSupplier

AbstractBeanDefinition

Supplier<?>

實例化對象的提供者,基本用不上自定義

isFactoryBean

RootBeanDefinition

Boolean

是否實現了FactoryBean接口,只要沒實現FactoryBean接口的都是false;

factoryBeanName

String

與isFactoryBean無關

可以是任意的beanName,且不需要實現FactoryBean接口

factoryMethodName

String

與isFactoryBean無關

通過該方法取bean,需要結合factoryBeanName進行使用

constructorArgumentValues

ConstructorArgumentValues

構造函數的定義,包含參數順序等

propertyValues

MutablePropertyValues

常用於xml聲明的bean;將xml該bean的所有property標籤鍵值對放這裡;

也可以用於spring內部上下文傳遞一些bean的信息,就像Servlet HttpRequest.setAttribute(key,value)

initMethodName

String

初始化方法名

destroyMethodName

String

Bean銷毀時觸發的方法名

role

int

是由什麼系統聲明的bean

ROLE_APPLICATION

ROLE_SUPPORT

ROLE_INFRASTRUCTURE

description

String

Bean的描述,常用於xml配置里的<description>節點

isSingleton

boolean

判斷scope.equals(「singleton」)

IsPrototype

boolean

判斷scope.equals(「prototype」)

isAbstract

String

是否抽象類

FactoryBean接口相關

兩種方式:

實現了spring FactoryBean接口的BeanDefinition屬性isFactoryBean=true(RootBeanDefinition,merge beandefinition後可見)

例如@Configuration註解生成的bean,mybatis的mapper都是用到了FactoryBean接口的內容

注意factoryBeanName、factoryMethodName 的使用是另一種實現方式,此時isFactoryBean=false

BeanDefinitionRegistryPostProcessor

根據菜單和客戶下單時的信息,生成訂單給後廚

作用於BeanFactory,是BeanFactory給外界留的門,具體的執行是在ApplicationContext的生命周期里。

在spring里就是將@Configuration、@Bean等配置信息解析生成BeanDefinition註冊到BeanFactory。

其繼承自BeanFactoryPostProcessor

 出單系統必須實現BeanDefinitionRegistryPostProcessor接口方法

(ApplicationContext飯店自己也可以給後廚下單 例如ClassPathXmlApplicationContext,就可以在applicationContext生命周期里(refresh方法內,下單給後廚之前)解析xml往Beanfactory里註冊BeanDefinition)

因為也繼承了BeanFactoryPostProcessor接口,所以一般也自帶了確認訂單的功能

 

例如一些典型的實現:

1、java config的關鍵實現類:ConfigurationClassPostProcessor

2、mybatis的 MapperScannerConfigurer,將@Mapper接口註冊成BeanDefinition

public class MapperScannerConfigurer implements BeanDefinitionRegistryPostProcessor

註:MapperScannerConfigurer只是Mybatis mapper注入的其中一種方式

ConfigurationClassPostProcessor

最重要的BeanDefinitionRegistryPostProcessor實現類沒有之一

 

我們用的java config都是經它之手轉變為BeanDefinition,因為BeanDefinitionRegistryPostProcessor繼承自BeanFactoryPostProcessor,所以也實現了postProcessBeanFactory方法。本文的重點在於IOC的過程理解,所以本章節主要還是描述該Processor源碼的實現過程。

 

ConfigurationClassPostProcessor其postProcessBeanFactoryRegistry方法負責註冊bean,將@Configuration @ComponentScan等註解解析成BeanDefinition註冊到BeanFactory;postProcessBeanFactory方法負責修改BeanDefinition,將@Configuration註解的類的BeanDefinition的beanClass替換為代理後的class name。

 

該BeanDefinitionRegistryPostProcessor也不是從石頭裡蹦出來的,後面ApplicationContext的生命周期會講到是何時觸發的。

postProcessBeanFactoryRegistry

循環當前所有的BeanDefinition,找那些有@Configuration註解的類進行處理,也可以有內部類,一樣會去加載,不過排序是跟着外層的@Configuration一起走,@Configuration也可以加@Order註解用於將所有@Configuration的bean進行排序後按從小到大的順序加載

也可以結合@Conditional註解使用,如果不滿足Conditional條件,則不加載該Configuration

 

一、找到當前BeanFactory里所有@Configuration註解的BeanDefinition並通過@Order進行排序

二、循環找到的BeanDefinition,將其轉化為ConfigurationClass

2.1先處理如果class有嵌套類,如果有@Configuration 或@Component 註解,同樣轉化為獨立的ConfigurationClass

2.2 @PropertySources @PropertySource的處理,添加PropertySource至environment

2.3 @ComponentScans @ComponentScan的處理,掃描java config註解。將掃描到的class轉換為BeanDefinition;然後再調用上面第一步,類似於遞歸調用

2.4 @Import的處理

註解value值是ImportSelector接口實現類的,直接調用接口方法selectImports拿到返回String[],class類名集合,再次去調用processImports方法

註解value值是ImportBeanDefinitionRegistrar接口實現類的,添加至ConfigutaionClass的屬性importBeanDefinitionRegistrars【關鍵】

註解value不是上面兩種接口實現類的,直接再走判斷註解轉化ConfiurationClass的處理

2.5 @ImportResource的處理,添加至ConfigutaionClass的屬性ImportedResource

2.6找到@Bean註解的method,添加至ConfigurationClass的屬性beanMethod

2.7找當前class的接口,如果有@Bean註解的method,添加至ConfigurationClass的屬性beanMethod(因為java8的interface有default method的存在,所以接口方法也可以生成bean)

2.8找當前class的父類,如果有@Configuration 或@Component 註解,同樣轉化為獨立的ConfigurationClass(同嵌套類的處理)

三、將所有ConfigurationClass轉換為BeanDefinition註冊到BeanFactory

四、將所有ConfigurationClass的beanMethod轉換為BeanDefinition註冊到BeanFactory.

五、將所有ConfigurationClass的ImportedResource轉換為BeanDefinition註冊到BeanFactory.

六、將所有ConfigurationClass的importBeanDefinitionRegistrars,調用其registerBeanDefinitions方法進行BeanDefinition的註冊,同BeanDefinitionRegistryPostProcessor,也是用於註冊BeanDefinition【關鍵】

 

另外@Conditional可以結合@Configuration、@Bean註解進行使用,不滿足條件的不加載為BeanDefinition

@Configuration

@Component
public @interface Configuration {

ConfigurationClass對象關鍵屬性(解析@Configuration、@Component註解的類後得到的結果)

屬性

類型

說明

metadata

AnnotationMetadata

@Configuration

類的註解集合

beanName

String

Pojo類名

beanMethods

Set<BeanMethod>

@Bean 的方法集合

importedResources

Map<String, Class<? extends BeanDefinitionReader>>

待加載的xml配置文件

importBeanDefinitionRegistrars

Map<ImportBeanDefinitionRegistrar, AnnotationMetadata>

待加載的

ImportBeanDefinitionRegistrar接口

用於註冊BeanDefinition

@Bean

這裡@Configuration或者@Component註解里的@Bean註解的方法,會註冊為BeanDefinition,然後其BeanClass為空,factoryBeanName為其@Configuration或者@Component類的beanName;factoryMethodName就是 @Bean註解的方法名。

這裡就是用到了BeanDefinition內容里的factoryBeanName和factoryMethodName進行處理,委託其他類(@Configuration類)來生成bean。

 

@Import

對後面理解spring boot AutoConfiguration至關重要

一、註解value值是ImportSelector接口實現類的,直接調用接口方法selectImports拿到返回String[],class類名集合,再次去進行掃描

二、註解value值是ImportBeanDefinitionRegistrar接口實現類的,會調用其registerBeanDefinitions方法,進行BeanDefinition註冊;
等於實現了BeanDefinitionRegistryPostProcessor的功能。

三、註解value值不是上面兩種接口實現類的,直接掃描該類

Spring boot starter自動引入的核心

@EnableAutoConfiguration
public @interface SpringBootApplication {
@Import(AutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {
public class AutoConfigurationImportSelector implements ImportSelector {

該selector會去掃描所有spring.factories文件里

org.springframework.boot.autoconfigure..EnableAutoConfiguration 屬性的類

 

Mybatis @MapperScan

@Import(MapperScannerRegistrar.class)

  

Feign @EnableFeignClients

@Import(FeignClientsRegistrar.class)

這裡一般都是註冊的GenericBeanDefinition

postProcessBeanFactory

這裡涉及到AOP的知識

 

處理java-config相關的註解 @Configuration @ComponentScan @Import等將他們轉換成BeanDefinition並註冊到BeanFactory中,具體的實現也涉及到AOP知識。因為我們關注的是整體的運作流程。

將@Configuration註解轉換的BeanDefinition,BeanClass屬性改成 AOP代理後生成的class,這樣後面bean生命周期實例化的就是代理後的class

 

為什麼要用代理?

@Configuration
public class MovieConfiguration {
    @Bean
    public MovieLister movieListener(){
        return new MovieLister (this.movieFinder ());
    }
    @Bean
    public MovieFinder movieFinder(){
        return new ColonDelimitedMovieFinder ("movies1.txt");
    }
}

而且此處通過AOP增強後,也不會面臨內部方法調用AOP失效的問題(為什麼?movieListener這裏面調用movieFinder不是已經在super class了嗎,其實並不是,這個父類其實也是被代理的,cglib的實踐,因為只要是@Bean註解的方法都被代理攔截了,如果方法名沒被代理的話,那就真是直接執行super的原始代碼),如上最後movieListener里的movieFinder屬性,和下面movieFinder方法生成的bean,是同一個bean;感興趣的可以去看看源碼

如何查看cglib增強後的代碼,可以再main函數第一行加上如下設置即可

public static void main(String[] args) {
    System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY,"C://");

@Component註解(包括其子類@Service @Controller)的類也可以聲明 @Bean,區別是什麼呢,就是是LITE 輕量模式,不會像上面這樣生成代理(@Configuration是FULL模式),即上面的 movieListenner里的movieFinder對象和 BeanFactory里的bean movieFinder 是兩個不同的對象。

BeanFactoryPostProcessor

有個客戶突然跑過來後廚說 我剛剛下單忘了說清蒸鱸魚不要魚

接單後可以對訂單做的事情,主要是對訂單的修改,取消基本上不可能的,就像你到飯店吃飯,中途想問問服務員有個菜沒做的話能不能幫我退掉,這時候服務員肯定給你講正在做了不能退了。

 

作用於BeanFactory,是BeanFactory給外界留的門,具體的執行是在ApplicationContext的生命周期里。

 

經典實用場景,BeanDefinition屬性值變量的替換

 

對BeanDefinition屬性值進行修改,不管哪裡註冊的,都會走這裡

 

經典常用的:

PropertySourcesPlaceholderConfigurer

 

BeanDefinition裏面如下屬性值變量的替換都在這裡執行(@Autowired和@Value里的變量並不在這進行替換 、SpEL也不在此處處理)

替換BeanDefinition屬性值的範圍和順序如下:

parentName

beanClassName

factoryBeanName

factoryMethodName

scope

propertyValues

constructorArgumentValues

Bean的生命周期

實在是編不下去了,這個用做菜來舉例的話,我頭髮要白一大片。這裡我們就不以做菜舉例了,只能以類的創建來舉例了

正常創建類的流程:

(1)MovieListener listener = new MovieListener();
   MovieFinder finder = new ColonDelimitedMovieFinder("movies1.txt");
(2)listener.setFinder(finder);
   listener.setStartTime(new Date());
(3)listener.init();

對應上面三個步驟,我們分為三個方法:

1、實例化

2、populateBean屬性賦值(@Autowired的屬性依賴注入都是在這裡處理)

3、初始化(屬性賦值完後,我們做好準備工作對外開始服務)

所以spring為了讓我們參與到bean的實例化與初始化過程,特意給我們留了口

 

為什麼要劃分出多一個第二步populateBean方法呢,拆分的更細其實也是有益處的,例如spring cloud的結合@RefreshScope和@ConfigurationProperties使用的bean,就用到了bean生命周期中的destroy和initializeBean 初始化的方法,跳過了實例化和屬性賦值,直接去觸發

ConfigurationPropertiesBindingPostProcessor進行@ConfigurationProperties註解類的屬性值的綁定(這裡的屬性賦值並不是@Autowired @Value這樣的屬性賦值,而是@ConfigurationProperties(prefix = “xxx”)註解類對應的屬性);因為是再次第三步初始化的initializeBean,所以第二步依賴注入的屬性不會被改變,節省了成本

BeanPostProcessor

在bean的生命周期中,我們能參與到哪些環節呢,spring給我們的最主要的還是通過三類BeanPostProcessor去參與進去。

BeanPostProcessor的類的繼承關係

典型常用的BeanPostProcessor如下:

CommonAnnotationBeanPostProcessor

spring中最重要的BeanPostProcessor之一

 

@Resource

註解屬性的賦值,找到對應的bean進行屬性賦值

 

@WebServiceRef

不常用,不解釋

 

繼承自InitDestroyAnnotationBeanPostProcessor又包含如下兩個註解的處理

@PostConstruct

執行該註解對應的方法,在何時執行請看「虛擬世界」的圖例

 

@PreDestroy

銷毀時執行改註解方法,我們要搞清楚如何出生的,再去想如何沒的,這裡不做解釋。

 

AutowiredAnnotationBeanPostProcessor

Spring boot中最重要的BeanPostProcessor 沒有之一

 

@Autowired

常見問題:

為什麼spring bean 一級緩存里沒有 ResourceLoader、ApplicationContext、ApplicationEventPublisher、BeanFactory相關的bean,@Autowired為什麼能注入對象進去呢?

也是這裡進行了代碼相關的類型判斷,如果是上面4種類型的,直接找到對應的對象賦值了。

 

@Value

${xx}  #{xx}  佔位符的區別是什麼 就是普通屬性替換和SpEL的執行

 

@Qualifier

需配合@Autowired使用,@Autowired在找相關的bean時,也會按@Qualifier指定的bean name去查找。

 

ConfigurationPropertiesBindingPostProcessor

Spring boot中重要的BeanPostProcessor

 

@ConfigurationProperties註解類的處理

 

AnnotationAwareAspectJAutoProxyCreator

AOP里用到,Spring AOP 最重要的BeanPostProcessor沒有之一

 

@Aspect的處理,將aop掃描到的方法類進行生成aop代理,這裡我們也不做過多的解釋,屬於AOP的內容。

 

下面的章節我們將正式進入bean的生命周期,這裡再回顧一下我們的虛擬世界(右鍵-新標籤頁打開 可查看大圖)

getMergeBeanDefinition

合併BeanDefinition

 

為什麼要合併BeanDefinition,我們這裡用詞還是跟spring源碼里的方法名保持了一致,是為了方便大家看源碼時能更好去對應上。

因為java面向對象設計的概念,類是可以繼承的

如下這種xml聲明方式,parent就有用,生成bean時會將parent bean的屬性也進行注入,這樣就能使用到父類的屬性和方法。

<bean id="movieListener" class="spring.MovieListener" parent="abstractMovieListener">
</bean>
<bean id="abstractMovieListener" class="spring.AbstractMovieListener">
</bean>

Java config基於@Component註解時基本用不上了,因為生成的都不是ChildBeanDefinition

怎麼合併,去看一下AbstractBeanDefinition的構造函數就知道各種其他類型的BeanDefinition如何轉換為RootBeanDefinition,我們這裡就不貼源碼了。

Instantiation 實例化

什麼叫實例化  new MovieListener() 這樣嗎

涉及到static 方法塊是否需要被執行,以及構造函數是否需要被執行,spring默認都是會執行到

postProcessBeforeInstantiation

由InstantiationAwareBeanPostProcessor觸發

這裡可以幹些什麼,有什麼使用場景,如果在這裡有返回對象,則會直接跳到BeanPostProcessor.postProcessAfterInitialization方法去,等於是直接跳到生命周期的末尾,期間的生命周期都不會執行

createBeanInstance

這裡呢,有什麼場景暫時沒發現,不過spring也給我們參與的機會=>BeanDefinition里的instanceSuplier

postProcessMergedBeanDefinition

由MergedBeanDefinitionPostProcessor觸發

 

這裡可以幹些什麼,有什麼使用場景,spring為什麼要給我們留這個口?

 

在BeanDefinitionRegistryPostProcessor註冊BeanDefinition可以是任意類型,但是在bean的生命周期里(實例化之前),都會轉變為RootBeanDefinition;所以這裡spring給我們留了一個口,讓我們還去訪問RootBeanDefinition,可以用於獲取信息和修改BeanDefinition信息;

首先搞清楚什麼是:merged bean definition

Return a RootBeanDefinition for the given bean, by merging with the

parent if the given bean’s definition is a child bean definition.

在BeanDefinition生成的時候就已經merge過了,不管如何,實例化之前會拿到mergedBeanDefinition(實際是RootBeanDefinition類型)

既然可以用於修改BeanDefinition,為什麼該後置處理不在對象實例化之前給我們去調用呢?如果是修改BeanDefinition,那麼其實是跟BeanFactoryPostProcessor去修改BeanDefinition是有歧義的。如果想要修改Bean的實例化的類,還是得去BeanFactoryPostProcessor,這裡只能影響實例化之後的生命周期。

這裡其實就是給最後一次機會,能夠去修改BeanDefinition(注意這裡bean已經實例化了)

AutowiredAnnotationBeanPostProcessor 和

CommonAnnotationBeanPostProcessor

在這裡 postProcessMergedBeanDefinition 就是去提前緩存了每個類的 @Autowired、@Value等註解屬性信息,後面postProcessProperties直接使用而已。 不到這裡加緩存後面再去取也可以

 

到這裡我們主要分析了applyMergedBeanDefinitionPostProcessors這段代碼的作用,它的執行時機是在創建對象之後,屬性注入之前。按照官方的定義來說,到這裡我們仍然可以使用這個方法來修改bd的定義,那麼相對於通過BeanFactoryPostProcessor的方式修改bd,applyMergedBeanDefinitionPostProcessors這個方法影響的範圍更小,BeanFactoryPostProcessor影響的是整個Bean的生命周期,而applyMergedBeanDefinitionPostProcessors只會影響屬性注入之後的生命周期。

polulateBean 屬性賦值

為什麼屬性賦值要單獨拿出來,其實是屬於初始化裏面的嗎;在spring里還真不是,不過要注意的是spring cloud的@RefreshScope是直接調用了destroy方法之後直接調用初始化方法,跳過了屬性賦值,其實也就是跳過了@Autowired @Value等的屬性賦值處理保留原有的。

postProcessProperties

由InstantiationAwareBeanPostProcessor觸發

給bean的屬性賦值

AutowiredAnnotationBeanPostProcessor 和

CommonAnnotationBeanPostProcessor

就是在此處分別給@Autowired @Value、@Resource屬性通過反射賦值

postProcessPropertiesValues(Deprecated)

由InstantiationAwareBeanPostProcessor觸發

目前已經作廢,被上面postProcessProperties方法替代

applyPropertyValues

BeanDefinition里propertyValues的SpEL的處理在這裡,通過java config配置的類基本上已經用不到這裡了

Initialization 初始化

什麼叫初始化,簡單點說就是執行類的各種方法

各類Aware接口方法

用於接收各類的spring對象

postProcessBeforeInitialization

由BeanPostProcessor觸發

 

CommonAnnotationBeanPostProcessor

@PostConstruct註解的方法在此處調用執行

InitializingBean.afterPropertySet

 調用實現了了InitializingBean接口的bean,調用其afterPropertySet方法

postProcessAfterInitialization

由BeanPostProcessor觸發

目前常用的使用場景就是AOP  AnnotationAwareAspectJAutoProxyCreator,將對象進行代理後返回代理對象,後面使用的都是代理對象。

invokeCustomInitMethod

觸發BeanDefinition里 initMethod方法,由xml定義的bean就可以設置方法名,基於java config的已經用不到了,可以用上面InitializingBean.afterPropertySet或者@PostConstruct作為代替實現

二級緩存

每個bean在創建過程中,實例化後將對象(引用類型)放入二級緩存(實例化完成),初始化完成後再將對象放入一級緩存,同時刪除二級緩存(實例化+初始化均完成,完整的bean)

 

解決對象之間循環依賴問題

例如A 依賴B ,B依賴A

A先創建的話:

A先實例化=》放入二級緩存(存儲實例化,但未初始化的對象)

A初始化時,發現屬性需要注入B

 

B實例化=》放入二級緩存

B初始化時,發現屬性需要注入A,從二級緩存取,取到A

B初始化完成=》放入一級緩存(存儲實例化、初始化都完成了的完整體)

 

A初始化完成=》放入一級緩存

 

就這樣,最後A,B都放入了一級緩存;在spring ioc container概念中,只需要了解到二級緩存就足矣,涉及到AOP的時候,再來看第三級緩存就明白用途了。

 

三級緩存

在沒有AOP之前,二級緩存足以,AOP加入之後,為了不影響原有的二級緩存,特意加上第三級緩存。對象代理後先放入三級緩存而不是二級緩存。

加上這個是為了跟AOP解耦,不影響原有IOC二級緩存的基礎上,獨立再加一層,即解決了問題,也實現了解耦。

ApplicationContext

如何銜接spring bean生命周期是本文關注的重點

也是實現了相關BeanFactory接口的,其實就是增強了BeanFactory,變成了跟應用相關的;等於是在廚子的上層加了自己的菜單設計

例如@Autowired @Component @Bean 這都是他們旗下的設計的一些特色菜,給客戶使用

以後下單不用描述那麼;

 

其實就是往上面BeanFactory里添加一個postProcessor;

我們只要知道何時何地添加了哪些postProcessor

 

AnnotationConfigServletWebServerApplicationContext

目前最關鍵最常用的ApplicationContext沒有之一(spring boot web servlet環境使用)

最常用的莫過於該ApplicationContext,spring boot servlet環境使用,全球海鮮加工飯店中的佼佼者。

 

類圖:

  

生命周期

注意這裡舉例的是spring boot web servlet環境使用的AnnotationConfigServletWebServerApplicationContext

(右鍵-新標籤頁打開 可查看大圖)

構造函數

ApplicationContext由spring boot自動創建,構造函數里會添加java config必須的Processor

 

添加

ConfigurationClassPostProcessor(BeanDefinitionRegistryPostProcessor)

其會掃描到@SpringBootApplication註解里的@Import(AutoConfigurationImportSelector.class)

會去把spring.factories里的所有AutoConfiguration類當成@Configuration進行解析處理

 

EventListenerMethodProcessor(BeanFactoryPostProcessor)

基於@EventListener 註解的listener類的處理,將其添加至事件廣播類中

 

添加

AutowiredAnnotationBeanPostProcessor(BeanPostProcessor)

CommonAnnotationBeanPostProcessor(BeanPostProcessor)

refresh

refresh方法包含如下各個子步驟

prepareBeanFactory


  添加ApplicationContextAwareProcessor(BeanPostProcessor)

  postProcessBeforeInitialization方法用於額外的aware接口(除BeanNameAware、BeanClassLoaderAware、BeanFactoryAware之外的)在這裡進行屬性賦值

  EnvironentAware

  …

  MessageSourceAware

  ApplicationContextAware

postProcessBeanFactory


  添加了WebApplicationContextServletContextAwareProcessor(BeanPostProcessor)

  用於ServletContextAware、ServletConfigAware,同BeanFactoryAware使用方式

invokeBeanFactoryPostProcessors


  1、執行所有BeanDefinitionRegistryPostProcessor

  ConfigurationClassPostProcessor就是在此處執行

  2、執行所有BeanFactoryPostProcessor

  所有的排序規則都是優先@PriorityOrdered註解或PriorityOrdered接口實現由小到大

  其次@Ordered註解或Ordered接口實現由小到大

  最後是沒實現排序的(不保證順序)

registerBeanPostProcessors


  將所有BeanDefinition class type為的BeanPostProcessor的bean找出來,添加到BeanFactory供其使用

initMessageSource


  c18n相關

initApplicationEventMulticaster


  初始化ApplicationContext的事件廣播類,可以多線程或者同步廣播,默認為同步

onRefresh


  ServletWebServerApplicationContext (AnnotationConfigServletWebServerApplicationContext的父類)重寫了onRefresh方法里去創建了內置servlet容器 

registerListeners


  將所有listener的bean(例如實現ApplicationListener接口的bean)註冊到事件廣播類中,用於後面事件發佈時去觸發到這些listener 

finishBeanFactoryInitialization


  這裡觸發beanFactory.preInstantiateSingletons,即輪詢beanDefinition進行bean的生命周期 

finishRefresh


  啟動webServer,發佈相關事件

  

各個環節添加了哪些東西,我們再來給「虛擬世界」加一些注釋鞏固一下

Spring Boot 之 IOC

其實這裡已經不涉及DI的實現了,我們主要關注如何銜接ApplicationContext的生命周期

生命周期

從源頭SpringApplication.run(xxx.class, args);進來之後,即開始了spring boot的生命周期

Spring boot的啟動流程是 執行流程+事件驅動來執行,其中事件驅動,是取spring.factories里的Listener去觸發相關的事件監聽,spring boot web servlet 環境默認的Listener有如下這些:

Spring boot會每個關鍵階段發佈對應的事件去觸發listener參與spring boot的構建過程,整體的生命周期如下:

關鍵內容都在圖裡了(右鍵-新標籤頁打開 可查看大圖)

starting


  主要是觸發listener初始化第三方日誌組件,用於後面設置level group等

  An ApplicationStartingEvent is sent at the start of a run but before any processing, except for the registration of listeners and initializers.

environmentPrepared


  核心步驟,spring.application.profile 就是在這個步驟觸發listener進行加載

  在這裡發佈event觸發listener去加載對應profile的 properties yaml 文件到environment

  An ApplicationEnvironmentPreparedEvent is sent when the Environment to be used in the context is known but before the context is created.

  printBanner

  打印banner信息

  createApplicationContext

  此處就跟上面ApplicationContext的構造函數銜接上

  ApplicationContextInitializer.initialize(context)

  為ApplicationContext加料

contextPrepared


  An ApplicationContextInitializedEvent is sent when the ApplicationContext is prepared and ApplicationContextInitializers have been called but before any bean definitions are loaded.

contextLoaded


  An ApplicationPreparedEvent is sent just before the refresh is started but after bean definitions have been loaded.

  refreshContext

  進入ApplicationContext.refresh()方法

started


  An ApplicationStartedEvent is sent after the context has been refreshed but before any application and command-line runners have been called.

  ApplicationRunner

  調用實現了ApplicationRunner接口的bean,然後調用其run方法

  調用實現了ApplicationRunner接口的bean,然後調用其run方法

  CommandLineRunner

  調用實現了CommandLineRunner接口的bean,然後調用其run方法

running


  An ApplicationReadyEvent is sent after any application and command-line runners have been called.

failed


  An ApplicationFailedEvent is sent if there is an exception on startup.

 

寫在最後的一點啰嗦話:

創作文章實屬不易,花費了筆者大量的業餘時間和精力,如果看完對你有幫助還請幫忙點點推薦 + 轉發。