使用 @Component
及其衍生 Annotation (@Service
, @Repository
, @Controller
),讓 Spring Boot 自動發現並管理我們自己寫的類別,真正體會到 IoC/DI 的強大之處。
在 Spring Boot 的世界裡,管理物件的工作就像經營一座樂高城堡。
想像一下,城堡裡有國王、騎士、廚師和工匠,不同角色各司其職。
在傳統的程式開發中,如果國王肚子餓了,他需要「自己」去尋找並「創造(new)」出一位廚師。
// 傳統的方式,國王自己建立廚師物件
public class King {
// 國王和廚師緊緊綁在一起,想換廚師就得改程式碼(高耦合)
private Cook cook = new Cook();
public void haveDinner() {
cook.prepareMeal();
}
}
問題:
這時候,Spring Boot 就像一位聰明的城堡總管 (Spring IoC Container, 控制反轉容器)。
這就是 控制反轉 (Inversion of Control, IoC) 與 相依性注入 (Dependency Injection, DI) 的精神。
但問題來了,總管(Spring)要怎麼知道城堡裡哪些人是需要被管理的「專業人士」呢?這就需要我們為他們貼上名牌。
@Component
:最通用的「專業人士」名牌@Component
就是 Spring 的「通用名牌」。
當我們在類別上加上 @Component
,就是告訴 Spring:
「嘿,這個類別很重要,把它登錄到人才庫 (IoC 容器) 裡!」
被 Spring 管理的物件,我們稱之為 Bean。
@Component
的核心作用
@Component
標記的類別。範例:訊息服務
假設我們有一個「訊息服務」MessageService
,它的功能是提供問候語。
MessageService
貼上名牌
package com.example.demo.service;
import org.springframework.stereotype.Component;
@Component // <-- 就是這個!告訴 Spring 這是一個需要被管理的元件
public class MessageService {
public String getMessage() {
return "哈囉!歡迎來到 Spring Boot 的世界!";
}
}
現在,控制器(Controller) 中,我們需要這個 MessageService
。而不是自己 new
,我們透過 @Autowired
註解 (Annotation),請 Spring「注入」給我們。
package com.example.demo.controller;
import com.example.demo.service.MessageService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class MyController {
private final MessageService messageService;
// 使用建構子注入 (推薦作法)
@Autowired // <-- 告訴 Spring:請自動從容器中找到 MessageService 的 Bean 並注入進來
public MyController(MessageService messageService) {
this.messageService = messageService;
}
@GetMapping("/")
public String home() {
// 直接使用,完全不需要關心它是如何被建立的
return messageService.getMessage();
}
}
整個過程,MyController
完全不關心 MessageService
的實例化細節,大大降低了元件之間的耦合度,讓程式碼更清晰、更易於測試與維護。
為什麼推薦建構子注入?
NullPointerException
風險。註:
在 Spring Boot 3 中,使用建構子注入時,已經並不需要添加@Autowired
註解 (Annotation)。這是因為從 Spring Framework 4.3 開始,如果一個類別帶有參數的建構子,Spring 會自動進行依賴注入,而無需顯式地使用@Autowired
註解 (Annotation)。然而,如果有多個建構子可用且沒有主要/預設建構子,則至少必須在其中一個建構子上加上@Autowired
註解 (Annotation),以指示容器使用哪一個。
因此程式碼調整後,如下:package com.example.demo.controller; import com.example.demo.service.MessageService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController; @RestController public class MyController { private final MessageService messageService; // 使用建構子注入 (推薦作法) public MyController(MessageService messageService) { this.messageService = messageService; } @GetMapping("/") public String home() { // 直接使用,完全不需要關心它是如何被建立的 return messageService.getMessage(); } }
@Component
的兄弟們:語意更清晰更具體的名牌雖然 @Component
很好用,如果所有角色都只掛 @Component
,我們很難知道他的職責。因此 Spring 提供了更具體的名牌,它們本質上都是 @Component
,但帶有更清晰的語意及更明確的名字。
這就是軟體開發中常見的三層式架構 (Three-Layer Architecture):
@Controller
/ @RestController
(表現層, Controller Layer): 就像「國王」,負責接收外部請求並做出回應。是使用者互動的第一線。@Service
(服務層, Service Layer): 就像「廚師」,負責處理核心的商業邏輯。例如處理訂單、計算價格等複雜流程。@Repository
(資料存取層, Repository Layer): 就像「工匠 / 圖書館員」,負責跟資料庫打交道,進行資料的增刪改查 (CRUD)。範例:將 MessageService
改成 @Service
import org.springframework.stereotype.Service;
@Service // <-- 使用語意更清晰的名牌,功能完全相同,但意圖更明確
public class MessageService {
public String getMessage() {
return "哈囉!這裡是服務層!";
}
}
為什麼要分這麼細?
@Service
,你立刻就能明白它的職責是處理業務邏輯。這讓團隊合作與後續維護變得更加輕鬆。@Repository
就附加了將底層資料庫異常轉換為 Spring 標準異常的功能,讓例外處理更統一。Spring 是如何找到這些角色的?
答案是 @ComponentScan
。
@SpringBootApplication
啟動時,@ComponentScan
會自動在所在套件下,往下掃描所有被 @Component
、@Service
、@Repository
、@Controller
等註解 (Annotation) 標記的類別,然後將它們註冊為 Bean。範例:
@SpringBootApplication // 內建 @ComponentScan
public class DemoApplication {
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
}
⚠️ 注意:如果主程式放在錯誤的 package
,可能導致掃不到元件。
註:
預設情況下,Spring Boot 會從主程式所在的套件 (package) 開始,向下掃描其所有的子套件。這就是為什麼我們通常會將所有的程式碼都放在主程式所在套件的子套件中。
應用程式啟動
↓
@ComponentScan 掃描類別
↓
找到 @Component/@Service/@Repository/@Controller
↓
建立 Bean,存放進 IoC 容器
↓
需要時 @Autowired 注入到對應的類別
↓
執行 @PostConstruct 初始化方法
↓
應用程式運行中...
↓
應用程式關閉時執行 @PreDestroy 方法