1. 程式人生 > >Spring學習筆記 關於Spring建立Bean的模式-Singleton(單例模式)和Prototype

Spring學習筆記 關於Spring建立Bean的模式-Singleton(單例模式)和Prototype

剛開始接觸Spring的時候一些基礎的教學在說到Bean的建立時會提到Spring的單例模式,就是說預設情況下Spring中定義的Bean是以單例模式建立的。如果以前瞭解設計模式中的單例模式的話很容易對這種說法產生先入為主的印象。事實上,Spring中的單例模式還有許多需要注意的地方。

在GoF中的單例模式是指一個ClassLoader中只存在類一個例項。

而在Spring中的單例實際上更確切的說應該是:
1.每個Spring Container中定義的Bean只存在一個例項
2.
每個Bean定義只存在一個例項。
 

如果對Spring的單例模式不夠了解在設計與開發過程中會留下很多隱患。以下是根據Spring中單例模式的特點寫的測試程式碼,通過程式碼可以更直觀的瞭解Spring中的單例模式。

先看一下這個測試中用到的類已經XML中Bean的定義.隨後給出Main方法中的測試程式碼:

根據以上類定義,執行以下main方法進行測試:

package beanscope;

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class BeanScopceMain
{

    /**
     * @comment [註釋說明]
     * @author 榮磊, 2012-7-17
     * 
     * @param args
     */
    public static void main(String[] args)
    {
        String springConfig = "beanscope/spring-config.xml";
        //建立一個SpringContainer
        ApplicationContext context = new ClassPathXmlApplicationContext(springConfig);
        
        //通過SpringContainer取得runnerBeanOne
        RunnerBeanRefToSingletonBean runnerBeanOne = 
               context.getBean("runnerBeanOne", RunnerBeanRefToSingletonBean.class);
        //通過SpringContainer取得runnerBeanTwo
        RunnerBeanRefToSingletonBeanTwo runnerBeanTwo = 
               context.getBean("runnerBeanTwo", RunnerBeanRefToSingletonBeanTwo.class);  
        //通過SpringContainer取得singletonBean
        SingletonBean singletonBean = 
               context.getBean("singletonBean", SingletonBean.class);      
        
        //輸出上一次操作singltonBean的Bean名稱,
        //因為在這之前沒有對singletonBean的lastOperatedBy屬性進行初始化,所以這裡應該會輸出none
        singletonBean.showLastOperateBean();
        
        //設定runnerBeanOne中singletonBean物件的上一次操作者資訊為runnerBeanOne
        runnerBeanOne.setMyNameToSingletonBean();
        //通過singletonBean輸出上一次操作者資訊
        singletonBean.showLastOperateBean();
        //輸出runnerBeanTwo中應用的singletonBean物件的上一次操作者資訊
        runnerBeanTwo.showLastOprBeanOfSingletonBean();
        
       //設定runnerBeanOne中singletonBean物件的上一次操作者資訊為runnerBeanTwo
       runnerBeanTwo.setMyNameToSingletonBean();
       //通過singletonBean輸出上一次操作者資訊
        singletonBean.showLastOperateBean();
       //輸出runnerBeanOne中應用的singletonBean物件的上一次操作者資訊
        runnerBeanOne.showLastOprBeanOfSingletonBean();
    }

}

輸出結果:

從這個執行結果來看已經可以看出單例模式的影子,兩個分別建立的runnerBean物件在定義中都依賴於singletonBean物件,第2步中,使用runnerBeanOne.setMyNameToSingletonBean()方法,設定了runnerBeanOne自己引用的singletonBean的操作者資訊,隨後輸出了runnerBeanTwo自己引用的singletonBean和單獨建立的singletonBean的操作者資訊,結果都是runnerBeanOne,這就說明,當runnerBeanOne操作自己引用的singletonBean時,對runnerBeanTwo中和單獨建立的singletonBean產生了影響,也就是他們所引用的singletonBean物件是同一個物件。

這是因為runnerBeanOne與runnerBeanTwo在Spring定義Bean時都設定了一個property ref引用到了同一個singletonBean的Bean定義。 這就是最前面說道的兩點中的第二點:2.每個Bean定義只存在一個例項。 如果在Spring配置檔案中定義runnerBeanOne與runnerBeanTwo時引用到不同的singletonBean定義,就會創建出兩個不同的singletonBean物件,即使兩個Bean定義實際上對應的class是相同的。如下:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://www.springframework.org/schema/beans 
	    http://www.springframework.org/schema/beans/spring-beans-3.0.xsd">

    <bean id="runnerBeanOne" class="beanscope.RunnerBeanRefToSingletonBean">
        <property name="beanName" value="runnerBeanOne" />
        <property name="singletonBean" ref="singletonBean" />
    </bean>
    
    <bean id="runnerBeanTwo" class="beanscope.RunnerBeanRefToSingletonBeanTwo">
        <property name="beanName" value="runnerBeanTwo" />
        <!-- 這裡更改了ref的值為singletonBeanTwo -->
        <property name="singletonBean" ref="singletonBeanTwo" />
    </bean>
    
	<bean id="singletonBean" class="beanscope.SingletonBean">
	   <property name="lastOperatedBy">
	       <null />
	   </property>
	</bean>
	<!-- 增加了以下Bean定義 -->
	<bean id="singletonBeanTwo" class="beanscope.SingletonBean">
       <property name="lastOperatedBy">
           <null />
       </property>
    </bean>
</beans>

更改配置檔案後執行main方法類,輸出:

none
runnerBeanOne
none
runnerBeanOne
runnerBeanOne

可以看出runnerBeanOne與runnerBeanTwo中引用的singletonBean已經不是同一個物件了,因為雖然都是SingletonBean類,但是在配置檔案中作出了兩個Bean的定義,而Spring的單例模式只保證每個Bean的定義只存在一個例項。

接下來看前面說到的第一點,1.每個Spring Container中定義的Bean只存在一個例項。

使用改動之前的Spring配置檔案,並更改main方法類如下:

package beanscope;

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class BeanScopceMain
{

    /**
     * @comment [註釋說明]
     * @author 榮磊, 2012-7-17
     * 
     * @param args
     */
    public static void main(String[] args)
    {
        String springConfig = "beanscope/spring-config.xml";
        //建立一個SpringContainer
        ApplicationContext context = new ClassPathXmlApplicationContext(springConfig);
        /****************************
         * 建立另外一個SpringContainer
         ********/
        ApplicationContext context2 = new ClassPathXmlApplicationContext(springConfig);
        //通過SpringContainer取得runnerBeanOne
        RunnerBeanRefToSingletonBean runnerBeanOne = 
               context.getBean("runnerBeanOne", RunnerBeanRefToSingletonBean.class);
        /**************************************************
         * 通過新建立的另外一個SpringContainer取得runnerBeanTwo
         *****************/
        RunnerBeanRefToSingletonBeanTwo runnerBeanTwo = 
                context2.getBean("runnerBeanTwo", RunnerBeanRefToSingletonBeanTwo.class);
  
        //通過SpringContainer取得singletonBean
        SingletonBean singletonBean = 
               context.getBean("singletonBean", SingletonBean.class);      
        
        //輸出上一次操作singltonBean的Bean名稱,
        //因為在這之前沒有對singletonBean的lastOperatedBy
        //屬性進行初始化,所以這裡應該會輸出none
        singletonBean.showLastOperateBean();
        
        //設定runnerBeanOne中singletonBean物件的上一次操作者
        //資訊為runnerBeanOne
        runnerBeanOne.setMyNameToSingletonBean();
        //通過singletonBean輸出上一次操作者資訊
        singletonBean.showLastOperateBean();
        //輸出runnerBeanTwo中應用的singletonBean物件的上一次操作者資訊
        runnerBeanTwo.showLastOprBeanOfSingletonBean();
        
        //設定runnerBeanOne中singletonBean物件的上一次操作者資訊為runnerBeanTwo
        runnerBeanTwo.setMyNameToSingletonBean();
        //通過singletonBean輸出上一次操作者資訊
        singletonBean.showLastOperateBean();
        //輸出runnerBeanOne中應用的singletonBean物件的上一次操作者資訊
        runnerBeanOne.showLastOprBeanOfSingletonBean();
    }

}

輸出結果:

none
runnerBeanOne
none
runnerBeanOne
runnerBeanOne

在上邊的程式碼中建立了兩個SpringContainer物件 context 和 context2.分別用context和context2獲取runnerBeanOne與runnerBeanTwo,再進行操作與輸出,從結果來看他們分別應用的singletonBean物件同樣是兩個不同的物件。雖然在配置檔案中兩個runnerBean都引用了同一個singletonBean定義,但是因為是通過不同的SpringContainer建立的,所以仍然建立除了兩個singletonBean物件這就可以理解第一點:1.每個Spring Container中定義的Bean只存在一個例項。

Spring預設是以上述單例模式建立物件的,但是通過Bean標籤中的設定scope="prototype"可以設定Spring不再以單例模式建立物件,而是每次都建立新的物件。在上邊的例子中,如果不再定義兩個runnerBean,而是指定義一個runnerBean,但是給他設定屬性scope=“prototype”,那在main方法中就可以通過同一個context獲取兩個不同的runnerBean物件。但是需要注意的是,因為runnerBean引用的singletonBean沒有被設定為prototype,所以雖然建立除了兩個不同的runnerBean物件,但他們所引用的singletonBean仍然是同一個。

    <bean id="runnerBeanOne" class="beanscope.RunnerBeanRefToSingletonBean" 
        scope="prototype">
        <property name="beanName" value="runnerBeanOne" />
        <property name="singletonBean" ref="singletonBean" />
    </bean>
    
    <bean id="singletonBean" class="beanscope.SingletonBean">
        <property name="lastOperatedBy">
           <null />
        </property>
    </bean>

所以單例模式在Spring中的使用需要額外的注意。

所有程式碼:

main方法類:

package beanscope;

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class BeanScopceMain
{

    /**
     * @comment [註釋說明]
     * @author 榮磊, 2012-7-17
     * 
     * @param args
     */
    public static void main(String[] args)
    {
        String springConfig = "beanscope/spring-config.xml";
        //建立一個SpringContainer
        ApplicationContext context = new ClassPathXmlApplicationContext(springConfig);
        
        //通過SpringContainer取得runnerBeanOne
        RunnerBeanRefToSingletonBean runnerBeanOne = 
               context.getBean("runnerBeanOne", RunnerBeanRefToSingletonBean.class);
        //通過SpringContainer取得runnerBeanTwo
        RunnerBeanRefToSingletonBeanTwo runnerBeanTwo = 
               context.getBean("runnerBeanTwo", RunnerBeanRefToSingletonBeanTwo.class);  
        //通過SpringContainer取得singletonBean
        SingletonBean singletonBean = 
               context.getBean("singletonBean", SingletonBean.class);      
        
        //輸出上一次操作singltonBean的Bean名稱,
        //因為在這之前沒有對singletonBean的lastOperatedBy屬性進行初始化,
        //所以這裡應該會輸出none
        singletonBean.showLastOperateBean();
        
        //設定runnerBeanOne中singletonBean物件的上一次操作者資訊為runnerBeanOne
        runnerBeanOne.setMyNameToSingletonBean();
        //通過singletonBean輸出上一次操作者資訊
        singletonBean.showLastOperateBean();
        //輸出runnerBeanTwo中應用的singletonBean物件的上一次操作者資訊
        runnerBeanTwo.showLastOprBeanOfSingletonBean();
        
        //設定runnerBeanOne中singletonBean物件的上一次操作者資訊為runnerBeanTwo
        runnerBeanTwo.setMyNameToSingletonBean();
        //通過singletonBean輸出上一次操作者資訊
        singletonBean.showLastOperateBean();
        //輸出runnerBeanOne中應用的singletonBean物件的上一次操作者資訊
        runnerBeanOne.showLastOprBeanOfSingletonBean();
    }

}


SingletonBean類:

package beanscope;

/**
 * <p>[功能描述]</p>
 * 
 * @author	榮磊
 * @version	1.0, 2012-7-17
 */
public class SingletonBean
{
    // 用來儲存上次操作此Bean的物件
    private String lastOperatedBy;
    public String getLastOperatedBy()
    {
        return lastOperatedBy;
    }
    public void setLastOperatedBy(String lastOperatedBy)
    {
        this.lastOperatedBy = lastOperatedBy;
    }
    
    /**
     * <p>[輸出lastOperatedBy屬性]</p>
     * @author	榮磊, 2012-7-17
     */
    public void showLastOperateBean()
    {
        if(lastOperatedBy!=null)
            System.out.println(lastOperatedBy);
        else
            System.out.println("none");
    }
}


RunnerBeanRefToSingletonBean類:

package beanscope;

public class RunnerBeanRefToSingletonBean
{
    private String beanName;
    private SingletonBean singletonBean;
    
    public String getBeanName()
    {
        return beanName;
    }
    public void setBeanName(String beanName)
    {
        this.beanName = beanName;
    }
    public SingletonBean getSingletonBean()
    {
        return singletonBean;
    }
    public void setSingletonBean(SingletonBean singletonBean)
    {
        this.singletonBean = singletonBean;
    }
    
    public void setMyNameToSingletonBean()
    {
        singletonBean.setLastOperatedBy(beanName);
    }
    
    public void showLastOprBeanOfSingletonBean()
    {
        singletonBean.showLastOperateBean();
    }
}


RunnerBeanRefToSingletonBeanTwo類:

package beanscope;

public class RunnerBeanRefToSingletonBeanTwo
{
    private String beanName;
    private SingletonBean singletonBean;
    
    public String getBeanName()
    {
        return beanName;
    }
    public void setBeanName(String beanName)
    {
        this.beanName = beanName;
    }
    public SingletonBean getSingletonBean()
    {
        return singletonBean;
    }
    public void setSingletonBean(SingletonBean singletonBean)
    {
        this.singletonBean = singletonBean;
    }
    
    public void setMyNameToSingletonBean()
    {
        singletonBean.setLastOperatedBy(beanName);
    }
    
    public void showLastOprBeanOfSingletonBean()
    {
        singletonBean.showLastOperateBean();
    }
}


spring-config.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://www.springframework.org/schema/beans 
	    http://www.springframework.org/schema/beans/spring-beans-3.0.xsd">

    <bean id="runnerBeanOne" class="beanscope.RunnerBeanRefToSingletonBean">
        <property name="beanName" value="runnerBeanOne" />
        <property name="singletonBean" ref="singletonBean" />
    </bean>
    
    <bean id="runnerBeanTwo" class="beanscope.RunnerBeanRefToSingletonBeanTwo">
        <property name="beanName" value="runnerBeanTwo" />
        <property name="singletonBean" ref="singletonBean" />
    </bean>
    
	<bean id="singletonBean" class="beanscope.SingletonBean">
	   <property name="lastOperatedBy">
	       <null />
	   </property>
	</bean>
	
</beans>