Day 9 - Bean 的初始化 - @PostConstruct

古古

2024/07/09


哈囉大家好,我是古古。

在前幾篇文章中,我們分別介紹了 Bean 的相關特性,像是:

  • 創建 Bean 的方法:@Component
  • 注入 Bean 的方法:@Autowired
  • 以及指定 Bean 名字的方法:@Qualifier

所以到目前為止,我們可以說是對 Bean 有了更多的認識,並且已經可以成功的在 Spring Boot 程式中運用 Bean 了!

那麼這篇文章,我們就會繼續來探討 Bean 的更多用法,來介紹要如何在創建一個 Bean 出來之後,去「初始化」這個 Bean 的值。

什麼是 Bean 的初始化? #

所謂的「Bean 的初始化」,就是指「在 Bean 被創建出來之後,對這個 Bean 去做一些初始值的設定」,譬如說把他內部的變數的值設定成 5、或是進行一些運算之類的,反正就是對這個 Bean 去做初始的出廠設定就對了。

舉例來說,我們可以先改寫一下之前所寫的 HpPrinter,先在這個 HpPrinter 中去加上一個 count 變數,用 count 變數計算這台印表機還可以印幾次。

所以每當我們 call 一次 print() 方法時,這個 count 的數量就要減一,表示已經印過一次了,實際程式如下:

@Component
public class HpPrinter implements Printer {

    private int count;

    @Override
    public void print(String message) {
        count--;
        System.out.println("HP 印表機: " + message);
        System.out.println("剩餘使用次數: " + count);
    }
}

又因為我們有在這個 HpPrinter 上面加上 @Component,將他變成 Bean,所以 Spring Boot 到時候就會為我們創建一個 hpPrinter 的 Bean 出來,並且存放在 Spring 容器裡面。

不過到目前為止,因為我們沒有去設定 Bean 的初始化,因此 Spring Boot 就只會去把這個 Bean 給創建出來,並不會為裡面的 count 值進行初始化,因此在這個 hpPrinter 中的 count 值,預設就會是 0。

不過,如果我們想要讓 Spring Boot 在創建這個 hpPrinter 出來之後,同時也去為這個 count 變數賦予一個初始值的話,那麼我們就可以透過 @PostConstruct 來幫助我們達成這件事!

初始化 Bean 的方法:@PostConstruct #

@PostConstruct 的用途,就是「為這個 Bean 去進行初始化」,因此我們就可以透過 @PostConstruct,去設定這個 Bean 中的變數的初始值了。

因此如果我們想要改寫上面的 HpPrinter,將他裡面的 count 變數的值「初始化成 5」的話,那麼我們就可以這樣做:

首先我們先在 HpPrinter 新增一個新的方法 initialize()(方法名稱可以隨意取,後面會解釋),並且在這個方法上面,加上一行 @PostConstruct,這樣我們等一下就可以在這個方法中, 去初始化 Bean 的值。

@PostConstruct
public void initialize() {
    count = 5;
}

像是我們可以在 initialize() 的方法中,去初始化這個 Bean 的值,譬如說我們可以把 count 的值設成 5,就可以將 count 變數的值初始化成 5。

所以到時候,當 Spring Boot 創建出 hpPrinter 這個 Bean 之後,Spring Boot 就會接著去執行 initialize() 方法,將 count 的設定成 5,進而就可以完成 Bean 的初始化了!

使用 @PostConstruct 的注意事項之一:方法有特定格式 #

在上面的實作中,當我們想要初始化 Bean 中的變數的值時,我們需要先在該 class 中,先去新增一個方法出來(像是 HpPrinter 中的 initialize() 方法),接著再為這個方法加上 @PostConstruct,這樣子我們就可以在這個方法裡面, 去實作初始化 Bean 的程式了。

不過這個 initialize() 方法(也就是被加上 @PostConstruct 的方法),其實也是有一些格式需要遵守的:

  • 這個方法必須是 public
  • 這個方法的返回值必須是 void
  • 這個方法 「不能」 有參數
  • 這個方法的名字可以隨意取,不影響 Spring Boot 運作

所以綜合以上四點的話,這個「初始化 Bean 的方法」,通常就會長得像是下面這個樣子:

@PostConstruct
public void XXX();

其中的方法名稱 XXX(),可以替換成大家喜歡的單字,常見的有 setup()init()initialize()…等等,這些單字都可以拿來使用,不會影響到 Spring Boot 的運作。

補充:Spring Boot 在判斷一個 Bean 中有沒有初始化的方法時,是 「尋找有沒有方法加上 @PostConstruct,如果有的話,就執行該方法」。也因為如此,所以這個初始化的方法名稱對 Spring Boot 而言是完全不重要的, 大家就選擇自己喜好的單字(ex: setup()init())來使用即可。

使用 @PostConstruct 的注意事項之二:在同一個 class 中,建議只有一個方法加上 @PostConstruct #

由於當某個方法上面加上 @PostConstruct,該方法就會變成「初始化 Bean 的程式」,因此假設在同一個 class 裡面,同時有多個方法都加上 @PostConstruct 的話,那麼 Spring Boot 就會不知道要先運行哪一個方法去初始化 Bean。

因此在這種情況下,雖然 Spring Boot 程式不會報錯,但是實際上初始化 Bean 的順序是完全隨機的,因此可能會造成程式邏輯的錯誤,並且後續也很難統一管理初始化的設定。

所以就建議大家,在使用 @PostConstruct 去初始化 Bean 的時候,在同一個 class 中,只在其中一個方法上面加上 @PostConstruct,統一的去管理初始化 Bean 的設定,這樣子不管是在運作上、還是後續的維護,都是比較好的做法。

補充一:我們真的需要 @PostConstruct 嗎? #

由於上面的例子因為比較簡單,所以有的人可能會覺得:「為什麼我們不直接在宣告 count 變數的同時,把 count 值也設成 5 就好?」,就像是下面這個樣子,直接在宣告 count 變數的同時,也將他初始化成 5,這樣子就可以省去 @PostConstruct 的程式了。

雖然在這個例子中,確實是可以透過上述的方式,簡單的將 count 值初始化成 5,但是在實際的工作中,@PostConstruct 的應用還是很廣泛的。

使用 @PostConstruct 來初始化的優勢,就是 @PostConstruct 可以進行「複雜的初始化」,譬如說在 map 變數裡生成初始的數據、或是取得注入的 Bean 的資訊、或者檢查注入的 Bean 是否為 null 值…等等,這些實作只能夠透過 @PostConstruct 做到,使用一般的簡單方式無法實作出這些效果。

因此在實務上,使用 @PostConstruct 來進行 Bean 的初始化仍舊是很常見的作法,建議大家還是要了解一下這個用法會比較好!

補充二:初始化 Bean 的另一種方法:afterPropertiesSet() #

想要在 Spring Boot 中初始化 Bean,除了可以使用這篇文章所介紹 @PostConstruct 之外,其實 Spring Boot 也有支援另一種方法去初始化 Bean 的,也就是「實作 InitializingBean interface 裡面的 afterPropertiesSet() 方法」。

afterPropertiesSet() 的方式所實作出來的效果,和 @PostConstruct 的效果一模一樣的。

不過因為實作 afterPropertiesSet() 方法算是比較舊的寫法,在業界中越來越少用到,因此在本系列文中就不會特別介紹這部分,如果大家有興趣的話,可以再上網查詢相關資料。

但一般在實作上,還是會建議使用 @PostConstruct 來初始化 Bean 會比較好!

總結 #

這篇文章我們介紹了要如何使用 @PostConstruct,去初始化 Spring Boot 所創建出來的 Bean,所以大家以後就可以透過 @PostConstruct,去對 Bean 進行初始化的設定了。

而到這篇文章為止,我們就算是對 Spring IoC 有了比較全面的認識,現在我們已經了解了什麼是 IoC、DI,也了解了 Spring 容器和 Bean,並且也知道要如何在 Spring Boot 中使用 @Component@Autowired@Qualifier 以及 @PostConstruct 這些註解,去對 Bean 進行創建、注入、以及初始化。

那麼下一篇文章,我們就會繼續深入來介紹,要如何透過 @Value,將 Spring Boot 設定檔中的值讀取到 Bean 裡面,讓我們所寫的 Java 程式可以去運用 Spring Boot 設定檔中的值,那我們就下一篇文章見啦!

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