iT邦幫忙

2025 iThome 鐵人賽

DAY 12
0
Software Development

spring boot 3 學習筆記系列 第 12

Day12 -Spring Boot 最常見的 Bean - 自動化元件掃描

  • 分享至 

  • xImage
  •  

前言:樂高城堡的故事

使用 @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();
    }
}

問題

  1. 國王和廚師緊密綁在一起 => 高耦合 (High Coupling)。
  2. 想換一個廚師就得修改程式碼 => 不易維護。
  3. 城堡裡角色一多,人人自己 new => 大混亂。

這時候,Spring Boot 就像一位聰明的城堡總管 (Spring IoC Container, 控制反轉容器)

  • 它會幫每個重要角色掛上名牌(Annotation)。
  • 所有人才都登錄在一個「人才庫」裡(IoC Container)。
  • 當國王需要廚師時,只要跟總管說一聲,總管就會派一個廚師過來。

這就是 控制反轉 (Inversion of Control, IoC)相依性注入 (Dependency Injection, DI) 的精神。

但問題來了,總管(Spring)要怎麼知道城堡裡哪些人是需要被管理的「專業人士」呢?這就需要我們為他們貼上名牌

@Component:最通用的「專業人士」名牌

@Component 就是 Spring 的「通用名牌」。

當我們在類別上加上 @Component,就是告訴 Spring:

「嘿,這個類別很重要,把它登錄到人才庫 (IoC 容器) 裡!」

被 Spring 管理的物件,我們稱之為 Bean

@Component 的核心作用

  1. 標記:將類別標記為 Spring 元件 (Component)。
  2. 掃描:Spring Boot 執行時,會自動掃描到所有被 @Component 標記的類別。
  3. 實例化:Spring 會為這些類別建立物件實例 (Instance),並將這些實例作為 Bean 存放在 IoC 容器中統一管理。

範例:訊息服務

假設我們有一個「訊息服務」MessageService,它的功能是提供問候語。

  1. MessageService 貼上名牌
package com.example.demo.service;

import org.springframework.stereotype.Component;

@Component // <-- 就是這個!告訴 Spring 這是一個需要被管理的元件
public class MessageService {

    public String getMessage() {
        return "哈囉!歡迎來到 Spring Boot 的世界!";
    }
}
  1. 在需要的地方,請總管指派

現在,控制器(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 風險。
  • 容易寫單元測試(可用假物件 Mock 傳入)。
  • 避免屬性注入 (Field Injection) 的隱藏相依性問題。
  • Spring 團隊官方推薦的最佳實踐。

註:
在 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 "哈囉!這裡是服務層!";
    }
}

為什麼要分這麼細?

  1. 程式碼可讀性:當你看到一個類別被標記為 @Service,你立刻就能明白它的職責是處理業務邏輯。這讓團隊合作與後續維護變得更加輕鬆。
  2. 特定功能:某些特定註解 (Annotation) 附帶了額外的功能。例如,@Repository 就附加了將底層資料庫異常轉換為 Spring 標準異常的功能,讓例外處理更統一。

@ComponentScan:總管的巡邏機制

Spring 是如何找到這些角色的?

答案是 @ComponentScan

  • 它就像「巡邏機制」,會掃描指定的套件 (Package)。
  • 預設由 @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) 開始,向下掃描其所有的子套件。這就是為什麼我們通常會將所有的程式碼都放在主程式所在套件的子套件中。

Spring IoC 容器工作流程圖

應用程式啟動
       ↓
@ComponentScan 掃描類別
       ↓
找到 @Component/@Service/@Repository/@Controller
       ↓
建立 Bean,存放進 IoC 容器
       ↓
需要時 @Autowired 注入到對應的類別
       ↓
執行 @PostConstruct 初始化方法
       ↓
應用程式運行中...
       ↓
應用程式關閉時執行 @PreDestroy 方法

相關資料來源


上一篇
Day11 - Spring Boot Annotation 簡介
下一篇
Day13 - Spring Boot 另一種 Bean - 手動 Java 配置
系列文
spring boot 3 學習筆記16
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言