Spring Bean 作用域詳解

  • 2019 年 10 月 11 日
  • 筆記

在 Spring 中,那些組成應用程序的主體,以及由 Spring IOC 容器所管理的對象,被稱之為 bean。簡單地講,bean 就是由 IOC 容器初始化、裝配及管理的對象,除此之外,bean 就與應用程序中的其他對象沒有什麼區別了,而 bean 的定義以及 bean 相互間的依賴關係,將通過配置元數據來描述。

Spring 中的 bean 默認都是單例的,在多線程程序下,這些單例 Bean 是如何保證線程安全的呢?

例如對於 Web 應用來說,Web 容器對於每個用戶的請求,都會創建一個單獨的 Sevlet 線程來處理請求,引入 Spring 框架後,每個 Action 都是單例的,那麼對於 Spring 託管的單例 Service Bean,如何保證其安全呢?

答案是:Spring 的單例是基於 BeanFactory,也就是 Spring 容器的,單例 Bean 在此容器內只有一個,Java 的單例是基於 JVM,每個 JVM 內只有一個實例。

在大多數情況下。單例 bean 是很理想的方案。不過,有時候系統所使用的類是易變的,它們會保持一些狀態,因此重用是不安全的。在這種情況下,將 class 聲明為單例就顯得不是那麼明智了。因為對象會被污染,重用的時候可能會出現意想不到的問題,所以 Spring 定義了支持多種作用域的 bean。

bean 的作用域

創建一個 bean 定義,其實質是用該 bean 定義對應的類來創建真正實例的 「配方」。把 bean 定義看成是配方很有意義,它與 class 很類似,只根據一張 「處方」 就可以創建多個實例,不僅可以控制注入到對象中的各種依賴和配置值,還可以控制該對象的作用域。這樣可以靈活選擇所建對象的作用域,而不必在 Java Class 級定義作用域。如下表所示,Spring Framework 支持五種作用域。

Spring 五種作用域

以上五種作用域中,request、sessionglobal session 三種作用域,僅能用在基於 web 的 Spring ApplicationContext 環境。

singleton:唯一 bean 實例

當一個 bean 的作用域為 singleton 時,那 Spring IoC 容器中只會存在一個共享的 bean 實例,並且所有對 bean 的請求,只要 id 與該 bean 定義相匹配,則只會返回 bean 的同一實例。 singleton 是單例類型 (對應於單例模式),就是在創建容器時就同時創建一個該 bean 的對象,不管是否使用,不過可以指定 Bean 節點的 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:每次請求都會創建一個新的 bean 實例

當一個 bean 的作用域為 prototype,表示一個 bean 的定義對應多個對象實例。prototype 作用域的 bean 會導致每次在對該 bean 請求(將其注入到另一個 bean 中,或者以程序的方式調用容器的 getBean() 方法)時,都會創建一個新的 bean 實例。prototype 是原型類型,它在我們創建容器的時候並沒有實例化,而是當我們獲取 bean 的時候才去創建一個對象,而且每次獲取到的對象都不是同一個對象。

總之,對有狀態的 bean 應該使用 prototype 作用域,而對無狀態的 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 請求都會產生新的 bean,該 bean 僅在當前 HTTP request 內有效

request 作用域只適用於 Web 程序,每一次 HTTP 請求都會產生一個新的 bean,同時該 bean 僅在當前 HTTP request 內有效,當請求結束後,該對象的生命周期即結束。在 XML 中將 bean 定義成 request ,可以這樣配置:

<bean id="loginAction" class=cn.mariojd.LoginAction" scope="request"/>

session:每次 HTTP 請求都會產生新的 bean,該 bean 僅在當前 HTTP session 內有效

session 只適用於 Web 程序,session 作用域表示該針對每一次 HTTP 請求都會產生一個新的 bean,同時該 bean 僅在當前 HTTP session 內有效。當HTTP session 最終被廢棄的時候,在該 HTTP session 作用域內的 bean 也會被銷毀掉。

<bean id="userPreferences" class="cn.mariojd.UserPreferences" scope="session"/>

globalSession:伴隨應用本身的全局作用域

global session 作用域類似於標準的 HTTP session 作用域,不過僅僅在基於 portlet 的 web 應用中才有意義。Portlet 規範定義了全局 Session 的概念,它被所有構成某個 portlet web 應用的各種不同的 portlet 所共享。在 global session 作用域中定義的 bean ,將被限定於全局 portlet Session 的生命周期範圍內。

<bean id="user" class="cn.mariojd.Preferences "scope="globalSession"/>