今天要深入探討 Spring 框架中一個非常核心的概念:Spring 如何管理它所建立的物件 (我們稱之為 Bean)。具體來說,我們會學習兩件事:
當我們請 Spring 幫我們管理一個類別的實例 (Instance) 時,我們需要告訴 Spring:「這個實例應該要有多少個?以及它應該在什麼範圍內共享?」這就是「作用域」的用途。
singleton
(單例模式) - (預設)這是 Spring 最常用、也是預設的作用域。它告訴 Spring:「在整個應用程式從啟動到關閉的過程中,這個類別只會有一個實例。」
Service
層、Repository
層、工具類、設定檔物件等。這些物件只提供方法,本身不儲存與特定使用者相關的資料。@Service
// @Scope("singleton") // 是預設值,所以可以省略
public class UserService {
// 這個 UserService 在整個應用程式中只會有一個實例
}
prototype
(原型模式)這個作用域告訴 Spring:「不要幫我管理共用實例!每次有人需要這個 Bean 的時候,都請給我一個全新的、獨立的實例。」
prototype
作用域的 Bean,Spring 在建立並交給你之後,就不再追蹤它的銷毀。你需要自行管理它的生命週期。@Component
@Scope("prototype")
public class ShoppingCart {
// 每次注入 ShoppingCart,都會得到一個全新的購物車實例
}
在開發網站時,還有一些與使用者互動相關的作用域,這裡我們簡要介紹:
request
:每個 HTTP 請求 (Request) 都會建立一個全新的 Bean 實例。請求結束後,實例就會被銷毀。適合用來存放單次請求的資料。session
:每個使用者會話 (HTTP Session) 會有一個獨立的 Bean 實例。使用者登入後開始,登出或 Session 過期後銷毀。適合存放使用者登入資訊等。application
:整個 Web 應用程式 (ServletContext) 的生命週期中只有一個實例。與 singleton
非常相似,但在 Web 環境中有其特定意涵。有時候,我們希望在 Bean 被 Spring 建立完成後,或在它即將被銷毀前,執行一些特定的初始化或清理工作。這就是生命週期回呼的用途。
這是目前業界的主流與最佳實踐,因為它讓我們的業務程式碼非常乾淨,不需要依賴任何 Spring 特定的介面,降低了程式碼與框架之間的耦合 (Coupling)。
@PostConstruct
(建構後):此註解標記的方法,會在 Bean 的建構子執行完畢、且所有依賴注入 (Dependency Injection, DI) 都完成之後被 Spring 自動呼叫。非常適合執行資源載入、初始化設定等任務。@PreDestroy
(銷毀前):此註解標記的方法,會在 Spring 容器準備銷毀這個 Bean 之前被呼叫。適合用來執行資源釋放、連線關閉等清理工作。(注意:此回呼對 prototype
作用域的 Bean 不會生效!)範例:一個設定檔載入器
假設我們有一個元件,它需要在啟動時讀取一個設定檔,並在程式關閉前釋放資源。
import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import org.springframework.stereotype.Component;
@Component
public class ConfigLoader {
public ConfigLoader() {
System.out.println("1. 執行建構子 (Constructor)...");
}
@PostConstruct
public void init() {
// 這個方法會在所有依賴注入完成後執行
System.out.println("2. Bean 已初始化完成,執行 @PostConstruct 方法:開始載入設定檔...");
// 在這裡可以加入讀取檔案、建立連線等邏輯
}
public void useConfig() {
System.out.println("3. Bean 正在使用中...");
}
@PreDestroy
public void cleanup() {
// 這個方法會在應用程式關閉、Bean 被銷毀前執行
System.out.println("4. Bean 即將被銷毀,執行 @PreDestroy 方法:開始釋放資源...");
// 在這裡可以加入關閉檔案、中斷連線等邏輯
}
}