本文適合初學者,也適合老手鞏固以前的知識,針對IOC的知識點進行詳細的講解。
在剛開始接觸IOC的時候,很多初學者回聽到很多名詞 IOC、DI、Spring IOC 等,往往聽的一頭霧水。所以在學習之前我們先梳理下這些概念,之後再去深入學習 IoC 的實現原理部分。
很多人都知道 IOC,都知道 IOC 是 Spring 容器的內核。
AOP、申明式事務等功能在此基礎上開發的。甚至在一提到 IOC 這個概念的時候就很自然的想到 Spring 容器,並將它們混爲一談,認爲 IoC 就是 Spring IoC。這樣理解雖然並不妨礙我們使用 Spring 框架,但是兩者卻不是一回事。
IOC的概念和思想
IOC (Inversion Of Control 譯爲 反轉控制),顧名思義,其一是控制,其二是反轉。即某一個接口具體實現類的選擇控制權從調用類中移除,轉交給第三方決定,在 Spring 容器中是由 Bean 配置來進行控制的。
這段話比較繞口,簡單來說就是有了 IoC 之後你需要什麼對象,IoC 容器幫你解決,你在家等着,IoC 容器主動送貨上門。沒有 IoC 之前是自己親手去創建管理對象,現在直接現成的送過來。
再回頭看看,IoC 的 控制 和 反轉 這個兩個名詞就很好理解了:
再未使用 IoC 之前,需要使用一個對象需要直接 new 一個。那麼你所依賴的對象就需要你自己去控制。但是有了 IoC 之後,直接由 IoC 容器來控制依賴的對象,當你依賴一個對象的時候就由 IoC 容器創建後注入到被注入的對象中。
- 控制表示對對象的控制
- 反轉表示依賴對象的獲取反轉了
而實現 IOC 概念的容器除了 Spring 還有 Guice,Jboss 等。
所以 IOC 和 Spring 中的 IOC 不能混爲一談。可以說,IOC 本身只是一種概念和設計思想,Spring 容器實現了 IOC 思想,是 IOC 的實踐。
但由於 IOC 這種命名確實不夠開門見山,因此業界曾進行了廣泛的討論,最終軟件界的泰斗級人物 Martin Fowler 提出了 DI(Dependency Injection,依賴注入)
的概念用來替代 IoC。即讓調用類對某一接口實現類的依賴關係由第三方(容器或協作類)注入,以移除掉用類對某一接口實現類的依賴。
可以看出 DI 依賴注入
的解釋 比 IoC 反轉控制
更直接明瞭,初學者更容易理解。
很多人將 IoC 和 DI 混爲一談,這樣雖然也不影響使用,但至少要知道它由 IoC 變爲 DI 的發展過程。
Spring IoC 的原理
Spring IoC 的底層實現是基於反射技術,不瞭解這塊知識的同學可以看下這兩篇文章學習一下:《反射一開,誰都不愛》,《工廠模式的三種實現,就這麼簡單!》。
前面說了 IoC 容器是一個大的工廠來管理所有對象和它們的依賴關係,Spring 再處理這些對象和依賴關係也很簡單:
- 使用反射技術獲取對象的信息包括:類信息、成員、方法等等
- 再通過 xml 配置 或者 註解 的方式,說明依賴關係
- 在調用類需要使用其他類的時候,不再通過調用類自己實現,而是通過 IoC 容器進行注入。
那 Spring 究竟是如何知道哪些對象是需要管理的呢?如何進行管理的呢?又是如何進行注入的呢?
Spring 通過一個配置文件或註解來描述 Bean 和 Bean 之間的依賴關係,根據 Bean 配置信息在容器內部創建Bean定義註冊表,根據註冊表加載、實例化 Bean、建立Bean與Bean之間的依賴關係,還提供了 Bean 實例緩存、生命週期管理、Bean 實例代理、事件發佈、資源裝載等高級服務。
Spring 框架和核心接口是 Bean 工廠,工廠分爲兩種:
- BeanFactory:這是最基礎的,面向 Spring 的
- ApplicationContext:是面向 Spring 框架開發者的,幾乎所有的應用場合都可以使用它。
BeanFactory 有着龐大的繼承、實現體系,有衆多的子接口、實現類。
來看一下 BeanFactory 的基本類體系:
ApplicationContext 的繼承體系:
BeanFactory 負責讀取bean配置文檔,管理bean的加載,實例化,維護bean之間的依賴關係,負責bean的聲明週期。
-
ApplicationContext 除了提供上述 BeanFactory 所能提供的功能之外,還提供了更完整的框架功能:
- 國際化支持
- 資源訪問:
Resource rs = ctx. getResource(“classpath:config.properties”), “file:c:/config.properties”
- 事件傳遞:通過實現ApplicationContextAware接口
介紹了 Bean 的工廠類,再來詳細的解答下 Spring 是如何管理這些類的?這個問題。
Bean的生命週期
Bean 的生命週期可以說是面試的時候常問的問題之一,我們可以用下面兩張圖直觀的感受下:
BeanFactory 中 Bean 的生命週期:
ApplicationContext 中 Bean 的生命週期:
可以看到在進行實例化、設置屬性值、通過 init-method
屬性配置的初始化方法、放入 Spring 緩衝池 前後都有方法可以對 Bean 進行再次加工。
Bean 的生命週期從開始到最終銷燬其中經歷的方法可以分爲四類:
- Bean自身的方法:如調用 Bean 構造函數實例化 Bean,調用 Setter 設置 Bean 的屬性值以及通過的 init-method 和 destroy-method 所指定的方法;
- Bean級生命週期接口方法:如 BeanNameAware、 BeanFactoryAware、 InitializingBean 和 DisposableBean,這些接口方法由 Bean 類直接實現;
- 容器級生命週期接口方法: InstantiationAwareBean PostProcessor 和 BeanPostProcessor 這兩個接口實現,一般稱它們的實現類爲“ 後處理器” 。這些後處理器的影響是全局性的。用戶可以通過合理地編寫後處理器,讓其僅對感興趣Bean 進行加工處理。
- 工廠後處理器接口方法
Bean 的裝配過程
簡單敘述下 Bean 的裝配過程:
- BeanDefinitionReader 讀取 Resource 所指向的配置文件資源,然後解析配置文件。配置文件中每一個解析成一個 BeanDefinition 對象,並保存到 BeanDefinitionRegistry 中;
- 容器掃描 BeanDefinitionRegistry 中的BeanDefinition;調用InstantiationStrategy 進行Bean實例化的工作;使用 BeanWrapper 完成Bean屬性的設置工作;
- 單例Bean緩存池:Spring 在DefaultSingletonBeanRegistry類中提供了一個用於緩存單實例 Bean 的緩存器,它是一個用HashMap實現的緩存器,單實例的 Bean 以beanName爲鍵保存在這個HashMap中。
ApplicationContext 和 BeanFactory 的區別
區別:
- BeanFactory 在啓動的時候不會去實例化 Bean,中有從容器中拿 Bean 的時候纔會去實例化,ApplicationContext 在啓動的時候就把所有的 Bean 全部實例化了。它還可以爲 Bean 配置 lazy-init=true 來讓 Bean 延遲實例化;
- BeanFacotry 是 spring 中比較原始的 Factory,無法支持 spring 的許多插件,如 AOP 功能、Web 應用等。 ApplicationContext接口
- MessageSource, 提供國際化的消息訪問
- 資源訪問,如URL和文件
- 事件傳播
- 載入多個(有繼承關係)上下文 ,使得每一個上下文都專注於一個特定的層次,比如應用的web層
Bean的裝配方式
Bean 的裝配方式有四種:
- 基於 xml 的配置
- 註解方式
- java類的配置
- 基於Groovy DSL的配置(不太常見)
一般都是用 xml 和 註解 兩種配置,其他的方式使用的不多。這裏不多做敘述
依賴注入的方式
依賴注入的方式有三種方式:
- 屬性注入:使用setter方法
- 構造函數注入
- 工廠方法注入
這其中屬性注入的方式比較常用。
對象之間關係
對象之間有三種關係:
- 依賴:挺少用的(在 xml 中使用depends-on就是依賴關係了-->前置依賴【依賴的Bean需要初始化之後,當前Bean纔會初始化】)
- 繼承:
- 引用:最常見(使用ref就是引用關係了)
Bean的作用域
- singleton:單例,只有一個對象
- prototype:多例,每次都會創建一個新對象
- request:每次請求都會參數一個對象
- session:每個session都會產生一個對象
- global session:所有的session都用一個對象
默認Bean的作用域是單例的,其他需要使用不同類型的作用域,需要單獨配置。
這裏有個知識點 @lookup
註解需要注意。如果我們需要在 singleton 的 Bean 中注入一個 prototype 的 Bean 並希望在每次調用 singleton 的 Bean 的時候獲取的都是 prototype 的 Bean ?這就需要使用 @lookup
註解。
IOC 的優缺點
- 優點:實現組件之間的解耦,提高程序的靈活性和可維護性。
- 缺點:生成一個對象的步驟變複雜了,生成因爲是使用反射編程,在效率上有些損耗。但相對於IoC提高的維護性和靈活性來說,這點損耗是微不足道的,除非某對象的生成對效率要求特別高。
總結
這裏,再次做一個知識點的總結:
IoC 是一種概念和設計思想,Guice,Spring,Jboss都實現了這種理念,Spring將其發揚光大
Spring IoC 容器實現基於反射技術
IoC 的3種類型,Spring IOC 支持2種,構造函數注入和屬性注入
Spring 容器工廠分爲兩種:ApplicationContext、BeanFactory。異同點:1. BeanFactory 在啓動的時候不會去實例化 Bean,ApplicationContext 在啓動的時候會。2. BeanFacotry 是 spring 中比較原始的 Factory,無法支持 spring 的許多插件,如 AOP 功能、Web 應用等。 ApplicationContext 支持。
-
Bean的生命週期的過程:
- 四類方法:
- Bean自身的方法:如Setter init-method 和 destroy-method 所指定的方法;
- Bean級生命週期接口方法:如 BeanNameAware、 BeanFactoryAware、 InitializingBean 和 DisposableBean,這些接口方法由 Bean 類直接實現;
- 容器級生命週期接口方法: InstantiationAwareBean PostProcessor 和 BeanPostProcessor 這兩個接口實現,一般稱它們的實現類爲“ 後處理器” 。這些後處理器的影響是全局性的。用戶可以通過合理地編寫後處理器,讓其僅對感興趣Bean 進行加工處理。
- 工廠後處理器接口方法
- 四類方法:
-
Bean 的裝配過程:
- BeanDefinitionReader 讀取 Resource 所指向的配置文件資源,然後解析配置文件。配置文件中每一個解析成一個 BeanDefinition 對象,並保存到 BeanDefinitionRegistry 中;
- 容器掃描 BeanDefinitionRegistry 中的BeanDefinition;調用InstantiationStrategy 進行Bean實例化的工作;使用 BeanWrapper 完成Bean屬性的設置工作;
- 單例Bean緩存池:Spring 在DefaultSingletonBeanRegistry類中提供了一個用於緩存單實例 Bean 的緩存器,它是一個用HashMap實現的緩存器,單實例的 Bean 以beanName爲鍵保存在這個HashMap中。
bean的裝配方式,四種:1. xml;2. 註解;3. java類的配置;4. 基於Groovy DSL的配置(不太常見)
依賴注入的方式,三種:1. 屬性注入:通過setXxx()方法注入Bean的屬性值;2. 構造器注入:通過構造函數注入;3. 工廠方法注入:用靜態工廠或非靜態工廠方法注入
bean的五個作用域:1. singleton:單例,只有一個對象;2. prototype:多例,每次都會創建一個新對象;3. request:每次請求都會參數一個對象;4. session:每個session都會產生一個對象;5. global session:所有的session都用一個對象
高級主題:1. 國際化:多語言;2. 容器事件:監聽對應的事件並做出反應,比如:RequestHandledEvent,當一個Http請求被處理後產生事件;3. 引用外部屬性文件:數據庫的用戶名密碼信息存儲在properties文件中,可以使用${XX} 進行引用;4. 屬性編輯器:轉化爲基本類型的利器
最後再附上Spring-Ioc的思維導圖,建議點贊、關注、收藏,以便隨時複習鞏固知識:
我是帥帥,一個在互聯網苟且偷生的工具人。