###7.5 Bean 的做用域html
當你建立一個bean定義時,你也建立了一個菜譜.你能夠經過一個菜譜來建立bean的多個實例.java
你不只能夠向bean裏配置各類依賴和配置值,也能夠定義bean的做用域.有7種做用域,你能夠選擇一種.你可使用5種web項目中獨有的scopes. bean做用域web
Scope Description singleton 當ioc容器啓動時就生成一個bean實例 prototype 定義一個bean能夠生成無數bean實例 request bean定義的生命週期和HTTP請求相關.每一個http請求都有本身須要的bean的實例,web項目專用 session 定義一個與Http session生命週期相關的bean,web項目專用 globalSession http全局session的生命週期相關的.通常只用於Portlet環境.通常只在web相關的ApplicationContext中獲取 application 定義一個bean定義與ServletContext相關. websocket 定義一個bean的做用域與一個WebSocket相關.
####7.5.1 單例做用域 一個單例Bean只有一個共享的實例,且全部能經過id或者ids匹配到bean的請求,spring容器都會返回一個特定的bean的實例. 另外一方面看,全部的單例bean都被容器統一輩子成,並存儲到緩存裏,有請求就到緩存裏取出實例對象的引用.spring
spring 的單例Bean的概念不一樣於GOF書裏提到的那個單例模式.GoF的單例模式是硬編碼來控制class的做用域,即每一個特定的類的實例只被ClassLoader加載一次.而spring的單例則是計算的是每一個容器每一個bean.這意味着若是你要在一個單一的spring容器只給一個class定義一個bean,那麼spring容器會爲該Bean加載有且只有一個實例.singleton是spring的默認做用域.要將一個bean定義爲單例,能夠這麼寫:編程
<bean id="accountService" class="com.foo.DefaultAccountService"/> <!-- 下面的例子和上面的是同樣的 (singleton scope 是默認的) --> <bean id="accountService" class="com.foo.DefaultAccountService" scope="singleton"/>
bean的非單例,原生做用域部署結果是當對一個特定的bean發起請求時,會建立一個bean的實例.這樣,該bean會注入到其餘bean裏或者你能夠經過getBean()方法調用容器來請求他.一般,使用prototype做用域來定義有狀態的bean,使用singleton來定義無狀態的bean.緩存
下面的圖標代表如何使用spring prototype做用域.DAO對象通常不適合定義爲原型,由於它不持有任何狀態. 以下定義:安全
<bean id="accountService" class="com.foo.DefaultAccountService" scope="prototype"/>
不一樣於其餘的做用域,spring不會管理原生bean的生命週期,容器會實例化,配置,另外還組裝一個原生對象,或者把他扔給客戶端,但不會對此有記錄.所以,儘管初始化調用的方法調用不區分做用域,但對於prototype來講,配置的銷燬時的調用不會被調用.客戶端必需要清理prototype做用域的對象,並釋放它們佔用的昂貴的資源.要使spring容器來釋放原生做用域持有的bean對象,你可用使用一個自定義的bean post-processor,它持有全部要清理的beans的引用.websocket
必定程度上,spring 容器原生bean的角色是替代new 操做符.全部的生命週期都要客戶端處理;session
###7.5.3 擁有原生bean依賴的單例bean 當你使用帶着原生bean依賴的單例bean時,可用看到這些依賴在實例化時會被釋放.若是有原生做用的依賴注入到一個單例bean中,一個新的原生bean就會實例化並注入到單例bean中.這個原生實例是有容器提供的專供給單例bean的. 可是,若是你想單例bean在運行期間得到一個原生bean的新實例.這個是不可能的.由於當容器初始化該singletion bean並解決依賴時,注入只發生一次.若是你想要在運行時獲得新的實例,能夠參看7.4.6,方法注入.mvc
這些bean是web項目專用的,你只能在spring ApplicationContext的web-aware應用中使用,例如XmlWebApplicationContext中才能獲取. ###初始web配置 爲了支持這五種做用域,定義這些bean以前須要一些少許配置;(single和prototype不須要). 如何完成這些配置取決於你的特定的Servlet環境.
若是你使用spring web mvc來獲取做用域bean,實際上,這個請求是由DispatcherServlet或DispatcherPortlet處理的,然後不須要特定的步驟.DispatcherServlet和DispatcherPortlet早已暴露了一切相關狀態.
若是你使用Servlet2.5的web 容器,請求不是有spring的DispatcherServlet來處理的,你須要註冊這個org.springframework.web.context.request.RequestContextListener servletRequestListener.對於Servlet 3.0+,這個能夠經過WebApplicationInitializer接口來實現.另外,對於更老的容器,你須要在web.xml裏這樣宣佈:
<web-app> ... <listener> <listener-class> org.springframework.web.context.request.RequestContextListener </listener-class> </listener> ... </web-app>
另外,若是你的監聽器安裝有問題,那就考慮spring的RequestCOntextFilter.這個攔截器映射依賴周邊的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請求對象綁定到你要請求的正在提供服務的線程上.這可使request,session做用域的bean在調用鏈中可用.
###請求做用域 以下的xml配置
<bean id="loginAction" class="com.foo.LoginAction" scope="request"/>
當有請求發生時,spring容器會根據loginAction bean的定義來建立一個loginAction的實例.這是由於loginAction定義爲http請求.你能夠改變這個實例的狀態,由於這個bean定義會產生不少實例,以對應每一個請求.當你的請求完成了,request做用域的bean就會被拋棄.
當你使用註解驅動組件或java配置時,@RequestScope註解將會用來標識一個組件爲requst做用域.
@RequestScope @Component public class LoginAction { // ... }
思考一下的xml配置.
<bean id="userPreferences" class="com.foo.UserPreferences" scope="session"/>
spring容器使用userPerferences的bean定義來建立了一個生命週期和HTTP Session 相同的userPerferences的實例.也就說,userperference Bean的定義是HTTP session 級別的.每一個單獨的HTTP Session都有一個特定的userPreferences 的bean.當一個Http Seesion被拋棄的話,那麼這個bean也會被拋棄.
註解驅動或java的配置形式以下:
@SessionScope @Component public class UserPreferences { // ... }
###全局Session做用域 思考一下bean的定義:
<bean id="userPreferences" class="com.foo.UserPreferences" scope="globalSession"/>
同session的相同,不過只是基於web的portlet項目使用.由於它只有global portlet Session有關.
固然,你編寫了一個標準的Servlet-based的web項目,只要寫了session scope的bean,即便有多個globalSession scope的bean,也不會報錯. ###Application scope 思考如下的配置
<bean id="appPreferences" class="com.foo.AppPreferences" scope="application"/>
spring容器會建立web項目相關的AppPreference Bean.這個bean的做用域是ServletContext級別的,做爲一個常規的ServletContext的屬性存儲.它能夠看出是一個singleton,但不一樣之處在於:它只是每一個ServletContext的singleton,singleton是針對每一個ApplicationContext的(一個應用中可能有好幾個ApplicationContext).它實際是ServletContext的一個屬性.
你可使用 @ApplicationScope註解來代表其做用域
@ApplicationScope @Component public class AppPreferences { // ... }
###Scoped beans as dependencies
spring 容器不只管理對象的實例化,還要組裝其協做對象.若是你想將一個request 做用域的bean注入到一個更長時間的bean裏,你須要選擇一個AOP代理的注入來替代這個request scope Bean.你須要注入一個和該scope Bean有相同接口的代理對象,能夠在相關的做用域(例如http請求)中獲得真實的目標對象,代理要調用該真實對象的方法.
你能夠在single Beans中使用aop:scoped-proxy/標籤,這個引用就能夠經過相關的代理進行序列化和反序列化,這樣就能從新得到單例bean的實例
你能夠在原型bean之間使用aop:scoped-proxy/,在公共代理上每次方法的調用均可以從新建立該方法目標bean新的實例.
然而,做用域代理不是惟一的以生命週期安全的方法去獲取更小做用域bean的方式.你能夠簡單的將你的注入點(如構造器/set方法 參數或者自動注入字段)宣佈爲ObjectFactory<MyTargetBean>,它容許你每次使用getObject()方法來按需獲取實例,不論是否已持有該對象或存儲該對象.
jsr-330 將該變種成爲供應商(Provider).使用一個Provider<MyTargetBean>聲明,每次獲取要調用相應的get()方法.
以下的配置只是簡單的一行,但你要明白這背後的緣由和how
<?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"> <!-- an HTTP Session-scoped bean exposed as a proxy --> <bean id="userPreferences" class="com.foo.UserPreferences" scope="session"> <!-- instructs the container to proxy the surrounding bean --> <aop:scoped-proxy/> </bean> <!-- a singleton-scoped bean injected with a proxy to the above bean --> <bean id="userService" class="com.foo.SimpleUserService"> <!-- a reference to the proxied userPreferences bean --> <property name="userPreferences" ref="userPreferences"/> </bean> </beans>
建立一個代理,你將aop:scoped-proxy/元素插入到scoped bean定義中.爲何request,session,globalSession,custom-scope級別的bean定義須要aop:scoped-proxy/元素呢?讓咱們分析一下單例bean的定義並對比爲何你要對上面的bean進行什麼額外定義.
<bean id="userPreferences" class="com.foo.UserPreferences" scope="session"/> <bean id="userManager" class="com.foo.UserManager"> <property name="userPreferences" ref="userPreferences"/> </bean>
上面的例子中,單例bean userManager將注入Session-scope 的bean userPreferences的實例.須要注意的是userManager是一個單例Bean,每一個容器它只實例化一次,它的依賴也只注入一次.這意味着userManager實際上使用的都是相同的userPreferences對象.
你不須要將短生命的協做bean注入到長生命週期裏,例如你不打算將session-scoped 協助bean注入到單例bean裏.實際上,你須要一個單例的userManager對象,對於http Session的生命週期,你須要一個特定於每一個http Session的UserPreferences對象.所以容器會建立一個擁有userPreference全部接口的對象,它還能夠從做用域機制中獲得真實UserPreference對象.容器將會把一個代理對象注入到UserManager bean裏,bean卻發現不到這點.在這個例子中,當UserManager實例調用了UserPreference的一個方法,它其實是調用了代理對象的一個方法.這個代理對象會從HTTP Session中獲取真正的UserPreference對象,而且代理已得到UserPreference對象的須要被調用的方法.
因此當你將request,session,globalSession-scoped 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>
####選擇正確的代理類型 Choosing the type of proxy to create 默認的,當spring容器爲標記aop:scoped-proxy/元素的bean建立一個代理時,一個CGLIB-based 類代理就會建立.
另外,你可用經過配置spring容器爲這些scoped bean建立標準的JDK 接口代理,你要指明aop:scoped-proxy/元素中的proxy-targer-class屬性爲false.使用JDK 基於接口的代理意味着你的應用不須要額外的jar包來影響這些代理.可是,這也意味着你的scoped bean的class至少要實現一個接口,而且全部的協做對象必須經過接口才能注入這些scoped bean的引用.
<!-- DefaultUserPreferences implements the UserPreferences interface --> <bean id="userPreferences" class="com.foo.DefaultUserPreferences" scope="session"> <aop:scoped-proxy proxy-target-class="false"/> </bean> <bean id="userManager" class="com.foo.UserManager"> <property name="userPreferences" ref="userPreferences"/> </bean>
對於選擇class-based後者interface-based代理,查看11.6章節,"proxy mechanisms".
###7.5.5自定義做用域(Custom scopes) bean做用域機制的擴展,你可用定義本身的做用域,甚至能夠重定義已存在的做用域.儘管從新定義做用域是一個壞的實踐,並且你不能重寫內置的singleton和prototype做用域. ####新建一個自定義做用域(Creating a custom scope) 要將你的自定義做用域集成到spring容器,你須要實現org.springframework.beans.factory.config.Scope接口,本節會描述. Scope接口有四種方法從scope裏獲取對象,把它們從scope移除,並使他們銷燬.
下面的方法是從如下的scope中返回對象.例如,session scope實現的將返回一個session-scoped 的bean(若是這個bean的實例不存在,這個方法會從新建立一個bean的新實例,並把它綁定到session中以便之後調用).
Object get(String name, ObjectFactory objectFactory)
下面的方法將從做用域中移除對象.仍是以session scope 的實現爲例,將會從如下的session裏移除session-scoped bean.這個對象會返回,可是你若是這個帶名詞的對象找不到就會返回null.
Object remove(String name)
下面的方法註冊做用域應該執行的回調,當這些它被銷燬或當在此做用域中特定的對象被銷燬時.查看javadoc或spring scoped實現來查看更多的銷燬回調信息.
void registerDestructionCallback(String name, Runnable destructionCallback)
下面的方法會獲得一個該做用域下的會話標誌.這個標誌不一樣於其餘scope.對於一個session scoped實現來講,這個標誌多是session標誌 ####Using a custom scope (使用自定義註解) 在你編寫並測試你的自定義scope時,你須要讓你的spring容器知道這些新的scope.下面的方法是spring註冊新的scope的核心方法:
void registerScope(String scopeName, Scope scope);
這個方法是由ConfigurableBeanFactory接口申明的,它能夠在大部分spring的ApplicationContext接口的實現類經過Beanfactory屬性獲的. registerScope(..)方法的第一參數是與scope相關的惟一命名,例如spring容器裏singleton和prototype.第二個參數是你要註冊的已經實現了Scope接口的類的實例.
你編寫了一個本身的接口,並如此實現它.
Scope threadScope = new SimpleThreadScope(); beanFactory.registerScope("thread", threadScope);
而後你可用使用你的自定義的scope來建立bean的定義.
<bean id="..." class="..." scope="thread">
對於已實現的自定義scope,你不只能夠經過編程的方式去註冊,也能夠經過xml的方式去註冊.你須要用到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 http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/aop http://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="bar" class="x.y.Bar" scope="thread"> <property name="name" value="Rick"/> <aop:scoped-proxy/> </bean> <bean id="foo" class="x.y.Foo"> <property name="bar" ref="bar"/> </bean> </beans>
當你將aop:scoped-proxy/放到一個 FactoryBean 實現裏,因爲這個工廠Bean自身的做用域,因此getObject()方法不會返回任何對象.