spring bean 的作用域request和session

原文轉自:https://blog.csdn.net/qq_36951116/article/details/79121887

先了解一下request和session這兩個作用域是幹嘛的

以下是官方文檔中文翻譯:

請求作用域

考慮如下的bean定義:

<bean id="loginAction" class="com.foo.LoginAction" scope="request"/>

對於每個http請求,Spring容器會創建一個 LoginAction bean 的新實例。也就是說,loginAction bean 的作用域限於 HTTP 請求範圍。 你可以在請求內隨意修改這個bean實例的狀態,因爲其他 loginAction bean實例看不到這些變化,bean實例是與特定的請求相關的。 當請求處理完畢,對應的bean實例也就銷燬(被回收)了。

會話作用域

考慮如下的bean定義:

<bean id="userPreferences" class="com.foo.UserPreferences" scope="session"/>

在每個HTTP Session的生命週期內,Spring容器會根據id爲 userPreferences 的bean定義創建一個 UserPreferences bean 的新實例。 也就是說,userPreferences bean 的作用域限於 HTTP Session範圍。和請求作用域 request-scoped bean 類似, 因爲每個會話域 session-scoped bean的範圍限於特定的 HTTP Session 內部,所以一個 Session 內的 userPreferences bean也是可以被隨意修改, 而不會影響到其他 Session 中的 userPreferences bean。當一個HTTP Session 最終用完被JVM回收時,相關的會話域 session-scoped bean也被一起回收。

上面都是spring官方文檔講的關於request和session的作用

這裏就不講使用request session這些作用域要在web.xml中做什麼了,這些對於使用spring mvc的我來說沒什麼用。

下面講講當作用域設置爲這兩個時,注入到其他bean中怎麼使用,以及爲什麼可以這樣用。

怎麼使用?

scope爲request時:

假設有bean兩個,A和B,A的scope設置爲request,B的scope隨意,在spring配置文件中如下:
........
<bean class="*.*.A" scope="request"></bean>
<bean class="*.*.B"></bean>
........

此時,B要注入A這個屬性,如下:
public class B{
 @Autowired
 private A a;
 //...........
}
如果就這麼使用,spring會拋異常(是什麼異常在這裏不重要,反正是你寫錯了)

根據前面貼出的關於request和session的官方文檔解釋,應該知道這兩個作用域的一些知識了。
在這裏A的scope爲request,說明每個請求擁有一個A的實例,
而B默認是單例的,那麼,當A注入到B中,豈不是B的a屬性只被注入爲web應用的第一個request對象?之後的新的request不會再注入給a了。B是單例的,在mvc中,B就是controller,那B的a不可能永遠是使用同一個request。。所以,當出現這種狀況之後,spring直接給你拋異常

spring自然有解決之道:
只要修改spring配置文件(也就是把bean A修改一下)

........
<bean class="*.*.A" scope="request">
 <aop:scoped-proxy/>
</bean>
<bean class="*.*.B"></bean>
........

就僅僅在A中加了<aop:scoped-proxy/>標籤,就OK了。


爲什麼加<aop:scoped-proxy/>標籤就可以了呢?

因爲這個標籤就是提示spring給A這個bean創建一個代理對象。
 
B要注入A時,如下:
public class B{
 @Autowired
 private A a;
 //...........
}
其實注入的是spring給A創建的代理對象。
每個請求都會創建一個A對象,然後由A的代理對象來判斷當前使用的實際對象時哪個。(太拗口了)

大概原理是這樣吧:

當你使用代理對象的任何方法時,會根據你當前所處線程獲得對應的請求(每個請求都是開啓一個線程,用ThreadLocal來做)

這樣spring就可以通過 你當前所處線程來獲得對應的請求。

再從請求中獲得實際使用的bean,再調用該bean中與代理對象對應的方法。

這樣,代理對象在整個spring上下文中只有一個,但通過代理對象使用的實際對象是屬於當前請求範圍內的。

簡而言之,scope爲request的bean,在每個請求到來時,會創建一個A的實際對象與當前的請求 對應。而使用A的代理對象時,會通過某種方式獲得當前的請求,再根據當前這個請求,獲得對應A的實際對象。使用代理對象間接操控的就是與當前請求對應的A的實際對象。

request作用域就是以上那樣。
至於session作用域,和request作用域差不多。
因爲web中的會話不就是從請求中獲得麼。。。

不管看不看得懂我說的,我還是貼出對應的官方文檔中文翻譯吧,如下:


將上述作用域的bean作爲依賴

Spring IoC 容器不僅負責管理對象(beans)的創建,也負責有合作(依賴)關係對象之間的組裝。 如果你想將一個HTTP 請求作用域的bean注入到另一個 bean,必須注入一個 AOP 代理來取代請求作用域的bean本身。 也就是說,你得有一個和作用域bean實現了相同接口、能在相應作用域(例如,HTTP 請求) 訪問真正的請求域bean目標對象的代理對象,而且這個代理對象要能把方法調用委派給真正的請求域bean, 然後把代理對象注入到請求域bean該注入的地方。

[Note]

對於作用域爲 singletons (單例)或 prototypes(原型)的bean , 不需要 使用 <aop:scoped‐proxy/> 元素

下述代碼雖然只有一行,但讀者不僅要“知其然”,更要“知其所以然”。

<?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">


    <!-- 將一個HTTP Session bean 暴露爲一個代理bean -->
    <bean id="userPreferences" class="com.foo.UserPreferences" scope="session">

        <!-- 通知Spring容器去代理這個bean -->
        <aop:scoped-proxy/>
    </bean>


    <!-- 將上述bean 的代理注入到一個單例bean -->
    <bean id="userService" class="com.foo.SimpleUserService">

        <!-- 引用被代理的 userPreferences bean -->
        <property name="userPreferences" ref="userPreferences"/>
    </bean>
</beans>

爲了創建上文提到的代理對象,要在 request 請求、 session 會話、 globalSession 全局會話 和自定義作用域的bean聲明中加入 <aop:scoped‐proxy/> 子元素。 (參考 the section called “選擇要創建的代理類型” 和 Chapter 33, XML Schema-based configuration) 。爲什麼要這麼做呢?讓我們將這幾個作用域的bean定義與單例作用域的bean定義做個對比。 (下列代碼的 userPreferences bean定義實際是 不完整的 )。

<bean id="userPreferences" class="com.foo.UserPreferences" scope="session"/>

<bean id="userManager" class="com.foo.UserManager">
    <property name="userPreferences" ref="userPreferences"/>
</bean>

在上述例子中,將HTTP Session 會話域的 userPreferences bean 注入進了單例域 userManager。 代碼的關鍵在於userManager  是個單例bean,它在每個Spring 容器中只會被初始化 一次, 它的依賴對象(在這個例子中是 userPreferences bean)也只會被注入一次。這就意味着 userManager   每次操作的都是在最開始注入進來的同一個 userPreferences 對象。

當你把一個生命週期較短的bean注入至一個生命週期較長的bean時,例如把HTTP Session bean注入到單例bean, 這種情況肯定 不是 你所期望的。相反,你希望有個唯一的 userManager 單例對象,在每個HTTP Session的生命週期內, 都能有一個專門的 userPreferences 對象供 userManager 使用。因此,Spring容器會創建一個代理類, 使之與 UserPreferences 類實現相同的接口(理論上也是一個 UserPreferences 對象),並能根據作用域(HTTP 請求、 Session,等等)去獲得真正的 UserPreferences 對象。 Spring容器將這個代理類的對象注入到 userManager , 但是 userManager 並不清楚它獲得的 UserPreferences 其實是個代理。 在這個例子中,當UserManager 實例調用注入的 UserPreferences 對象的某個方法時,實際上調用的是代理對象的方法。 然後,代理對象從HTTP Session 中獲取、並將方法調用委派給真正的 UserPreferences 對象。

所以,當你需要將 request-session-, and globalSession-scoped 作用域的 bean 注入至其他合作者bean的時候,要按照下面的方法去正確地配置。

<bean id="userPreferences" class="com.foo.UserPreferences" scope="session">
    <aop:scoped-proxy/>
</bean>
<bean id="userManager" class="com.foo.UserManager">
    <property name="userPreferences" ref="userPreferences"/>
</bean>
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章