Day 5 - Spring IoC 簡介

古古

2024/07/05


哈囉大家好,我是古古。

在前面的文章中,我們有先去安裝開發環境,並且成功使用 IntelliJ 這套軟體,去創建出了第一個 Spring Boot 程式,先對如何撰寫 Spring Boot 程式有了一個最基本的了解。

那麼從這篇文章開始,我們就會開始來介紹 Spring 框架中一個非常重要的特性,也就是 IoC,所以我們就開始吧!

什麼是 IoC? #

IoC 的全稱是 Inversion of Control,中文翻譯成「控制反轉」,不過這個控制是控制什麼、反轉又是反轉什麼,我們可以直接透過一個印表機的例子來了解一下。

例子:印表機的故事 #

假設今天我們先設計一個印表機 Printer 的 interface,這個印表機裡面只有一個方法,就是 print() 方法印東西,程式如下:

public interface Printer {
    void print(String message);
}

而有了 interface 之後,我們就可以去寫一個 class 來實作這個 Printer interface,所以我們可以寫一個 HpPrinter 的 class,表示這是一個 HP 牌子的印表機,程式如下:

public class HpPrinter implements Printer {

    @Override
    public void print(String message) {
        System.out.println("HP印表機: " + message);
    }
}

並且我們也可以再寫一個 CanonPrinter 的 class,表示這是一個 Canon 牌子的印表機,程式如下:

public class CanonPrinter implements Printer {

    @Override
    public void print(String message) {
        System.out.println("Canon印表機: " + message);
    }
}

所以到目前為止,我們就有了兩個 class,一個是 HpPrinter、另一個則是 CanonPrinter,而這兩個印表機裡面,就都會去 implements Printer 這個 interface,去實作他裡面的 print() 方法。

新增一個 Teacher class #

有了兩個印表機之後,我們可以再去設計一個 Teacher class,然後在這個 Teacher class 裡面,宣告一個 Printer 類型的變數,並且在後面去 new 一個 HpPrinter 出來。

public class Teacher {

    private Printer printer = new HpPrinter();

    public void teach() {
        printer.print("I'm a teacher");
    }
}

補充:這裡使用到 Java 中的「多型(polymorphism)」的概念,因此 HpPrinter 才可以被向上轉型成 Printer 類型。如果對於 Java 中的多型概念不熟悉的話,建議一定要回頭去了解一下會比較好,Java 多型的應用,會非常常出現在 Spring Boot 的程式裡面。

所以到這裡為止,整個結構圖就會像是下面這個樣子,剛剛我們所新增的 Teacher class,他就在 class 裡面宣告了一個 Printer 的變數,並且在裡面去 new 了一個 HpPrinter 出來,然後使用這個 HpPrinter 的印表機去印東西了。

但是…如果 HpPrinter 印表機壞掉了怎麼辦? #

但是假設這個時候,HpPrinter 如果突然壞掉了,那 Teacher 就只能改成去使用 CanonPrinter 印表機,繼續去印東西出來。

所以這個時候,我們就只能去改寫 Teacher 裡面的程式,將裡面的 Printer 變數,改成是去 new 一個 CanonPrinter 出來,因此結構圖就會變成是下面這個樣子:

所以到這邊,我們可能就會產生了一個新的想法:就是身為一個 Teacher,我們其實只是想要一台印表機去印東西而已,不論這個印表機是 HP 還是 Canon 品牌,只要他可以印東西出來就好,我們根本不在意我們所使用的是哪個牌子的印表機。

但是因為我們在 Teacher 的程式裡面,「具體的去指定了」要使用的是哪一牌的印表機,所以每當我們換一次牌子,我們就必須要把所有使用到印表機的程式,全部都修改一遍,因此就會變得非常麻煩(現在只有 Teacher 這個 class 需要修改,所以大家可能覺得還好,大不了就動手改一下,但是當 Project 越寫越大之後,要這樣一個一個修改是非常困難的)。

所以這時候,Spring 就提出了一個新的概念來解決這個問題,也就是 IoC(Inversion of Control,控制反轉)

IoC(Inversion of Control,控制反轉) #

為了解決上面的「替換印表機」的問題,Spring 就提出了一個新的想法,也就是 「將這個 Printer 的 object(物件)交由 Spring 保管,當誰要使用印表機時,就再去跟 Spring 拿就好」。

反正我們作為 Teacher 來說,也不是很在意使用到的是 HP 印表機、還是 Canon 牌的印表機,我們只要確保能夠拿到一個印表機來印東西就好。

所以在這個情境下,HpPrinter 和 CanonPrinter 這兩個印表機,就統一交由 Spring 進行保管,而我們作為 Teacher,就不需要提前將 HpPrinter 和 CanonPrinter 的資訊寫死在 Java 程式裡面,所以 Teacher class 就可以改寫成像是下面這樣:

可以看到在 Teacher class 中,我們只定義了一個 Printer 的變數,並且我們沒有為這個 printer 變數,去 new 一個 HpPrinter 或是 CanonPrinter 出來,因此這個 printer 變數的值,預設就會是 null。

但是,當 Spring Boot 程式運作起來之後,Spring 就會去啟動一個 「Spring 容器(Spring Contianer)」,然後 Spring 會預先去 new 一台印表機出來(此處以 HpPrinter 當作例子),並且存放在 Spring 容器裡面保存。

因此後續當 Teacher 想要使用印表機時,Spring 就會把這個預先 new 好、並且存放在 Spring 容器中的 HpPrinter,把他交給 Teacher,因此 Teacher 就可以正常的使用這個 HP 印表機,去印他想印的東西了。

所以大家也可以簡單的想像成是:Spring 他會預先去買一台印表機,然後儲存在「Spring 容器」裡面,而誰想要印東西,他就去跟 Spring 借,所以印東西的人就再也不用自己去準備一台印表機,只要一直去跟 Spring 借就好了,這個就是「Spring IoC」的概念了!

Spring IoC 的定義 #

所以透過上面的印表機例子,大概了解了 Spring IoC 的概念之後,我們也可以回頭來看一下 IoC 的定義。

IoC 的全稱 是 Inversion of Control,中文翻譯成「控制反轉」,而 IoC 的概念,就是「將 object(物件)的控制權,交給了外部的 Spring 容器來管理」,所謂的 Control(控制),就是對於 object 的控制權。

所以像是我們以前在寫 Java 程式時,我們就會在 Teacher class 中去 new 一個 HpPrinter 出來,因此這個 HpPrinter 的控制權,就是在自己(Teacher class)的手上(如下圖左)。

而當我們用了 Spring 框架之後,則是會由 Spring 容器統一去 new 一個 HpPrinter 出來,再去提供給大家使用,因此 HpPrinter 的控制權,就是在外部容器(Spring 容器)的手上(如下圖右)。

也因為我們 將 HpPrinter 的「控制權」,從自己手上「轉移到」外部的 Spring 容器,由外部的 Spring 容器統一去做管理,所以就稱為是 Inversion of Control,簡稱為 IoC。

Spring IoC 的優點 #

了解了 IoC 的定義之後,接著我們可以來看一些使用 Spring IoC 的優點。

使用 Spring IoC有三大好處,分別是:

1. Loose coupling(鬆耦合) #

使用 Spring IoC 的第一個優點,就是可以達到 class 之間的鬆耦合,即是可以「降低各個 class 之間的關聯性」。

像是我們本來必須要在 Teacher 中,手動去指定我們要使用的是 HpPrinter 印表機,而這就會讓 Teacher 和 HpPrinter 的關聯性變大。

但是當我們改用了 Spring IoC 之後,Teacher 就只要去和 Spring 容器拿一台印表機,就可以直接直接去印東西了,所以 Teacher 就不需要了解這個印表機到底是 HP 牌還是 Canon 牌,如此就降低了 Teacher 和 HpPrinter 之間的關聯性。

因此使用 Spring IoC 的第一個好處,就是可以降低 class 之間的關聯性。

2. Lifecycle management(生命週期管理) #

使用 Spring IoC 的第二個優點,則是統一的生命週期管理。

因為我們將 HpPrinter 改成交由 Spring 容器來管理,因此 Spring 就會負責 HpPrinter 的創建、初始化、以及銷毀,所以就不需要我們親自去處理這件事情。

所以使用 Spring IoC 的第二個好處,就是讓 Spring 容器能夠統一的,對所有 object 進行生命週期的管理。

3. More testable(方便測試程式) #

使用 Spring IoC 的第三個優點,即是更加方便的測試程式。

因為所有的 object 都是由外部的 Spring 容器來做管理,因此我們就可以使用 Mock 的技術,在測試的過程中,將 Spring 容器中的 object 給替換掉,這樣子就可以變免受到其他的外部服務影響,更聚焦在當前這個單元測試想要測試的部分。

補充:不過有關單元測試和 Mock 的部分,在本系列文中不會提及,大家如果有興趣的話,可以參考我撰寫的另一篇文章: SpringBoot - 單元測試工具 Mockito

總結 #

這篇文章我們有先透過印表機的例子,先介紹了 Spring IoC 的原理,並且也一併介紹了使用 Spring IoC 的優點,希望可以透過比較生活化的例子,讓大 家更好理解 Spring IoC 的概念。

那麼下一篇文章,我們就會接著來介紹,在 Spring IoC 中也很重要的兩個名詞:DI 和 Bean,讓大家更全面的了解 Spring IoC,那我們就下一篇文章見啦!

補充:本文是擷取自我開設的線上課程 「Java 工程師必備!Spring Boot 零基礎入門」 的內容,如果你想了解更多的 Spring Boot 的用法,歡迎參考課程簡介 (輸入折扣碼「HH202504KU」即可享 85 折優惠)。