IoC容器的實現( IoC 容器概述和在 Spring 中的應用場景)

1 IoC 容器概述

1.1 IOC 容器和依賴反轉模式

  • 我們日常的 java 項目開發都是由兩個或多個類的彼此合作來實現業務邏輯的,這使得每個對象都需要與其合作的對象的引用(稱為所依賴的對象),如果合作的對象的引用或依賴關系由具體的對象來實現,這對復雜的面向對象系統的設計與開發是非常不利的,由此,我們如果能把這些依賴關系和對象的注入交給框架來實現,讓具體對象交出手中對于依賴對象的控制,那么就能很大程度上解耦代碼,這顯然是極有價值的。而這,就是“依賴反轉”,即反轉對依賴的控制,把控制權從具體的對象中轉交到平臺或者框架。

  • 實現依賴反轉的實現有很多種,在 Spring 中,IoC 容器就是實現這個模式的載體,它可以在對象生成或初始化的過程中,直接將數據或者依賴對象的引用注入到對象的數據域中從而來實現方法調用的依賴。而這種依賴注入是遞歸的,依賴對象會被逐層注入,從而建立起一套有序的對象依賴關系,簡化了對象依賴關系的管理,把面向對象過程中需要執行的如何新建對象、為對象引用賦值等操作交由容器統一管理,極大程度上減低了面向對象編程的復雜性。

1.2 IoC 在 Spring 中的應用場景

  • 在 Spring 中,Spring IoC 提供了一個基本 JavaBean 容器,通過 IoC 模式管理依賴關系,并通過依賴注入和 AOP 切面增強了為 JavaBean 這樣的 POJO 對象提供了事務管理、聲明周期管理等功能。

  • 在應用開發中,當我們在設計組件時,往往需要引入和調用其他組件的服務時,這種依賴關系如果固化在組件設計中就會導致組件之間的耦合和維護難度的增大,這個時候如果使用 IoC 容器,把資源獲取的方式反轉,讓 IoC 容器主動管理這些依賴關系,將依賴關系注入到組件中,那么這些依賴關系的適配和管理就會更加靈活。

  • 在應用管理依賴關系時,如果在 IOC 實現依賴反轉的過程中,能通過可視化的文本來完成配置,并且通過工具對這些配置信息進行可視化的管理和瀏覽,那么肯定能提高依賴關系的管理水平,而且如果耦合關系變動,并不需要重新修改和編譯 Java 代碼,這符合在面向對象過程中的開閉原則。

2 IoC 容器系列的設計與實現:BeanFactory 和 ApplicationContext

使用過 Spring 的同學應該都接觸過 BeanFactory 和 ApplicationContext,其實它們就可以看做 Spring IoC 容器的具體表現形式,了解這兩者的區別與聯系對于我們理解和使用 IoC 容器是比較重要的。我們先來看一下 IoC 容器的接口設計圖:

IoC 容器的接口設計圖

下面我們來對這張接口設計圖做一下解析:

  • 第一條接口設計主線是:BeanFactory -> HierarchicalBeanFactory -> ConfigureBeanFactory。在這條設計路徑中,BeanFactory 定義了 IoC 容器的基本規范,包括了例如 getBean()(從容器中取得 Bean)這樣的 IoC 容器基本方法。而 HierarchicalBeanFactory 接口則在繼承 BeanFactory 基礎上,增加了 getParentBeanFactory() 的接口功能,使得 BeanFactory 具備了雙親 IoC容器的管理功能。接下來的 ConfigureBeanFactory 接口中,主要定義了一些對 BeanFactory 的配置功能,如通過 setParentBeanFactory() 設置雙親容器和通過 addBeanPostProcessor() 設置 Bean 后置處理器等。通過這些接口的疊加,定義了 BeanFactory 就是簡單 IoC 容器的基本功能。

  • 第二條設計主線是以 ApplicationContext 為核心的路徑:BeanFactory -> ListableBeanFactory -> ApplicationContext 再到我們常用的 WebApplicationContext 或 ConfigureApplicationContext。在這條路徑中,ListableBeanFactory 和 HierarchicalBeanFactory 連接了 BeanFactory 接口和 ApplicationContext 應用上下文,ListableBeanFactory 細化了 BeanFactory 接口功能,HierarchicalBeanFactory 上文已經提到了。對于 ApplicationContext,它通過集成 MessageSource、ResourceLoader、ApplicationEventPublisher 接口,在 BeanFactory 簡單的 IoC 容器的基礎上添加了許多對高級容器的特性和支持。

  • 這里主要涉及的是接口關系,而具體的容器是在這個接口體系下實現的,必須 DefaultListableBeanFactory 這個基本的 IoC 容器就實現了 ConfigureListableBeanFactory,從而成為一個簡單的 IoC 容器的實現。而其他的 BeanFactory 必須 XmlBeanFactory 都是在 DefaultListableBeanFactory 的基礎上做擴展,同樣的 ApplicationContext 體系也是一樣。

我們通過以上的接口設計圖跟分析可以看出,整個 Spring IoC 容器就是以 BeanFactory 和 ApplicationContext 作為核心的。BeanFactory 定義了 IoC 容器的基本功能,而 ApplicationContext 體系則在 BeanFactory 基礎上通過繼承其他接口來實現高級容器特征。下面我們來看一下這兩個體系的應用場景:

  • BeanFactory 的應用場景

    • BeanFactory 提供的是最基本的 IoC 容器的功能,關于這些功能,我們可以在接口中看到:
      BeanFactory接口
    • BeanFactory 接口定義了 IoC 容器最基本的形式,并且提供了 IoC 容器所應該遵循的最基本服務契約,同時也是我們使用 IoC 容器所應該遵守的最底層和最基本的編程規范,這些接口方法定義勾畫出了 IoC 容器的基本輪廓。而在 Spring 的的代碼實現中,BeanFactory 只是一個接口類,而后面的 DefaultListableBeanFactory、XmlBeanFactory、ApplicationContext 等都可以看成是容器附加了某種功能的實現。
    • BeanFactory 中定義了 getBean 方法,其參數類型有 Bean 的名字和其他參數,這些都是對 IoC 容器中存在的 Bean 進行索引。同時還定義了其他方法,方法的目的可以通過名字很明顯的看出來,這里就不一一說明了。
    • 可以看到,這里定義的只是接口方法,而這一系列的接口,使用不同的 Bean 的檢索方法,很方便的從 IoC 容器中得到所需要的 Bean,從這個角度來看,這些方法代表的是最基本的 IoC 容器入口。
  • BeanFacory 的設計原理

    • BeanFactory 接口提供了使用 IoC 容器的基本規范,而在這個規范之上, Spring 還提供了符合這個 IoC 容器接口的一系列容器的實現來供開發人員使用,下面我們以 XmlBeanFactory 為例來簡單說明一下 IoC 容器的實現原理。


      XmlBeanFactory設計的類繼承關系
    • 可以看到,作為最簡單 IoC 容器系列最底層實現的 XmlBeanFactory,與我們 Spring 應用中用到的那些上下文相比,有一個明顯的特點:它只提供最基本的 IoC 容器的功能,理解這一點有助于幫我我們理解 ApplicationContext 和 BeanFactory 之間的區別與聯系。

    • XmlBeanFactory 在繼承 DafaultListableBeanFactory 類的基礎上,新增了新的功能。從類的名字上可以看出應該是與讀取 xml 配置文件相關的 IoC 容器。那么該讀取過程是怎么實現的?

      • 我們先來看一下 XmlBeanFactory 的源碼:
      @Deprecated
      public class XmlBeanFactory extends DefaultListableBeanFactory {
      
          private final XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(this);
          
          public XmlBeanFactory(Resource resource) throws BeansException {
              this(resource, null);
          }   
          public XmlBeanFactory(Resource resource, BeanFactory parentBeanFactory) throws BeansException {
              super(parentBeanFactory);
              this.reader.loadBeanDefinitions(resource);
          }
      }
      
      • 可以看出,在 XmlBeanFactory 中,初始化了一個 XmlBeanDefinitionReader 對象,實際上,該對象就是處理以 XML 方式定義的 BeanDefinition 的地方。
      • 在構造 XmlBeanFactory 這個 IoC 容器,需要指定 BeanDefinition 的信息來源,而這個來源需要封裝成 Spring 中的 Resource 類(Spring 用來封裝 I/O 操作的類)。將 Resource 作為構造參數傳遞給 XmlBeanFactory 構造函數。這樣, IoC 容器就可以方便的定位到需要的 BeanDefinition 信息來對 Bean 完成容器的初始化和依賴注入的過程。至于如何獲取 Resource 和將 Resouce 轉化為 BeanDefinition,這是我們后面要提的重點。
    • 我們看到 XmlBeanFactory 使用 DefaultListableBeanFactory 作為基類,該類非常重要,是我們經常用到的一個 IoC 容器的實現,必須在設計應用上下文 ApplicationContext 就會用到它,我們可以看到這個類包含了基本 IoC 容器所具有的重要功能,也是在很多地方都會用到的容器系列中的一個基本產品,其他很多的 IoC 容器都是通過持有和擴展 DefaultListableBeanFactory 來獲得特性功能的 IoC 容器的。

    • 參考 XmlBeanFactory 的實現,我們使用編程的方式來使用 DefaultListableBeanFactory。從中我們可以看到 IoC 容器使用的一些基本過程,盡管實際開發中我們很少用到這種方式,但這對于我們理解 IoC 容器的工作原理是非常有幫助的。因為在這個編程式使用容器的過程中,很清楚的揭示了 IoC 容器實現中的那些關鍵類(Resource、DefaultListableBeanFactory、BeanDefinitionReader)之間的相互關系:

      // 編程式使用容器的代碼:
      ClassPathResource res = new ClassPathResource("bean.xml") ;
      DefaultListableBeanFactory factory = new DefaultListableBeanFactory() ;
      XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(factory) ;
      reader.loadBeanDefinitions(res) ;
      

      通過上面的代碼,我們可以看到使用 IoC 容器大概有以下四個步驟:

      1. 創建 IoC 配置文件的 Resource 資源,這個資源包含了 BeanDefinition 的定義信息。
      2. 創建一個 BeanFactory,這里使用 DefaultListableBeanFactory。
      3. 創建一個載入 BeanDefinition 的解讀器,這里使用 XmlBeanDefinitionReader 來載入 XML 文件形式 BeanDefinition,通過一個回調配置給 factory。
      4. 從定義好的資源位置讀入配置信息,具體的解析過程由 XmlBeanDefinitionReader 的 loadBeanDefinitions() 方法來完成。完成整個載入和注冊 Bean 定義之后,需要的 IoC 容器就建立起來可以直接使用了。
  • ApplicationContext 的應用場景:

    • 在 Spring 中,系統已經為用戶提供了很多定義好的容器實現。而相比那些簡單擴展 BeanFactory 的基本 IoC 容器,開發人員常使用的 ApplicationContext 除了能夠提供前面容器介紹的基本功能之外,還為用戶提供了以下附加服務,可以讓用戶更加方便的使用。所以說 ApplicationContext 是一個高級形態的 IoC 容器,如下圖所示可以看到 ApplicationContext 在 BeanFactory 的基礎上所提供的附加服務:


      ApplicationContext 類繼承關系
      • 支持不同的信息源。我們看到 ApplicationContext 擴展了 MessageSource 接口,這些信息源的擴展功能可以支持國際化的實現,為開發多語言版本的應用提供服務。
      • 訪問資源。這一點體現在對 ResourcePatternResolver 的繼承上。這樣我們可以從不同的地方得到 Bean 定義資源。這種抽象使用戶程序可以靈活地定義 Bean 信息,尤其是從不同的 I/O 途徑得到 Bean 的定義信息。關于 Resource 在 IoC 容器中的使用,我們后面會詳細介紹。
      • 支持應用事件。繼承了接口 ApplicationEventPublisher,從而在上下文引入了事件機制,這些事件和 Bean 的生命周期的結合為 Bean 的管理提供了便利。
      • 在 ApplicationContext 中提供的附加服務,使得基本 IoC 容器的功能更加豐富,對它的使用是一種面向框架的使用風格,所以建議在開發應用時使用 ApplicationContext 作為 IoC 容器的基本形式
  • ApplicationContext 的設計原理:

    • 我們通過 FileSystemXmlApplicationContext 為例子來分析 ApplicationContext 的設計原理。
    • 作為具體的應用上下文,我們只需要關注和它自身設計相關的功能,而其主要功能已經在其基類 AbstractXmlApplicationContext 中實現了。
      • 一個功能是啟動 IoC 容器的 refresh() 過程:
      public FileSystemXmlApplicationContext(String[] configLocations, boolean refresh, ApplicationContext parent)
          throws BeansException {
      
          super(parent);
          setConfigLocations(configLocations);
          if (refresh) {
              refresh();
          }
      }
      
      這個 refresh() 涉及到 IoC 容器啟動時候一系列復雜的操作,而不同的容器,其操作都是類似的,因此將其封裝在基類 AbstractApplicationContext 中。在 FileSystemXmlApplicationContext 中僅僅涉及到簡單的調用而已。關于 refresh() 在 IoC 容器啟動時的表現,該方法可以說是所有 IoC 容器的入口,這是一個很重要的方法,我們將在后面具體分析。
      • 另外一個功能是與這個獨特的應用上下文的設計有關,即從文件系統中加載 XML 的 Bean 定義資源。
      protected Resource getResourceByPath(String path) {
          if (path != null && path.startsWith("/")) {
              path = path.substring(1);
          }
          return new FileSystemResource(path);
      }
      
      可以看到,通過調用這個方法,可以得到 FileSystemResouce 的資源定位。

到此為止,我們大概了解了 IoC 容器的概念跟 Spring 中對于 IoC 容器的設計跟應用,之后我們要詳細的分析 IoC 容器的初始化、依賴注入跟其他相關特征的設計與實現等。

?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 227,837評論 6 531
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,196評論 3 414
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 175,688評論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,654評論 1 309
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,456評論 6 406
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 54,955評論 1 321
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,044評論 3 440
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,195評論 0 287
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,725評論 1 333
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,608評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,802評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,318評論 5 358
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,048評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,422評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,673評論 1 281
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,424評論 3 390
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,762評論 2 372

推薦閱讀更多精彩內容