1. 程式人生 > >spring學習總結——高階裝配學習三(Bean的作用域)

spring學習總結——高階裝配學習三(Bean的作用域)

 

一、bean的作用域

  在預設情況下,Spring應用上下文中所有bean都是作為以單例(singleton)的形式建立的。也就是說,不管給定的一個bean被注入到其他bean多少次,每次所注入的都是同一個例項。如果你所使用的類是易變的(mutable),它們會保持一些狀態,因此重用是不安全的。在這種情況下,將class宣告為單例的bean就不是什麼好主意了,因為物件會被汙染,稍後重用的時候會出現意想不到的問題。

1、Spring定義了多種作用域,可以基於這些作用域建立bean,包括:

  • 單例(Singleton):在整個應用中,只建立bean的一個例項。
  • 原型(Prototype):每次注入或者通過Spring應用上下文獲取的時候,都會建立一個新的bean例項。
  • 會話(Session):在Web應用中,為每個會話建立一個bean例項。
  • 請求(Rquest):在Web應用中,為每個請求建立一個bean例項。

 

2、scope使用

單例是預設的作用域,要使用@Scope註解對bean進行作用域的設定,它可以與@Component或@Bean一起使用。

@Component

例如,使用@Scope註解,將其宣告為原型bean:

這裡,使用ConfigurableBeanFactory類的SCOPE_PROTOTYPE常量設定了原型作用域。你當然也可以使用@Scope("prototype"),但是使用SCOPE_PROTOTYPE常量更加安全並且不易出錯。

@Bean:Java配置中使用

 

XML中使用

 

 

二、使用會話和請求作用域

使用情景:

  在Web應用中,如果能夠例項化在會話和請求範圍內共享的bean,那將是非常有價值的事情。例如,在典型的電子商務應用中,可能會有一個bean代表使用者的購物車。如果購物車是單例的話,那麼將會導致所有的使用者都會向同一個購物車中新增商品。另一方面,如果購物車是原型作用域的,那麼在應用中某一個地方往購物車中新增商品,在應用的另外一個地方可能就不可用了,因為在這裡注入的是另外一個原型作用域的購物車。

 

 1、使用@Scope註解指定會話作用域

就購物車bean來說,會話作用域是最為合適的,如下例子:

 

 

這裡,我們將value設定成了WebApplicationContext中的SCOPE_SESSION常量(它的值是session)。這會告訴Spring為Web應用中的每個會話建立一個ShoppingCart。這會建立多個ShoppingCart bean的例項,但是對於給定的會話只會建立一個例項,在當前會話相關的操作中,這個bean實際上相當於單例的。

2、proxyMode屬性(解決:單例bean中注入會話bean引發的問題)

上面的例子中,@Scope同時還有一個proxyMode屬性,它被設定成了ScopedProxyMode.INTERFACES。這個屬性解決了將會話或請求作用域的bean注入到單例bean中所遇到的問題。在描述proxyMode屬性之前,我們先來看一下proxyMode所解決問題的場景。

假設我們要將ShoppingCart bean注入到單例StoreService bean的Setter方法中,如下所示:

 

使用proxyMode的原因:

  1、因為StoreService是一個單例的bean,會在Spring應用上下文載入的時候建立。當它建立的時候,Spring會試圖將ShoppingCart bean注入到setShoppingCart()方法中。但是ShoppingCart bean是會話作用域的,此時並不存在。直到某個使用者進入系統,建立了會話之後,才會出現ShoppingCart例項。

   2、另外,系統中將會有多個ShoppingCart例項:每個使用者一個。我們並不想讓Spring注入某個固定的ShoppingCart例項到StoreService中。我們希望的是當StoreService處理購物車功能時,它所使用的ShoppingCart例項恰好是當前會話所對應的那一個。

   3、Spring並不會將實際的ShoppingCart bean注入到StoreService中,Spring會注入一個到ShoppingCart bean的代理,如上例子所示。這個代理會暴露與ShoppingCart相同的方法,所以StoreService會認為它就是一個購物車。但是,當StoreService呼叫ShoppingCart的方法時,代理會對其進行懶解析並將呼叫委託給會話作用域內真正的ShoppingCart bean。

 

3、proxyMode屬性值的配置說明

  現在,我們帶著對這個作用域的理解,討論一下proxyMode屬性。如配置所示,proxyMode屬性被設定成了ScopedProxyMode.INTERFACES,這表明這個代理要實現ShoppingCart介面,並將呼叫委託給實現bean。

  如果ShoppingCart是介面而不是類的話,這是可以的(也是最為理想的代理模式)。但如果ShoppingCart是一個具體的類的話,Spring就沒有辦法建立基於介面的代理了。此時,它必須使用CGLib來生成基於類的代理。所以,如果bean型別是具體類的話,我們必須要將proxyMode屬性設定為ScopedProxyMode.TARGET_CLASS,以此來表明要以生成目標類擴充套件的方式建立代理。

  儘管我主要關注了會話作用域,但是請求作用域的bean會面臨相同的裝配問題。因此,請求作用域的bean應該也以作用域代理的方式進行注入。

 

 

 

三、在XML中宣告作用域代理

 

 使用XML來宣告會話或請求作用域的bean,要設定代理模式,我們需要使用Spring aop名稱空間的一個新元素:

 

 

<aop:scoped-proxy>是與@Scope註解的proxyMode屬性功能相同的Spring XML配置元素。它會告訴Spring為bean建立一個作用域代理。預設情況下,它會使用CGLib建立目標類的代理。但是我們也可以將proxy-target-class屬性設定為false,進而要求它生成基於介面的代理:

 

為了使用<aop:scoped-proxy>元素,我們必須在XML配置中宣告Spring的aop名稱空間: