1. 程式人生 > >Spring 中的 Bean 預設是單例的

Spring 中的 Bean 預設是單例的

單例模式是一種常用的軟體設計模式。在它的核心結構中只包含一個被稱為單例的特殊類。通過單例模式可以保證系統中一個類只有一個例項

注:Spring原始碼的版本4.3.4

Spring依賴注入Bean例項預設是單例的,我們由此展開。

Spring的依賴注入(包括lazy-init方式)都是發生在AbstractBeanFactory的getBean裡。getBean的doGetBean方法呼叫getSingleton進行bean的建立。lazy-init方式,在容器初始化時候進行呼叫,非lazy-init方式,在使用者向容器第一次索要bean時進行呼叫

同步執行緒安全的單例核心程式碼:

複製程式碼
/**
     * Return the (raw) singleton object registered under the given name.
     * <p>Checks already instantiated singletons and also allows for an early
     * reference to a currently created singleton (resolving a circular reference).
     * 
@param beanName the name of the bean to look for * @param allowEarlyReference whether early references should be created or not * @return the registered singleton object, or {@code null} if none found */ protected Object getSingleton(String beanName, boolean allowEarlyReference) { Object singletonObject
= this.singletonObjects.get(beanName); if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) { synchronized (this.singletonObjects) { singletonObject = this.earlySingletonObjects.get(beanName); if (singletonObject == null
&& allowEarlyReference) { ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName); if (singletonFactory != null) { singletonObject = singletonFactory.getObject(); this.earlySingletonObjects.put(beanName, singletonObject); this.singletonFactories.remove(beanName); } } } } return (singletonObject != NULL_OBJECT ? singletonObject : null); }
複製程式碼

從上面程式碼可以看到,spring依賴注入時,使用了雙重判斷加鎖的單例模式,首先從快取中獲取bean例項,如果為null,對快取map加鎖,然後再從快取中獲取bean,如果繼續為null,就建立一個bean。這樣雙重判斷,能夠避免在加鎖的瞬間,有其他依賴注入引發bean例項的建立,從而造成重複建立的結果。

    在這裡Spring並沒有使用私有構造方法來建立bean,而是通過singletonFactory.getObject()返回具體beanName對應的ObjectFactory來建立bean。我們一路跟蹤下去,發現實際上是呼叫了AbstractAutowireCapableBeanFactory的doCreateBean方法,返回了BeanWrapper包裝並建立的bean例項。

ObjectFactory主要檢查是否有使用者定義的BeanPostProcessor後處理內容,並在建立bean時進行處理,如果沒有,就直接返回bean本身)

見如下程式碼:

512行建立bean例項返回給BeanWrapper

540addSingletonFactory增加beanName和ObjectFactory的鍵值對應關係。

 

複製程式碼
    /**
     * Actually create the specified bean. Pre-creation processing has already happened
     * at this point, e.g. checking {@code postProcessBeforeInstantiation} callbacks.
     * <p>Differentiates between default bean instantiation, use of a
     * factory method, and autowiring a constructor.
     * @param beanName the name of the bean
     * @param mbd the merged bean definition for the bean
     * @param args explicit arguments to use for constructor or factory method invocation
     * @return a new instance of the bean
     * @throws BeanCreationException if the bean could not be created
     * @see #instantiateBean
     * @see #instantiateUsingFactoryMethod
     * @see #autowireConstructor
     */
    protected Object doCreateBean(final String beanName, final RootBeanDefinition mbd, final Object[] args)
            throws BeanCreationException {

        // Instantiate the bean.
        BeanWrapper instanceWrapper = null;
        if (mbd.isSingleton()) {
            instanceWrapper = this.factoryBeanInstanceCache.remove(beanName);
        }
        if (instanceWrapper == null) {
            instanceWrapper = createBeanInstance(beanName, mbd, args);
        }
        final Object bean = (instanceWrapper != null ? instanceWrapper.getWrappedInstance() : null);
        Class<?> beanType = (instanceWrapper != null ? instanceWrapper.getWrappedClass() : null);

        // Allow post-processors to modify the merged bean definition.
        synchronized (mbd.postProcessingLock) {
            if (!mbd.postProcessed) {
                try {
                    applyMergedBeanDefinitionPostProcessors(mbd, beanType, beanName);
                }
                catch (Throwable ex) {
                    throw new BeanCreationException(mbd.getResourceDescription(), beanName,
                            "Post-processing of merged bean definition failed", ex);
                }
                mbd.postProcessed = true;
            }
        }

        // Eagerly cache singletons to be able to resolve circular references
        // even when triggered by lifecycle interfaces like BeanFactoryAware.
        boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences &&
                isSingletonCurrentlyInCreation(beanName));
        if (earlySingletonExposure) {
            if (logger.isDebugEnabled()) {
                logger.debug("Eagerly caching bean '" + beanName +
                        "' to allow for resolving potential circular references");
            }
            addSingletonFactory(beanName, new ObjectFactory<Object>() {
                @Override
                public Object getObject() throws BeansException {
                    return getEarlyBeanReference(beanName, mbd, bean);
                }
            });
        }
複製程式碼

getEarlyBeanReference獲取bean的所有後處理器,並進行處理。如果是SmartInstantiationAwareBeanPostProcessor型別,就進行處理,如果沒有相關處理內容,就返回預設的實現。

 

複製程式碼
    /**
     * Obtain a reference for early access to the specified bean,
     * typically for the purpose of resolving a circular reference.
     * @param beanName the name of the bean (for error handling purposes)
     * @param mbd the merged bean definition for the bean
     * @param bean the raw bean instance
     * @return the object to expose as bean reference
     */
    protected Object getEarlyBeanReference(String beanName, RootBeanDefinition mbd, Object bean) {
        Object exposedObject = bean;
        if (bean != null && !mbd.isSynthetic() && hasInstantiationAwareBeanPostProcessors()) {
            for (BeanPostProcessor bp : getBeanPostProcessors()) {
                if (bp instanceof SmartInstantiationAwareBeanPostProcessor) {
                    SmartInstantiationAwareBeanPostProcessor ibp = (SmartInstantiationAwareBeanPostProcessor) bp;
                    exposedObject = ibp.getEarlyBeanReference(exposedObject, beanName);
                    if (exposedObject == null) {
                        return null;
                    }
                }
            }
        }
        return exposedObject;
    }
複製程式碼

彩蛋在此:

各種單例實現方式(5種):懶漢模式,餓漢執行緒非安全模式,餓漢執行緒安全模式,內部類模式,列舉模式。現在最推薦的方式是列舉單例模式。對這些模式的描述和介紹,請仔細看程式碼中的註釋,會有意想不到的收穫呦!

複製程式碼
package com.xhengxuyuanzhi;

/**
* @author 微信公眾號:程式設計師之路
 *                  部落格:http://www.cnblogs.com/chengxuyuanzhilu/
 * 
 * 餓漢式單例模式
 * 特點:可以通過反射機制攻擊;執行緒安全[多個類載入器除外]。
 */
public class HungryType {
    public static final HungryType instance = new HungryType();

    private HungryType(){
        //初始化HungryType要做的事
    }

    public void splitAlipay() {
        System.out.println("餓漢式單利模式");
    }

    public static void main(String[] args) {
        HungryType ht =    HungryType.instance;
        ht.splitAlipay();

    }
}
複製程式碼 複製程式碼
package com.xhengxuyuanzhi;

/**
 * @author 微信公眾號:程式設計師之路
 *                  部落格:http://www.cnblogs.com/chengxuyuanzhilu/
 * 
 * 懶漢模式單例
 * 特點:延時載入;執行緒不安全,多執行緒下不能正常工作;
 */
public class SluggardType {
    private static SluggardType instance = null;

    private SluggardType() {

    }

    public static SluggardType getInstance(){
        if(instance == null){
            instance = new SluggardType();
        }
        return instance;
    }

    public void say(){
        System.out.println("懶漢模式單例");
    }

    public static void main(String[] args) {
        SluggardType.getInstance().say();
    }
}
複製程式碼 複製程式碼
package com.xhengxuyuanzhi;

/**
 * @author 微信公眾號:程式設計師之路
 *                  部落格:http://www.cnblogs.com/chengxuyuanzhilu/
 * 
 * 懶漢模式(雙重校驗鎖[不推薦])單例
 */
public class SluggardType2 {

    //volatile 關鍵字可以禁止指令重排 :可以確保instance = new SluggardType2()對應的指令不會重排序
    //但是因為對volatile的操作都在Main Memory中,而Main Memory是被所有執行緒所共享的,這裡的代價就是犧牲了效能,無法利用暫存器或Cache
    private volatile static SluggardType2 instance = null;

    private SluggardType2(){

    }

    public static SluggardType2 getInstance(){
        if(instance == null){
            synchronized (SluggardType2.class) {
                if(instance == null){
                    instance = new SluggardType2();
                }
            }
        }

        return instance;
    }

    public void say(){
        System.out.println(" 懶漢模式(雙重校驗鎖[不推薦])單例");
    }

    public static void main(String[] args) {
        SluggardType2.getInstance().say();
    }


}
複製程式碼 複製程式碼
package com.xhengxuyuanzhi;

/**
 * @author 微信公眾號:程式設計師之路 
 *         部落格:http://www.cnblogs.com/chengxuyuanzhilu/
 * 
 * 藉助內部類實現單利模式:
 * 特點:既能實現延遲載入,又能實現執行緒安全
 */
public class InnerClassSingleton {
    /**
     * 類級的內部類,也就是靜態的成員式內部類,該內部類的例項與外部類的例項沒有繫結關係,而且只有被呼叫到時才會裝載(裝在過程是由jvm保證執行緒安全)
     * ,從而實現了延遲載入
     */
    private static class SingletonHolder {
        /**
         * 靜態初始化器,由JVM來保證執行緒安全
         */
        private static InnerClassSingleton instance = new InnerClassSingleton();
    }

    /**
     * 私有化構造方法
     */
    private InnerClassSingleton() {
    }

    /**
     * 這個模式的優勢在於:getInstance方法並沒有被同步,並且只是執行一個域的訪問,因此延遲初始化並沒有增加任何訪問成本
     */
    public static InnerClassSingleton getInstance() {
        return SingletonHolder.instance;
    }

}
複製程式碼 複製程式碼
package com.xhengxuyuanzhi;

/**
 * @author 微信公眾號:程式設計師之路 
 *             部落格:http://www.cnblogs.com/chengxuyuanzhilu/
 *
 * 列舉實現執行緒安全的單例模式:
 * 特點:JVM會保證enum不能被反射並且構造器方法只執行一次
 * 
 */
public class EnumSingleton {
    private EnumSingleton() {
    }

    public static EnumSingleton getInstance() {
        return Singleton.INSTANCE.getInstance();
    }

    private static enum Singleton {
        INSTANCE;

        private EnumSingleton singleton;

        // JVM會保證此方法絕對只調用一次
        private Singleton() {
            singleton = new EnumSingleton();
        }

        public EnumSingleton getInstance() {
            return singleton;
        }
    }
}
複製程式碼