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í)例。