Spring Bean作用域【譯】

官方的文檔有些說明的很清楚,大致瀏覽一遍很有收穫。

地址:https://docs.spring.io/spring-framework/docs/current/spring-framework-reference/core.html#beans-factory-scopes

1.5. Bean Scopes

      創建bean定義時,將創建一個配方(recipe,怎麼翻譯好呢?)來創建該bean定義所定義的類的實際實例。 bean定義是配方的想 法很重要,因爲它意味着與類一樣,您可以從一個配方中創建許多對象實例。

       您不僅可以控制要插入到從特定be an定義創建的對象中的各種依賴項和配置值,還可以控制從特定bean定義創建的對象的作用域。這種方法功能強大且靈活,因爲您可以選擇通過配置創建的對象的作用域,而不必在Java類級別烘焙對象的作用域。bean可以定義爲部署在多個作用域中的一個。Spring框架支持六個作用域,其中四個作用域只有在使用支持web的ApplicationContext時纔可用。您還可以創建自定義作用域(a custom scope.)。

下表描述了受支持的作用域作用域:

Scope

Description

singleton

(默認值)將每個Spring IoC容器的單個bean定義作用域限定爲單個對象實例。

prototype

將單個bean定義的作用域限定爲任意數量的對象實例。

request

將單個bean定義限定爲單個HTTP請求的生命週期。也就是說,每個HTTP請求都有自己的bean實例,該實例是在單個bean定義的後面創建的。 僅在可支持web的Spring ApplicationContext上下文中有效。

session

將單個bean定義的作用域限定爲HTTP會話的生命週期。 僅在可支持web的Spring ApplicationContext上下文中有效。

application

將單個bean定義的作用域限定爲ServletContext的生命週期。 僅在可支持web的Spring ApplicationContext上下文中有效。

websocket

將單個bean定義限定爲WebSocket的生命週期。僅在支持web的Spring應用程序上下文中有效。

注:

      從Spring 3.0開始,線程作用域可用,但默認情況下未註冊。 有關更多信息,請參見SimpleThreadScope文檔。 有關如何註冊此自定義作用域或任何其他自定義作用域的說明,請參閱使用自定義作用域。

1.5.1. The Singleton Scope

       只管理一個單例bean的共享實例,所有對ID或ID與該bean定義匹配的bean的請求都會導致Spring容器返回一個特定的bean實例。

       換句話說,當您定義一個bean定義並且其作用域爲單例時,Spring IoC容器將爲該bean定義所定義的對象創建一個實例。 該單個實例存儲在此類單例bean的高速緩存中,並且對該命名bean的所有後續請求和引用都返回該高速緩存的對象。 下圖顯示了單例作用域的工作方式:

singleton

       Spring的singleton bean概念不同於Gang of Four(GoF)模式書中定義的singleton模式。GoF singleton硬編碼對象的作用域,以便每個類加載器只創建一個特定類的實例。Spring singleton的作用域最好描述爲每個容器和每個bean。這意味着,如果爲單個Spring容器中的特定類定義一個bean,那麼Spring容器將創建該bean定義的類的一個且僅創建一個實例。singleton作用域是Spring中的默認作用域。要在XML中將bean定義爲單例,可以定義bean,如下例所示:

<bean id="accountService" class="com.something.DefaultAccountService"/>

<!-- the following is equivalent, though redundant (singleton scope is the default) -->
<bean id="accountService" class="com.something.DefaultAccountService" scope="singleton"/>

1.5.2. The Prototype Scope

       每次對特定bean提出請求時,非單例原型bean都會導致創建一個新bean實例。 也就是說,該Bean被注入到另一個Bean中,或者您可以通過容器上的getBean()方法調用來請求它。 通常,應將原型作用域用於所有有狀態Bean,將單例作用域用於無狀態Bean。

下圖說明了Spring原型作用域:

prototype

(數據訪問對象(DAO)通常不配置爲原型,因爲典型的DAO不擁有任何對話狀態。對於我們而言,重用單例圖的核心更爲容易。)

以下XML示例將bean定義爲原型bean:

<bean id="accountService" class="com.something.DefaultAccountService" scope="prototype"/>

與其他作用域相比,Spring不能管理原型Bean的完整生命週期。 容器實例化,配置或組裝原型對象,然後將其交給客戶端,而沒有該原型實例的進一步記錄。 因此,儘管在不考慮作用域的情況下在所有對象上都調用了初始化生命週期回調方法,但對於原型而言,不會調用已配置的銷燬生命週期回調。 客戶端代碼必須清除原型作用域內的對象並釋放原型Bean擁有的昂貴資源。 爲了使Spring容器釋放原型作用下的bean所擁有的資源,請嘗試使用自定義bean後置處理器,該處理器具有對需要清理的bean的引用。

       在某些方面,Spring容器在原型作用域Bean方面的角色是Java new運算符的替代。 超過該時間點的所有生命週期管理必須由客戶端處理。 (有關Spring容器中bean生命週期的詳細信息,請參閱生命週期回調。)

1.5.3 具有原型bean依賴項的單例bean

       當您使用對原型bean有依賴性的單例作用域Bean時,請注意,依賴關係在實例化時已解決。 因此,如果將依賴項原型的bean依賴項注入到單例作用域的bean中,則將實例化新的原型bean,然後將依賴項注入到單例bean中。 該原型實例是曾經提供給單例作用域的bean的唯一實例。

      但是,假設您希望單例作用域的bean在運行時重複獲取原型作用域的bean的新實例。 您不能將原型作用域的bean依賴項注入到您的單例bean中,因爲當Spring容器實例化單例bean並解析並注入其依賴項時,該注入僅發生一次。 如果在運行時不止一次需要原型bean的新實例,請參見方法注入

1.5.4. Request, Session, Application, and WebSocket Scopes

       請求、會話、應用程序和websocket作用域只有在使用支持web的Spring ApplicationContext實現(例如XmlWebApplicationContext)時纔可用。如果將這些作用域與常規的Spring IoC容器(如ClassPathXmlApplicationContext)一起使用,則會引發一個IllegalStateException,該異常抱怨未知的bean作用域。

初始化web配置

       爲了在請求、會話、應用程序和websocket級別(web作用域的bean)支持bean的作用域,在定義bean之前需要一些小的初始配置。(對於標準作用域:singleton和prototype,此初始設置不是必需的。)

如何完成此初始設置取決於您的特定Servlet環境。

       如果在Spring Web MVC中訪問作用域bean,實際上,在Spring DispatcherServlet處理的請求中,不需要特殊設置。DispatcherServlet已公開所有相關狀態。 

       如果您使用Servlet 2.5 Web容器,並且在Spring的DispatcherServlet之外處理請求(例如,使用JSF或Struts時),則需要註冊org.springframework.web.context.request.RequestContextListener ServletRequestListener。 對於Servlet 3.0+,可以使用WebApplicationInitializer接口以編程方式完成此操作。 或者,或者對於較舊的容器,將以下聲明添加到Web應用程序的web.xml文件中:

<web-app>
    ...
    <listener>
        <listener-class>
            org.springframework.web.context.request.RequestContextListener
        </listener-class>
    </listener>
    ...
</web-app>

另外,如果您的監聽器設置存在問題,請考慮使用Spring的RequestContextFilter。 過濾器映射取決於周圍的Web應用程序配置,因此您必須適當地對其進行更改。 以下清單顯示了Web應用程序的過濾器部分:

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

 DispatcherServlet,RequestContextListener和RequestContextFilter都做完全相同的事情,即將HTTP請求對象綁定到爲該請求提供服務的Thread。 這使得在請求鏈和會話作用域內的bean可以在調用鏈的更下游使用。

Request scope

考慮將以下XML配置用於bean定義:

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

 Spring容器通過爲每個HTTP請求使用loginAction bean定義來創建LoginAction bean的新實例。 也就是說,loginAction bean的作用域爲HTTP請求級別。 您可以根據需要更改創建實例的內部狀態,因爲從同一loginAction bean定義創建的其他實例看不到狀態的這些變化。 它們特定於單個請求。 當請求完成處理時,將丟棄作用於該請求的Bean。

       當使用註解驅動的組件或Java配置時,@RequestScope註釋可用於將組件分配給Request作用域。 以下示例顯示瞭如何執行此操作:

@RequestScope
@Component
public class LoginAction {
    // ...
}

Session Scope

考慮將以下XML配置用於bean定義:

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

       Spring容器通過在單個HTTP會話的生存期內使用userPreferences bean定義來創建UserPreferences bean的新實例。 換句話說,userPreferences bean有效地作用在HTTP會話級別。 與請求作用域的Bean一樣,您可以根據需要任意更改所創建實例的內部狀態,因爲知道其他也在使用從同一userPreferences Bean定義創建的實例的HTTP Session實例也看不到這些狀態變化 ,因爲它們特定於單個HTTP會話。 當HTTP會話最終被丟棄時,作用於該特定HTTP會話的bean也將被丟棄。

       使用註釋驅動的組件或Java配置時,可以使用@SessionScope註釋將組件分配給Session作用域。

@SessionScope
@Component
public class UserPreferences {
    // ...
}

Application Scope

考慮將以下XML配置用於bean定義:

<bean id="appPreferences" class="com.something.AppPreferences" scope="application"/>

       Spring容器通過對整個Web應用程序使用一次appPreferences bean定義來創建AppPreferences bean的新實例。 也就是說,appPreferences bean的作用域位於ServletContext級別,並存儲爲常規ServletContext屬性。 這有點類似於Spring單例bean,但是在兩個重要方面有所不同:它是每個ServletContext的單例,而不是每個Spring'ApplicationContext'的單例(在任何給定的Web應用程序中可能都有多個),並且實際上是公開的,因此 可見爲ServletContext屬性。

        使用註解驅動的組件或Java配置時,可以使用@ApplicationScope註解將組件分配給Application作用域。 以下示例顯示瞭如何執行此操作:

@Component
public class AppPreferences {
    // ...
}

作用域bean作爲依賴項

       Spring IoC容器不僅管理對象(bean)的實例化,而且還管理協作者(或依賴項)的裝配。 如果要將(例如)HTTP請求作用域的Bean注入(例如)另一個作用域更長的Bean,則可以選擇注入AOP代理來代替已定義作用域的Bean。 也就是說,您需要注入一個代理對象,該對象公開與作用域對象相同的公共接口,但也可以從相關作用域(例如HTTP請求)中檢索實際目標對象,並將方法調用委託給實際對象。

注:

      您還可以在作用域爲單例的bean之間使用<aop:scoped-proxy />,然後引用通過經過可序列化的中間代理,從而能夠在反序列化時重新獲得目標單例bean。

      當針對作用域原型的bean聲明<aop:scoped-proxy />時,共享代理上的每個方法調用都會導致創建新的目標實例,然後將該調用轉發到該目標實例。

      此外,作用域代理並不是以生命週期安全的方式從較短作用域訪問bean的唯一方法。您還可以將注入點(即構造函數或setter參數或autowired字段)聲明爲ObjectFactory<MyTargetBean>,從而允許getObject()調用在每次需要時按需檢索當前實例,而無需保留實例或單獨存儲實例。

      作爲擴展變量,您可以聲明ObjectProvider<MyTargetBean>,它提供了幾個額外的訪問變量,包括getIfAvailable和getIfUnique。

     JSR-330的這種變體稱爲Provider,並與Provider <MyTargetBean>聲明和每次檢索嘗試的相應get()調用一起使用。 有關JSR-330總體的更多詳細信息,請參見此處

以下示例中的配置僅一行,但是瞭解其背後的“原因”和“方式”很重要:

<?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
        https://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/aop
        https://www.springframework.org/schema/aop/spring-aop.xsd">

    <!-- an HTTP Session-scoped bean exposed as a proxy -->
    <bean id="userPreferences" class="com.something.UserPreferences" scope="session">
        <!-- instructs the container to proxy the surrounding bean -->
        <aop:scoped-proxy/> 1⃣️
    </bean>

    <!-- a singleton-scoped bean injected with a proxy to the above bean -->
    <bean id="userService" class="com.something.SimpleUserService">
        <!-- a reference to the proxied userPreferences bean -->
        <property name="userPreferences" ref="userPreferences"/>
    </bean>
</beans>

1⃣️該行定義了代理。

 要創建這樣的代理,請將子<aop:scoped-proxy />元素插入到作用域bean定義中(請參閱選擇要創建的代理類型基於XML Schema的配置)。 爲什麼在請求,會話和自定義作用域級別定義的bean定義需要<aop:scoped-proxy />元素? 考慮以下單例bean定義,並將其與需要爲上述作用域定義的內容進行對比(請注意,以下userPreferences bean定義不完整):

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

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

 在前面的示例中,單例bean(userManager)注入了對HTTP會話作用域bean(userPreferences)的引用。 這裏的重點是userManager bean是單例的:每個容器僅實例化一次,並且其依賴項(在本例下,僅一個,userPreferences bean)也僅注入一次。 這意味着userManager bean僅在完全相同的userPreferences對象(即最初與之注入對象)上操作。

      將壽命較短的作用域bean注入壽命較長的作用域bean時,這不是您想要的行爲(例如,將HTTP會話作用域的協作bean作爲依賴項注入到singleton bean中)。相反,您只需要一個userManager對象,並且在HTTP會話的生存期內,您需要一個特定於HTTP會話的userPreferences對象。因此,容器創建一個對象,該對象公開與UserPreferences類完全相同的公共接口(理想情況下是一個UserPreferences實例的對象),該對象可以從作用域機制(HTTP請求,Session等)中獲取實際的UserPreferences對象。 。容器將此代理對象注入到userManager bean中,而後者不知道此UserPreferences引用是代理。在此示例中,當UserManager實例在注入依賴項的UserPreferences對象上調用方法時,實際上是在代理上調用方法。然後,代理從HTTP會話(在本例中)獲取真實的UserPreferences對象,並將方法調用委託給檢索到的真實的UserPreferences對象。

       因此,在將請求作用域和會話作用域的bean注入到協作對象中時,需要以下(正確和完整)配置,如以下示例所示:

<bean id="userPreferences" class="com.something.UserPreferences" scope="session">
    <aop:scoped-proxy/>
</bean>

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

選擇要創建的代理類型

        默認情況下,當Spring容器爲使用<aop:scoped-proxy />元素標記的bean創建代理時,將創建基於CGLIB的類代理。
注意:CGLIB代理僅攔截public方法調用! 不要在此類代理上調用非public方法。 它們沒有被委派給實際的作用域目標對象。
       另外,您可以通過爲<aop:scoped-proxy />元素的proxy-target-class屬性值指定false,來配置Spring容器爲此類作用域的bean創建基於標準JDK接口的代理。 使用基於JDK接口的代理意味着您不需要應用程序類路徑中的其他庫即可影響此類代理。 但是,這也意味着作用域Bean的類必須實現至少一個接口,並且作用域Bean注入到其中的所有協作者必須通過其接口之一引用該Bean。 以下示例顯示基於接口的代理:
<!-- DefaultUserPreferences implements the UserPreferences interface -->
<bean id="userPreferences" class="com.stuff.DefaultUserPreferences" scope="session">
    <aop:scoped-proxy proxy-target-class="false"/>
</bean>

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

有關選擇基於類或基於接口的代理的更多詳細信息,請參閱代理機制

1.5.5 自定義bean作用域

        Bean作用域機制是可擴展的。 您可以定義自己的作用域,甚至重新定義現有的作用域,儘管後者被認爲是不好的做法,並且您不能覆蓋內置的singleton和prototype作用域。

創建自定義作用域

       要將自定義作用域集成到Spring容器中,需要實現org.springframework.beans.factory.config.Scope接口,本節對此進行了介紹。 有關如何實現自己的作用域的想法,請參見Spring框架本身提供的Scope實現和Scope javadoc,其中詳細說明了需要實現的方法。

Scope接口有四個方法可以從作用域中獲取對象,從作用域中刪除對象,然後銷燬它們。

例如,Session 作用域實現返回會話作用域的Bean(如果不存在,則該方法將其綁定到會話上以供將來參考之後,將返回該Bean的新實例)。 以下方法從基礎作用域返回對象:

Object get(String name, ObjectFactory<?> objectFactory)

例如,會話作用域實現從底層會話中移除會話作用域bean。應該返回對象,但如果找不到具有指定名稱的對象,則可以返回null。以下方法將對象從基礎作用域中移除:

Object remove(String name)

以下方法註冊在作用域被銷燬或作用域中的指定對象被銷燬時應執行的回調:

void registerDestructionCallback(String name, Runnable destructionCallback)

有關銷燬回調的更多信息,請參見javadoc或Spring作用域實現。

以下方法獲取基礎作用域的會話標識符:

String getConversationId()

每個作用域的標識符都不同。對於會話作用域的實現,此標識符可以是會話標識符。

使用自定義作用域:

       在編寫和測試一個或多個自定義作用域實現之後,需要使Spring容器意識到您的新作用域。 以下方法是在Spring容器中註冊新作用域的主要方法:

void registerScope(String scopeName, Scope scope);

       此方法在ConfigurableBeanFactory接口上聲明,該接口可通過Spring附帶的大多數具體ApplicationContext實現上的BeanFactory屬性獲得。

       registerScope(..)方法的第一個參數是與作用域關聯的唯一名稱。Spring容器本身中的這些名稱的例子包括singleton和prototype。registerScope(..)方法的第二個參數是要註冊和使用的自定義作用域實現的實際實例。

      假設您編寫了自定義的Scope實現,然後註冊它,如下面的示例所示:

(注意:下一個例子使用SimpleThreadScope,它包含在Spring中,但默認情況下沒有註冊。對於您自己的自定義作用域實現,這些說明將是相同的。)

Scope threadScope = new SimpleThreadScope();
beanFactory.registerScope("thread", threadScope);

然後,您可以創建符合自定義範圍的作用域規則的bean定義,如下所示:

<bean id="..." class="..." scope="thread">

        對於自定義作用域實現,您不限於作用域的編程註冊。您還可以使用CustomScopeConfigurer類以聲明方式進行作用域註冊,如下例所示:

<?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
        https://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/aop
        https://www.springframework.org/schema/aop/spring-aop.xsd">

    <bean class="org.springframework.beans.factory.config.CustomScopeConfigurer">
        <property name="scopes">
            <map>
                <entry key="thread">
                    <bean class="org.springframework.context.support.SimpleThreadScope"/>
                </entry>
            </map>
        </property>
    </bean>

    <bean id="thing2" class="x.y.Thing2" scope="thread">
        <property name="name" value="Rick"/>
        <aop:scoped-proxy/>
    </bean>

    <bean id="thing1" class="x.y.Thing1">
        <property name="thing2" ref="thing2"/>
    </bean>

</beans>
注意:當將<aop:scoped-proxy />放置在FactoryBean實現中時,作用域是工廠bean本身,而不是從getObject()返回的對象。
 
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章