單例模式 – 單例登錄檔與 Spring 實現單例剖析

樑桂釗發表於2019-03-02

在 Spring 中,單例模式是十分常用的,因為 Spring Bean 預設是單例模式,即只有一個共享的例項存在,所有對這個 Bean 的請求都會返回這個唯一的例項。

原文地址:單例模式 – 單例登錄檔與 Spring 實現單例剖析
部落格地址:blog.720ui.com/

單例登錄檔

首先,我們先來寫一個案例。這個案例中,我們通過 Map 快取單例物件,實現單例登錄檔。值得注意的是,我採用了 ConcurrentHashMap 是出於執行緒安全的考量。

public class SingletonReg {
    private final static Map<String, Object> singletonObjects = new ConcurrentHashMap<String, Object>(64);

    static {
        SingletonReg singletonReg = new SingletonReg();
        singletonObjects.put(singletonReg.getClass().getName(), singletonReg);
    }

    private SingletonReg() {}

    public static SingletonReg getInstance(String name) {
        if (name == null) {
            name = "com.lianggzone.designpattern.singleton.sample.SingletonReg";
        }
        if (singletonObjects.get(name) == null) {
            try {
                singletonObjects.put(name, Class.forName(name).newInstance());
            } catch (Exception ex) {
                ex.printStackTrace();
            }
        }
        return (SingletonReg) singletonObjects.get(name);
    }
}複製程式碼

我們來分析下,上面案例中的原始碼。

private final static Map<String, Object> singletonObjects = new ConcurrentHashMap<String, Object>(64);複製程式碼

此外,getInstance() 方法通過傳入類名進行判斷,如果引數為 null,我們預設分配一個 SingletonReg 例項物件,如果例項物件在不存在,我們註冊到單例登錄檔中。第二次獲取時,直接從快取的單例登錄檔中獲取。

Spring 原始碼分析

實際上,Spring 就是採用了這種單例登錄檔的特殊方式實現單例模式。

public abstract class AbstractBeanFactory extends FactoryBeanRegistrySupport implements ConfigurableBeanFactory {

    @SuppressWarnings("unchecked")
    protected <T> T doGetBean(
            final String name, final Class<T> requiredType, final Object[] args, boolean typeCheckOnly)
            throws BeansException {
        // 對 Bean 的 name 進行處理,防止非法字元
        final String beanName = transformedBeanName(name);
        Object bean;
        // 從單例登錄檔中檢查是否存在單例快取
        Object sharedInstance = getSingleton(beanName);
        if (sharedInstance != null && args == null) {
            // ...忽略程式碼
            // 返回快取例項 
            bean = getObjectForBeanInstance(sharedInstance, name, beanName, null);
        }
        else {
            // ...忽略程式碼
            try {
                // ...忽略程式碼

                // 單例模式,處理分支
                if (mbd.isSingleton()) {
                    sharedInstance = getSingleton(beanName, new ObjectFactory<Object>() {
                        @Override
                        public Object getObject() throws BeansException {
                            try {
                                return createBean(beanName, mbd, args);
                            }
                            catch (BeansException ex) {
                                // ...忽略程式碼
                            }
                        }
                    });
                    bean = getObjectForBeanInstance(sharedInstance, name, beanName, mbd);
                }
                // 原型模式,處理分支
                else if (mbd.isPrototype()) {

                }
                // 其他
                else {

                }
            }
            catch (BeansException ex) {
                // ...忽略程式碼
            }
        }
        return (T) bean;
    }
}複製程式碼

其中,最重要的核心程式碼是 getSingleton() 方法。下面,我們再深入分析下這個方法。

public class DefaultSingletonBeanRegistry extends SimpleAliasRegistry implements SingletonBeanRegistry {

    // 通過 Map 實現單例登錄檔
    private final Map<String, Object> singletonObjects = new ConcurrentHashMap<String, Object>(64);

    public Object getSingleton(String beanName, ObjectFactory<?> singletonFactory) {
        Assert.notNull(beanName, "`beanName` must not be null");
        synchronized (this.singletonObjects) {
            // 檢查快取中是否存在例項  
            Object singletonObject = this.singletonObjects.get(beanName);
            if (singletonObject == null) {
                // ...忽略程式碼
                try {
                    singletonObject = singletonFactory.getObject();
                }
                catch (BeanCreationException ex) {
                    // ...忽略程式碼
                }
                finally {
                    // ...忽略程式碼
                }
                // 如果例項物件在不存在,我們註冊到單例登錄檔中。
                addSingleton(beanName, singletonObject);
            }
            return (singletonObject != NULL_OBJECT ? singletonObject : null);
        }
    }

    protected void addSingleton(String beanName, Object singletonObject) {
        synchronized (this.singletonObjects) {
            this.singletonObjects.put(beanName, (singletonObject != null ? singletonObject : NULL_OBJECT));

        }
    }
}複製程式碼

此時,我們得出一個結論, Spring 對 Bean 例項的建立是採用單例登錄檔的方式進行實現的,而這個登錄檔的快取是 ConcurrentHashMap 物件。

(完)

更多精彩文章,盡在「服務端思維」微信公眾號!

單例模式 – 單例登錄檔與 Spring 實現單例剖析

相關文章