在 Spring Boot 中,有一個非常核心的觀念就是 依賴注入 (Dependency Injection, DI)。它與 控制反轉 (Inversion of Control, IoC) 密切相關,能幫助我們降低程式碼的耦合度、提升可維護性與可測試性。本節會透過生活化的比喻(珍珠奶茶、咖啡機)、程式碼比較、圖解.. 等方式快速理解與掌握。
名詞 | 英文 | 簡單解釋 |
---|---|---|
Bean | Bean | Spring 容器所管理的物件 |
IoC | Inversion of Control | 將建立物件的責任交給 Spring 容器 |
DI | Dependency Injection | Spring 容器將物件「注入」到需要它的地方 |
在 Spring Boot 中,Bean 是由 Spring 控制反轉 (IoC) 容器建立並管理的 Java 物件。當應用程式啟動時,Spring 會自動建立、設定並管理這些物件,讓開發者專注於業務邏輯,而不需要自己處理物件的生命週期。
為什麼需要 Bean?
在程式中,一個類別 (Class) 經常需要另一個類別幫忙才能完成工作,這就是「依賴 (Dependency)」。
生活比喻:
Customer
)」,想喝「珍珠奶茶 (BubbleTea
)」。TeaShop
)」來製作。如果用程式中來表示,在沒有 DI 的世界裡,你可能會這樣做:
// 飲料店類別
public class TeaShop {
public String makeBubbleTea() {
return "一杯香濃的珍珠奶茶";
}
}
// 顧客類別
public class Customer {
private TeaShop teaShop;
public Customer() {
// **自己動手建立依賴,控制權在自己手上**,建立一個飲料店物件
this.teaShop = new TeaShop();
}
public void drink() {
String bubbleTea = teaShop.makeBubbleTea();
System.out.println("顧客喝到了:" + bubbleTea);
}
}
// 主程式
public class Main {
public static void main(String[] args) {
Customer customer = new Customer();
customer.drink();
}
}
問題在哪?
Customer
類別自己負責 new TeaShop()
。這代表 Customer
和 TeaShop
緊緊綁在一起這種情況我們稱為「高耦合 (High Coupling)」。CornerTeaShop
)」,就必須修改 Customer
內部的程式碼。如果是在大型專案中,這樣的修改會像惡夢一樣,牽一髮而動全身。為了解決這個問題,Spring 提出了 控制反轉 (Inversion of Control, IoC) 的概念。
概念:
TeaShop
,並把它交給你。這個「把依賴交給你」的動作,就是 依賴注入 (Dependency Injection, DI)。
[Spring IoC 容器]
│
├── 1. 建立 TeaShop Bean
├── 2. 建立 CustomerService Bean
└── 3. 將 TeaShop Bean「注入」到 CustomerService 中
首先,我們要讓 Spring 知道 TeaShop
是需要被它管理的 Bean。
import org.springframework.stereotype.Service;
@Service // 告訴 Spring:這是一個需要被你管理的 Bean
public class TeaShop {
public String makeBubbleTea() {
return "一杯來自 Spring 容器的珍珠奶茶";
}
}
接下來,看看 CustomerService
如何取得 TeaShop
。
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service
public class CustomerService {
@Autowired // 直接在屬性上請求注入
private TeaShop teaShop;
public void drink() {
System.out.println("顧客喝到了:" + teaShop.makeBubbleTea());
}
}
@Autowired
註解(Annotation)。CustomerService
物件並模擬 (Mock) teaShop
。NullPointerException
:如果 Spring 因為某些原因沒有成功注入,teaShop
就會是 null
。import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service
public class CustomerService {
private TeaShop teaShop;
@Autowired // 透過 setter 方法注入
public void setTeaShop(TeaShop teaShop) {
this.teaShop = teaShop;
}
public void drink() {
System.out.println("顧客喝到了:" + teaShop.makeBubbleTea());
}
}
final
,代表它在建立後可能被修改。import org.springframework.stereotype.Service;
@Service
public class CustomerService {
// 可以宣告為 final,確保它不會被修改
private final TeaShop teaShop;
// 透過建構子請求依賴
// 當類別只有一個建構子時,@Autowired 可以省略
public CustomerService(TeaShop teaShop) {
this.teaShop = teaShop;
}
public void drink() {
System.out.println("顧客喝到了:" + teaShop.makeBubbleTea());
}
}
NullPointerException
。new CustomerService(new MockTeaShop())
,傳入一個假的 TeaShop
來進行測試,完全不需要 Spring 容器的介入。final
,確保它在物件生命週期內不會被意外修改,這在多執行緒環境下更安全。注入方式 | 優點 | 缺點 | 推薦度 |
---|---|---|---|
屬性注入 | 寫法最簡單 | 隱藏依賴、不易測試 | ❌ 不推薦 |
Setter 注入 | 彈性高 | 無法保證依賴完整 | ⚠️ 少數情境使用 |
建構子注入 | 明確依賴、可測試、安全 | 程式碼稍多 | ✅ 官方推薦 |
@Configuration
和 @Bean
建立自訂 Bean當你需要管理的物件來自第三方函式庫(你無法修改其原始碼),就無法使用 @Service
等註解 (Annotation)。這時,可以使用 @Configuration
+ @Bean
來手動註冊。
// 假設這是一個來自第三方套件的類別
public class CoffeeMachine {
public String brew() {
return "一杯由高級咖啡機沖泡的咖啡";
}
}
// 建立一個設定檔類別
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration // 告訴 Spring:這是一個設定檔
public class AppConfig {
@Bean // 告訴 Spring:請將這個方法回傳的物件,當作一個 Bean 來管理
public CoffeeMachine coffeeMachine() {
// 這裡可以進行複雜的初始化設定
return new CoffeeMachine();
}
}
import org.springframework.stereotype.Service;
// 在服務中使用它
@Service
public class BaristaService {
private final CoffeeMachine coffeeMachine;
// 同樣使用建構子注入
public BaristaService(CoffeeMachine coffeeMachine) {
this.coffeeMachine = coffeeMachine;
}
public void serveCoffee() {
System.out.println("咖啡師為您送上:" + coffeeMachine.brew());
}
}