在默認狀況下,Spring應用上下文中全部bean都是做爲以單例(singleton)的形式建立的。也就是說,無論給定的一個bean被注入到其餘bean多少次,每次所注入的都是同一個實例。若是你所使用的類是易變的(mutable),它們會保持一些狀態,所以重用是不安全的。在這種狀況下,將class聲明爲單例的bean就不是什麼好主意了,由於對象會被污染,稍後重用的時候會出現意想不到的問題。安全
一、Spring定義了多種做用域,能夠基於這些做用域建立bean,包括:session
單例是默認的做用域,要使用@Scope註解對bean進行做用域的設置,它能夠與@Component或@Bean一塊兒使用。spa
@Componentprototype
例如,使用@Scope註解,將其聲明爲原型bean:3d
這裏,使用ConfigurableBeanFactory類的SCOPE_PROTOTYPE常量設置了原型做用域。你固然也可使用@Scope("prototype"),可是使用SCOPE_PROTOTYPE常量更加安全而且不易出錯。代理
@Bean:Java配置中使用對象
XML中使用blog
使用情景:接口
在Web應用中,若是可以實例化在會話和請求範圍內共享的bean,那將是很是有價值的事情。例如,在典型的電子商務應用中,可能會有一個bean表明用戶的購物車。若是購物車是單例的話,那麼將會致使全部的用戶都會向同一個購物車中添加商品。另外一方面,若是購物車是原型做用域的,那麼在應用中某一個地方往購物車中添加商品,在應用的另一個地方可能就不可用了,由於在這裏注入的是另一個原型做用域的購物車。作用域
就購物車bean來講,會話做用域是最爲合適的,以下例子:
這裏,咱們將value設置成了WebApplicationContext中的SCOPE_SESSION常量(它的值是session)。這會告訴Spring爲Web應用中的每一個會話建立一個ShoppingCart。這會建立多個ShoppingCart bean的實例,可是對於給定的會話只會建立一個實例,在當前會話相關的操做中,這個bean實際上至關於單例的。
上面的例子中,@Scope同時還有一個proxyMode屬性,它被設置成了ScopedProxyMode.INTERFACES。這個屬性解決了將會話或請求做用域的bean注入到單例bean中所遇到的問題。在描述proxyMode屬性以前,咱們先來看一下proxyMode所解決問題的場景。
假設咱們要將ShoppingCart bean注入到單例StoreService bean的Setter方法中,以下所示:
使用proxyMode的緣由:
一、由於StoreService是一個單例的bean,會在Spring應用上下文加載的時候建立。當它建立的時候,Spring會試圖將ShoppingCart bean注入到setShoppingCart()方法中。可是ShoppingCart bean是會話做用域的,此時並不存在。直到某個用戶進入系統,建立了會話以後,纔會出現ShoppingCart實例。
二、另外,系統中將會有多個ShoppingCart實例:每一個用戶一個。咱們並不想讓Spring注入某個固定的ShoppingCart實例到StoreService中。咱們但願的是當StoreService處理購物車功能時,它所使用的ShoppingCart實例剛好是當前會話所對應的那一個。
三、Spring並不會將實際的ShoppingCart bean注入到StoreService中,Spring會注入一個到ShoppingCart bean的代理,如上例子所示。這個代理會暴露與ShoppingCart相同的方法,因此StoreService會認爲它就是一個購物車。可是,當StoreService調用ShoppingCart的方法時,代理會對其進行懶解析並將調用委託給會話做用域內真正的ShoppingCart bean。
如今,咱們帶着對這個做用域的理解,討論一下proxyMode屬性。如配置所示,proxyMode屬性被設置成了ScopedProxyMode.INTERFACES,這代表這個代理要實現ShoppingCart接口,並將調用委託給實現bean。
若是ShoppingCart是接口而不是類的話,這是能夠的(也是最爲理想的代理模式)。但若是ShoppingCart是一個具體的類的話,Spring就沒有辦法建立基於接口的代理了。此時,它必須使用CGLib來生成基於類的代理。因此,若是bean類型是具體類的話,咱們必需要將proxyMode屬性設置爲ScopedProxyMode.TARGET_CLASS,以此來代表要以生成目標類擴展的方式建立代理。
儘管我主要關注了會話做用域,可是請求做用域的bean會面臨相同的裝配問題。所以,請求做用域的bean應該也以做用域代理的方式進行注入。
使用XML來聲明會話或請求做用域的bean,要設置代理模式,咱們須要使用Spring aop命名空間的一個新元素:
<aop:scoped-proxy>是與@Scope註解的proxyMode屬性功能相同的Spring XML配置元素。它會告訴Spring爲bean建立一個做用域代理。默認狀況下,它會使用CGLib建立目標類的代理。可是咱們也能夠將proxy-target-class屬性設置爲false,進而要求它生成基於接口的代理:
爲了使用<aop:scoped-proxy>元素,咱們必須在XML配置中聲明Spring的aop命名空間: