1. 程式人生 > >Spring學習筆記之Bean的作用域

Spring學習筆記之Bean的作用域

在預設情況下,Spring的應用上下文中所有的bean都是單例的形式建立的。也就是說,不管給定的一個bean被注入到其它bean多少次,每次注入的都是同一個例項。
在大多數情況下,單例bean是非常理想的方案。初始化和垃圾回收物件例項所帶來的成本只留給一些小規模任務,在這些任務中,讓物件保持無狀態並且在應用中反覆重用這些物件可能並不合理。 有時候你所使用的類可能是易變的,它們會保持一些狀態,比如我們在Web購物商城中常見的購物車功能,不同的使用者不可能同時使用同一個購物車例項,因此重用是不安全的。

(一)Spring中的作用域

Spring提供了多種作用域,可以基於這些作用域來建立bean,包括:
  • 單例(Singleton):在整個應用中,只建立bean的一個例項;
  • 原型(Prototype):每次注入或者通過Spring上下文獲取的時候,都會建立一個新的bean例項;
  • 會話(Session):在Web應用中,為每個會話建立一個bean例項;
  • 請求(Request):在Web應用中,為每次請求建立一個bean例項;
如果需要自定義bean的作用域,需要使用@Scope註解,他可以與@Component或@Bean組合使用:
@Component
@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
public class Cake implements Dessert {
}
這裡使用ConfigurableBeanFactory類的SCOPE_PROTOTYPE常亮設定為原型作用域。當然你也可以使用下面這種方式:
@Component
@Scope("prototype")
public class Cake implements Dessert {
}
但是儘可能使用ConfigurableBeanFactory.SCOPE_PROTOTYPE,這更不容易出錯。 當然也可以在Java配置中將作用域設定為原型bean,例如:
    @Bean
    @Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
    public Dessert cake(){
        return new Cake();
    }
同樣,也可以在XML中配置,應用<bean>元素的scope屬性:
<bean id="cake" class="cn.javacodes.spring.beans.Cake" scope="prototype"></bean>

(二)使用會話和請求作用域

在Web應用中,我們經常需要操作兩種作用域:會話和請求。 就像前面所說,在購物商城的購物車例項上,單例和原型作用域自然不能滿足我們的需求,我們希望為每一個會話都建立一個購物車,那麼這裡會話作用域就是最完美的選擇。 下面來簡單模擬一下購物車的作用域場景:
    @Bean
    @Scope(
        value = WebApplicationContext.SCOPE_SESSION,
        proxyMode = ScopedProxyMode.INTERFACES)
    public ShoppingCart cart(){
       // ....
    }
這裡,將value值設定成了WebApplicationContext中的SCOPTE_SESSION常量(值為session)。這會告訴Spring為Web應用中的每個會話建立一個ShoppingCart。對於每一個會話來說,這個bean實際上相當於是單例的。 這裡需要注意,@Scope還有一個proxyMode屬性,它被設定為ScopedProxyMode.INTERFACES。我們先不考慮這個屬性,先來理解一下對Spring作用域的理解。 現在假設我們要將ShoppingCart bean注入到單例StoreService bean的Setter中,如下所示:
@Component
public class StoreService {
    
    private ShoppingCart shoppingCart ;
    
    @Autowired
    public  void setShoppingCart(ShoppingCart shoppingCart){
        this.shoppingCart = shoppingCart;
    }
}
因為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。如下圖所示: QQ截圖20161020163515 現在我們來討論一下proxyMode屬性。我們將proxyMode屬性設定為了ScopedProxyMode.INTERFACES,這表明這個代理要實現ShoppingCart介面,並將呼叫委託給實現bean。 這裡我們的ShoppingCart是介面而不是具體的類,這當然是可以的(也是最理想的代理模式)。但如果ShoppingCart是具體的實現類而不是介面的話,Spring就沒辦法建立基於介面的代理了。此時必須使用CGLib來生成基於類的代理。所以,如果bean型別是具體的類的話,我們必須要將proxyMode設定為ScopedProxyMode.TARGET_CLASS,以此來表明要以生成目標類擴充套件的方式建立代理。 請求作用域與會話作用域十分類似,也應該以作用域代理的方式進行注入,再次不做贅述。

(三)在XML中宣告作用域代理

在XML中設定作用域代理需要使用Spring aop名稱空間的一個元素:
    <bean id="cart" class="cn.javacodes.spring.beans.ShoppingCart" scope="session">
        <aop:scoped-proxy />
    </bean>
當然了,在使用aop名稱空間之前一定要在xml的頂部進行對名稱空間進行宣告:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:aop="http://www.springframework.org/schema/aop"
       xsi:schemaLocation="
       http://www.springframework.org/schema/beans
       http://www.springframework.org/schema/beans/spring-beans.xsd
       http://www.springframework.org/schema/aop
       http://www.springframework.org/schema/aop/spring-aop.xsd">
    ......
</beans>
注意:在使用Spring開發web專案時,需要在web.xml中加入如下內容(web2.4以上):
<web-app>
   ...
  <listener>
<listener-class>org.springframework.web.context.request.RequestContextListener</listener-class>
  </listener>
   ...
</web-app>
web 2.4以下版本需要加入:
<web-app>
 ..
 <filter> 
    <filter-name>requestContextFilter</filter-name> 
    <filter-class>org.springframework.web.filter.RequestContextFilter</filter-class>
 </filter> 
 <filter-mapping> 
    <filter-name>requestContextFilter</filter-name> 
    <url-pattern>/*</url-pattern>
 </filter-mapping>
   ...
</web-app>
另外,<aop:scoped-proxy />是與@Scope註解的proxyMode屬性功能相同的Spring XML配置元素。它會告訴Spring為bean建立一個作用域代理。預設情況下,它會使用CGLib建立目標類的代理。但是我們也可以將proxy-target-class屬性設定為false,進而要求它生成基於介面的代理:
<bean id="cart" class="cn.javacodes.spring.beans.ShoppingCart" scope="session">
        <aop:scoped-proxy proxy-target-class="false"/>
    </bean>


本文為博主獨立部落格(https://javacodes.cn)同步發表,轉載請註明出處。
檢視原文:https://javacodes.cn/336.html