在默認情況下,Spring應用上下文中所有bean都是作為以單例(singleton)的形式創建的。也就是說,不管給定的一個bean被注入到其他bean多少次,每次所注入的都是同一個實例。在大多數情況下,單例bean是很理想的方案。初始化和垃圾回收對象實例所帶來的成本只留給一些小規模任務,在這些任務中,讓對象保持無狀態並且在應用中反復重用這些對象可能並不合理。有時候,可能會發現,你所使用的類是易變的(mutable),它們會保持一些狀態,因此重用是不安全的。在這種情況下,將class聲明為單例的bean就不是什么好主意了,因為對象會被污染,稍后重用的時候會出現意想不到的問題。
Spring定義了多種作用域,可以基於這些作用域創建bean,包括:
單例(Singleton):在整個應用中,只創建bean的一個實例。
原型(Prototype):每次注入或者通過Spring應用上下文獲取的
時候,都會創建一個新的bean實例。
會話(Session):在Web應用中,為每個會話創建一個bean實
例。
請求(Rquest):在Web應用中,為每個請求創建一個bean實
例。
單例是默認的作用域,但是正如之前所述,對於易變的類型,這並不合適。如果選擇其他的作用域,要使用@Scope注解,它可以與@Component或@Bean一起使用。例如,如果你使用組件掃描來發現和聲明bean,那么你可以在bean的類上使用@Scope注解,將其聲明為原型bean:

這里,使用ConfigurableBeanFactory類的SCOPE_PROTOTYPE常量設置了原型作用域。你當然也可以使用@Scope("prototype"),但是使用SCOPE_PROTOTYPE常量更加安全並且不易出錯。如果你想在Java配置中將Notepad聲明為原型bean,那么可以組合使用@Scope和@Bean來指定所需的作用域:

同樣,如果你使用XML來配置bean的話,可以使用<bean>元素的
scope屬性來設置作用域:

1.1使用會話和請求作用域
在Web應用中,如果能夠實例化在會話和請求范圍內共享的bean,那將是非常有價值的事情。例如,在典型的電子商務應用中,可能會有一個bean代表用戶的購物車。如果購物車是單例的話,那么將會導致所有的用戶都會向同一個購物車中添加商品。另一方面,如果購物車是原型作用域的,那么在應用中某一個地方往購物車中添加商品,在應用的另外一個地方可能就不可用了,因為在這里注入的是另外一個原型作用域的購物車。就購物車bean來說,會話作用域是最為合適的,因為它與給定的用戶關聯性最大。要指定會話作用域,我們可以使用@Scope注解,它的使用方式與指定原型作用域是相同的:

這里,我們將value設置成了WebApplicationContext中的SCOPE_SESSION常量(它的值是session)。這會告訴Spring為Web應用中的每個會話創建一個ShoppingCart。這會創建多個ShoppingCart bean的實例,但是對於給定的會話只會創建一個實例,在當前會話相關的操作中,這個bean實際上相當於單例的。要注意的是,@Scope同時還有一個proxyMode屬性,它被設置成了ScopedProxyMode.INTERFACES。這個屬性解決了將會話或請求作用域的bean注入到單例bean中所遇到的問題。在描述proxyMode屬性之前,我們先來看一下proxyMode所解決問題的場景。
假設我們要將ShoppingCart bean注入到單例StoreService bean的Setter方法中,如下所示:

因為StoreService是一個單例的bean,會在Spring應用上下文加載的時候創建。當它創建的時候,Spring會試圖將ShoppingCart bean注入到setShoppingCart()方法中。但是ShoppingCart bean是會話作用域的,此時並不存在。直到某個用戶進入系統,創建了會話之后,才會出現ShoppingCart實例。
另外,系統中將會有多個ShoppingCart實例:每個用戶一個。我們並不想讓Spring注入某個固定的ShoppingCart實例到StoreService中。我們希望的是當StoreService處理購物車功能時,它所使用的ShoppingCart實例恰好是當前會話所對應的那一個。
Spring並不會將實際的ShoppingCart bean注入到StoreService中,Spring會注入一個到ShoppingCart bean的代理,如圖3.1所示。這個代理會暴露與ShoppingCart相同的方法,所以StoreService會認為它就是一個購物車。但是,當StoreService調用ShoppingCart的方法時,代理會對其進行懶解析並將調用委托給會話作用域內真正的ShoppingCart bean。現在,我們帶着對這個作用域的理解,討論一下proxyMode屬性。如配置所示,proxyMode屬性被設置成了ScopedProxyMode.INTERFACES,這表明這個代理要實現ShoppingCart接口,並將調用委托給實現bean。
如果ShoppingCart是接口而不是類的話,這是可以的(也是最為理想的代理模式)。但如果ShoppingCart是一個具體的類的話,Spring就沒有辦法創建基於接口的代理了。此時,它必須使用CGLib來生成基於類的代理。所以,如果bean類型是具體類的話,我們必須要將proxyMode屬性設置為ScopedProxyMode.TARGET_CLASS,以此來表明要以生成目標類擴展的方式創建代理。盡管我主要關注了會話作用域,但是請求作用域的bean會面臨相同的裝配問題。因此,請求作用域的bean應該也以作用域代理的方式進行

1.2在XML中聲明作用域代理
如果你需要使用XML來聲明會話或請求作用域的bean,那么就不能使用@Scope注解及其proxyMode屬性了。<bean>元素的scope屬性能夠設置bean的作用域,但是該怎樣指定代理模式呢?
要設置代理模式,我們需要使用Spring aop命名空間的一個新元素:

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


