當你建立一個 bean bean bean
的定義時候,你可以建立一個模版(recipe)通過
定義的類定義去建立一個真實的執行個體。
定義是模版(recipe)的概念很重要,因為這意味着,與使用類一樣,你可以從一個模版(recipe)建立多個對象執行個體。
你不僅可以控制要插入到從特定
bean
定義建立的對象中的各種依賴項和配置值,還可以控制從特定 bean
定義建立的對象的作用域。這種方法是非常有用的和靈活的,因為你可以選擇通過配置建立的對象的作用域,而不必在Java類級别上考慮對象的作用域。 bean
能夠定義部署到一個或多個作用域。 Spring
架構支撐6種作用域,4種僅僅使用 web
環境。你可以建立定制的作用域。
下面的表格描述了支撐的作用域:
Scope | Description |
---|---|
singleton | (預設)将每個Spring IoC容器的單個bean定義範圍限定為單個對象執行個體。 |
prototype | 将單個bean定義的作用域限定為任意數量的對象執行個體 |
request | 将單個bean定義的範圍限定為單個HTTP請求的生命周期。也就是,每個HTTP請擁有一個被建立的bean執行個體。僅在Spring ApplicationContext Web容器有效 |
session | 将單個bean定義的範圍限制在HTTP Session生命周期。僅在Spring ApplicationContext Web容器有效 |
application | 将單個bean定義的範圍限制在ServletContext生命周期。僅在Spring ApplicationContext Web容器有效 |
websocket | 将單個bean定義限制在WebSocket生命周期。僅在Spring ApplicationContext Web容器有效 |
從
Spring3.0
後,線程安全作用域是有效的但預設沒有注冊。更多的資訊,檢視文檔
SimpleThreadScope
。更多關于怎樣去注冊和自定義作用域,檢視
自定義作用域1.5.1 單例bean作用域
單例
bean
僅僅隻有一個共享執行個體被容器管理,并且所有對具有與該
bean
定義相比對的
ID
的
bean
的請求都會導緻該特定
bean
執行個體被
Spring
容器傳回。換一種方式,當你定義一個
bean
的定義并且它的作用域是單例的時候,
Spring IoC
容器建立通過
bean
定義的對象定義的執行個體。這個單例存儲在緩存中,并且對命名
bean
的所有請求和引用傳回的是緩存對象。下面圖檔展示了單例
bean
作用域是怎樣工作的:

Spring
的單例
bean
概念與在
GoF
設計模式書中的單例模式不同。
GoF
單例寫死對應的作用域例如:隻有一個特定類的對象執行個體對每一個
ClassLoader
隻建立一個對象執行個體。最好将
Spring
單例的範圍描述為每個容器和每個
bean
(備注:
GoF
設計模式中的單例
bean
是針對不同
ClassLoader
來說的,而
Spring
的單例是針對不同容器級别的)。這意味着,如果在單個
Spring
容器對指定類定義一個
bean
,
Spring
容器通過
bean
定義的類建立一個執行個體。在
Spring
中單例作用域是預設的。在XML中去定義一個
bean
為單例,你可以定義一個
bean
類似下面例子:
<bean id="accountService" class="com.something.DefaultAccountService"/>
<!-- 通過scope指定bean作用域 單例:singleton ,原型:prototype-->
<bean id="accountService" class="com.something.DefaultAccountService" scope="singleton"/>
1.5.2 原型作用域
非單例原型
bean
的作用域部署結果是在每一次請求指定
bean
的時候都會建立一個
bean
執行個體。也就是,
bean
被注入到其他
bean
或在容器通過
getBean()
方法調用都會建立一個新
bean
。通常,為所有的無狀态bean使用原型作用域并且有狀态
bean
使用單例
bean
作用域。
下面的圖說明
Spring
的單例作用域:
資料通路對象(
DAO
)通常不被配置作為一個原型,因為典型的
DAO
不會維持任何會話狀态。我們可以更容易地重用單例圖的核心。
下面例子在
XML
中定義一個原型
bean
:
<bean id="accountService" class="com.something.DefaultAccountService" scope="prototype"/>
與其他作用域對比,
Spring
沒有管理原型
bean
的完整生命周期。容器将執行個體化、配置或以其他方式組裝原型對象,然後将其交給用戶端,無需對該原型執行個體的進一步記錄。是以,盡管初始化生命周期回調函數在所有對象上被回調而不管作用域如何,在原型情況下,配置銷毀生命周期回調是不被回調。用戶端代碼必須清除原型作用域内的對象并釋放原型
Bean
占用的昂貴資源。為了讓
Spring
容器釋放原型作用域
bean
所擁有的資源,請嘗試使用自定義
bean
post-processor 後置處理器,該後處理器包含對需要清理的
bean
的引用(可以通過後置處理器釋放引用資源)。
在某些方面,
Spring
容器在原型範圍内的
bean
角色是
Java new
運算符的替代。所有超過該點的生命周期管理都必須由用戶端處理。(更多關于在
Spring
容器中的
bean
生命周期,檢視
生命周期回調)
1.5.3 單例bean與原型bean的依賴
當你使用依賴于原型
bean
的單例作用域
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
request
、
session
application
、和
websocket
作用域僅僅在你使用
Spring
ApplicationContext
實作(例如:
XmlWebApplicationContext
)時有效。如果你将這些作用域與正常的
Spring IoC
容器(例如
ClassPathXmlApplicationContext
)一起使用,則會抛出一個
IllegalStateException
異常,該錯抛出未知的
bean
- 初始化Web配置
為了支援這些bean的作用域在
request
session
application
websocket
級别(web作用域bean)。一些次要的初始化配置在你定義你的bean之前是需要的。(這個初始化安裝對于标準的作用域是不需要的:
singleton
prototype
)。
如何完成這個初始化安裝依賴于你的特定
Servlet
環境。
如果在
Spring Web MVC
中通路作用域
bean
,實際上,在由
Spring
DispatcherServlet
處理的請求中,不需要特殊的設定。
DispatcherServlet
已經暴露了所有相關狀态。
如果你使用
Servlet 2.5
Web
容器,請求處理在
Spring
DispatcherServlet
外(例如:當使用
JSF
或
Structs
),你需要去注冊
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
filter
的部配置設定置:
<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在調用鍊的更下方可用。
-
作用域Request
考慮下面的XML關于
bean
的定義:
<!--請求作用域為request-->
<bean id="loginAction" class="com.something.LoginAction" scope="request"/>
Spring
容器通過使用
LoginAction
bean定義為每個
HTTP
的請求建立一個
LoginAction
新執行個體bean。也就是說,
loginAction
bean的作用域在
HTTP
請求級别。你可以根據需要更改所建立執行個體的内部狀态。因為從同一
loginAction
bean定義建立的其他執行個體看不到狀态的這些變化。當這個請求處理完成,bean的作用域從
request
丢棄。(備注:
scope="request"
每個請求是線程級别隔離的、互不幹擾)
當使用注解驅動元件或
Java Config
時,
@RequestScope
注解能夠指派一個元件到
request
作用域。下面的例子展示怎樣使用:
@RequestScope//指定作用域通路為request
@Component
public class LoginAction {
// ...
}
- Session作用域
考慮下面為
bean
定義的
XML
配置:
<bean id="userPreferences" class="com.something.UserPreferences" scope="session"/>
Spring
userPreferences
的bean定義為單個
HTTP
Session
的生命周期内的建立一個
UserPreferences
的新執行個體。換句話說,
userPreferences
bean有效地作用在HTTP會話級别。與請求範圍的Bean一樣,您可以根據需要任意更改所建立執行個體的内部狀态,因為知道其他也在使用從同一
`userPreferences
Bean定義建立的執行個體的HTTP Session執行個體也看不到這些狀态變化,因為它們特定于單個HTTP會話。當
HTTP
會話最終被丢棄時,作用于該特定
HTTP
會話的
bean
也将被丢棄。
Java Config
@SessionScope
session
@SessionScope
@Component
public class UserPreferences {
// ...
}
- Application作用域
bean
<bean id="appPreferences" class="com.something.AppPreferences" scope="application"/>
Spring
appPreferences
的bean定義為整個
Web
應用建立一個
AppPreferences
的bean新執行個體。也就是說,
appPreferences
的作用域在
ServletContext
級别并且作為一個正常的
ServletContext
屬性被儲存。這個和
Spring
的單例bean類似,但有兩個重要的差別:每個
ServletContext
是一個單例,而不是每個
Spring
ApplicationContext
(在給定的
Web
應用程式中可能有多個),并且它實際上是暴露的,是以作為
ServletContext
屬性可見。
Java Config
@ApplicationScope
application
@ApplicationScope
@Component
public class AppPreferences {
// ...
}
- 作用域bean作為依賴項
Spring IoC容器不僅管理對象(bean)的執行個體化,而且還管理協同者(或依賴項)的連接配接。(例如)如果要将HTTP請求範圍的Bean注入另一個作用域更長的Bean,則可以選擇注入AOP代理來代替已定義範圍的Bean。也就是說,你需要注入一個代理對象,該對象暴露與範圍對象相同的公共接口,但也可以從相關範圍(例如HTTP請求)中檢索實際目标對象,并将方法調用委托給實際對象。
在這些作用域是單例之間,你可以使用
bean
。然後通過一個可序列化的中間代理引用,進而能夠在反序列化時重新獲得目标單例
<aop:scoped-proxy/>
bean
。
當申明
原型作用域
<aop:scoped-proxy/>
bean
,每個方法調用共享代理導緻一個新目标執行個體被建立,然後将該調用轉發到該目标執行個體。
同樣,作用域代理不是以生命周期安全的方式從較短的作用域通路
的唯一方法。你也可以聲明你的注入點(也就是,構造函數或者
bean
參數或自動注入字段)例如:
Setter
,允許
ObjectFactory<MyTargetBean>
getObject()
調用在每次需要時按需檢索目前執行個體-不保留執行個體或單獨存儲執行個體。
作為一個擴充的變體,你可以聲明
,提供了一些附加的擷取方式,包括
ObjectProvider<MyTargetBean>
getIfAvailable
JSR-330的這種變體稱為
getIfUnique
,并與
Provider
聲明和每次檢索嘗試的相應
Provider <MyTargetBean>
調用一起使用。有關整體JSR-330的更多詳細資訊, 請參見此處
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
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/aop
https://www.springframework.org/schema/aop/spring-aop.xsd">
<!-- 一個HTTP Session作用域的bean暴露為一個代理 -->
<bean id="userPreferences" class="com.something.UserPreferences" scope="session">
<!-- 訓示容器代理周圍的bean -->
<aop:scoped-proxy/> //1.
</bean>
<!-- 一個單例作用域bean 被注入一個上面的代理bean -->
<bean id="userService" class="com.something.SimpleUserService">
<!-- a reference to the proxied userPreferences bean -->
<property name="userPreferences" ref="userPreferences"/>
</bean>
</beans>
- 這行定義代理。
建立一個代理,通過插入一個子
<aop:scoped-proxy/>
元素到一個作用域
bean
定義中(檢視
選擇代理類型去建立 基于Schema的XML配置)。為什麼這些
bean
的定義在
request
session
和自定義作用域需要
<aop:scoped-proxy/>
元素?考慮以下單例
bean
定義,并将其與需要為上述範圍定義的内容進行對比(請注意,以下
userPreferences
bean定義不完整):
<!--沒有<aop:scoped-proxy/> 元素-->
<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
Session
作用域的
bean
(
userPreferences
)。這個顯著點是
userManager
bean是一個單例bean:這個執行個體在每個容器值初始化一次,并且它的依賴(在這個例子僅僅一個,
userPreferences
bean)僅僅被注入一次。這意味着
userManager
bean運作僅僅在相同的
userPreferences
對象上(也就是,最初注入的那個)。
當注入一個短生命周期作用域的
bean
到一個長生命周期作用域
bean
的時候這個不是我們期望的方式(例如:注入一個HTTP
Session
作用域的協同者
bean
作為一個依賴注入到單例
bean
)。相反,你隻需要一個
userManager
對象,并且在
HTTP
會話的生存期内,你需要一個特定于HTTP會話的
userPreferences
對象。是以,容器建立一個對象,該對象公開與
UserPreferences
類完全相同的公共接口(理想地,對象是
UserPreferences
執行個體),可以從作用域機制(HTTP 請求,
Session
,以此類推)擷取真正的
UserPreferences
對象。容器注入這個代理對象到
userManager
bean,這并不知道此
UserPreferences
引用是代理。在這個例子中,當
UserManager
執行個體調用在依賴注入
UserPreferences
對象上的方法時,它實際上是在代理上調用方法。然後代理從(在本例中)
HTTP
會話中擷取實際的
UserPreferences
對象,并将方法調用委托給檢索到的實際
UserPreferences
對象。
是以,在将
request-scoped
session-scoped
的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
容器為
bean
建立一個代理,這個
bean
通過
<aop:scoped-proxy/>
元素被标記,基于
CGLIB
的類代理被建立。
CGLIB
代理攔截器僅僅公共方法被調用!在代理上不要調用非公共方法。
或者,你可以為作用域
bean
配置
Spring
容器建立标準的
JDK
基于接口的代理,通過指定
<aop:scoped-proxy/>
元素的
proxy-target-class
屬性值為
false
。使用基于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>
更多詳細資訊關于選擇基于
class
或基于接口代理,參考
代理機制 參考代碼: com.liyong.ioccontainer.starter.XmlBeanScopeIocContainer
1.5.5 自定義作用域
bean
作用域機制是可擴充的。你可以定義你自己的作用域或者甚至重定義存在的作用域,盡管後者被認為是不好的做法,你不能覆寫内置的單例和原型範圍。
- 建立一個自定義作用域
去內建你的自定義作用域到
Spring
容器中,你需要去實作
org.springframework.beans.factory.config.Scope
接口,在這章中描述。有關如何實作自己的作用域的想法,檢視
Scope
實作提供關于
Spring
架構自身和
Scope
的文檔,其中詳細說明了你需要實作的方法。
Scope
接口有四個方法從作用域擷取對象,從作用域移除它們,并且讓它們銷毀。
例如:
Sesson
scope
實作傳回
Season
bean
(如果它不存在,這個方法傳回一個新的
bean
執行個體,将其綁定到會話以供将來引用)。 下面的方法從底層作用域傳回對象:
Object get(String name, ObjectFactory<?> objectFactory)
Session
scope
實作移除Season作用域
bean
從底層的
Session
。對象應該被傳回,但是如果這個對象指定的名稱不存在你也可以傳回
null
。下面的方法從底層作用域移除對象:
Object remove(String name)
以下方法注冊在銷毀作用域或銷毀作用域中的指定對象時應執行的回調:
void registerDestructionCallback(String name, Runnable destructionCallback)
檢視
javadoc或者
Spring
作用域實作關于更多銷毀回調的資訊。
以下方法擷取基礎作用域的會話辨別符:
String getConversationId()
這個表示每個作用域是不同的。對于
Session
作用域的實作,此辨別符可以是
Session
辨別符。
- 使用自定義作用域
在你寫并且測試一個或更多自定義
Scope
實作,你需要去讓
Spring
容器知道你的新作用域。以下方法是在
Spring
容器中注冊新範圍的主要方法:
void registerScope(String scopeName, Scope scope);
這個方法在
ConfigurableBeanFactory
接口上被定義,該接口可通過
Spring
附帶的大多數具體
ApplicationContext
實作上的
BeanFactory
屬性獲得。
registerScope(..)
方法第一個參數是唯一的名字關于作用域。
Spring
容器本身中的此類名稱示例包括單例和原型。
registerScope(..)
方法第二個參數是自定義
Scope
實作
假設你寫你的自定義
Scope
實作并且像下面的例子注冊它。
接下來例子使用,它包括
SimpleThreadScope
但是預設是不被注冊的。對于你自己的自定義範圍實作,是相同的。
Spring
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
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>
當在實作中配置
FactoryBean
時,限定作用域的是工廠
<aop:scoped-proxy/>
本身,而不是從
bean
傳回對象。
getObject()
com.liyong.ioccontainer.starter.XmlCustomScopeIocContainer
作者
個人從事金融行業,就職過易極付、思建科技、某網約車平台等重慶一流技術團隊,目前就職于某銀行負責統一支付系統建設。自身對金融行業有強烈的愛好。同時也實踐大資料、資料存儲、自動化內建和部署、分布式微服務、響應式程式設計、人工智能等領域。同時也熱衷于技術分享創立公衆号和部落格站點對知識體系進行分享。
部落格位址:
http://youngitman.techCSDN:
https://blog.csdn.net/liyong1028826685微信公衆号:
技術交流群: