當建立bean的定義時,就建立了如何建立類實例的規則。bean定義是一個規則的思想很重要,由於這意味着能夠從一個規則建立許多對象的實例,與類同樣。java
從一個特定的bean定義中建立的bean,不只能夠控制它的各類依賴關係和配置值,還能夠控制對象的做用域。這種方法是強大、靈活的,能夠經過配置選擇要建立對象的做用域,而不須要在Java類的層面。能夠爲Bean指定許多做用域中的一個,而且開箱即用,Spring Framework支持七個做用域,其中有五個只在基於web ApplicationContext中有效。web
下面的做用域開箱即用。也能夠建立用戶自定義的做用域。spring
從Spring 3.0開始,線程做用域可用,但默認狀況下未註冊。編程
只管理一個共享的singleton bean 的實例;而且全部使用id的bean請求若是匹配到這個bean定義,Spring容器都返回同一個特定的bean的實例。設計模式
換句話說,當定義一個bean定義並將它的做用域設置爲singleton,Spring IoC容器將僅建立一個由該bean定義指定的對象的實例。這個單獨的實例存儲在singleton bean的緩存中,對該命名bean的全部後續請求和引用都返回緩存的對象。緩存
Spring的singleton bean的概念有別於GoF 設計模式書中的單例模式。GoF單例硬編碼對象的做用域,使得每一個ClassLoader只建立一個特定類的實例。每一個容器一個bean是對Spring singleton做用域的最好描述。若是在單個Spring容器中爲指定的類定義一個bean,則Spring容器將建立由bean定義指定的類的惟一一個實例。Singleton做用域是Spring bean的默認做用域。使用XML定義一個singleton bean的形式以下:安全
<bean id="accountService" class="com.foo.DefaultAccountService"/> <!-- the following is equivalent, though redundant (singleton scope is the default) --> <bean id="accountService" class="com.foo.DefaultAccountService" scope="singleton"/>
使用prototype 的bean部署方式會在每次請求一個特定bean的時候都建立一個bean的實例。對bean的每次請求意味着將bean注入到其它bean中或者經過調用容器的getBean()方法請求它。應該對有狀態的bean使用prototype做用域,對無狀態的bean使用singleton做用域。websocket
下圖闡述了Spring prototype 做用域。注意,一個dao通常不配置成prototype,由於一個典型的DAO不保持任何的會話狀態;使用這幅圖僅僅是由於能夠重用singleton圖的核心內容。session
下面的例子在XML中將一個bean定義爲prototype:app
<bean id="accountService" class="com.foo.DefaultAccountService" scope="prototype"/>
相對於其它做用域,Spring無論理prototype bean 的整個生命週期:容器實例化、配置並裝配一個prototype對象,最後將其交給客戶,此後再也不記錄prototype實例。所以,儘管全部做用域的bean的initialization生命週期的回掉函數都會被調用,可是在prototype狀況下,配置的destruction生命週期回掉函數不會被調用。客戶端代碼必須本身清楚prototype對象並釋放它持有的昂貴資源。爲了讓Spring容器釋放prototype bean的資源,可使用自定義的bean post-processor,它會持有須要清理的bean的引用。
在某些方面,Spring 規則認爲prototype bean是Java new 操做法的替代。全部new以後的生命週期都必須由客戶端管理。
當你使用有prototype bean依賴的singleton bean時,要注意依賴關係是在實例化時被解析。所以若是你依賴注入一個prototype bean到singleton bean中,一個新的prototype bean 會被實例化而且被依賴注入singleton bean。提供給singleton bean的prototype的實例是惟一的。
可是,假設你想在運行時讓singleton bean每次都獲得一個prototype bean的新實例。你就不能將prototype bean依賴注入到singleton bean中,由於注入僅僅發生一次,是在Spring容器實例化singleton bean並解析和注入依賴關係時。若是想要在運行時獲取prototype bean的新實例不止一次,可使用「方法注入」。
Request,session,global session,application 和 WebSocket 做用域僅在使用基於web的Spring應用上下文(例如XmlWebApplicationContext)中可用。若是在普通的Spring IoC容器(例如ClassPathXmlApplicationContext)中使用這些做用域,會拋出IllegalStateException異常來講明未知的bean做用域。
爲支持Request,session,global session,application 和 WebSocket 做用域,在定義bean以前須要一些基礎的初始化配置。(對於標準的做用域,singleton和prototype,不須要這些初始化配置)。
如何進行初始化設置取決於不一樣的Servlet環境。
若是你使用Spring Web MVC來得到做用域中的bean,實際上就是使用Spring的DispatcherServlet或者DispatcherPortlet來處理一個請求,那麼不須要特別的配置:DispatcherServlet和DispatcherPortlet已經暴露了全部相關的狀態。
若是使用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>
若是配置listener時出現問題,可使用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定義:
<bean id="loginAction" class="com.foo.LoginAction" scope="request"/>
Spring容器使用loginAction bean 定義爲每一個HTTP 請求建立一個LoginAction bean的實例。即loginAction bean 的做用域被設置爲Http request級別。能夠爲所欲爲的改變實例的內部狀態,由於從相同的loginAction bean 定義建立的其它實例對這個狀態的修改不可見;它們特定於單個請求。當請求處理完成,request做用域的bean就失效了。
當使用註解的方式配置,@RequestScope註解用來指定request做用域。
@RequestScope @Component public class LoginAction { // ... }
對於下面的XML格式的一個bean定義:
<bean id="userPreferences" class="com.foo.UserPreferences" scope="session"/>
Spring容器使用userPreferences bean定義爲每一個HTTP Session生命週期建立一個UserPreferences bean的實例。換言之,userPreference bean的做用域被設置爲Http session級別。能夠爲所欲爲的改變實例的內部狀態,由於從相同的userPreference bean 定義建立的其它實例對這個狀態的修改不可見;它們特定於每一個會話。當會話結束,session做用域的bean就失效了。
當使用註解的方式配置,@SessionScope註解用來指定session做用域。
@SessionScope @Component public class UserPreferences { // ... }
對於下面的XML格式的一個bean定義:
<bean id="userPreferences" class="com.foo.UserPreferences" scope="globalSession"/>
globalSession做用域與標準的HTTP session做用域類似,而且僅應用於基於portlet的web應用的上下文。portlet文檔定義了global session的概念——在全部組成一個portlet web應用的所用portlet中共享的會話。globalSession bean的做用域與global portlet session的生命週期一致。
若是是在標準的基於Servlet的web應用程序中定義了globalSession做用域,會使用標準的Http session做用域,不會發生錯誤。
對於下面的XML格式的一個bean定義:
<bean id="appPreferences" class="com.foo.AppPreferences" scope="application"/>
Spring容器在整個web應用中使用appPreferences bean定義僅僅建立一次AppPreferences的新實例。即appPreferences bean的做用域是ServletContext級別的,它被保存爲ServletContext屬性。它與Spring singleton bean在某種程度上類似,但有如下兩點重要的不一樣:它對於每一個ServletContext是單例,而不是每一個ApplicationContext(一個web應用中可能有多個ApplicationContext);它被暴露爲一個ServletContext屬性。
當使用註解的方式配置,@ApplicationScope註解用來指定application做用域。
@ApplicationScope @Component public class AppPreferences { // ... }
Spring IoC容器不只管理對象的實例化,還組織它們之間的協做(或者依賴)。若是你想注入一個HTTP request做用域的bean到另一個更長生命週期的bean,也許得選擇注入一個AOP代理來替代做用域bean。也就是說,須要注入一個代理對象來暴露與做用域bean相同的公有接口,能夠在相關的做用域(例如HTTP request)取回真正的目標對象而且在實際的對象上調用代理方法。
能夠在singleton bean之間使用aop:scoped-proxy/,經過可序列化的中間代理的引用,能夠在從新得到目標單例bean時將其反序列化。
當聲明瞭一個指向prototype做用域的aop:scoped-proxy/時,共享代理的每一次方法調用都會建立新的目標bean.
做用域代理並非獲取做用域較小的bean惟一的生命週期安全的途徑。也能夠簡單的聲明注入點(即構造函數/setter方法的參數或者自動裝配的字段)爲ObjectFactory<MyTargetBean>,它容許在每次須要的時候調用getObject()方法獲取如今的實例,而不用持用實例或單獨保存它。
JSR-330將其稱爲Provider,使用Provider<MyTargetBean>聲明和相關的get()方法調用。
下面的例子中僅有一行配置,可是去理解背後的原理很重要:
<?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>
在做用域bean定義中插入了子元素aop:scoped-proxy/來建立一個代理。爲何做用域爲request 、session、globalSession和用戶自定義做用域的bean須要aop:scoped-proxy/元素?能夠分析接下來的單例定義並與上述定義作比較(注意,下面的userPreferences 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有一個Http session做用域的bean userPreferences的引用被注入。突出的問題是userManager bean是一個單例:每一個容器只會實例化一次,而且它的依賴關係(在這裏只有一個userPreferences bean)也只被注入一次。這意味着userManager bean僅僅只會操做一個相同的userPreferences對象,即最開始注入的那個。
這並非將一個短生命週期bean注入到長生命週期bean所須要的行爲,例如將一個HTTP session做用域的bean注入到singleton bean中。而 你須要一個userManager對象的單例、每一個session須要一個userPreference對象。所以容器建立了一個對象用來暴露UserPreferences類的相同公有接口,能夠經過這個對象來獲取做用域(HTTP request,Session等)機制上的實際UserPreferences對象。容器將這個代理對象注入userManager bean,並不知道這是UserPreferences 引用的一個代理。在這個例子中,當UserManager實例調用被依賴注入的UserPreferences對象的方法時,它其實是調用了代理的方法。而後代理獲取HTTP Session(在這個例子中)上的實際的UserPreferences對象,而且代理了實際UserPreferences對象的方法調用。
所以,當注入request、session和globalSession做用域的協做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>
默認狀況下,當使用aop:scoped-proxy/元素來爲bean建立一個代理,使用的是基於CGLIB的類代理。
CGLIB代理僅僅攔截公有方法調用。在此代理商不要待用非公有方法;這不會委派給實際的做用域目標對象。
做爲替代,能夠配置Spring容器爲做用域bean建立標準JDK的基於接口的代理,將aop:scoped-proxy/標籤的proxy-target-class屬性聲明爲false。使用JDK基於接口的代理不須要嚮應用的classpath添加額外的庫。可是,這也要求做用域bean的類必須至少實現一個接口,而且所用被注入到做用域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>
bean做用域機制是可擴展的;能夠定義本身的做用域,或者重定義現有的做用域,儘管重定義被認爲是一種很差的實踐而且不能覆蓋內建的singleton和prototype做用域。
爲了集成自定義做用域到Spring容器,須要實現org.springframework.beans.factory.config.Scope接口。如何實現自定義的做用域,能夠查看Spring Frameworkd自身提供的Scope實現和Scope javadocs,它更詳細的解釋了須要實現的方法。
Scope接口有四個方法用來從做用域獲取對象、從做用域刪除對象,和容許它們被銷燬。
下面的方法從底層做用域返回對象。例如,會話做用域的實現返回session做用域的bean(若是不存在,方法在綁定bean的新實例到session以後返回它)。
Object get(String name, ObjectFactory objectFactory)
下面的方法從底層做用域移除對象。方法須要返回對象,可是當指定名稱的對象不存在時能夠返回null。
Object remove(String name)
下面的方法註冊當scope被銷燬時或scope中的指定對象被銷燬時,scope須要執行的回掉函數。
void registerDestructionCallback(String name, Runnable destructionCallback)
下面的方法獲取底層做用域的會話標識符。不一樣做用域的標識符不一樣。對於會話做用域的實現,這個標識符能夠是會話的標識符。
String getConversationId()
在測試幾個用戶自定義做用域實現後,須要讓Spring容器瞭解你的新做用域。下面的方法是註冊新做用域的核心方法:
void registerScope(String scopeName, Scope scope);
這個方法在ConfigurableBeanFactory接口中聲明,在大多數的具體ApplicationContext實現上可用。
registerScope(...)方法的第一個參數是與做用域相關的惟一名稱;Spring容器自身的名稱例子是singleton和prototype。第二個參數是一個要註冊和使用的Scope實現的實例。
假設實現一個Scope,能夠用以下方法定義:
下面的例子中使用的SimpleThreadScope包含在Spring中,可是默認未被註冊。這個註冊方法對用戶自定義的Scope實現相同。
Scope threadScope = new SimpleThreadScope(); beanFactory.registerScope("thread", threadScope);
而後就能夠建立遵循自定義範圍規則的bean定義:
<bean id="..." class="..." scope="thread"/>
對於自定義的Scope實現,不只限於編程的註冊方法。也可使用聲明式的Scope註冊,使用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>
當在FactoryBean實現中放置aop:scoped-proxy/元素,它做用域工廠bean自身,而不是它的getObject()方法返回的對象。