單例模式也屬於創建型模式,所謂單例,顧名思義,所指的就是單個實例,也就是說要保證一個類僅有一個實例。
單例模式有以下的特點:
①
單例類只能有一個實例
②
單例類必須自己創建自己的唯一實例
③
單例類必須給所有其他對象提供這一實例
下面我們就來寫一個簡單的單例模式的例子
package spring; public class SingletonWW { private static final SingletonWW instance=new SingletonWW(); //私有的默認構造函數 private SingletonWW(){} //靜態工廠方法 public static SingletonWW getInstance(){ return instance; } }
大家可以看出來,在這個類被加載時,靜態變量instance會被初始化,此時該類的私有構造函數被調用,這時候,單例類的唯一實例就被創建出來了
值得注意的是:由於構造函數是私有的,因此該類不能被繼承
還有一種寫法也可以實現單例模式:
package spring; public class SingletonWW { private static SingletonWW instance = null; //私有的默認構造函數 private SingletonWW(){} //靜態工廠方法 public synchronized static SingletonWW getInstance(){ if(instance==null){ instance = new SingletonWW(); } return instance; } }
這種寫法和第一種的區別在於:實例並沒有直接實例化,而是在靜態工廠方法被調用的時候才進行的,而且對靜態工廠方法使用了同步化,以處理多線程並發的環境。
這兩種寫法還有兩個非常有意思的名字:第一種稱為餓漢式單例,第二種稱為懶漢式單例。
餓漢式單例在自己被加載時就將自己實例化,如果從資源利用效率角度來講,比懶漢式單例類稍差些。但是從速度和反應時間角度來講,則比懶漢式要稍好些。
但是遺憾的是:懶漢式單例類也不能被繼承。
我們克服前兩種單例類不能被繼承的缺點,我們可以使用另外一種特殊化的單例模式,它被稱為單例注冊表。
package spring; import java.util.HashMap; public class SingletonWW { private static HashMap registry = new HashMap(); public SingletonWW(){} //靜態工廠方法 public synchronized static SingletonWW getInstance(String name){ if(name != null) { if(registry.get(name) == null) { try { registry.put(name, Class.forName(name).newInstance()); } catch(Exception ex) { ex.printStackTrace(); } } else { return (SingletonWW) registry.get(name); } } return null; } }
<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就會每次給客戶端返回一個新的對象實例。
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; } }
剛才的源碼中,大家真正要記住的是Spring對bean實例的創建是采用單例注冊表的方式進行實現的,而這個注冊表的緩存是HashMap對象,如果配置文件中的配置信息不要求使用單例,Spring會采用新建實例的方式返回對象實例
如果還對 Spring 的AOP、IOC、ORM感興趣的,可以看看我寫的輕量級框架 swift-framework 框架,簡單易懂,而且可用。包含了Spring 中的基本核心功能。