DI 之 3.4 Bean的作用域(捌)
3.4? Bean的作用域
?????? 什么是作用域呢?即“scope”,在面向?qū)ο蟪绦蛟O(shè)計中一般指對象或變量之間的可見范圍。而在Spring容器中是指其創(chuàng)建的Bean對象相對于其他Bean對象的請求可見范圍。
Spring提供“singleton”和“prototype”兩種基本作用域,另外提供“request”、“session”、“global session”三種web作用域;Spring還允許用戶定制自己的作用域。
3.4.1? 基本的作用域
???????一、singleton:指“singleton”作用域的Bean只會在每個Spring IoC容器中存在一個實例,而且其完整生命周期完全由Spring容器管理。對于所有獲取該Bean的操作Spring容器將只返回同一個Bean。
GoF單例設(shè)計模式指“保證一個類僅有一個實例,并提供一個訪問它的全局訪問點”,介紹了兩種實現(xiàn):通過在類上定義靜態(tài)屬性保持該實例和通過注冊表方式。1)通過在類上定義靜態(tài)屬性保持該實例:一般指一個Java虛擬機 ClassLoader裝載的類只有一個實例,一般通過類靜態(tài)屬性保持該實例,這樣就造成需要單例的類都需要按照單例設(shè)計模式進行編碼;Spring沒采用這種方式,因為該方式屬于侵入式設(shè)計;代碼樣例如下:
package cn.javass.spring.chapter3.bean; public class Singleton { //1.私有化構(gòu)造器 private Singleton() {} //2.單例緩存者,惰性初始化,第一次使用時初始化 private static class InstanceHolder { private static final Singleton INSTANCE = new Singleton(); } //3.提供全局訪問點 public static Singleton getInstance() { return InstanceHolder.INSTANCE; } //4.提供一個計數(shù)器來驗證一個ClassLoader一個實例 private int counter=0; }以上定義個了個單例類,首先要私有化類構(gòu)造器;其次使用InstanceHolder靜態(tài)內(nèi)部類持有單例對象,這樣可以得到惰性初始化好處;最后提供全局訪問點getInstance,使得需要該單例實例的對象能獲取到;我們在此還提供了一個counter計數(shù)器來驗證一個ClassLoader一個實例。具體一個ClassLoader有一個單例實例測試請參考代碼“cn.javass.spring.chapter3. SingletonTest”中的“testSingleton”測試方法,里邊詳細演示了一個ClassLoader有一個單例實例。
?
1)??通過注冊表方式:?首先將需要單例的實例通過唯一鍵注冊到注冊表,然后通過鍵來獲取單例,讓我們直接看實現(xiàn)吧,注意本注冊表實現(xiàn)了Spring接口“SingletonBeanRegistry”,該接口定義了操作共享的單例對象,Spring容器實現(xiàn)將實現(xiàn)此接口;所以共享單例對象通過“registerSingleton”方法注冊,通過“getSingleton”方法獲取,消除了編程方式單例,注意在實現(xiàn)中不考慮并發(fā):
package cn.javass.spring.chapter3; import java.util.HashMap; import java.util.Map; import org.springframework.beans.factory.config.SingletonBeanRegistry; public class SingletonBeanRegister implements SingletonBeanRegistry { //單例Bean緩存池,此處不考慮并發(fā) private final Map<String, Object> BEANS = new HashMap<String, Object>(); public boolean containsSingleton(String beanName) { return BEANS.containsKey(beanName); } public Object getSingleton(String beanName) { return BEANS.get(beanName); } @Override public int getSingletonCount() { return BEANS.size(); } @Override public String[] getSingletonNames() { return BEANS.keySet().toArray(new String[0]); } @Override public void registerSingleton(String beanName, Object bean) { if(BEANS.containsKey(beanName)) { throw new RuntimeException("[" + beanName + "] 已存在"); } BEANS.put(beanName, bean); } }Spring是注冊表單例設(shè)計模式的實現(xiàn),消除了編程式單例,而且對代碼是非入侵式。
接下來讓我們看看在Spring中如何配置單例Bean吧,在Spring容器中如果沒指定作用域默認就是“singleton”,配置方式通過scope屬性配置,具體配置如下:
<bean class="cn.javass.spring.chapter3.bean.Printer" scope="singleton"/>Spring管理單例對象在Spring容器中存儲如圖3-5所示,Spring不僅會緩存單例對象,Bean定義也是會緩存的,對于惰性初始化的對象是在首次使用時根據(jù)Bean定義創(chuàng)建并存放于單例緩存池。
圖3-5 單例處理
?
二、prototype:即原型,指每次向Spring容器請求獲取Bean都返回一個全新的Bean,相對于“singleton”來說就是不緩存Bean,每次都是一個根據(jù)Bean定義創(chuàng)建的全新Bean。
GoF原型設(shè)計模式,指用原型實例指定創(chuàng)建對象的種類,并且通過拷貝這些原型創(chuàng)建新的對象。Spring中的原型和GoF中介紹的原型含義是不一樣的:
? ? ? ? ?GoF通過用原型實例指定創(chuàng)建對象的種類,而Spring容器用Bean定義指定創(chuàng)建對象的種類;
? ? ? ? ?GoF通過拷貝這些原型創(chuàng)建新的對象,而Spring容器根據(jù)Bean定義創(chuàng)建新對象。
其相同地方都是根據(jù)某些東西創(chuàng)建新東西,而且GoF原型必須顯示實現(xiàn)克隆操作,屬于侵入式,而Spring容器只需配置即可,屬于非侵入式。
?
接下來讓我們看看Spring如何實現(xiàn)原型呢?
?
1)首先讓我們來定義Bean“原型”:Bean定義,所有對象將根據(jù)Bean定義創(chuàng)建;在此我們只是簡單示例一下,不會涉及依賴注入等復(fù)雜實現(xiàn):BeanDefinition類定義屬性“class”表示原型類,“id”表示唯一標識,“scope”表示作用域,具體如下:
package cn.javass.spring.chapter3; public class BeanDefinition { //單例 public static final int SCOPE_SINGLETON = 0; //原型 public static final int SCOPE_PROTOTYPE = 1; //唯一標識 private String id; //class全限定名 private String clazz; //作用域 private int scope = SCOPE_SINGLETON; //鑒于篇幅,省略setter和getter方法; }2)接下來讓我們看看Bean定義注冊表,類似于單例注冊表:
package cn.javass.spring.chapter3; import java.util.HashMap; import java.util.Map; public class BeanDifinitionRegister { //bean定義緩存,此處不考慮并發(fā)問題 private final Map<String, BeanDefinition> DEFINITIONS = new HashMap<String, BeanDefinition>(); public void registerBeanDefinition(String beanName, BeanDefinition bd) { //1.本實現(xiàn)不允許覆蓋Bean定義 if(DEFINITIONS.containsKey(bd.getId())) { throw new RuntimeException("已存在Bean定義,此實現(xiàn)不允許覆蓋"); } //2.將Bean定義放入Bean定義緩存池 DEFINITIONS.put(bd.getId(), bd); } public BeanDefinition getBeanDefinition(String beanName) { return DEFINITIONS.get(beanName); } public boolean containsBeanDefinition(String beanName) { return DEFINITIONS.containsKey(beanName); } }3)接下來應(yīng)該來定義BeanFactory了:
package cn.javass.spring.chapter3; import org.springframework.beans.factory.config.SingletonBeanRegistry; public class DefaultBeanFactory { //Bean定義注冊表 private BeanDifinitionRegister DEFINITIONS = new BeanDifinitionRegister(); //單例注冊表 private final SingletonBeanRegistry SINGLETONS = new SingletonBeanRegister(); public Object getBean(String beanName) { //1.驗證Bean定義是否存在 if(!DEFINITIONS.containsBeanDefinition(beanName)) { throw new RuntimeException("不存在[" + beanName + "]Bean定義"); } //2.獲取Bean定義 BeanDefinition bd = DEFINITIONS.getBeanDefinition(beanName); //3.是否該Bean定義是單例作用域 if(bd.getScope() == BeanDefinition.SCOPE_SINGLETON) { //3.1 如果單例注冊表包含Bean,則直接返回該Bean if(SINGLETONS.containsSingleton(beanName)) { return SINGLETONS.getSingleton(beanName); } //3.2單例注冊表不包含該Bean, //則創(chuàng)建并注冊到單例注冊表,從而緩存 SINGLETONS.registerSingleton(beanName, createBean(bd)); return SINGLETONS.getSingleton(beanName); } //4.如果是原型Bean定義,則直接返回根據(jù)Bean定義創(chuàng)建的新Bean, //每次都是新的,無緩存 if(bd.getScope() == BeanDefinition.SCOPE_PROTOTYPE) { return createBean(bd); } //5.其他情況錯誤的Bean定義 throw new RuntimeException("錯誤的Bean定義"); } public void registerBeanDefinition(BeanDefinition bd) { DEFINITIONS.registerBeanDefinition(bd.getId(), bd); } private Object createBean(BeanDefinition bd) { //根據(jù)Bean定義創(chuàng)建Bean try { Class clazz = Class.forName(bd.getClazz()); //通過反射使用無參數(shù)構(gòu)造器創(chuàng)建Bean return clazz.getConstructor().newInstance(); } catch (ClassNotFoundException e) { throw new RuntimeException("沒有找到Bean[" + bd.getId() + "]類"); } catch (Exception e) { throw new RuntimeException("創(chuàng)建Bean[" + bd.getId() + "]失敗"); } }其中方法getBean用于獲取根據(jù)beanName對于的Bean定義創(chuàng)建的對象,有單例和原型兩類Bean;registerBeanDefinition方法用于注冊Bean定義,私有方法createBean用于根據(jù)Bean定義中的類型信息創(chuàng)建Bean。
?
3)測試一下吧,在此我們只測試原型作用域Bean,對于每次從Bean工廠中獲取的Bean都是一個全新的對象,代碼片段(BeanFatoryTest)如下:
@Test public void testPrototype () throws Exception { //1.創(chuàng)建Bean工廠 DefaultBeanFactory bf = new DefaultBeanFactory(); //2.創(chuàng)建原型 Bean定義 BeanDefinition bd = new BeanDefinition(); bd.setId("bean"); bd.setScope(BeanDefinition.SCOPE_PROTOTYPE); bd.setClazz(HelloImpl2.class.getName()); bf.registerBeanDefinition(bd); //對于原型Bean每次應(yīng)該返回一個全新的Bean System.out.println(bf.getBean("bean") != bf.getBean("bean")); }最后讓我們看看如何在Spring中進行配置吧,只需指定<bean>標簽屬性“scope”屬性為“prototype”即可:
<bean class="cn.javass.spring.chapter3.bean.Printer" scope="prototype"/>?Spring管理原型對象在Spring容器中存儲如圖3-6所示,Spring不會緩存原型對象,而是根據(jù)Bean定義每次請求返回一個全新的Bean:
圖3-6 原型處理
?????? 單例和原型作用域我們已經(jīng)講完,接下來讓我們學(xué)習(xí)一些在Web應(yīng)用中有哪些作用域:
?
3.4.2? Web應(yīng)用中的作用域
在Web應(yīng)用中,我們可能需要將數(shù)據(jù)存儲到request、session、global?session。因此Spring提供了三種Web作用域:request、session、globalSession。
?
一、request作用域:表示每個請求需要容器創(chuàng)建一個全新Bean。比如提交表單的數(shù)據(jù)必須是對每次請求新建一個Bean來保持這些表單數(shù)據(jù),請求結(jié)束釋放這些數(shù)據(jù)。
?
二、session作用域:表示每個會話需要容器創(chuàng)建一個全新Bean。比如對于每個用戶一般會有一個會話,該用戶的用戶信息需要存儲到會話中,此時可以將該Bean配置為web作用域。
?
三、globalSession:類似于session作用域,只是其用于portlet環(huán)境的web應(yīng)用。如果在非portlet環(huán)境將視為session作用域。
?
配置方式和基本的作用域相同,只是必須要有web環(huán)境支持,并配置相應(yīng)的容器監(jiān)聽器或攔截器從而能應(yīng)用這些作用域,我們會在集成web時講解具體使用,大家只需要知道有這些作用域就可以了。
?
3.4.4?自定義作用域
在日常程序開發(fā)中,幾乎用不到自定義作用域,除非又必要才進行自定義作用域。
首先讓我們看下Scope接口吧:
package org.springframework.beans.factory.config; import org.springframework.beans.factory.ObjectFactory; public interface Scope { Object get(String name, ObjectFactory<?> objectFactory); Object remove(String name); void registerDestructionCallback(String name, Runnable callback); Object resolveContextualObject(String key); String getConversationId(); }?
1)Object get(String name, ObjectFactory<?> objectFactory):用于從作用域中獲取Bean,其中參數(shù)objectFactory是當在當前作用域沒找到合適Bean時使用它創(chuàng)建一個新的Bean;
?
2)void registerDestructionCallback(String name, Runnable callback):用于注冊銷毀回調(diào),如果想要銷毀相應(yīng)的對象則由Spring容器注冊相應(yīng)的銷毀回調(diào),而由自定義作用域選擇是不是要銷毀相應(yīng)的對象;
?
3)Object resolveContextualObject(String key):用于解析相應(yīng)的上下文數(shù)據(jù),比如request作用域?qū)⒎祷豶equest中的屬性。
?
4)String getConversationId():作用域的會話標識,比如session作用域?qū)⑹莝essionId。
package cn.javass.spring.chapter3; import java.util.HashMap; import java.util.Map; import org.springframework.beans.factory.ObjectFactory; import org.springframework.beans.factory.config.Scope; public class ThreadScope implements Scope { private final ThreadLocal<Map<String, Object>> THREAD_SCOPE = new ThreadLocal<Map<String, Object>>() { protected Map<String, Object> initialValue() { //用于存放線程相關(guān)Bean return new HashMap<String, Object>(); } };?讓我們來實現(xiàn)個簡單的thread作用域,該作用域內(nèi)創(chuàng)建的對象將綁定到ThreadLocal內(nèi)。
@Override public Object get(String name, ObjectFactory<?> objectFactory) { //如果當前線程已經(jīng)綁定了相應(yīng)Bean,直接返回 if(THREAD_SCOPE.get().containsKey(name)) { return THREAD_SCOPE.get().get(name); } //使用objectFactory創(chuàng)建Bean并綁定到當前線程上 THREAD_SCOPE.get().put(name, objectFactory.getObject()); return THREAD_SCOPE.get().get(name); } @Override public String getConversationId() { return null; } @Override public void registerDestructionCallback(String name, Runnable callback) { //此處不實現(xiàn)就代表類似proytotype,容器返回給用戶后就不管了 } @Override public Object remove(String name) { return THREAD_SCOPE.get().remove(name); } @Override public Object resolveContextualObject(String key) { return null; } }Scope已經(jīng)實現(xiàn)了,讓我們將其注冊到Spring容器,使其發(fā)揮作用:
<bean class="org.springframework.beans.factory.config.CustomScopeConfigurer"> <property name="scopes"> <map><entry> <!-- 指定scope關(guān)鍵字 --><key><value>thread</value></key> <!-- scope實現(xiàn) --> <bean class="cn.javass.spring.chapter3.ThreadScope"/> </entry></map> </property> </bean>通過CustomScopeConfigurer的scopes屬性注冊自定義作用域?qū)崿F(xiàn),在此需要指定使用作用域的關(guān)鍵字“thread”,并指定自定義作用域?qū)崿F(xiàn)。來讓我們來定義一個“thread”作用域的Bean,配置(chapter3/threadScope.xml)如下:
<bean id="helloApi" class="cn.javass.spring.chapter2.helloworld.HelloImpl" scope="thread"/>最后測試(cn.javass.spring.chapter3.ThreadScopeTest)一下吧,首先在一個線程中測試,在同一線程中獲取的Bean應(yīng)該是一樣的;再讓我們開啟兩個線程,然后應(yīng)該這兩個線程創(chuàng)建的Bean是不一樣:
?
?
自定義作用域?qū)崿F(xiàn)其實是非常簡單的,其實復(fù)雜的是如果需要銷毀Bean,自定義作用域如何正確的銷毀Bean。
總結(jié)
以上是生活随笔為你收集整理的DI 之 3.4 Bean的作用域(捌)的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 有向图与关联矩阵
- 下一篇: java中ATM与数据库Mysql的连接