2020-11-02-Spring單例 vs. 單例模式

Spring 單例不是 Java 單例。本文討論 Spring 的單例與單例模式的區(qū)別。

前言

單例是 Spring 當(dāng)中 bean 的默認(rèn)范圍(Scope)。Spring 容器會(huì)為某個(gè) bean 定義對(duì)象創(chuàng)建唯一的實(shí)例,很多時(shí)候我們會(huì)將這種設(shè)計(jì)跟《設(shè)計(jì)模式》(GoF) 書中定義的單例模式作比較。

1. 單例范圍 vs 單例模式

Spring 當(dāng)中的單例范圍跟單例模式不是同一個(gè)東西。其中的兩點(diǎn)差異如下:

  • 單例模式確保某個(gè)類加載器的某個(gè)類只有一個(gè)實(shí)例
  • 而 Spring 單例范圍是每個(gè)容器的每個(gè)bean

1.1 單例范圍的例子

Spring 的單例實(shí)例會(huì)被放在緩存中,下次再訪問(wèn)那個(gè)命名的 bean 的時(shí)候就會(huì)從緩存里面取。下邊看看例子。

public class Account {

    private String name;

    public Account() {
    }

    public Account(String name) {
        this.name = name;
    }

    public String getName() {
        return name;
    }

    @Override
    public String toString() {
        return "Account{" +
                "name='" + name + '\'' +
                '}';
    }
}

Spring Boot 的 main 方法:

@SpringBootApplication
@SpringBootApplication
public class SpringSingletonApp {

    public static void main(String[] args) {
        SpringApplication.run(SpringSingletonApp.class, args);
    }

    @Bean(name = "bean1")
    public Account account() {
        return new Account("Test User 1");
    }

    @Bean(name = "bean2")
    public Account account1() {
        return new Account("Test User 2");
    }
}

理解上面的代碼:

  • 我們創(chuàng)建了同一個(gè)類的 2 個(gè)實(shí)例,并有不同的 bean id。

那么上面代碼中 Spring 的 IoC 容器創(chuàng)建了多少個(gè)實(shí)例?

  • 2 個(gè)不同的實(shí)例,在容器中分別綁定到它們的 id?
  • 還是 1 個(gè)實(shí)例綁定到 2 個(gè) bean id?

1.2 測(cè)試用例

我們使用單元測(cè)試找出答案。

@SpringBootTest
class SpringSingletonAppTests {

    private static final Logger log = LoggerFactory.getLogger(SpringSingletonAppTests.class);

    @Resource(name = "bean1")
    Account account1;

    @Resource(name = "bean1")
    Account duplicateAccount;

    @Resource(name = "bean2")
    Account account2;

    @Test
    public void testSingletonScope() {
        log.info(account1.getName());
        log.info(account2.getName());

        log.info("Accounts are equal -> {}", account1 == account2);
        log.info("Duplicate account  -> {}", account1 == duplicateAccount);
    }
}

輸出為:

20:06:31.165 [main] INFO  i.z.s.SpringSingletonAppTests - Test User 1
20:06:31.165 [main] INFO  i.z.s.SpringSingletonAppTests - Test User 2
20:06:31.165 [main] INFO  i.z.s.SpringSingletonAppTests - Accounts are equal -> false
20:06:31.167 [main] INFO  i.z.s.SpringSingletonAppTests - Duplicate account  -> true

從上面的輸出我們發(fā)現(xiàn):
Spring 返回了兩個(gè)不同的實(shí)例,單例范圍的同一個(gè)類可以有多于一個(gè)的對(duì)象實(shí)例。

對(duì)于某個(gè) bean id,Spring 容器僅維護(hù)唯一的共享單例 bean,在我們上面的例子中,Spring IoC 容器基于同一個(gè)類的 bean 定義創(chuàng)建了兩個(gè)實(shí)例,并將它們綁定到對(duì)應(yīng)的 id。
Spring 的 bean 定義就像鍵值對(duì)那樣,bean id 就是 key,bean 的實(shí)例就是 value。每個(gè) key 引用都會(huì)返回同一個(gè) bean 實(shí)例(例如 bean1 引用始終返回 id 為 bean1 的 bean)

總結(jié)

Spring 單例跟傳統(tǒng)的單例模式是不同的。Spring 確保在每個(gè)容器對(duì)給定 bean id 定義只創(chuàng)建一個(gè) bean 實(shí)例。 傳統(tǒng)單例模式是保證給定一個(gè)類加載器所加載的某個(gè)類只有唯一的一個(gè)實(shí)例。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

友情鏈接更多精彩內(nèi)容