Spring IoC?看這篇帶你吃透 IoC 原理

前言

剛開始聽到 IoC,會以爲特別高大上,但其實明白原理了很簡單。程序員

跟着個人腳步,一文帶你吃透 IoC 原理。spring

本文圍繞 是何、爲什麼、如何 來談:express

Spring IoC?看這篇帶你吃透 IoC 原理。

初識 IoC

根據上一篇文章咱們說的,Spring 全家桶中最重要的幾個項目都是基於 Spring Framework 的,因此咱們就以 Spring Framework 爲例來看文檔[2]編程

首先它的右側有 Github 的連接,另外點到「LEARN」這裏,就會看到各個版本的文檔。bash

Spring IoC?看這篇帶你吃透 IoC 原理。

那咱們點「Reference Doc」,就可以看到它的一些模塊的介紹:架構

(等下... 模塊?什麼是模塊?這個問題下文回答。)框架

Spring IoC?看這篇帶你吃透 IoC 原理。

第一章 Overview,講述它的歷史、設計原理等等;maven

第二章 Core,包含了 IoC 容器,AOP 等等,那天然是講 Spring 的核心了,要點進去好好看了。ide

Spring IoC?看這篇帶你吃透 IoC 原理。

點進去以後發現了寶貴的學習資料,一切的 what, why, how 均可以在這裏找到答案。模塊化

Spring IoC?看這篇帶你吃透 IoC 原理。

這裏很好的解釋了大名鼎鼎的 IoC - Inversion of Control, 控制反轉。

我粗略的總結一下:控制反轉就是把建立和管理 bean 的過程轉移給了第三方。而這個第三方,就是 Spring IoC Container,對於 IoC 來講,最重要的就是容器

容器負責建立、配置和管理 bean,也就是它管理着 bean 的生命,控制着 bean 的依賴注入。

通俗點講,由於項目中每次建立對象是很麻煩的,因此咱們使用 Spring IoC 容器來管理這些對象,須要的時候你就直接用,不用管它是怎麼來的、何時要銷燬,只管用就行了。

舉個例子,就好像父母沒時間管孩子,就把小朋友交給託管所,就安心的去上班而不用管孩子了。託兒所,就是第三方容器,負責管理小朋友的吃喝玩樂;父母,至關於程序員,只管接送孩子,不用管他們吃喝。

等下,bean 又是什麼?

Bean 其實就是包裝了的 Object,不管是控制反轉仍是依賴注入,它們的主語都是 object,而 bean 就是由第三方包裝好了的 object。(想一下別人送禮物給你的時候都是要包裝一下的,本身造的就免了。

Bean 是 Spring 的主角,有種說法叫 Spring 就是面向 bean 的編程(Bean Oriented Programming, BOP)。

IoC 容器

既然說容器是 IoC 最重要的部分,那麼 Spring 如何設計容器的呢?仍是回到官網,第二段有介紹哦:

Spring IoC?看這篇帶你吃透 IoC 原理。

答:使用 ApplicationContext,它是 BeanFactory 的子類,更好的補充並實現了 BeanFactory 的。

BeanFactory 簡單粗暴,能夠理解爲 HashMap:

  • Key - bean name
  • Value - bean object

但它通常只有 get, put 兩個功能,因此稱之爲「低級容器」。

而 ApplicationContext 多了不少功能,由於它繼承了多個接口,可稱之爲「高級容器」。在下文的搭建項目中,咱們會使用它。

Spring IoC?看這篇帶你吃透 IoC 原理。

ApplicationContext 的裏面有兩個具體的實現子類,用來讀取配置配件的:

  • ClassPathXmlApplicationContext - 從 class path 中加載配置文件,更經常使用一些;
  • FileSystemXmlApplicationContext - 從本地文件中加載配置文件,不是很經常使用,若是再到 Linux 環境中,還要改路徑,不是很方便。

當咱們點開 ClassPathXmlApplicationContext 時,發現它並非直接繼承 ApplicationContext 的,它有不少層的依賴關係,每層的子類都是對父類的補充實現。

而再往上找,發現最上層的 class 回到了 BeanFactory,因此它很是重要。

要注意,Spring 中還有個 FactoryBean,二者並無特別的關係,只是名字比較接近,因此不要弄混了順序。

爲了好理解 IoC,咱們先來回顧一下不用 IoC 時寫代碼的過程。

深刻理解 IoC

這裏用經典 class Rectangle 來舉例:

  • 兩個變量:長和寬
  • 自動生成 set() 方法和 toString() 方法

注意 ⚠️:必定要生成 set() 方法,由於 Spring IoC 就是經過這個 set() 方法注入的;toString() 方法是爲了咱們方便打印查看。

public class Rectangle {
    private int width;
    private int length;

    public Rectangle() {
        System.out.println("Hello World!");
    }


    public void setWidth(int widTth) {
        this.width = widTth;
    }

    public void setLength(int length) {
        this.length = length;
    }

    @Override
    public String toString() {
        return "Rectangle{" +
                "width=" + width +
                ", length=" + length +
                '}';
    }
}
複製代碼

而後在 test 文件中手動用 set() 方法給變量賦值。

嗯,其實這個就是「解藕」的過程!

public class MyTest {
  @Test
  public void myTest() {
    Rectangle rect = new Rectangle();
    rect.setLength(2);
    rect.setWidth(3);
    System.out.println(rect);
  }
}
複製代碼

其實這就是 IoC 給屬性賦值的實現方法,咱們把「建立對象的過程」轉移給了 set() 方法,而不是靠本身去 new,就不是本身建立的了。

這裏我所說的「本身建立」,指的是直接在對象內部來 new,是程序主動建立對象的正向的過程;這裏使用 set() 方法,是別人(test)給個人;而 IoC 是用它的容器來建立、管理這些對象的,其實也是用的這個 set() 方法,不信,你把這個這個方法去掉或者改個名字試試?

幾個關鍵問題:

何爲控制,控制的是什麼?

答:是 bean 的建立、管理的權利,控制 bean 的整個生命週期。

何爲反轉,反轉了什麼?

答:把這個權利交給了 Spring 容器,而不是本身去控制,就是反轉。由以前的本身主動建立對象,變成如今被動接收別人給咱們的對象的過程,這就是反轉。

舉個生活中的例子,主動投資和被動投資。

本身炒股、選股票的人就是主動投資,主動權掌握在本身的手中;而買基金的人就是被動投資,把主動權交給了基金經理,除非你把這個基金賣了,不然具體選哪些投資產品都是基金經理決定的。

依賴注入

回到文檔中,第二句話它說:IoC is also known as DI.

咱們來談談 dependency injection - 依賴注入。

何爲依賴,依賴什麼?

程序運行須要依賴外部的資源,提供程序內對象的所須要的數據、資源。

何爲注入,注入什麼?

配置文件把資源從外部注入到內部,容器加載了外部的文件、對象、數據,而後把這些資源注入給程序內的對象,維護了程序內外對象之間的依賴關係。

因此說,控制反轉是經過依賴注入實現的。可是你品,你細品,它們是有差異的,像是「從不一樣角度描述的同一件事」:

  • IoC 是設計思想,DI 是具體的實現方式;
  • IoC 是理論,DI 是實踐;

從而實現對象之間的解藕。

固然,IoC 也能夠經過其餘的方式來實現,而 DI 只是 Spring 的選擇。

IoC 和 DI 也並不是 Spring 框架提出來的,Spring 只是應用了這個設計思想和理念到本身的框架裏去。

爲什麼

那麼爲何要用 IoC 這種思想呢?換句話說,IoC 能給咱們帶來什麼好處?

答:解藕。

它把對象之間的依賴關係轉成用配置文件來管理,由 Spring IoC Container 來管理。

在項目中,底層的實現都是由不少個對象組成的,對象之間彼此合做實現項目的業務邏輯。可是,不少不少對象緊密結合在一塊兒,一旦有一方出問題了,必然會對其餘對象有所影響,因此纔有瞭解藕的這種設計思想。

Spring IoC?看這篇帶你吃透 IoC 原理。

Spring IoC?看這篇帶你吃透 IoC 原理。

如上圖所示,原本 ABCD 是互相關聯在一塊兒的,當加入第三方容器的管理以後,每一個對象都和第三方法的 IoC 容器關聯,彼此之間再也不直接聯繫在一塊兒了,沒有了耦合關係,所有對象都交由容器來控制,下降了這些對象的親密度,就叫「解藕」。

如何

最後到了實踐部分,咱們來真的搭建一個 Spring 項目,使用下 IoC 感覺一下。

如今大都使用 maven 來構建項目,方便咱們管理 jar 包;但我這裏先講一下手動導入 jar 包的過程,中間會遇到不少問題,都是很好的學習機會。

在開始以前,咱們先來看下圖 - 大名鼎鼎的 Spring 模塊圖。

Spring Framework 八大模塊

Spring IoC?看這篇帶你吃透 IoC 原理。

模塊化的思想是 Spring 中很是重要的思想。

Spring 框架是一個分層架構,每一個模塊既能夠單獨使用,又可與其餘模塊聯合使用。

每一個「綠框」,對應一個模塊,總共8個模塊;「黑色包」,表示要實現這個模塊的 jar 包。

Core Container,咱們剛纔已經在文檔裏看到過了,就是 IoC 容器,是核心,能夠看到它依賴於這4個 jar 包:

  • Beans
  • Core
  • Context
  • SpEL, spring express language

那這裏咱們就知道了,若是想要用 IoC 這個功能,須要把這 4個 jar 包導進去。其中,Core 模塊是 Spring 的核心,Spring 的全部功能都依賴於這個 jar 包,Core 主要是實現 IoC 功能,那麼說白了 Spring 的全部功能都是藉助於 IoC 實現的。

其餘的模塊和本文關係不大,不在這裏展開了。

那當咱們想搭建 Spring 項目時,固然能夠把全部 jar 包都導進去,可是你的電腦能受得了嗎。。 可是包越大,項目越大,問題就越多,因此儘可能按需選擇,不用囤貨。。

Btw, 這張圖在網上有不少,可是在我卻沒有在最新版的 reference doc 上找到。。不過,既然那些老的教程裏有,說明老版本的 doc 裏有,那去老版本的介紹[3] 裏找找看

在本文第一張圖 Spring Framework - Documentation 中咱們選 4.3.26 的 Reference Doc.,而後搜索「Framework Modules」,就有啦~ 具體連接能夠看文末參考資料。

還有一個方法,待會咱們講到 jar 包中的內容時再說。

搭建 Spring 項目

知道要導入哪些 jar 包了,那就找吧。

1、手動加載 jar 包的方式

1. 下載

下載地址:

Spring IoC?看這篇帶你吃透 IoC 原理。

若是你要問我怎麼找的,那就仍是從剛纔 4.3.26 版本的 Reference Doc 中進去,而後剛開頭就有一個 Distribution Zip Files,

Spring IoC?看這篇帶你吃透 IoC 原理。

好奇心帶着我打開了它,發現...

Spring IoC?看這篇帶你吃透 IoC 原理。

發現了倉庫地址!

打開後發現是各個版本的 jar 包啊~

咱們搜 5.2.3 版的,它在最下面:

Spring IoC?看這篇帶你吃透 IoC 原理。

而後就能夠愉快的使用了~

  • Dist.zip 是 jar 包
  • Docs.zip 是文檔

其餘的暫時先不用管~

下載好了以後,就好好看看 Spring 送咱們的這份大禮包吧。

此處回答上文的遺留問題:哪裏找 Spring Framework 框架圖。

答案是:下載的 docs.zip → spring-framework-reference → images → spring-overview

Spring IoC?看這篇帶你吃透 IoC 原理。

咱們須要導入 Intellij 的 jar 包在哪裏呢?Dist.zip → libs

Spring IoC?看這篇帶你吃透 IoC 原理。

這裏能夠看到,每一個黑色框對應3個 jar 包,咱們要導入 Intellij 的是 RELEASE.jar.

2. 不用 IoC 構建項目

咱們 new project,不用 maven 構架,就新建一個普通的 Java 項目,好比我就叫它 Spring_HelloWorld,而後仍是用我經常使用的 class Rectangle 的例子。

而後在 External Libraries 中導入咱們剛纔在模塊圖裏看到的那4個模塊所對應的 jar 包,結構以下:

Spring IoC?看這篇帶你吃透 IoC 原理。

這樣你覺得就大功告成了嗎?Too young too simple 啊~

來運行一下:

Spring IoC?看這篇帶你吃透 IoC 原理。

出現了老盆友:no class def found error, 就是找不到這個類。

咱們谷歌 Maven common logging 並下載它的 jar 包,再加到項目裏就能夠了。

我上圖裏是已經加過了的,因此你會看到一個 commons-logging-1.2.

再運行一下就能夠了。這裏的兩個文件上文都有截圖。

目前爲止咱們是手動用 set() 方法設置對象的,那怎麼用 Spring IoC 呢?

3. Spring IoC 配置文件詳解

還須要有一個配置文件,但是這個文件須要配置啥,該怎麼配置呢?

官網裏都給咱們寫好了:

Spring IoC?看這篇帶你吃透 IoC 原理。

第一段是一些命名空間及其規範的介紹,

第二段就是給 bean 的屬性賦值了。

這裏注意下 bean 裏面的配置要改一下,改爲咱們這個項目對應的。這裏的 id, class 是什麼意思呢?官網上也有解釋,我這裏簡單歸納下:

  • bean 標籤:告訴 Spring 要建立的對象
  • id: 對象的惟一標識,就像每一個人的身份證同樣,不可重複
  • class: bean 的徹底限定名,即從 package name 到 class name
  • property:給屬性賦值,name 的名稱取決於 set() 方法後面的參數;

其實也能夠用 constructor 來賦值,name 的名稱取決於參數列表;更多給複雜數據類型賦值的使用能夠在官網查到。

固然,在工做中更經常使用的是註解。可是每每也會有 xml 文件配合着一塊兒使用的,因此仍是要懂的。

個人 service 文件配置以下:

Spring IoC?看這篇帶你吃透 IoC 原理。

4. 最後一步,咱們再來看它是怎麼用的:

Spring IoC?看這篇帶你吃透 IoC 原理。

這裏面並無直接的 new 這個 service,可是 Spring 容器幫咱們建立了這個對象。

那麼 Spring 是如何幫咱們建立對象的呢?

ApplicationContext 是 IoC 容器的入口,其實也就是 Spring 程序的入口, 剛纔已經說過了它的兩個具體的實現子類,在這裏用了從 class path 中讀取數據的方式;

而後第二行,就是獲取具體的 bean 了。這個其實有不少方式,在使用的時候就能看到:

Spring IoC?看這篇帶你吃透 IoC 原理。

點進去發現,是在 BeanFactory.class 裏定義的:

Spring IoC?看這篇帶你吃透 IoC 原理。

這其中比較經常使用的是經過

  • Id → 須要 cast
  • Bean 的類型 → 只能在 Singleton 的時候使用,不然不知道用哪一個呀
  • Id + 類型 → 下圖代碼示例

來獲取對象,最後兩種 String, Class objects 這種可變參數的方式用的不多。

照貓畫虎,個人 test 文件改動以下:

Spring IoC?看這篇帶你吃透 IoC 原理。

成功運行~~

Follow up 1. 對象在容器中默認是單例的

實踐是檢驗的惟一標準:

再用 getBean() 獲得一個對象,測試是否仍是同一個。

即:

public class MyTest {
  public void test myTest() {
    ApplicationContext context = new ClassPathXmlApplicationContext("service.xml");
        Rectangle rect = context.getBean("rectangle", Rectangle.class);
        Rectangle rect2 = context.getBean("rectangle", Rectangle.class);
        System.out.println(rect == rect2);
    }
  }
}
複製代碼

返回 True or False?

答:True

由於默認是單例的,若是要改,須要在配置文件裏改<bean … scope = 「prototype」>.

至於這些標籤的用法,這裏再也不延伸了~

Follow up 2. 容器中的對象是何時建立的?

實踐是檢驗的惟一標準:

定義一個無參的 constructor,裏面打印一句話,而後只 new ClassPathXmlApplicationContext,以下圖:

Spring IoC?看這篇帶你吃透 IoC 原理。

發現也是能夠打印的,因此實際上是每次啓動容器的時候,就已經建立好容器中的全部對象了。(固然,這在 scope = "prototype" 的時候不適用,只是 singleton 的時候。)

多說一句,其實最好應該一直保留一個無參的 constructor,由於這裏 bean 對象的建立是經過反射,

  • clazz.newInstance() 默認是調用無參的 constructor

不過,如今已經被棄用掉了,換用了這個:

  • clazz.getDeclaredConstructor().newInstance()

2、使用 Maven 構建項目

咱們再回到最開始的構建項目,相信你們都體會到了手動導入 jar 包的繁瑣之處,其實咱們還能夠用 Maven 來管理項目中的 jar 包,在公司中也是比較經常使用的一種方式,免除了手動下載 jar 包的過程。

1. 新建項目

使用 Maven 的話就簡化不少了,首先咱們建立一個 Maven 項目,不一樣於剛纔的過程在於:

New Project 的時候要選擇從 Maven 構建,而不是一個簡單的 Java 項目。

Spring IoC?看這篇帶你吃透 IoC 原理。

建好以後,咱們會發現比起剛纔的 Java 項目,多了不少東西:

和以前的空項目不太同樣,這裏有 main, test,其中 resources 是放配置文件的地方,也就是咱們剛纔的 service.xml 應該放在這裏,若是沒有放對位置是代碼找不到哦~

Spring IoC?看這篇帶你吃透 IoC 原理。

2. 添加對應的 pom 依賴,就不用手動導 jar 包了

  1. 倉庫地址 mvnrepository.com/
  2. 搜 spring
  3. 選擇 Spring context → 5.2.3 release,把裏面的配置 copy 到 pom.xml 中

最終在左邊 external libraries 會自動出現所需的包,一鍵導入,不要太方便~

3. 寫代碼~~

小結

咱們最後再來體會一下用 Spring 建立對象的過程:

經過 ApplicationContext 這個 IoC 容器的入口,用它的兩個具體的實現子類,從 class path 或者 file path 中讀取數據,用 getBean() 獲取具體的 bean instance。

那使用 Spring 到底省略了咱們什麼工做?

答:new 的過程。把 new 的過程交給第三方來建立、管理,這就是「解藕」。

Spring IoC?看這篇帶你吃透 IoC 原理。

Spring 也是用的 set() 方法,它只不過提供了一套更加完善的實現機制而已。

而說到底,底層的原理並無很複雜,只是爲了提升擴展性、兼容性,Spring 提供了豐富的支持,因此才以爲源碼比較難。

由於框架是要給各類各樣的用戶來使用的,它們考慮的更多的是擴展性。若是讓咱們來實現,或許三五行就能搞定,可是咱們實現的不完善、不完整、不嚴謹,總之不高大上,因此它寫三五十行,把框架設計的儘量的完善,提供了豐富的支持,知足不一樣用戶的需求,才能佔領更大的市場啊。

原文連接:mp.weixin.qq.com/s/F_ISmJwNu…

相關文章
相關標籤/搜索