iT邦幫忙

2025 iThome 鐵人賽

DAY 17
0
Software Development

spring boot 3 學習筆記系列 第 17

Day17 - Spring Boot 依賴注入指南:從外部讀取設定 - @Value

  • 分享至 

  • xImage
  •  

在現代應用程式開發中,我們需要管理大量的設定資訊,例如資料庫連線帳密、第三方服務的 API 金鑰 (API Key),或是在不同環境(如開發、測試、生產環境)中需要切換的開關。如果將這些資訊「寫死 (Hard-code)」在程式碼裡,會帶來一場災難。

🤔 為什麼我們極力避免「硬編碼 (Hard-coding)」?

想像一下,您將資料庫密碼直接寫在程式碼中:

  1. 維護性極差:每次密碼變更,都必須修改程式碼、重新編譯、打包、再重新部署整個應用程式。過程繁瑣且容易出錯。
  2. 安全性風險:程式碼儲存在版本控制系統(如 Git)中,敏感資訊(如密碼、API 金鑰)會暴露給所有有權限讀取程式碼的人,這是嚴重的安全漏洞。
  3. 缺乏彈性:開發環境和正式環境的設定通常不同。硬編碼意味著您需要維護不同版本的程式碼,這是不切實際的。

為了解決這些問題,Spring 框架 (Spring Framework) 提供了強大而優雅的解決方案:@Value 註解 (Annotation)。它能幫助我們將設定值從程式碼中分離,注入到我們的 Spring Bean 元件 (Spring Bean Component) 中,實現所謂的「外部化設定 (Externalized Configuration)」。

@Value 最核心的用途,就是讀取專案中 src/main/resources/ 目錄下的 application.propertiesapplication.yml 檔案中的設定值。

基礎入門:讀取 application.properties 中的設定值

這是 @Value 最常見、最直接的用法。我們使用 ${...} 的語法來指定要讀取的屬性鍵名 (Property Key)。

$ 符號代表:我要從「設定檔」中拿一個值。

假設我們的 application.properties 檔案內容如下:

# application.properties

app.name=我的第一個 Spring Boot 應用程式
app.version=1.0.0
app.port=8080
app.enabled=true

現在,我們可以在一個 Spring 元件中,輕鬆地將這些值注入進來:

import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

@Component
public class AppInfoService {

    // 將 app.name 的值注入到 appName 變數中
    @Value("${app.name}")
    private String appName;

    @Value("${app.version}")
    private String appVersion;

    // Spring 會自動將 "8080" 這個字串轉換為 int 型別
    @Value("${app.port}")
    private int appPort;

    // Spring 會自動將 "true" 這個字串轉換為 boolean 型別
    @Value("${app.enabled}")
    private boolean isEnabled;

    public void printAppInfo() {
        System.out.println("應用程式名稱: " + appName);
        System.out.println("應用程式版本: " + appVersion);
        System.out.println("通訊埠: " + appPort);
        System.out.println("是否啟用: " + isEnabled);
    }
}

當 Spring 容器 (Spring Container) 初始化 AppInfoService 這個 Bean 時,@Value 就會像一個魔法師,自動從設定檔中找到對應的鍵,並將其值賦予給標記的屬性。Spring Boot 的自動型別轉換功能非常強大,能處理大部分的基本資料型態。

讓程式更健壯:設定預設值

如果在設定檔中找不到對應的屬性,又沒有提供預設值,那麼應用程式在啟動時會拋出 IllegalArgumentException 錯誤而失敗。為了增加程式的健壯性(Robustness),我們可以提供一個預設值。

語法非常簡單,在鍵名後面加上冒號 : 即可。 ${app.key:defaultValue}

import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

@Component
public class AnotherService {

    // 如果在設定檔中找不到 'app.author',authorName 的值將會是 "預設作者"
    @Value("${app.author:預設作者}")
    private String authorName;

    // 預設值也適用於數字等其他型別
    @Value("${app.timeout:5000}")
    private int timeout;

    public void printDetails() {
        System.out.println("作者: " + authorName);
        System.out.println("超時設定 (ms): " + timeout);
    }
}

這樣一來,即使 application.properties 中沒有 app.authorapp.timeout 的設定,應用程式也能順利啟動並使用我們提供的預設值。

處理集合類型:注入陣列 (Array) 或列表 (List)

如果設定檔中的值是以逗號 , 分隔的列表,@Value 也可以輕鬆地將它注入為陣列或 List 集合。

application.properties:

app.servers=192.168.1.1,192.168.1.2,10.0.0.1
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import java.util.List;
import java.util.Arrays;

@Component
public class ServerConfig {

    // 直接注入為 List<String>
    @Value("${app.servers}")
    private List<String> serverList;

    // 也可以注入為 String[] 陣列
    @Value("${app.servers}")
    private String[] serverArray;

    public void printServers() {
        System.out.println("伺服器列表 (List): " + serverList);
        System.out.println("伺服器陣列 (Array): " + Arrays.toString(serverArray));
    }
}

【進階主題】@Value 的另一面:SpEL (Spring Expression Language)

@Value 的能力不僅限於讀取設定檔。它還能結合 Spring 表達式語言 (Spring Expression Language, SpEL),讓您執行更複雜的動態操作。

對於初學者來說,最重要的是要區分 ${}#{} 的不同:

重點區分

  • $ { ... }:用於讀取外部設定檔 (Properties) 的值。這是最常用、最基礎的用法。
  • # { ... }:用於執行 Spring 表達式語言 (SpEL),功能更為強大與動態,例如引用其他 Bean 的屬性或方法。

# 符號代表:我要執行一個「表達式 (Expression)」。

範例 1:引用系統屬性

您可以直接讀取 Java 虛擬機 (Java Virtual Machine, JVM) 的系統屬性。

import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

@Component
public class SystemInfoService {

    // 使用 SpEL 讀取 Java Home 路徑 (這是一個 JVM 系統屬性)
    @Value("#{systemProperties['java.home']}")
    private String javaHome;

    public void printSystemInfo() {
        System.out.println("Java Home Path: " + javaHome);
    }
}

範例 2:引用其他 Bean 的屬性

假設您有另一個 Bean,@Value 也可以透過 SpEL 直接引用它的屬性或調用它的方法。

首先,我們定義一個被引用的 Bean:

import org.springframework.stereotype.Component;

@Component("someBean") // 給這個 Bean 一個明確的名字 "someBean"
public class SomeBean {
    public String getSomeValue() {
        return "這是一個來自 SomeBean 的值";
    }
}

然後,在另一個 Bean 中使用 SpEL 來引用它:

import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

@Component
public class SpELService {

    // 引用名為 'someBean' 的 Bean,並調用它的 getSomeValue() 方法
    // SpEL 會自動對應到 getSomeValue() 這個 getter
    @Value("#{someBean.someValue}")
    private String valueFromOtherBean;

    public void printValue() {
        System.out.println("從其他 Bean 獲取的值: " + valueFromOtherBean);
    }
}

SpEL 的功能遠不止於此,但對於初學者而言,理解它可以動態執行程式碼片段,並與 ${} 做出區分,是邁向進階的關鍵第一步。

總結

這份講義涵蓋了 @Value 從基礎到進階的核心用法。掌握它,您就能夠撰寫出更乾淨、更安全、也更具彈性的 Spring Boot 應用程式。

用途 語法 範例 說明
基本屬性注入 ${key} @Value("${app.name}") 從設定檔讀取鍵 (Key) 為 app.name 的值。
提供預設值 ${key:defaultValue} @Value("${app.author:Guest}") 如果 app.author 不存在,則使用 Guest 作為預設值。
SpEL 表達式 #{expression} @Value("#{systemProperties['java.home']}") 執行 SpEL 表達式,例如讀取系統屬性。

相關資料來源


上一篇
Day16 - Spring Boot 依賴注入指南:用 @Primary 與 @Qualifier 解決多實作困境
系列文
spring boot 3 學習筆記17
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言