一、前言
剛剛花了點時間,閱讀了一下Spring
官方文檔中,關於bean
的作用域這一塊的內容。Spring-4.3.21
官方文檔中,共介紹了七種bean
作用域,這篇博客就來簡單介紹一下這七種作用域的含義。畢竟只是閱讀了一下文檔,沒有實際的使用經驗,所有對於這些作用域的理解比較淺顯,這篇博客就當是記筆記,或者也可以算是翻譯文檔中的內容了。
二、正文
2.1 Bean作用域的種類
在Spring
官方文檔中,共提到了7
種不同的Bean
作用域,分別是:
- singleton(默認)
- prototype
- request
- session
- globalSession
- application
- websocket
需要注意的是,前兩種是Spring
中bean
的基本作用域,而后五種,算是擴展的作用域,只能在web
應用中使用。下面我就來分別介紹一下這7
種不同的作用域。
2.2 singleton作用域
singleton(單例)是Spring中,bean默認的作用域。若一個bean
的作用域是單例的,那么每個IoC
容器只會創建這個bean
的一個實例對象。所有對這個bean
的依賴,以及獲取這個bean
的代碼,拿到的都是同一個bean
實例。Spring
容器在創建這個bean
后,會將它緩存在容器中(實際上是放在一個ConcurrentHashMap
中)。Spring中的bean不是線程安全的,所以只有在我們只關注bean能夠提供的功能,而不在意它的狀態(屬性)時,才應該使用這個作用域。下面引用一張圖來看看單例bean
:
需要注意的一點是,這里所說的單例,和設計模式中所提到的單例模式不同。設計模式中的單例,是強制一個類有且只有一個對象,我們如果不通過特殊的手段,將無法為這個單例類創建多個對象。而Spring
中的單例作用域不同,這里的單例指的是在一個Spring
容器中,只會緩存bean
的唯一對象,所有通過容器獲取這個bean
的方式,最終拿到的都是同一個對象。但是在不同的Spring
容器中,每一個Spring
容器都可以擁有單例bean
的一個實例對象,也就是說,這里的單例限定在一個Spring
容器中,而不是整個應用程序。並且我們依然可以通過new
的方式去自己創建bean
。
2.3 prototype作用域
prototype
可以理解為多例。若一個bean
的作用域是prototype
,那么Spring
容器並不會緩存創建的bean
,程序中對這個bean
的每一次獲取,容器都會重新實例化一個bean
對象。通常,如果我們需要使用bean的狀態(屬性),且這個狀態是會改變的,那么我們就可以將它配置為這個作用域,以解決線程安全的問題。因為對於單例bean
來說,多個線程共享它的可變屬性,會存在線程安全問題。下面引用一張圖來描述這個作用域:
前面也提過,如果bean
的作用域是prototype
的,那么容器在創建完這個bean
后,並不會將它保存在容器中,這也就意味着,Spring
容器並不能為我們做這個對象的銷毀工作(比如資源釋放)。此時我們可以通過Spring
提供的接口,自定義一個后處理器,然后將這些bean
的引用存儲在這個后處理器中,當容器回調這個后處理器的方法時,我們可以在方法中通過提前存儲的bean
的引用,將它們銷毀。
2.4 request作用域
request
作用域將bean
的使用范圍限定在一個http
請求中,對於每一個請求,都會單獨創建一個bean
,若請求結束,bean
也會隨之銷毀。使用request
作用域一般不會存在線程安全問題,因為在Web
應用中,每個請求都是由一個單獨的線程進行處理,所有線程之間並不會共享bean
,從而不會存在線程安全的問題。
這個作用域只能使用在Web
應用中。如果使用的是注解掃描配置bean
,那么在bean
所屬的類上使用@RequestScope
注解即可使用此作用域,若是基於xml
文件,則通過bean
的scope
配置項:
<bean id="userPreferences" class="com.foo.UserPreferences" scope="request"/>
2.5 session作用域
session
作用域將bean
的使用范圍一次在一次http會話
中,對於每一個會話,Spring
容器都會創建一個單獨的bean
,若session
被銷毀,則bean
也隨之銷毀。我們可以修改bean
的狀態,這個修改只對當前會話可見,但是是否線程安全呢?Spring
文檔中並未提及,但我認為不是線程安全的,因為每一個session
可以對應於多個request
,這些請求不一定就是串行執行的,比如說用戶打開多個界面,同時進行多次操作,那后台將同時處理同一個session
的多個request
,此時並不能保證bean
的線程安全。
與request
作用域一樣,session
作用域只能使用在Web
應用中。我們可以使用@SessionScope
將bean
指定為session
作用域,也可以使用xml
配置方式:
<bean id="userPreferences" class="com.foo.UserPreferences" scope="session"/>
2.6 globalSession作用域
這個作用域就比較特殊了,globalSession
作用域的效果與session
作用域類似,但是只適用於基於portlet
的web
應用程序中。Portlet
規范定義了globalSession
的概念,該概念在組成單個Portlet Web
應用程序的所有Portlet
之間共享(引用自Spring
文檔)。說實話,在看到這里之前,我從來沒聽說過portlet
。我現在所學的,基本上都是基於Servlet
的web
應用程序,所有關於這個作用域,我也不理解。但是Spring
文檔中有提到一點,那就是如果我們在基於Servlet的web應用程序中使用globalSession作用域,實際上容器使用session作用域進行處理。
這個作用域也只在web
應用中有效,上面也提過,具體是在基於portlet
的應用中有效。文檔中只提出了一種使用方式,就是基於xml
:
<bean id="userPreferences" class="com.foo.UserPreferences" scope="globalSession"/>
2.7 application作用域
學過Servlet
的應該對application
作用域有所了解,在Servlet
程序中,有一個全局的ServletContext
對象,這個對象被整個web
應用所共享,我們可以通過setAttribute
方法向其中添加全局共享的數據。而Spring
中,application
作用域就是這么實現的,作用域為application
的bean
,將會被作為ServletContext
的屬性,存儲在其中,然后可以被全局訪問,而且一個ServletContext
只會存儲這個bean
的一個實例對象。ServletContext
被銷毀,這個bean
自然也跟着被銷毀。我們發現,這好像有點類似於singleton
這個作用域,確實非常類似,但是也有一些區別。單例bean
是一個Spring
只會創建一個,而這里的卻是每個ServletContext
包含一個,不論有多少Spring
容器,bean
的數量只取決於ServletContext
,而且單例bean
只能通過容器去獲取,是隱式的,而這種作用域的bean
卻是公開的,存儲在ServletContext
中,可直接通過ServletContext
獲取。
application
作用域也只能用於web
應用中。使用方式和之前幾種類似,可以通過@ApplicationScope
注解,也可以使用xml
配置文件:
<bean id="appPreferences" class="com.foo.AppPreferences" scope="application"/>
2.8 websocket
websocket
是一種應用層的通信協議,它提供應用層的全雙工通信,關於websocket
協議,可以參考我的這篇博客:計算機網絡——簡單說說WebSocket協議。而Spring
提供對websocket
協議的支持,於是就有了這么一個作用域。在我看的這個Spring
官方文檔中,並沒有對這個作用域進行詳細描述,但是我們也可以通過名稱推斷出來。若一個bean
的作用域為websocket
,則只作用於一次websocket
通信,若連接被釋放,則bean
自然也會被銷毀。
2.9 單例bean依賴於多例bean
在我們的應用程序中,可能有這樣一種情況——一個作用域為singleton
的bean
,有一個或多個作用域為prototype
的bean
,此時將會發生什么問題。對於單例bean
來說,屬性注入只會發生在創建這個bean
的過程中,這也就意味着,單例bean
只會經歷一次屬性注入。也就是說,盡管這個單例bean
的屬性是多例的,但是由於只有一次注入,所以后續使用到的這個多例屬性,永遠都是同一個。此時多例就失去了意義。那該如何解決呢?
方法主要有兩種,第一種比較容易想到,就是對於單例bean
的多例屬性,我們不讓Spring
容器幫我們自動注入,而是我們自己編寫一個工廠方法,在方法中通過getBean
等方式,手動地向容器請求這個多例bean
。由於bean
是多例的,每一次getBean
,實際上返回的都是一個新的實例對象。而在單例bean
需要用到這個多例bean
時,通過工廠方法獲取。但是這種方式比較麻煩,也不利於維護。
第二種方式就比較簡單了,Spring
提供了一種機制解決這個問題,那就是——方法注入。關於方法注入,可以參考我的這篇博客:Spring方法注入的使用與實現原理
三、總結
以上就對Spring
中,bean
的作用域做了一個大致的介紹,至少我們知道了每一個作用域是什么,以及大致的功能,不會在被問到的時候,連是什么都不知道。以上內容是我直接參考Spring
官方文檔所編寫,文檔中的內容也不是太詳細,有些描述也不是特別清晰,所以上面有些是我自己的理解,若存在不足或者錯誤,歡迎指正,共同進步。