在 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 物件。
(完)
更多精彩文章,盡在「服務端思維」微信公眾號!