国产探花免费观看_亚洲丰满少妇自慰呻吟_97日韩有码在线_资源在线日韩欧美_一区二区精品毛片,辰东完美世界有声小说,欢乐颂第一季,yy玄幻小说排行榜完本

首頁 > 學院 > 開發設計 > 正文

【第三章】 DI 之 3.4 Bean的作用域 ——跟我學spring3

2019-11-08 18:49:44
字體:
來源:轉載
供稿:網友

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沒采用這種方式,因為該方式屬于侵入式設計;代碼樣例如下:

 

java代碼:查看復制到剪貼板打印package cn.javass.spring.chapter3.bean;  public class Singleton {      //1.私有化構造器      private Singleton() {}      //2.單例緩存者,惰性初始化,第一次使用時初始化      private static class InstanceHolder {          private static final Singleton INSTANCE = new Singleton();      }      //3.提供全局訪問點      public static Singleton getInstance() {          return InstanceHolder.INSTANCE;      }      //4.提供一個計數器來驗證一個ClassLoader一個實例      private int counter=0;  }  

 

 

以上定義個了個單例類,首先要私有化類構造器;其次使用InstanceHolder靜態內部類持有單例對象,這樣可以得到惰性初始化好處;最后提供全局訪問點getInstance,使得需要該單例實例的對象能獲取到;我們在此還提供了一個counter計數器來驗證一個ClassLoader一個實例。具體一個ClassLoader有一個單例實例測試請參考代碼“cn.javass.spring.chapter3. SingletonTest”中的“testSingleton”測試方法,里邊詳細演示了一個ClassLoader有一個單例實例。

 

 

1)  通過注冊表方式: 首先將需要單例的實例通過唯一鍵注冊到注冊表,然后通過鍵來獲取單例,讓我們直接看實現吧,注意本注冊表實現了Spring接口“SingletonBeanRegistry”,該接口定義了操作共享的單例對象,Spring容器實現將實現此接口;所以共享單例對象通過“registerSingleton”方法注冊,通過“getSingleton”方法獲取,消除了編程方式單例,注意在實現中不考慮并發:

 

 

java代碼:查看復制到剪貼板打印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緩存池,此處不考慮并發      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是注冊表單例設計模式的實現,消除了編程式單例,而且對代碼是非入侵式。

接下來讓我們看看在Spring中如何配置單例Bean吧,在Spring容器中如果沒指定作用域默認就是“singleton”,配置方式通過scope屬性配置,具體配置如下:

 

java代碼:查看復制到剪貼板打印<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”表示作用域,具體如下:

 

java代碼:查看復制到剪貼板打印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定義注冊表,類似于單例注冊表:

 

java代碼:查看復制到剪貼板打印package cn.javass.spring.chapter3;  import java.util.HashMap;  import java.util.Map;  public class BeanDifinitionRegister {      //bean定義緩存,此處不考慮并發問題  private final Map<String, BeanDefinition> DEFINITIONS =   new HashMap<String, BeanDefinition>();      public void registerBeanDefinition(String beanName, BeanDefinition bd) {          //1.本實現不允許覆蓋Bean定義          if(DEFINITIONS.containsKey(bd.getId())) {              throw new RuntimeException("已存在Bean定義,此實現不允許覆蓋");          }          //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)接下來應該來定義BeanFactory了:

 

java代碼:查看復制到剪貼板打印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,              //則創建并注冊到單例注冊表,從而緩存              SINGLETONS.registerSingleton(beanName, createBean(bd));              return SINGLETONS.getSingleton(beanName);          }          //4.如果是原型Bean定義,則直接返回根據Bean定義創建的新Bean,  //每次都是新的,無緩存          if(bd.getScope() == BeanDefinition.SCOPE_PROTOTYPE) {              return createBean(bd);          }          //5.其他情況錯誤的Bean定義          throw new RuntimeException("錯誤的Bean定義");      }     

 

 

java代碼:查看復制到剪貼板打印public void registerBeanDefinition(BeanDefinition bd) {       DEFINITIONS.registerBeanDefinition(bd.getId(), bd);   }     private Object createBean(BeanDefinition bd) {       //根據Bean定義創建Bean       try {           Class clazz = Class.forName(bd.getClazz());           //通過反射使用無參數構造器創建Bean           return clazz.getConstructor().newInstance();       } catch (ClassNotFoundException e) {           throw new RuntimeException("沒有找到Bean[" + bd.getId() + "]類");       } catch (Exception e) {           throw new RuntimeException("創建Bean[" + bd.getId() + "]失敗");       }   }  

 

       其中方法getBean用于獲取根據beanName對于的Bean定義創建的對象,有單例和原型兩類Bean;registerBeanDefinition方法用于注冊Bean定義,私有方法createBean用于根據Bean定義中的類型信息創建Bean。

 

3)測試一下吧,在此我們只測試原型作用域Bean,對于每次從Bean工廠中獲取的Bean都是一個全新的對象,代碼片段(BeanFatoryTest)如下:

 

java代碼:查看復制到剪貼板打印@Test  public void testPrototype () throws Exception {  //1.創建Bean工廠  DefaultBeanFactory bf = new DefaultBeanFactory();  //2.創建原型 Bean定義  BeanDefinition bd = new BeanDefinition();  bd.setId("bean");  bd.setScope(BeanDefinition.SCOPE_PROTOTYPE);  bd.setClazz(HelloImpl2.class.getName());  bf.registerBeanDefinition(bd);  //對于原型Bean每次應該返回一個全新的Bean  System.out.println(bf.getBean("bean") != bf.getBean("bean"));  }  

 

 

最后讓我們看看如何在Spring中進行配置吧,只需指定<bean>標簽屬性“scope”屬性為“prototype”即可:

 

java代碼:查看復制到剪貼板打印<bean class="cn.javass.spring.chapter3.bean.Printer" />  

 

 

       Spring管理原型對象在Spring容器中存儲如圖3-6所示,Spring不會緩存原型對象,而是根據Bean定義每次請求返回一個全新的Bean:

 

圖3-6 原型處理

       單例和原型作用域我們已經講完,接下來讓我們學習一些在Web應用中有哪些作用域:

 

3.4.2  Web應用中的作用域

在Web應用中,我們可能需要將數據存儲到request、session、global 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接口吧:

 

java代碼:查看復制到剪貼板打印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,其中參數objectFactory是當在當前作用域沒找到合適Bean時使用它創建一個新的Bean;

 

2)void registerDestructionCallback(String name, Runnable callback):用于注冊銷毀回調,如果想要銷毀相應的對象則由Spring容器注冊相應的銷毀回調,而由自定義作用域選擇是不是要銷毀相應的對象;

 

3)Object resolveContextualObject(String key):用于解析相應的上下文數據,比如request作用域將返回request中的屬性。

 

4)String getConversationId():作用域的會話標識,比如session作用域將是sessionId。

 

 

java代碼:查看復制到剪貼板打印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() {            //用于存放線程相關Bean            return new HashMap<String, Object>();        }      };  

 

       讓我們來實現個簡單的thread作用域,該作用域內創建的對象將綁定到ThreadLocal內。

      

java代碼:查看復制到剪貼板打印    @Override      public Object get(String name, ObjectFactory<?> objectFactory) {          //如果當前線程已經綁定了相應Bean,直接返回          if(THREAD_SCOPE.get().containsKey(name)) {             return THREAD_SCOPE.get().get(name);          }          //使用objectFactory創建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) {          //此處不實現就代表類似proytotype,容器返回給用戶后就不管了      }      @Override      public Object remove(String name) {          return THREAD_SCOPE.get().remove(name);      }      @Override      public Object resolveContextualObject(String key) {          return null;      }  }     

 

Scope已經實現了,讓我們將其注冊到Spring容器,使其發揮作用:

 

java代碼:查看復制到剪貼板打印<bean class="org.springframework.beans.factory.config.CustomScopeConfigurer">          <property name="scopes">             <map><entry>                     <!-- 指定scope關鍵字 --><key><value>thread</value></key>                     <!-- scope實現 -->      <bean class="cn.javass.spring.chapter3.ThreadScope"/>             </entry></map>              </property>      </bean>  

 

通過CustomScopeConfigurer的scopes屬性注冊自定義作用域實現,在此需要指定使用作用域的關鍵字“thread”,并指定自定義作用域實現。來讓我們來定義一個“thread”作用域的Bean,配置(chapter3/threadScope.xml)如下:

 

java代碼:查看復制到剪貼板打印<bean id="helloApi"      class="cn.javass.spring.chapter2.helloworld.HelloImpl"      scope="thread"/>  

 

最后測試(cn.javass.spring.chapter3.ThreadScopeTest)一下吧,首先在一個線程中測試,在同一線程中獲取的Bean應該是一樣的;再讓我們開啟兩個線程,然后應該這兩個線程創建的Bean是不一樣:

 

 

自定義作用域實現其實是非常簡單的,其實復雜的是如果需要銷毀Bean,自定義作用域如何正確的銷毀Bean。

 

原創內容 】


發表評論 共有條評論
用戶名: 密碼:
驗證碼: 匿名發表
主站蜘蛛池模板: 瑞昌市| 桦南县| 丘北县| 闵行区| 溆浦县| 广灵县| 华坪县| 台中市| 绥化市| 搜索| 宁都县| 镇安县| 监利县| 庆元县| 葫芦岛市| 昌平区| 荥阳市| 霍城县| 新安县| 宜章县| 香格里拉县| 洪洞县| 宕昌县| 富宁县| 普洱| 盐津县| 乌拉特前旗| 比如县| 兴安县| 阿尔山市| 玛曲县| 色达县| 蒲江县| 淮滨县| 赣州市| 利川市| 红河县| 白玉县| 黄平县| 襄樊市| 浦东新区|