Spring Boot 02:Bean 是誰建立的?看懂注入與自動設定
TicketController 沒有寫 new TicketService,卻可以呼叫 service.find。這個物件不是 Java 自己變出來的;Spring 在建立 Controller 時,把它需要的相依物件交給建構子。先找到這條建立關係,才知道 annotation 在替你處理什麼。
Bean 是受容器管理的物件
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 決定套用條件 | 不是讀懂業務後替你做決策 |
本例的 TicketStore 是掃描找到的;JdbcClient 與 DataSource 則來自套件及設定滿足條件後的組裝。你寫自己的 Bean 時,部分自動設定會退讓,實際條件要回查對應文件或報告。不要把所有功能都說成「Boot 預設會幫我處理」,尤其是換成 EAP 管理資源時。
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 與覆寫