您好,登錄后才能下訂單哦!
這篇文章給大家介紹使用SpringBean怎么實現作用域管理,內容非常詳細,感興趣的小伙伴們可以參考借鑒,希望對大家能有所幫助。
創建 BeanDefinition 時,就等于創建了一個配方,用于創建由 BeanDefinition 所定義的類實例。BeanDefinition 是配方的這種思想很重要,因為這意味著,與使用類一樣,也可通過一個配方創建多個對象實例。
有如下優點:
可以控制要插入到從特定 BeanDefinition 創建的對象中的各種依賴項和配置值
可以控制從特定 BeanDefinition 創建的對象的作用域。
這種方式功能強大且靈活,因為開發者可以選擇通過配置創建的對象的作用域,而不必在Java類級別上考慮對象的范圍。
當然了,作為靈活的框架,Spring 還允許開發者創建自定義的作用域。
僅管理一個singleton bean的一個共享實例,并且所有對具有ID或與該 BeanDefinition 相匹配的ID的bean的請求都將導致該特定的bean實例由Spring容器返回。
換言之,當我們定義了一個 BeanDefinition 并且其作用域為 singleton 時,IoC容器將為該 BeanDefinition 所定義的對象創建一個實例。該單實例存儲在此類單例bean的緩存中,并且對該命名bean的所有后續請求和引用都返回該緩存的對象。
Spring的 singleton bean概念與傳說中的四人幫創建的《Gang of Four (GoF) patterns》一書中定義的單例模式并不一樣。
GoF的單例模式會硬編碼對象的作用域,使得每個類加載器只能創建一個特定類的唯一實例
因此,最恰當的應該將Spring單例的作用域描述為一個容器對應一個bean。若我們在單個Spring容器中為特定類定義一個bean,則Spring容器將創建該 BeanDefinition 所定義的類的一個且只有一個實例。
單例作用域是Spring中的默認作用域。要將bean定義為XML中的單例,可以定義bean,如以下示例所示:
Bean部署的非單一原型作用域會在每次請求特定bean時創建一個新bean實例。也就是說,該Bean被注入到另一個Bean中,或者您可以通過容器上的getBean()方法調用來請求它。通常,應將原型作用域用于所有有狀態Bean,將單例作用域用于無狀態Bean。
在 Spring 中,那些組成應用程序的主體及由 Spring IOC 容器所管理的對象,被稱之為 bean。
簡單地講,bean 就是由 IOC 容器初始化、裝配及管理的對象,除此之外,bean 就與應用程序中的其他對象沒有什么區別了。
而 bean 的定義以及 bean 相互間的依賴關系將通過配置元數據來描述。
Spring中的bean默認都是單例的,這些單例Bean在多線程程序下如何保證線程安全呢?
例如對于Web應用來說,Web容器對于每個用戶請求都創建一個單獨的Sevlet線程來處理請求,引入Spring框架之后,每個Action都是單例的,那么對于Spring托管的單例Service Bean,如何保證其安全呢?
Spring的單例是基于BeanFactory也就是Spring容器的,單例Bean在此容器內只有一個Java的單例是基于 JVM,每個 JVM 內只有一個實例
創建一個bean定義,其實質是用該bean定義對應的類來創建真正實例的“配方”。
把bean定義看成一個配方很有意義,它與class很類似,只根據一張“處方”就可以創建多個實例。
不僅可以控制注入到對象中的各種依賴和配置值,還可以控制該對象的作用域。
這樣可以靈活選擇所建對象的作用域,而不必在Java Class級定義作用域。
Spring Framework支持五種作用域,分別闡述如下表。
五種作用域中,request、session 和 global session 三種作用域僅在基于web的應用中使用(不必關心你所采用的是什么web應用框架),只能用在基于 web 的 Spring ApplicationContext 環境。
當一個 bean 的作用域為 singleton,那么Spring IoC容器中只會存在一個共享的 bean 實例,并且所有對 bean 的請求,只要 id 與該 bean 定義相匹配,則只會返回bean的同一實例。
singleton 是單例類型(對應于單例模式),就是在創建起容器時就同時自動創建了一個bean的對象,不管你是否使用,但我們可以指定Bean節點的 lazy-init=”true”
來延遲初始化bean,這時候,只有在第一次獲取bean時才會初始化bean,即第一次請求該bean時才初始化。
每次獲取到的對象都是同一個對象。注意,singleton 作用域是Spring中的缺省作用域。要在XML中將 bean 定義成 singleton ,可以這樣配置:
<bean id="ServiceImpl" class="cn.csdn.service.ServiceImpl" scope="singleton">
也可以通過 @Scope
注解(它可以顯示指定bean的作用范圍。)的方式
@Service @Scope("singleton") public class ServiceImpl{ }
當一個bean的作用域為 prototype,表示一個 bean 定義對應多個對象實例。
prototype 作用域的 bean 會導致在每次對該 bean 請求(將其注入到另一個 bean 中,或者以程序的方式調用容器的 getBean() 方法)時都會創建一個新的 bean 實例。prototype 是原型類型,它在我們創建容器的時候并沒有實例化,而是當我們獲取bean的時候才會去創建一個對象,而且我們每次獲取到的對象都不是同一個對象。
對有狀態的 bean 應該使用 prototype 作用域,而對無狀態的 bean 則應該使用 singleton 作用域。
在 XML 中將 bean 定義成 prototype
<bean id="account" class="com.foo.DefaultAccount" scope="prototype"/> 或者 <bean id="account" class="com.foo.DefaultAccount" singleton="false"/>
通過 @Scope
注解的方式實現就不做演示了。
該bean僅在當前HTTP request內有效
request只適用于Web程序,每一次 HTTP 請求都會產生一個新的bean,同時該bean僅在當前HTTP request內有效,當請求結束后,該對象的生命周期即告結束。
在 XML 中將 bean 定義成 request ,可以這樣配置:
<bean id="loginAction" class=cn.csdn.LoginAction" scope="request"/>
該bean僅在當前 HTTP session 內有效。
session只適用于Web程序,session 作用域表示該針對每一次 HTTP 請求都會產生一個新的 bean,同時該 bean 僅在當前 HTTP session 內有效.與request作用域一樣,可以根據需要放心的更改所創建實例的內部狀態,而別的 HTTP session 中根據 userPreferences 創建的實例,將不會看到這些特定于某個 HTTP session 的狀態變化。當HTTP session最終被廢棄的時候,在該HTTP session作用域內的bean也會被廢棄掉。
<bean id="userPreferences" class="com.foo.UserPreferences" scope="session"/>
global session 作用域類似于標準的 HTTP session 作用域,不過僅僅在基于 portlet 的 web 應用中才有意義。Portlet 規范定義了全局 Session 的概念,它被所有構成某個 portlet web 應用的各種不同的 portle t所共享。在global session 作用域中定義的 bean 被限定于全局portlet Session的生命周期范圍內。
<bean id="user" class="com.foo.Preferences "scope="globalSession"/>
Spring框架支持5種作用域,有三種作用域是當開發者使用基于web的ApplicationContext
的時候才生效的
下面就是Spring內置支持的作用域
作用域 | 描述 |
單例(singleton) | (默認)每一個Spring IoC容器都擁有唯一的一個實例對象 |
原型(prototype) | 一個Bean定義可以創建任意多個實例對象 |
請求(request) | 一個HTTP請求會產生一個Bean對象,也就是說,每一個HTTP請求都有自己的Bean實例。只在基于web的Spring ApplicationContext 中可用 |
會話(session) | 限定一個Bean的作用域為HTTPsession 的生命周期。同樣,只有基于web的Spring ApplicationContext 才能使用 |
全局會話(global session) | 限定一個Bean的作用域為全局HTTPSession 的生命周期。通常用于門戶網站場景,同樣,只有基于web的Spring ApplicationContext 可用 |
應用(application) | 限定一個Bean的作用域為ServletContext 的生命周期。同樣,只有基于web的Spring ApplicationContext 可用 |
在Spring 3.0中,線程作用域是可用的,但不是默認注冊的
全局只有一個共享的實例,所有將單例Bean作為依賴的情況下,容器返回將是同一個實例
換言之,當開發者定義一個Bean的作用域為單例時,Spring IoC容器只會根據Bean定義來創建該Bean的唯一實例。這些唯一的實例會緩存到容器中,后續針對單例Bean的請求和引用,都會從這個緩存中拿到這個唯一的實例
單例模式是將一個對象的作用域硬編碼,一個ClassLoader只有唯一的一個實例
而Spring的單例作用域,是基于每個容器
,每個Bean只有一個實例
這意味著,如果開發者根據一個類定義了一個Bean在單個的Spring容器中,那么Spring容器會根據Bean定義創建一個唯一的Bean實例。默認情況下,它們為每個給定的org.springframework.context.ApplicationContext實例存在唯一的一個bean (有點別扭,也就是可以有多個Spring容器,每一個容器內存在唯一bean實例).這意味著如果你有兩個或更多上下文,所有這些上下文都由同一Java的類加載器管理(因為在同一個jvm環境中),則可能會有多個給定bean的實例。唯一需要做到的是必須在每個上下文中定義此bean.
所以你可以看到,bean只是一個上下文的單例
你不應該將Spring的單例概念與設計模式中的的單例混淆
單例作用域是Spring的默認作用域,下面的例子是在基于XML的配置中配置單例模式的Bean。
<bean id="accountService" class="com.sss.DefaultAccountService"/> <!-- the following is equivalent, though redundant (singleton scope is the default) --> <bean id="accountService" class="com.sss.DefaultAccountService" scope="singleton"/>
每次請求Bean實例時,返回的都是新實例的Bean對象
也就是說,每次注入到另外的Bean或者通過調用getBean()
來獲得的Bean都將是全新的實例。
這是基于線程安全性:
有狀態的Bean對象用prototype作用域
無狀態的Bean對象用singleton 作用域
下面的例子說明了Spring的原型作用域。DAO通常不會配置為原型對象,因為典型的DAO是不會有任何的狀態的。
下面的例子展示了XML中如何定義一個原型的Bean:
<bean id="accountService" class="com.javaedge.DefaultAccountService" scope="prototype"/>
與其他的作用域相比,Spring不會完全管理原型Bean的生命周期:
Spring容器只會初始化配置以及裝載這些Bean,傳遞給Client。
但是之后就不會再去管原型Bean之后的動作了。
也就是說,初始化生命周期回調方法在所有作用域的Bean是都會調用的,但是銷毀生命周期回調方法在原型Bean是不會調用的
所以,客戶端代碼必須注意清理原型Bean以及釋放原型Bean所持有的一些資源。
可以通過使用自定義的bean post-processor
來讓Spring釋放掉原型Bean所持有的資源。
在某些方面來說,Spring容器的角色就是取代了Java的new
操作符,所有的生命周期的控制需要由客戶端來處理。
Singleton beans with prototype-bean dependencies 在原型bean中放置單例
如果注入的單例對象真的是一個單例的bean(沒有狀態),這個真的沒一點問題
想象一下,對于我們的購物車,我們需要注入產品服務。此服務只會檢查添加到購物車的產品是否庫存。由于服務沒有狀態,并且會基于在方法簽名中所傳遞的對象進行驗證,因此不存在風險
當使用單例Bean的時候,而該單例Bean的依賴是原型Bean時,需要注意的是依賴的解析都是在初始化的階段
因此,如果將原型Bean注入到單例的Bean之中,只會請求一次原型Bean,然后注入到單例Bean中。這個依賴的原型Bean仍然屬于只有一個實例的。
然而,假設你需要單例Bean對原型的Bean的依賴
需要每次在運行時都請求一個新的實例,那么你就不能夠將一個原型的Bean來注入到一個單例的Bean當中了,因為依賴注入只會進行一次
當Spring容器在實例化單例Bean的時候,就會解析以及注入它所需的依賴
如果實在需要每次都請求一個新的實例,可以通過bean工廠手動獲取實例,也可以參考Dependencies中的方法注入部分。
##使用單例還是原型?
Singleton適用于無狀態的bean,比如一個service,DAO或者controller
他們都沒有自己的狀態(舉個簡單的例子,一個函數sin(x),這個函數本身就是無狀態的,所以我們現在喜歡的函數式編程也遵循這個理念)。而是根據傳輸的參數執行一些操作(作為HTTP請求參數)。
另一方面,我們可以通過狀態bean管理一些狀態。比如購物車bean,假如它是一個單例,那么兩個不同消費者購買的產品將被放置在同一個對象上。而如果其中一個消費者想要刪除一個產品,另一個消費者就鐵定不高興。這也就是狀態類對象應該是原型
#3. Request
Spring容器會在每次用到loginAction
來處理每個HTTP請求的時候都會創建一個新的LoginAction
實例。也就是說,loginAction
Bean的作用域是HTTPRequest
級別的。 開發者可以隨意改變實例的狀態,因為其他通過loginAction
請求來創建的實例根本看不到開發者改變的實例狀態,所有創建的Bean實例都是根據獨立的請求來的。當請求處理完畢,這個Bean也會銷毀。
每個請求初始化具有此作用域的Bean注解。這聽起來像是原型作用域的描述,但它們有一些差異。
原型作用域在Spring的上下文中可用。而請求作用域僅適用于Web應用程序
原型bean根據需求進行初始化,而請求bean是在每個請求下構建的
需要說的是,request作用域bean在其作用域內有且僅有一個實例。而你可以擁有一個或多個原型作用域bean實例
在以下代碼中,你可以看到請求作用域bean的示例:
<bean id="shoppingCartRequest" class="com.sss.scope.ShoppingCartRequest" scope="request"> <aop:scoped-proxy/> </bean>
當使用注解驅動組件或Java Config時,@RequestScope注解可以用于將一個組件分配給request作用域。
@RequestScope @Component public class ShoppingCartRequest { // ... } // request bean // injection sample @Controller public class TestController { @Autowired private ShoppingCartRequest shoppingCartRequest; @RequestMapping(value = "/test", method = RequestMethod.GET) public String test(HttpServletRequest request) { LOGGER.debug("shoppingCartRequest is :"+shoppingCartRequest); // ... } }
請注意定義內存在的<aop: scoped-proxy />標簽。這代表著使用代理對象。所以實際上,TestController持有的是代理對象的引用。我們所有的調用該對象都會轉發到真正的ShoppingCartRequest對象。
有時我們需要使用DispatcherServlet的另一個servlet來處理請求。在這種情況下,我們必須確保Spring中所有請求都可用(否則可以拋出與下面類似的異常)。為此,我們需要在web.xml中定義一個監聽器:
<listener> <listener-class>org.springframework.web.context.request.RequestContextListener</listener-class> </listener>
調用/測試URL后,你應該能在日志中的發現以下信息:
shoppingCartRequest is :com.migo.scope.ShoppingCartRequest@2586b11c shoppingCartRequest is :com.migo.scope.ShoppingCartRequest@3bd5b945
如果我們嘗試在單例bean中使用request作用域的bean,則會在應用程序上下文加載階段拋出一個BeanCreationException
#4. session
參考如下的Bean定義:
<bean id="userPreferences" class="com.foo.UserPreferences" scope="session"/>
Spring容器會在每次調用到userPreferences
在一個單獨的HTTP會話周期來創建一個新的UserPreferences
實例。換言之,userPreferences
Bean的作用域是HTTPSession
級別的。 在request-scoped
作用域的Bean上,開發者可以隨意的更改實例的狀態,同樣,其他的HTTPSession
基本的實例在每個Session都會請求userPreferences
來創建新的實例,所以開發者更改Bean的狀態,對于其他的Bean仍然是不可見的。當HTTPSession
銷毀了,那么根據這個Session
來創建的Bean也就銷毀了。
Session作用域的bean與request 作用域的bean沒有太大不同。它們也與純Web應用程序上下文相關聯。注解為Session作用域的Bean對于每個用戶的會話僅創建一次。他們在會話結束時被破壞銷毀掉。
由Session作用域限制的Bean可以被認為是面向Web的單例,因為給定環境(用戶會話)僅存在一個實例。但請記住,你無法在Web應用程序上下文中使用它們(說個好理解點的,就是一個函數內部自定義變量所在的作用域,函數執行完就銷毀了,沒有什么逃逸)。
想知道Session作用域bean在Spring中的操作,我們需要在配置文件中定義一個bean:
<bean id="shoppingCartRequest" class="com.migo.scope.ShoppingCartSession" scope="session"> <aop:scoped-proxy/> </bean>
通過@Autowired
注解,查找這個bean的方式與request 作用域的bean相同。可以看到以下結果:
你可以看到,前5個打印輸出代表相同的對象。最后一個是不同的。這是什么意思 ?簡單來說,這代表 著一個新的用戶使用自動注入的Session作用域訪問該頁面。我們可以通過打開兩個瀏覽器的測試頁(/test)來觀察它。每個都將初始化一個新的會話Session,因此也就創建新的ShoppingCartSession bean
實例。
#5. global session
該部分主要是描述
portlet
的,詳情可以Google更多關于portlet
的相關信息。
參考如下的Bean定義:
<bean id="userPreferences" class="com.foo.UserPreferences" scope="globalSession"/>
global session
作用域比較類似之前提到的標準的HTTPSession
,這種作用域是只應用于基于門戶(portlet-based)的web應用的上下之中的。門戶的Spec中定義的global session
的意義:global session
被所有構成門戶的web應用所共享。定義為global session
作用域的Bean是作用在全局門戶Session
的聲明周期的。
如果在使用標準的基于Servlet的Web應用,而且定義了global session
作用域的Bean,那么只是會使用標準的HTTPSession
作用域,不會報錯。
關于全局會話作用域(Global session scope)屬于4.3x的范疇了,Spring5已經沒有了,Spring5文檔是去掉了因為4的存在所以還是說兩句,它保留給portlet應用程序。 是不是一臉懵逼,so,來解釋一下portlet是什么。Portlet是能夠生成語義代碼(例如:HTML)片段的小型Java Web插件。它們基于portlet容器,可以像servlet一樣處理HTTP請求。但是,與servlet不同,每個portlet都有不同的會話。在這種情況下,Spring提供了一個名為global-session
的作用域。通過它,一個bean可以通過應用程序中的多個portlet共享。
關于使用SpringBean怎么實現作用域管理就分享到這里了,希望以上內容可以對大家有一定的幫助,可以學到更多知識。如果覺得文章不錯,可以把它分享出去讓更多的人看到。
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。