Java開發大型互聯網-深入理解Spring IOC容器的神秘之旅

引言

Spring是一個開放源代碼的設計層面框架,他解決的是業務邏輯層和其他各層的松耦合問題,因此它將面向介面的編程思想貫穿整個系統應用。Spring是於2003 年興起的一個輕量級的Java 開發框架,由Rod Johnson創建。簡單來說,Spring是一個分層的JavaSE/EEfull-stack(一站式)輕量級開源框架。

IOC就是控制反轉(Inversion of Control,英文縮寫為IoC)是框架的重要特徵,並非面向對象編程的專用術語。它包括依賴注入(Dependency Injection,簡稱DI)和依賴查找(Dependency Lookup)。

Spring IoC容器

可以把Spring IoC容器比作一間餐館,當你來到餐館,通常會直接招呼服務員:點菜!至於菜的原料是什麼?如何用原料把菜做出來?可能你根本就不關心。IoC容器也是一樣,你只需要告訴它需要某個bean,它就把對應的實例(instance)扔給你,至於這個bean是否依賴其他組件,怎樣完成它的初始化,根本就不需要你關心。

作為餐館,想要做出菜肴,得知道菜的原料和菜譜,同樣地,IoC容器想要管理各個業務對象以及它們之間的依賴關係,需要通過某種途徑來記錄和管理這些信息。

BeanDefinition

對象就承擔了這個責任:容器中的每一個bean都會有一個對應的BeanDefinition實例,該實例負責保存bean對象的所有必要信息,包括bean對象的class類型、是否是抽象類、構造方法和參數、其它屬性等等。當客戶端向容器請求相應對象時,容器就會通過這些信息為客戶端返回一個完整可用的bean實例。

原材料已經準備好(把BeanDefinition看著原料),開始做菜吧,等等,你還需要一份菜譜,

BeanDefinitionRegistry

BeanFactory

就是這份菜譜,BeanDefinitionRegistry抽象出bean的註冊邏輯,而BeanFactory則抽象出了bean的管理邏輯,而各個BeanFactory的實現類就具體承擔了bean的註冊以及管理工作。它們之間的關係就如下圖:

BeanFactory、BeanDefinitionRegistry關係圖

DefaultListableBeanFactory

作為一個比較通用的BeanFactory實現,它同時也實現了BeanDefinitionRegistry介面,因此它就承擔了Bean的註冊管理工作。從圖中也可以看出,BeanFactory介面中主要包含getBean、containBean、getType、getAliases等管理bean的方法,而BeanDefinitionRegistry介面則包含registerBeanDefinition、removeBeanDefinition、getBeanDefinition等註冊管理BeanDefinition的方法。

下面通過一段簡單的代碼來模擬BeanFactory底層是如何工作的:

// 默認容器實現

DefaultListableBeanFactory beanRegistry = new DefaultListableBeanFactory();

// 根據業務對象構造相應的BeanDefinition

AbstractBeanDefinition definition = new RootBeanDefinition(Business.class,true);

// 將bean定義註冊到容器中

beanRegistry.registerBeanDefinition("beanName",definition);

// 如果有多個bean,還可以指定各個bean之間的依賴關係

// ........

// 然後可以從容器中獲取這個bean的實例

// 注意:這裡的beanRegistry其實實現了BeanFactory介面,所以可以強轉,

// 單純的BeanDefinitionRegistry是無法強制轉換到BeanFactory類型的

BeanFactory container = (BeanFactory)beanRegistry;

Business business = (Business)container.getBean("beanName");

這段代碼僅為了說明BeanFactory底層的大致工作流程,實際情況會更加複雜,比如bean之間的依賴關係可能定義在外部配置文件(XML/Properties)中、也可能是註解方式。Spring IoC容器的整個工作流程大致可以分為兩個階段:

①、容器啟動階段

容器啟動時,會通過某種途徑載入

Configuration MetaData

。除了代碼方式比較直接外,在大部分情況下,容器需要依賴某些工具類,比如:

BeanDefinitionReader

,BeanDefinitionReader會對載入的

Configuration MetaData

進行解析和分析,並將分析后的信息組裝為相應的BeanDefinition,最後把這些保存了bean定義的BeanDefinition,註冊到相應的BeanDefinitionRegistry,這樣容器的啟動工作就完成了。這個階段主要完成一些準備性工作,更側重於bean對象管理信息的收集,當然一些驗證性或者輔助性的工作也在這一階段完成。

來看一個簡單的例子吧,過往,所有的bean都定義在XML配置文件中,下面的代碼將模擬BeanFactory如何從配置文件中載入bean的定義以及依賴關係:

// 通常為BeanDefinitionRegistry的實現類,這裡以DeFaultListabeBeanFactory為例

BeanDefinitionRegistry beanRegistry = new DefaultListableBeanFactory();

// XmlBeanDefinitionReader實現了BeanDefinitionReader介面,用於解析XML文件

XmlBeanDefinitionReader beanDefinitionReader = new XmlBeanDefinitionReaderImpl(beanRegistry);

// 載入配置文件

beanDefinitionReader.loadBeanDefinitions("classpath:spring-bean.xml");

// 從容器中獲取bean實例

BeanFactory container = (BeanFactory)beanRegistry;

Business business = (Business)container.getBean("beanName");

②、Bean的實例化階段

經過第一階段,所有bean定義都通過BeanDefinition的方式註冊到BeanDefinitionRegistry中,當某個請求通過容器的getBean方法請求某個對象,或者因為依賴關係容器需要隱式的調用getBean時,就會觸發第二階段的活動:容器會首先檢查所請求的對象之前是否已經實例化完成。如果沒有,則會根據註冊的BeanDefinition所提供的信息實例化被請求對象,並為其注入依賴。當該對象裝配完畢后,容器會立即將其返回給請求方法使用。

BeanFactory只是Spring IoC容器的一種實現,如果沒有特殊指定,它採用採用延遲初始化策略:只有當訪問容器中的某個對象時,才對該對象進行初始化和依賴注入操作。而在實際場景下,我們更多的使用另外一種類型的容器:

ApplicationContext

,它構建在BeanFactory之上,屬於更高級的容器,除了具有BeanFactory的所有能力之外,還提供對事件監聽機制以及國際化的支持等。它管理的bean,在容器啟動時全部完成初始化和依賴注入操作。

Spring容器擴展機制

IoC容器負責管理容器中所有bean的生命周期,而在bean生命周期的不同階段,Spring提供了不同的擴展點來改變bean的命運。在容器的啟動階段,

BeanFactoryPostProcessor

允許我們在容器實例化相應對象之前,對註冊到容器的BeanDefinition所保存的信息做一些額外的操作,比如修改bean定義的某些屬性或者增加其他信息等。

如果要自定義擴展類,通常需要實現

org.springframework.beans.factory.config.BeanFactoryPostProcessor

介面,與此同時,因為容器中可能有多個BeanFactoryPostProcessor,可能還需要實現

org.springframework.core.Ordered

介面,以保證BeanFactoryPostProcessor按照順序執行。Spring提供了為數不多的BeanFactoryPostProcessor實現,我們以

PropertyPlaceholderConfigurer

來說明其大致的工作流程。

在Spring項目的XML配置文件中,經常可以看到許多配置項的值使用佔位符,而將佔位符所代表的值單獨配置到獨立的properties文件,這樣可以將散落在不同XML文件中的配置集中管理,而且也方便運維根據不同的環境進行配置不同的值。這個非常實用的功能就是由PropertyPlaceholderConfigurer負責實現的。

根據前文,當BeanFactory在第一階段載入完所有配置信息時,BeanFactory中保存的對象的屬性還是以佔位符方式存在的,比如

${jdbc.mysql.url}

。當PropertyPlaceholderConfigurer作為BeanFactoryPostProcessor被應用時,它會使用properties配置文件中的值來替換相應的BeanDefinition中佔位符所表示的屬性值。當需要實例化bean時,bean定義中的屬性值就已經被替換成我們配置的值。當然其實現比上面描述的要複雜一些,這裡僅說明其大致工作原理,更詳細的實現可以參考其源碼。

與之相似的,還有

BeanPostProcessor

,其存在於對象實例化階段。跟BeanFactoryPostProcessor類似,它會處理容器內所有符合條件並且已經實例化后的對象。簡單的對比,BeanFactoryPostProcessor處理bean的定義,而BeanPostProcessor則處理bean完成實例化后的對象。BeanPostProcessor定義了兩個介面:

public interface BeanPostProcessor {

// 前置處理

Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException;

// 後置處理

Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException;

}

為了理解這兩個方法執行的時機,簡單的了解下bean的整個生命周期:

Bean的實例化過程(來自:Spring揭秘)

postProcessBeforeInitialization()

方法與

postProcessAfterInitialization()

分別對應圖中前置處理和後置處理兩個步驟將執行的方法。這兩個方法中都傳入了bean對象實例的引用,為擴展容器的對象實例化過程提供了很大便利,在這兒幾乎可以對傳入的實例執行任何操作。註解、AOP等功能的實現均大量使用了

BeanPostProcessor

,比如有一個自定義註解,你完全可以實現BeanPostProcessor的介面,在其中判斷bean對象的腦袋上是否有該註解,如果有,你可以對這個bean實例執行任何操作,想想是不是非常的簡單?

再來看一個更常見的例子,在Spring中經常能夠看到各種各樣的Aware介面,其作用就是在對象實例化完成以後將Aware介面定義中規定的依賴注入到當前實例中。比如最常見的

ApplicationContextAware

介面,實現了這個介面的類都可以獲取到一個ApplicationContext對象。當容器中每個對象的實例化過程走到BeanPostProcessor前置處理這一步時,容器會檢測到之前註冊到容器的ApplicationContextAwareProcessor,然後就會調用其postProcessBeforeInitialization()方法,檢查並設置Aware相關依賴。看看代碼吧,是不是很簡單:

// 代碼來自:org.springframework.context.support.ApplicationContextAwareProcessor

// 其postProcessBeforeInitialization方法調用了invokeAwareInterfaces方法

private void invokeAwareInterfaces(Object bean) {

if (bean instanceof EnvironmentAware) {

((EnvironmentAware) bean).setEnvironment(this.applicationContext.getEnvironment());

}

if (bean instanceof ApplicationContextAware) {

((ApplicationContextAware) bean).setApplicationContext(this.applicationContext);

}

// ......

}

總結

以 上就是我對Java開發大型互聯網-深入理解Spring IOC容器的神秘之旅問題及其優化總結,分享給大家,希望大家知道什麼是Java開發大型互聯網-深入理解Spring IOC容器的神秘之旅問題及其優化。覺得收穫的話可以點個關注收藏轉發一波喔,謝謝大佬們支持!

  • 1、多寫多敲代碼,好的代碼與紮實的基礎知識一定是實踐出來的

  • 2、可以去百度搜索騰訊課堂圖靈學院的視頻來學習一下java架構實戰案例,還挺不錯的。

  • 最後,每一位讀到這裡的網友,感謝你們能耐心地看完。希望在成為一名更優秀的Java程序員的道路上,我們可以一起學習、一起進步!都能贏取白富美,走向架構師的人生巔峰!

  • 3丶想了解學習以上課程內容可加群:469717771 驗證碼頭條(06 必過)歡迎大家的加入喲!

你可能會喜歡