# 【第三章】 DI 之 3.4 Bean的作用域 ——跟我學spring3
## 3.4? Bean的作用域
什么是作用域呢?即“scope”,在面向對象程序設計中一般指對象或變量之間的可見范圍。而在Spring容器中是指其創建的Bean對象相對于其他Bean對象的請求可見范圍。
Spring提供“singleton”和“prototype”兩種基本作用域,另外提供“request”、“session”、“global session”三種web作用域;Spring還允許用戶定制自己的作用域。
### 3.4.1? 基本的作用域
**一、singleton:**指“singleton”作用域的Bean只會在每個Spring IoC容器中存在一個實例,而且其完整生命周期完全由Spring容器管理。對于所有獲取該Bean的操作Spring容器將只返回同一個Bean。
> |
>
> GoF單例設計模式指“**保證一個類僅有一個實例,并提供一個訪問它的全局訪問點**”,介紹了兩種實現:通過在類上定義靜態屬性保持該實例和通過注冊表方式。
>
> |
**1)通過在類上定義靜態屬性保持該實例:**一般指一個Java虛擬機 ClassLoader裝載的類只有一個實例,一般通過類靜態屬性保持該實例,這樣就造成需要單例的類都需要按照單例設計模式進行編碼;Spring沒采用這種方式,因為該方式屬于侵入式設計;代碼樣例如下:
1. package?cn.javass.spring.chapter3.bean;??
2. public?class?Singleton?{??
3. //1.私有化構造器??
4. private?Singleton()?{}??
5. //2.單例緩存者,惰性初始化,第一次使用時初始化??
6. private?static?class?InstanceHolder?{??
7. private?static?final?Singleton?INSTANCE?=?new?Singleton();??
8. }??
9. //3.提供全局訪問點??
10. public?static?Singleton?getInstance()?{??
11. return?InstanceHolder.INSTANCE;??
12. }??
13. //4.提供一個計數器來驗證一個ClassLoader一個實例??
14. private?int?counter=0;??
15. }??
以上定義個了個單例類,首先要私有化類構造器;其次使用InstanceHolder靜態內部類持有單例對象,這樣可以得到惰性初始化好處;最后提供全局訪問點getInstance,使得需要該單例實例的對象能獲取到;我們在此還提供了一個counter計數器來驗證一個ClassLoader一個實例。具體一個ClassLoader有一個單例實例測試請參考代碼“cn.javass.spring.chapter3\. SingletonTest”中的“testSingleton”測試方法,里邊詳細演示了一個ClassLoader有一個單例實例。
1)??**通過注冊表方式:**?首先將需要單例的實例通過唯一鍵注冊到注冊表,然后通過鍵來獲取單例,讓我們直接看實現吧,注意本注冊表實現了Spring接口“SingletonBeanRegistry”,該接口定義了操作共享的單例對象,Spring容器實現將實現此接口;所以共享單例對象通過“registerSingleton”方法注冊,通過“getSingleton”方法獲取,消除了編程方式單例,注意在實現中不考慮并發:
1. package?cn.javass.spring.chapter3;??
2. import?java.util.HashMap;??
3. import?java.util.Map;??
4. import?org.springframework.beans.factory.config.SingletonBeanRegistry;??
5. public?class?SingletonBeanRegister?implements?SingletonBeanRegistry?{??
6. //單例Bean緩存池,此處不考慮并發??
7. private?final?Map<String,?Object>?BEANS?=?new?HashMap<String,?Object>();??
8. public?boolean?containsSingleton(String?beanName)?{??
9. return?BEANS.containsKey(beanName);??
10. }??
11. public?Object?getSingleton(String?beanName)?{??
12. return?BEANS.get(beanName);??
13. }??
14. @Override??
15. public?int?getSingletonCount()?{??
16. return?BEANS.size();??
17. }??
18. @Override??
19. public?String[]?getSingletonNames()?{??
20. return?BEANS.keySet().toArray(new?String[0]);??
21. }??
22. @Override??
23. public?void?registerSingleton(String?beanName,?Object?bean)?{??
24. if(BEANS.containsKey(beanName))?{??
25. throw?new?RuntimeException("["?+?beanName?+?"]?已存在");??
26. }??
27. BEANS.put(beanName,?bean);??
28. }??
29. }??
Spring是注冊表單例設計模式的實現,消除了編程式單例,而且對代碼是非入侵式。
接下來讓我們看看在Spring中如何配置單例Bean吧,在Spring容器中如果沒指定作用域默認就是“singleton”,配置方式通過scope屬性配置,具體配置如下:
1. <bean??class="cn.javass.spring.chapter3.bean.Printer"?scope="singleton"/>??
Spring管理單例對象在Spring容器中存儲如圖3-5所示,Spring不僅會緩存單例對象,Bean定義也是會緩存的,對于惰性初始化的對象是在首次使用時根據Bean定義創建并存放于單例緩存池。

圖3-5 單例處理
**二、prototype:**即原型,指每次向Spring容器請求獲取Bean都返回一個全新的Bean,相對于“singleton”來說就是不緩存Bean,每次都是一個根據Bean定義創建的全新Bean。
> GoF原型設計模式,指用原型實例指定創建對象的種類,并且通過拷貝這些原型創建新的對象。
Spring中的原型和GoF中介紹的原型含義是不一樣的:
GoF通過用原型實例指定創建對象的種類,而Spring容器用Bean定義指定創建對象的種類;
GoF通過拷貝這些原型創建新的對象,而Spring容器根據Bean定義創建新對象。
其相同地方都是根據某些東西創建新東西,而且GoF原型必須顯示實現克隆操作,屬于侵入式,而Spring容器只需配置即可,屬于非侵入式。
接下來讓我們看看Spring如何實現原型呢?
1)首先讓我們來定義Bean“原型”:Bean定義,所有對象將根據Bean定義創建;在此我們只是簡單示例一下,不會涉及依賴注入等復雜實現:BeanDefinition類定義屬性“class”表示原型類,“id”表示唯一標識,“scope”表示作用域,具體如下:
1. package?cn.javass.spring.chapter3;??
2. public?class?BeanDefinition?{??
3. //單例??
4. public?static?final?int?SCOPE_SINGLETON?=?0;??
5. //原型??
6. public?static?final?int?SCOPE_PROTOTYPE?=?1;??
7. //唯一標識??
8. private?String?id;??
9. //class全限定名??
10. private?String?clazz;??
11. //作用域??
12. private?int?scope?=?SCOPE_SINGLETON;??
13. //鑒于篇幅,省略setter和getter方法;??
14. }??
2)接下來讓我們看看Bean定義注冊表,類似于單例注冊表:
1. package?cn.javass.spring.chapter3;??
2. import?java.util.HashMap;??
3. import?java.util.Map;??
4. public?class?BeanDifinitionRegister?{??
5. //bean定義緩存,此處不考慮并發問題??
6. private?final?Map<String,?BeanDefinition>?DEFINITIONS?=??
7. new?HashMap<String,?BeanDefinition>();??
8. public?void?registerBeanDefinition(String?beanName,?BeanDefinition?bd)?{??
9. //1.本實現不允許覆蓋Bean定義??
10. if(DEFINITIONS.containsKey(bd.getId()))?{??
11. throw?new?RuntimeException("已存在Bean定義,此實現不允許覆蓋");??
12. }??
13. //2.將Bean定義放入Bean定義緩存池??
14. DEFINITIONS.put(bd.getId(),?bd);??
15. }??
16. public?BeanDefinition?getBeanDefinition(String?beanName)?{??
17. return?DEFINITIONS.get(beanName);??
18. }??
19. public?boolean?containsBeanDefinition(String?beanName)?{????????
20. return?DEFINITIONS.containsKey(beanName);??
21. }??
22. }??
3)接下來應該來定義BeanFactory了:
1. package?cn.javass.spring.chapter3;??
2. import?org.springframework.beans.factory.config.SingletonBeanRegistry;??
3. public?class?DefaultBeanFactory?{??
4. //Bean定義注冊表??
5. private?BeanDifinitionRegister?DEFINITIONS?=?new?BeanDifinitionRegister();??
7. //單例注冊表??
8. private?final?SingletonBeanRegistry?SINGLETONS?=?new?SingletonBeanRegister();??
10. public?Object?getBean(String?beanName)?{??
11. //1.驗證Bean定義是否存在??
12. if(!DEFINITIONS.containsBeanDefinition(beanName))?{??
13. throw?new?RuntimeException("不存在["?+?beanName?+?"]Bean定義");??
14. }??
15. //2.獲取Bean定義??
16. BeanDefinition?bd?=?DEFINITIONS.getBeanDefinition(beanName);??
17. //3.是否該Bean定義是單例作用域??
18. if(bd.getScope()?==?BeanDefinition.SCOPE_SINGLETON)?{??
19. //3.1?如果單例注冊表包含Bean,則直接返回該Bean??
20. if(SINGLETONS.containsSingleton(beanName))?{??
21. return?SINGLETONS.getSingleton(beanName);??
22. }??
23. //3.2單例注冊表不包含該Bean,??
24. //則創建并注冊到單例注冊表,從而緩存??
25. SINGLETONS.registerSingleton(beanName,?createBean(bd));??
26. return?SINGLETONS.getSingleton(beanName);??
27. }??
28. //4.如果是原型Bean定義,則直接返回根據Bean定義創建的新Bean,??
29. //每次都是新的,無緩存??
30. if(bd.getScope()?==?BeanDefinition.SCOPE_PROTOTYPE)?{??
31. return?createBean(bd);??
32. }??
33. //5.其他情況錯誤的Bean定義??
34. throw?new?RuntimeException("錯誤的Bean定義");??
35. }??
1. public?void?registerBeanDefinition(BeanDefinition?bd)?{??
2. DEFINITIONS.registerBeanDefinition(bd.getId(),?bd);??
3. }??
5. private?Object?createBean(BeanDefinition?bd)?{??
6. //根據Bean定義創建Bean??
7. try?{??
8. Class?clazz?=?Class.forName(bd.getClazz());??
9. //通過反射使用無參數構造器創建Bean??
10. return?clazz.getConstructor().newInstance();??
11. }?catch?(ClassNotFoundException?e)?{??
12. throw?new?RuntimeException("沒有找到Bean["?+?bd.getId()?+?"]類");??
13. }?catch?(Exception?e)?{??
14. throw?new?RuntimeException("創建Bean["?+?bd.getId()?+?"]失敗");??
15. }??
16. }??
其中方法getBean用于獲取根據beanName對于的Bean定義創建的對象,有單例和原型兩類Bean;registerBeanDefinition方法用于注冊Bean定義,私有方法createBean用于根據Bean定義中的類型信息創建Bean。
3)測試一下吧,在此我們只測試原型作用域Bean,對于每次從Bean工廠中獲取的Bean都是一個全新的對象,代碼片段(BeanFatoryTest)如下:
1. @Test??
2. public?void?testPrototype?()?throws?Exception?{??
3. //1.創建Bean工廠??
4. DefaultBeanFactory?bf?=?new?DefaultBeanFactory();??
5. //2.創建原型?Bean定義??
6. BeanDefinition?bd?=?new?BeanDefinition();??
7. bd.setId("bean");??
8. bd.setScope(BeanDefinition.SCOPE_PROTOTYPE);??
9. bd.setClazz(HelloImpl2.class.getName());??
10. bf.registerBeanDefinition(bd);??
11. //對于原型Bean每次應該返回一個全新的Bean??
12. System.out.println(bf.getBean("bean")?!=?bf.getBean("bean"));??
13. }??
最后讓我們看看如何在Spring中進行配置吧,只需指定<bean>標簽屬性“scope”屬性為“prototype”即可:
1. <bean?class="cn.javass.spring.chapter3.bean.Printer"?scope="prototype"/>??
Spring管理原型對象在Spring容器中存儲如圖3-6所示,Spring不會緩存原型對象,而是根據Bean定義每次請求返回一個全新的Bean:

圖3-6 原型處理
單例和原型作用域我們已經講完,接下來讓我們學習一些在Web應用中有哪些作用域:
### 3.4.2? Web應用中的作用域
在Web應用中,我們可能需要將數據存儲到request、session、?session。因此Spring提供了三種Web作用域:request、session、globalSession。
**一、request作用域:表示每個請求需要容器創建一個全新Bean。**比如提交表單的數據必須是對每次請求新建一個Bean來保持這些表單數據,請求結束釋放這些數據。
**二、session作用域:表示每個會話需要容器創建一個全新Bean。**比如對于每個用戶一般會有一個會話,該用戶的用戶信息需要存儲到會話中,此時可以將該Bean配置為web作用域。
**三、globalSession:**類似于session作用域,只是其用于portlet環境的web應用。如果在非portlet環境將視為session作用域。
配置方式和基本的作用域相同,只是必須要有web環境支持,并配置相應的容器監聽器或攔截器從而能應用這些作用域,我們會在集成web時講解具體使用,大家只需要知道有這些作用域就可以了。
### 3.4.4?自定義作用域
在日常程序開發中,幾乎用不到自定義作用域,除非又必要才進行自定義作用域。
首先讓我們看下Scope接口吧:
1. package?org.springframework.beans.factory.config;??
2. import?org.springframework.beans.factory.ObjectFactory;??
3. public?interface?Scope?{??
4. Object?get(String?name,?ObjectFactory<?>?objectFactory);??
5. Object?remove(String?name);??
6. void?registerDestructionCallback(String?name,?Runnable?callback);??
7. Object?resolveContextualObject(String?key);??
8. String?getConversationId();??
9. }??
1)**Object get(String name, ObjectFactory<?> objectFactory):**用于從作用域中獲取Bean,其中參數objectFactory是當在當前作用域沒找到合適Bean時使用它創建一個新的Bean;
2)**void registerDestructionCallback(String name, Runnable callback):**用于注冊銷毀回調,如果想要銷毀相應的對象則由Spring容器注冊相應的銷毀回調,而由自定義作用域選擇是不是要銷毀相應的對象;
3)**Object resolveContextualObject(String key):**用于解析相應的上下文數據,比如request作用域將返回request中的屬性。
4)**String getConversationId():**作用域的會話標識,比如session作用域將是sessionId。
1. package?cn.javass.spring.chapter3;??
2. import?java.util.HashMap;??
3. import?java.util.Map;??
4. import?org.springframework.beans.factory.ObjectFactory;??
5. import?org.springframework.beans.factory.config.Scope;??
6. public?class?ThreadScope?implements?Scope?{??
7. private?final?ThreadLocal<Map<String,?Object>>?THREAD_SCOPE?=??
8. new?ThreadLocal<Map<String,?Object>>()?{??
9. protected?Map<String,?Object>?initialValue()?{??
10. //用于存放線程相關Bean??
11. return?new?HashMap<String,?Object>();??
12. }??
13. };??
讓我們來實現個簡單的thread作用域,該作用域內創建的對象將綁定到ThreadLocal內。
1. @Override??
2. public?Object?get(String?name,?ObjectFactory<?>?objectFactory)?{??
3. //如果當前線程已經綁定了相應Bean,直接返回??
4. if(THREAD_SCOPE.get().containsKey(name))?{??
5. return?THREAD_SCOPE.get().get(name);??
6. }??
7. //使用objectFactory創建Bean并綁定到當前線程上??
8. THREAD_SCOPE.get().put(name,?objectFactory.getObject());??
9. return?THREAD_SCOPE.get().get(name);??
10. }??
11. @Override??
12. public?String?getConversationId()?{??
13. return?null;??
14. }??
15. @Override??
16. public?void?registerDestructionCallback(String?name,?Runnable?callback)?{??
17. //此處不實現就代表類似proytotype,容器返回給用戶后就不管了??
18. }??
19. @Override??
20. public?Object?remove(String?name)?{??
21. return?THREAD_SCOPE.get().remove(name);??
22. }??
23. @Override??
24. public?Object?resolveContextualObject(String?key)?{??
25. return?null;??
26. }??
27. }??
Scope已經實現了,讓我們將其注冊到Spring容器,使其發揮作用:
1. <bean?class="org.springframework.beans.factory.config.CustomScopeConfigurer">??
2. <property?name="scopes">??
3. <map><entry>??
4. <!--?指定scope關鍵字?--><key><value>thread</value></key>??
5. <!--?scope實現?-->??????<bean?class="cn.javass.spring.chapter3.ThreadScope"/>??
6. </entry></map>??????
7. </property>??
8. </bean>??
通過CustomScopeConfigurer的scopes屬性注冊自定義作用域實現,在此需要指定使用作用域的關鍵字“thread”,并指定自定義作用域實現。來讓我們來定義一個“thread”作用域的Bean,配置(chapter3/threadScope.xml)如下:
1. <bean?id="helloApi"??
2. class="cn.javass.spring.chapter2.helloworld.HelloImpl"??
3. scope="thread"/>??
最后測試(cn.javass.spring.chapter3.ThreadScopeTest)一下吧,首先在一個線程中測試,在同一線程中獲取的Bean應該是一樣的;再讓我們開啟兩個線程,然后應該這兩個線程創建的Bean是不一樣:
自定義作用域實現其實是非常簡單的,其實復雜的是如果需要銷毀Bean,自定義作用域如何正確的銷毀Bean。
原創內容 轉載請注明出處【[http://sishuok.com/forum/blogPost/list/2454.html](http://sishuok.com/forum/blogPost/list/2454.html#7084)】
- 跟我學 Spring3
- 【第二章】 IoC 之 2.1 IoC基礎 ——跟我學Spring3
- 【第二章】 IoC 之 2.2 IoC 容器基本原理 ——跟我學Spring3
- 【第二章】 IoC 之 2.3 IoC的配置使用——跟我學Spring3
- 【第三章】 DI 之 3.1 DI的配置使用 ——跟我學spring3
- 【第三章】 DI 之 3.2 循環依賴 ——跟我學spring3
- 【第三章】 DI 之 3.3 更多DI的知識 ——跟我學spring3
- 【第三章】 DI 之 3.4 Bean的作用域 ——跟我學spring3
- 【第四章】 資源 之 4.1 基礎知識 ——跟我學spring3
- 【第四章】 資源 之 4.2 內置Resource實現 ——跟我學spring3
- 【第四章】 資源 之 4.3 訪問Resource ——跟我學spring3
- 【第四章】 資源 之 4.4 Resource通配符路徑 ——跟我學spring3
- 【第五章】Spring表達式語言 之 5.1 概述 5.2 SpEL基礎 ——跟我學spring3
- 【第五章】Spring表達式語言 之 5.3 SpEL語法 ——跟我學spring3
- 【第五章】Spring表達式語言 之 5.4在Bean定義中使用EL—跟我學spring3
- 【第六章】 AOP 之 6.1 AOP基礎 ——跟我學spring3
- 【第六章】 AOP 之 6.2 AOP的HelloWorld ——跟我學spring3
- 【第六章】 AOP 之 6.3 基于Schema的AOP ——跟我學spring3
- 【第六章】 AOP 之 6.4 基于@AspectJ的AOP ——跟我學spring3
- 【第六章】 AOP 之 6.5 AspectJ切入點語法詳解 ——跟我學spring3
- 【第六章】 AOP 之 6.6 通知參數 ——跟我學spring3
- 【第六章】 AOP 之 6.7 通知順序 ——跟我學spring3
- 【第六章】 AOP 之 6.8 切面實例化模型 ——跟我學spring3
- 【第六章】 AOP 之 6.9 代理機制 ——跟我學spring3
- 【第七章】 對JDBC的支持 之 7.1 概述 ——跟我學spring3
- 【第七章】 對JDBC的支持 之 7.2 JDBC模板類 ——跟我學spring3
- 【第七章】 對JDBC的支持 之 7.3 關系數據庫操作對象化 ——跟我學spring3
- 【第七章】 對JDBC的支持 之 7.4 Spring提供的其它幫助 ——跟我學spring3【私塾在線原創】
- 【第七章】 對JDBC的支持 之 7.5 集成Spring JDBC及最佳實踐 ——跟我學spring3
- 【第八章】 對ORM的支持 之 8.1 概述 ——跟我學spring3
- 【第八章】 對ORM的支持 之 8.2 集成Hibernate3 ——跟我學spring3
- 【第八章】 對ORM的支持 之 8.3 集成iBATIS ——跟我學spring3
- 【第八章】 對ORM的支持 之 8.4 集成JPA ——跟我學spring3
- 【第九章】 Spring的事務 之 9.1 數據庫事務概述 ——跟我學spring3
- 【第九章】 Spring的事務 之 9.2 事務管理器 ——跟我學spring3
- 【第九章】 Spring的事務 之 9.3 編程式事務 ——跟我學spring3
- 【第九章】 Spring的事務 之 9.4 聲明式事務 ——跟我學spring3
- 【第十章】集成其它Web框架 之 10.1 概述 ——跟我學spring3
- 【第十章】集成其它Web框架 之 10.2 集成Struts1.x ——跟我學spring3
- 【第十章】集成其它Web框架 之 10.3 集成Struts2.x ——跟我學spring3
- 【第十章】集成其它Web框架 之 10.4 集成JSF ——跟我學spring3
- 【第十一章】 SSH集成開發積分商城 之 11.1 概述 ——跟我學spring3
- 【第十一章】 SSH集成開發積分商城 之 11.2 實現通用層 ——跟我學spring3
- 【第十一章】 SSH集成開發積分商城 之 11.3 實現積分商城層 ——跟我學spring3
- 【第十二章】零配置 之 12.1 概述 ——跟我學spring3
- 【第十二章】零配置 之 12.2 注解實現Bean依賴注入 ——跟我學spring3
- 【第十二章】零配置 之 12.3 注解實現Bean定義 ——跟我學spring3
- 【第十二章】零配置 之 12.4 基于Java類定義Bean配置元數據 ——跟我學spring3
- 【第十二章】零配置 之 12.5 綜合示例-積分商城 ——跟我學spring3
- 【第十三章】 測試 之 13.1 概述 13.2 單元測試 ——跟我學spring3
- 【第十三章】 測試 之 13.3 集成測試 ——跟我學spring3
- 跟我學 Spring MVC
- SpringMVC + spring3.1.1 + hibernate4.1.0 集成及常見問題總結
- Spring Web MVC中的頁面緩存支持 ——跟我學SpringMVC系列
- Spring3 Web MVC下的數據類型轉換(第一篇)——《跟我學Spring3 Web MVC》搶先看
- Spring3 Web MVC下的數據格式化(第二篇)——《跟我學Spring3 Web MVC》搶先看
- 第一章 Web MVC簡介 —— 跟開濤學SpringMVC
- 第二章 Spring MVC入門 —— 跟開濤學SpringMVC
- 第三章 DispatcherServlet詳解 ——跟開濤學SpringMVC
- 第四章 Controller接口控制器詳解(1)——跟著開濤學SpringMVC
- 第四章 Controller接口控制器詳解(2)——跟著開濤學SpringMVC
- 第四章 Controller接口控制器詳解(3)——跟著開濤學SpringMVC
- 第四章 Controller接口控制器詳解 (4)——跟著開濤學SpringMVC
- 第四章 Controller接口控制器詳解(5)——跟著開濤學SpringMVC
- 跟著開濤學SpringMVC 第一章源代碼下載
- 第二章 Spring MVC入門 源代碼下載
- 第四章 Controller接口控制器詳解 源代碼下載
- 第四章 Controller接口控制器詳解(6)——跟著開濤學SpringMVC
- 第四章 Controller接口控制器詳解(7 完)——跟著開濤學SpringMVC
- 第五章 處理器攔截器詳解——跟著開濤學SpringMVC
- 源代碼下載 第五章 處理器攔截器詳解——跟著開濤學SpringMVC
- 注解式控制器運行流程及處理器定義 第六章 注解式控制器詳解——跟著開濤學SpringMVC
- 源代碼下載 第六章 注解式控制器詳解
- SpringMVC3強大的請求映射規則詳解 第六章 注解式控制器詳解——跟著開濤學SpringMVC
- Spring MVC 3.1新特性 生產者、消費者請求限定 —— 第六章 注解式控制器詳解——跟著開濤學SpringMVC
- SpringMVC強大的數據綁定(1)——第六章 注解式控制器詳解——跟著開濤學SpringMVC
- SpringMVC強大的數據綁定(2)——第六章 注解式控制器詳解——跟著開濤學SpringMVC
- SpringMVC數據類型轉換——第七章 注解式控制器的數據驗證、類型轉換及格式化——跟著開濤學SpringMVC
- SpringMVC數據格式化——第七章 注解式控制器的數據驗證、類型轉換及格式化——跟著開濤學SpringMVC
- SpringMVC數據驗證——第七章 注解式控制器的數據驗證、類型轉換及格式化——跟著開濤學SpringMVC