Spring IoC實現原理(附思維導圖)

本文適合初學者,也適合老手鞏固以前的知識,針對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 所能提供的功能之外,還提供了更完整的框架功能:

    1. 國際化支持
    2. 資源訪問:Resource rs = ctx. getResource(“classpath:config.properties”), “file:c:/config.properties”
    3. 事件傳遞:通過實現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的思維導圖,建議點贊、關注、收藏,以便隨時複習鞏固知識:

我是帥帥,一個在互聯網苟且偷生的工具人。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章