在 Spring 中早抠,那些組成應用程序的主體,以及由 Spring IOC 容器所管理的對象募寨,被稱之為 bean。簡單地講森缠,bean 就是由 IOC 容器初始化拔鹰、裝配及管理的對象,除此之外贵涵,bean 就與應用程序中的其他對象沒有什么區(qū)別了列肢,而 bean 的定義以及 bean 相互間的依賴關(guān)系,將通過配置元數(shù)據(jù)來描述宾茂。
Spring 中的 bean 默認都是單例的瓷马,在多線程程序下,這些單例 Bean 是如何保證線程安全的呢跨晴?
例如對于 Web 應用來說欧聘,Web 容器對于每個用戶的請求,都會創(chuàng)建一個單獨的 Sevlet 線程來處理請求端盆,引入 Spring 框架后怀骤,每個 Action 都是單例的,那么對于 Spring 托管的單例 Service Bean焕妙,如何保證其安全呢蒋伦?
答案是:Spring 的單例是基于 BeanFactory,也就是 Spring 容器的焚鹊,單例 Bean 在此容器內(nèi)只有一個痕届,Java 的單例是基于 JVM,每個 JVM 內(nèi)只有一個實例。
在大多數(shù)情況下研叫。單例 bean 是很理想的方案锤窑。不過,有時候系統(tǒng)所使用的類是易變的蓝撇,它們會保持一些狀態(tài)果复,因此重用是不安全的。在這種情況下渤昌,將 class 聲明為單例就顯得不是那么明智了虽抄。因為對象會被污染,重用的時候可能會出現(xiàn)意想不到的問題独柑,所以 Spring 定義了支持多種作用域的 bean迈窟。
bean 的作用域
創(chuàng)建一個 bean 定義,其實質(zhì)是用該 bean 定義對應的類來創(chuàng)建真正實例的 “配方”忌栅。把 bean 定義看成是配方很有意義车酣,它與 class 很類似,只根據(jù)一張 “處方” 就可以創(chuàng)建多個實例索绪,不僅可以控制注入到對象中的各種依賴和配置值湖员,還可以控制該對象的作用域。這樣可以靈活選擇所建對象的作用域瑞驱,而不必在 Java Class 級定義作用域娘摔。如下表所示,Spring Framework 支持五種作用域唤反。
以上五種作用域中凳寺,request、session 和 global session 三種作用域彤侍,僅能用在基于 web 的 Spring ApplicationContext 環(huán)境肠缨。
singleton:唯一 bean 實例
當一個 bean 的作用域為 singleton 時,那 Spring IoC 容器中只會存在一個共享的 bean 實例盏阶,并且所有對 bean 的請求晒奕,只要 id 與該 bean 定義相匹配,則只會返回 bean 的同一實例般哼。 singleton 是單例類型 (對應于單例模式)吴汪,就是在創(chuàng)建容器時就同時創(chuàng)建一個該 bean 的對象,不管是否使用蒸眠,不過可以指定 Bean 節(jié)點的 lazy-init="true"
來延遲初始化 bean,這時候杆融,只有在第一次獲取 bean 時才會初始化 bean楞卡,以后每次獲取到的對象都是同一個對象。注意,singleton 作用域是 Spring 中的缺省作用域蒋腮。如果要顯示在 XML 中將 bean 定義成 singleton 淘捡,可以這樣配置:
<bean id="ServiceImpl" class="cn.mariojd.service.ServiceImpl" scope="singleton">
也可以通過 @Scope
注解(還可以顯示指定 bean 的作用范圍)的方式:
@Service
@Scope("singleton")
public class ServiceImpl {
}
prototype:每次請求都會創(chuàng)建一個新的 bean 實例
當一個 bean 的作用域為 prototype,表示一個 bean 的定義對應多個對象實例池摧。prototype 作用域的 bean 會導致每次在對該 bean 請求(將其注入到另一個 bean 中焦除,或者以程序的方式調(diào)用容器的 getBean() 方法)時,都會創(chuàng)建一個新的 bean 實例作彤。prototype 是原型類型膘魄,它在我們創(chuàng)建容器的時候并沒有實例化,而是當我們獲取 bean 的時候才去創(chuàng)建一個對象竭讳,而且每次獲取到的對象都不是同一個對象创葡。
總之,對有狀態(tài)的 bean 應該使用 prototype 作用域绢慢,而對無狀態(tài)的 bean 則應該使用 singleton 作用域灿渴。在 XML 中將 bean 定義成 prototype ,可以這樣配置:
<bean id="account" class="cn.mariojd.DefaultAccount" scope="prototype"/>
# 或者
<bean id="account" class="cn.mariojd.DefaultAccount" singleton="false"/>
request:每次 HTTP 請求都會產(chǎn)生新的 bean胰舆,該 bean 僅在當前 HTTP request 內(nèi)有效
request 作用域只適用于 Web 程序骚露,每一次 HTTP 請求都會產(chǎn)生一個新的 bean,同時該 bean 僅在當前 HTTP request 內(nèi)有效缚窿,當請求結(jié)束后棘幸,該對象的生命周期即結(jié)束。在 XML 中將 bean 定義成 request 滨攻,可以這樣配置:
<bean id="loginAction" class=cn.mariojd.LoginAction" scope="request"/>
session:每次 HTTP 請求都會產(chǎn)生新的 bean够话,該 bean 僅在當前 HTTP session 內(nèi)有效
session 只適用于 Web 程序,session 作用域表示該針對每一次 HTTP 請求都會產(chǎn)生一個新的 bean光绕,同時該 bean 僅在當前 HTTP session 內(nèi)有效女嘲。當HTTP session 最終被廢棄的時候,在該 HTTP session 作用域內(nèi)的 bean 也會被銷毀掉诞帐。
<bean id="userPreferences" class="cn.mariojd.UserPreferences" scope="session"/>
globalSession:伴隨應用本身的全局作用域
global session 作用域類似于標準的 HTTP session 作用域欣尼,不過僅僅在基于 portlet 的 web 應用中才有意義。Portlet 規(guī)范定義了全局 Session 的概念停蕉,它被所有構(gòu)成某個 portlet web 應用的各種不同的 portlet 所共享愕鼓。在 global session 作用域中定義的 bean ,將被限定于全局 portlet Session 的生命周期范圍內(nèi)慧起。
<bean id="user" class="cn.mariojd.Preferences "scope="globalSession"/>