知識庫 / Spring / Spring Boot RSS 訂閱

Spring Boot 中單例設計模式與單例 Bean 的對比

Spring Boot
HongKong
4
11:42 AM · Dec 06 ,2025

1. 概述

單例對象通常被開發者用於創建單個實例,該實例可以被應用程序中的許多對象重用。在 Spring 中,我們可以通過利用 Spring 的單例 Bean 或自行實現單例設計模式來創建它們。

在本教程中,我們首先將研究單例設計模式及其線程安全的實現。然後,我們將研究 Spring 中的單例 Bean 作用域,並比較使用單例 Bean 創建的對象與使用單例設計模式創建的對象。

最後,我們將探討一些最佳實踐。

2. 單例設計模式

單例設計模式是 1994 年由“四體不捨”小組(Gang of Four)發佈的最簡單的設計模式之一。它屬於創建型模式,單例提供了一種創建只有一個實例的對象的方式

2.1 設計模式:單例模式

單例模式涉及一個負責創建對象並確保只有一個實例被創建的類。我們經常使用單例模式來共享狀態或避免設置多個對象的成本。

單例模式的實現通過以下方式確保只有一個實例創建:

  • 隱藏所有構造函數,通過實現一個私有的構造函數來完成。
  • 僅在實例不存在時創建實例,並將其存儲在私有的靜態變量中。
  • 使用公共的靜態 getter 方法提供對該實例的簡單訪問。

以下是一個使用單例對象的一些類的示例:

在上面的類圖中,我們可以看到多個服務如何使用創建的唯一單例實例。

2.2. 延遲初始化

單例模式的實現通常使用延遲初始化,以推遲實例創建,直到第一次實際需要時。為了確保延遲實例化,可以在首次調用靜態獲取器時創建實例:

public final class ThreadSafeSingleInstance {

    private static volatile ThreadSafeSingleInstance instance = null;

    private ThreadSafeSingleInstance() {}

    public static ThreadSafeSingleInstance getInstance() {
        if (instance == null) {
            synchronized(ThreadSafeSingleInstance.class) {
                if (instance == null) {
                    instance = new ThreadSafeSingleInstance();
                }
            }
        }
        return instance;
    }

    //standard getters

}

在多線程應用程序中,懶加載可能導致競爭條件。因此,我們還採用了雙重檢查鎖定,以防止不同線程創建多個實例。

3. Spring 中的單例 Bean

在 Spring 框架中,一個 Bean 是在 Spring IoC 容器中 創建、管理和銷燬的對象

3.1. Bean 作用域

使用 Spring Bean 時,我們可以通過反轉控制 (IoC) 將對象注入到 Spring 容器中。 實際上,一個 對象可以定義其依賴項,而無需創建它們,並將這項工作委託給 IoC 容器

以下是 Spring 框架最新版本中定義的六種作用域類型:

  • singleton
  • prototype
  • request
  • session
  • application
  • websocket

Bean 的作用域定義了其生命週期和可見性。 它還確定了 Bean 的實際實例將如何創建。 例如,我們可能希望創建一個全局唯一實例,或者每次請求 Bean 時創建一個不同的實例。

3.2. 單例 Bean

我們可以使用 Spring 中位於配置類中的 <em @Bean</em> 註解來聲明 Bean。Spring 的單例作用域創建 容器中每個 Bean 標識符對應一個 Bean

@Configuration
public class SingletonBeanConfig {

    @Bean
    @Scope(value = ConfigurableBeanFactory.SCOPE_SINGLETON)
    public SingletonBean singletonBean() {
        return new SingletonBean();
    }

}

單例是 Spring 中所有定義的 Bean 的默認作用域。因此,即使我們沒有使用 @Scope 註解指定特定的作用域,我們仍然會得到一個單例 Bean。該作用域僅用於説明目的,通常用於表達其他可用的作用域。

3.3. Bean 標識符

與純 Singleton 設計模式不同,我們可以從同一類中創建多個 Singleton Bean

@Bean
@Scope(value = ConfigurableBeanFactory.SCOPE_SINGLETON)
public SingletonBean singletonBean() {
    return new SingletonBean();
}

@Bean
@Scope(value = ConfigurableBeanFactory.SCOPE_SINGLETON)
public SingletonBean anotherSingletonBean() {
    return new SingletonBean();
}

所有具有匹配標識符的 Bean 請求都將導致框架返回一個特定的 Bean 實例。當我們使用 @Bean 註解的方法時,Spring 將使用方法名作為 Bean 標識符。

在注入 Bean 時,如果容器中存在同類型的多個 Bean,框架會拋出 NoUniqueBeanDefinitionException 異常:

@Autowired
private SingletonBeanConfig.SingletonBean bean; //throws exception

在這種情況下,我們可以利用 @Qualifier 註解來指定正確的 Bean 標識符,以便進行注入:

@Autowired
@Qualifier("singletonBean")
private SingletonBeanConfig.SingletonBean beanOne;

@Autowired
@Qualifier("anotherSingletonBean")
private SingletonBeanConfig.SingletonBean beanThree;

或者,另一種標註 – @Primary – 也可以用於在存在多個相同類型的 Bean 時定義主 Bean。

4. 比較

現在,讓我們比較這兩種方法,並確定在 Spring 中遵循的最佳實踐。

4.1. 單例模式(反模式)

某些人認為單例模式是一種反模式,因為它引入了應用程序級別的全局狀態。任何其他對象都直接依賴於該單例。這導致類和模塊之間不必要的依賴關係。

單例模式也違反了單一職責原則。單例對象同時負責至少兩件事:

  • 確保只創建單個實例
  • 執行其正常的操作

此外,在多線程環境中,單例對象需要特殊處理,以確保不同的線程不會創建多個實例。它們也可能使單元測試和模擬更困難。因為許多模擬框架依賴於繼承,私有構造函數使得單例對象難以模擬。

4.2. 推薦方法

使用 Spring 的單例 Bean 而不是實現單例設計模式,可以消除上述許多缺點。

Spring 框架會將 Bean 注入到所有使用它的類中,但保留了替換或擴展它的靈活性。 框架通過對 Bean 生命週期進行控制來實現這一點,因此可以無需修改任何代碼,稍後將其替換為另一種方法。

此外,Spring Bean 使單元測試更加簡單。 Spring Bean 易於 Mock,並且框架可以將它們注入到測試類中。 我們可以選擇注入實際的 Bean 實現或它們的 Mock。

需要注意的是,單例 Bean 不僅會創建一個類的單個實例,還會創建一個 Bean 標識符在容器中的單個 Bean。

5. 結論

在本文中,我們探討了如何在 Spring 框架中創建單例實例。我們研究了實現單例設計模式,以及利用 Spring 的單例 Bean。

我們研究了使用延遲加載和線程安全來實施單例模式。然後,我們調查了 Spring 中的單例 Bean 作用域,並探討了如何實現和注入單例 Bean。我們還看到了單例 Bean 如何與使用 singleton 設計模式創建的對象區分開來。

最後,我們研究了在 Spring 中使用單例 Bean 如何消除傳統實現中的一些缺點。

user avatar
0 位用戶收藏了這個故事!
收藏

發佈 評論

Some HTML is okay.