Spring BootJava 21Spring Framework深入淺出

Spring Boot 02:Bean 是誰建立的?看懂注入與自動設定

··閱讀約 4 分鐘
回到 Spring Boot 系列導讀

TicketController 沒有寫 new TicketService,卻可以呼叫 service.find。這個物件不是 Java 自己變出來的;Spring 在建立 Controller 時,把它需要的相依物件交給建構子。先找到這條建立關係,才知道 annotation 在替你處理什麼。

Bean 是受容器管理的物件

java
public TicketController(TicketService service, LabProperties properties) {
    this.service = service;
    this.properties = properties;
}

public TicketService(TicketStore store, LabProperties properties) {
    this.store = store;
    this.properties = properties;
}

這兩段來自範例的 Controller 與 Service。建構子把必要依賴列出來,final 欄位保存它們。Spring 解析 TicketService 的依賴後,將可用的 Service 注入 Controller;不是固定照原始碼檔名順序逐一 new。單一建構子在這裡不需要再加 @Autowired。

你仍然可以用 new 建立普通物件。差別在於自行建立的那個 instance 沒有因為類別上寫 @Service,就自動經過 Spring 的注入、生命週期與代理處理。Ticket 是回傳資料,所以用 new Ticket 合理;TicketService 的交易行為依賴容器代理,繞過它就會改變驗證條件。

四個常混在一起的機制

機制在本例的工作不代表什麼
starter讓 Maven 加入相關依賴不是一個正在執行的 Bean
component scan找到 @RestController/@Service/@Repository不會掃到所有任意 package
@Bean用設定方法提供要管理的物件不必每個類別都用它註冊
auto-configuration依 classpath、設定與既有 Bean 決定套用條件不是讀懂業務後替你做決策
先問它處理的是 jar 還是物件。

本例的 TicketStore 是掃描找到的;JdbcClient 與 DataSource 則來自套件及設定滿足條件後的組裝。你寫自己的 Bean 時,部分自動設定會退讓,實際條件要回查對應文件或報告。不要把所有功能都說成「Boot 預設會幫我處理」,尤其是換成 EAP 管理資源時。

bash
java -jar target/boot-learning-lab-1.0.0.jar --debug

用 --debug 啟動可以檢查條件評估報告。先帶著問題找 DataSource 相關條目,確認為何符合或不符合。這份報告處理設定條件,不能證明資料庫帳號可用、SQL 正確,或你的 API 符合舊契約。

物件可共用,不表示每個欄位都適合放請求資料

Spring Bean 預設是容器內的 singleton。這個 Controller 可能同時處理不同請求,所以不應把目前使用者、當次 request 或交易中的工單放在會被覆寫的 instance 欄位。範例只保存穩定的 collaborator,個別工單是方法裡的區域變數。singleton 不等於全 JVM 只能有一個,更不自動保證 thread-safe。

動手:讓 Spring 找不到一個必要物件

在可還原的範例副本暫時移除 TicketStore 的 @Repository,執行 mvn package,觀察測試啟動 context 的錯誤。預期缺少 TicketStore,而不是 HTTP 404。讀完原因後恢復 annotation 並重新測試;不要用略過測試把錯誤藏起來。

答案與延伸:兩個同型別 Bean 呢?

如果必要依賴找不到,容器無法完成建立;如果有多個候選且沒有足夠的選擇資訊,也可能失敗。此時先確認為什麼有兩個,再使用 qualifier 或明確設定表達選擇。不要為了讓錯誤消失隨意加 @Primary。能說出「哪個物件需要哪個 collaborator」,比背 annotation 清單更有助於定位啟動問題。

下載本系列完整工單 API 範例(ZIP)下一課:03:設定、Profile 與覆寫

參考資料