spring怎么實現單例模式?


Spring學習之路——單例模式和多例模式

 

在Spring中,bean可以被定義為兩種模式:prototype(多例)和singleton(單例)

singleton(單例):只有一個共享的實例存在,所有對這個bean的請求都會返回這個唯一的實例。

prototype(多例):對這個bean的每次請求都會創建一個新的bean實例,類似於new。

Spring bean 默認是單例模式。

實戰演示:

代碼詳見上一篇文章中的代碼。

在配置文件中,修改這句代碼為:

1 <bean id="hi" class="com.test.Hi" init-method="init" scope="singleton">

在測試類中,修改代碼為:

1 ApplicationContext context = new FileSystemXmlApplicationContext("applicationContext.xml");
2         Hi hi1 = (Hi) context.getBean("hi");
3         Hi hi2 = (Hi) context.getBean("hi");
4         System.out.println(hi1);
5         System.out.println(hi2);

結果為:

結論:二個變量指向一個對象。

將配置文件改為:

<bean id="hi" class="com.test.Hi" init-method="init" scope="prototype">

其他的不變,運行測試類,結果為:

結論:每次訪問bean,均創建一個新實例。

 
 
Spring怎么實現單例模式
單例模式也屬於創建型模式,所謂單例,顧名思義,所指的就是單個實例,也就是說要保證一個類僅有一個實例。
單例模式有以下的特點:

單例類只能有一個實例

單例類必須自己創建自己的唯一實例

單例類必須給所有其他對象提供這一實例
下面我們就來寫一個簡單的單例模式的例子:
    Public class Singleton1{  
    Private static final Singleton1 instance=new Singleton1();  
    //私有的默認構造函數  
    Public Singleton1(){}  
    //靜態工廠方法  
    Public static Singleton1 getInstance(){  
        Return instance;  
    }  
    }   

大家可以看出來,在這個類被加載時,靜態變量instance會被初始化,此時該類的私有構造函數被調用,這時候,單例類的唯一實例就被創建出來了

值得注意的是:由於構造函數是私有的,因此該類不能被繼承

還有一種寫法也可以實現單例模式:

    Public class Singleton2{  
       Private static final Singleton2 instance=null;  
       //私有的默認構造函數  
       Public Singleton1(){}  
       //靜態工廠方法  
       Public synchronized static Singleton2 getInstance(){  
         If(instance==null){  
           Instance=new Singleton2();  
         }  
         Return instance;  
       }  
    }   

這種寫法和第一種的區別在於:實例並沒有直接實例化,而是在靜態工廠方法被調用的時候才進行的,而且對靜態工廠方法使用了同步化,以處理多線程並發的環境。
這兩種寫法還有兩個非常有意思的名字:第一種稱為餓漢式單例,第二種稱為懶漢式單例。至於為什么起這個名字,自己好好想想吧。
餓漢式單例在自己被加載時就將自己實例化,如果從資源利用效率角度來講,比懶漢式單例類稍差些。但是從速度和反應時間角度來講,則比懶漢式要稍好些。

但是遺憾的是:懶漢式單例類也不能被繼承。

我們克服前兩種單例類不能被繼承的缺點,我們可以使用另外一種特殊化的單例模式,它被稱為單例注冊表。

 
    Import java.util.HashMap;  
    Public class RegSingleton{  
       Static private HashMap registry=new HashMap();  
       //靜態塊,在類被加載時自動執行  
        Static{  
         RegSingleton rs=new RegSingleton();  
         Registry.put(rs.getClass().getName(),rs);  
       }  
    //受保護的默認構造函數,如果為繼承關系,則可以調用,克服了單例類不能為繼承的缺點  
    Protected RegSingleton(){}  
    //靜態工廠方法,返回此類的唯一實例  
    public static RegSingleton getInstance(String name){  
        if(name==null){  
          name=” RegSingleton”;  
        }if(registry.get(name)==null){  
          try{  
              registry.put(name,Class.forName(name).newInstance());  
           }Catch(Exception ex){ex.printStackTrace();}  
        }  
        Return (RegSingleton)registry.get(name);  
    }  
    }   
下面我們來看看Spring中的單例實現,當我們試圖從Spring容器中取得某個類的實例時,默認情況下,Spring會才用單例模式進行創建。
<bean id="date" class="java.util.Date"/>
<bean id="date" class="java.util.Date" scope="singleton"/> (僅為Spring2.0支持)
<bean id="date" class="java.util.Date" singleton="true"/>

以上三種創建對象的方式是完全相同的,容器都會向客戶返回Date類的單例引用。那么如果我不想使用默認的單例模式,每次請求我都希望獲得一個新的對象怎么辦呢?很簡單,將scope屬性值設置為prototype(原型)就可以了

<bean id="date" class="java.util.Date" scope="prototype"/>

通過以上配置信息,Spring就會每次給客戶端返回一個新的對象實例。

那么Spring對單例的底層實現,到底是餓漢式單例還是懶漢式單例呢?呵呵,都不是。Spring框架對單例的支持是采用單例注冊表的方式進行實現的,源碼如下:
    public abstract class AbstractBeanFactory implements ConfigurableBeanFactory{  
       /** 
        * 充當了Bean實例的緩存,實現方式和單例注冊表相同 
        */  
       private final Map singletonCache=new HashMap();  
       public Object getBean(String name)throws BeansException{  
           return getBean(name,null,null);  
       }  
    ...  
       public Object getBean(String name,Class requiredType,Object[] args)throws BeansException{  
          //對傳入的Bean name稍做處理,防止傳入的Bean name名有非法字符(或則做轉碼)  
          String beanName=transformedBeanName(name);  
          Object bean=null;  
          //手工檢測單例注冊表  
          Object sharedInstance=null;  
          //使用了代碼鎖定同步塊,原理和同步方法相似,但是這種寫法效率更高  
          synchronized(this.singletonCache){  
             sharedInstance=this.singletonCache.get(beanName);  
           }  
          if(sharedInstance!=null){  
             ...  
             //返回合適的緩存Bean實例  
             bean=getObjectForSharedInstance(name,sharedInstance);  
          }else{  
            ...  
            //取得Bean的定義  
            RootBeanDefinition mergedBeanDefinition=getMergedBeanDefinition(beanName,false);  
             ...  
            //根據Bean定義判斷,此判斷依據通常來自於組件配置文件的單例屬性開關  
            //<bean id="date" class="java.util.Date" scope="singleton"/>  
            //如果是單例,做如下處理  
            if(mergedBeanDefinition.isSingleton()){  
               synchronized(this.singletonCache){  
                //再次檢測單例注冊表  
                 sharedInstance=this.singletonCache.get(beanName);  
                 if(sharedInstance==null){  
                    ...  
                   try {  
                      //真正創建Bean實例  
                      sharedInstance=createBean(beanName,mergedBeanDefinition,args);  
                      //向單例注冊表注冊Bean實例  
                       addSingleton(beanName,sharedInstance);  
                   }catch (Exception ex) {  
                      ...  
                   }finally{  
                      ...  
                  }  
                 }  
               }  
              bean=getObjectForSharedInstance(name,sharedInstance);  
            }  
           //如果是非單例,即prototpye,每次都要新創建一個Bean實例  
           //<bean id="date" class="java.util.Date" scope="prototype"/>  
           else{  
              bean=createBean(beanName,mergedBeanDefinition,args);  
           }  
    }  
    ...  
       return bean;  
    }  
    }   

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM