淺析Spring中bean的作用域


一、前言

  剛剛花了點時間,閱讀了一下Spring官方文檔中,關於bean的作用域這一塊的內容。Spring-4.3.21官方文檔中,共介紹了七種bean作用域,這篇博客就來簡單介紹一下這七種作用域的含義。畢竟只是閱讀了一下文檔,沒有實際的使用經驗,所有對於這些作用域的理解比較淺顯,這篇博客就當是記筆記,或者也可以算是翻譯文檔中的內容了。


二、正文

2.1 Bean作用域的種類

  在Spring官方文檔中,共提到了7種不同的Bean作用域,分別是:

  • singleton(默認)
  • prototype
  • request
  • session
  • globalSession
  • application
  • websocket

  需要注意的是,前兩種是Springbean的基本作用域,而后五種,算是擴展的作用域,只能在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文件,則通過beanscope配置項:

<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應用中。我們可以使用@SessionScopebean指定為session作用域,也可以使用xml配置方式:

<bean id="userPreferences" class="com.foo.UserPreferences" scope="session"/>

2.6 globalSession作用域

  這個作用域就比較特殊了,globalSession作用域的效果與session作用域類似,但是只適用於基於portletweb應用程序中。Portlet規范定義了globalSession的概念,該概念在組成單個Portlet Web應用程序的所有Portlet之間共享(引用自Spring文檔)。說實話,在看到這里之前,我從來沒聽說過portlet。我現在所學的,基本上都是基於Servletweb應用程序,所有關於這個作用域,我也不理解。但是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作用域就是這么實現的,作用域為applicationbean,將會被作為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

  在我們的應用程序中,可能有這樣一種情況——一個作用域為singletonbean,有一個或多個作用域為prototypebean,此時將會發生什么問題。對於單例bean來說,屬性注入只會發生在創建這個bean的過程中,這也就意味着,單例bean只會經歷一次屬性注入。也就是說,盡管這個單例bean的屬性是多例的,但是由於只有一次注入,所以后續使用到的這個多例屬性,永遠都是同一個。此時多例就失去了意義。那該如何解決呢?

  方法主要有兩種,第一種比較容易想到,就是對於單例bean的多例屬性,我們不讓Spring容器幫我們自動注入,而是我們自己編寫一個工廠方法,在方法中通過getBean等方式,手動地向容器請求這個多例bean。由於bean是多例的,每一次getBean,實際上返回的都是一個新的實例對象。而在單例bean需要用到這個多例bean時,通過工廠方法獲取。但是這種方式比較麻煩,也不利於維護。

  第二種方式就比較簡單了,Spring提供了一種機制解決這個問題,那就是——方法注入。關於方法注入,可以參考我的這篇博客:Spring方法注入的使用與實現原理


三、總結

  以上就對Spring中,bean的作用域做了一個大致的介紹,至少我們知道了每一個作用域是什么,以及大致的功能,不會在被問到的時候,連是什么都不知道。以上內容是我直接參考Spring官方文檔所編寫,文檔中的內容也不是太詳細,有些描述也不是特別清晰,所以上面有些是我自己的理解,若存在不足或者錯誤,歡迎指正,共同進步。


四、參考


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM