前言
上一篇簡單的介紹了spi
的基本一些概念,但是其實Dubbo
對jdk的spi進行了一些改進,具體改進了什麼,來看看文件的描述
- JDK 標準的 SPI 會一次性例項化擴充套件點所有實現,如果有擴充套件實現初始化很耗時,但如果沒用上也載入,會很浪費資源。
- 如果擴充套件點載入失敗,連擴充套件點的名稱都拿不到了。比如:JDK 標準的 ScriptEngine,通過 getName() 獲取指令碼型別的名稱,但如果 RubyScriptEngine 因為所依賴的 jruby.jar 不存在,導致 RubyScriptEngine 類載入失敗,這個失敗原因被吃掉了,和 ruby 對應不起來,當使用者執行 ruby 指令碼時,會報不支援 ruby,而不是真正失敗的原因。
- 增加了對擴充套件點 IoC 和 AOP 的支援,一個擴充套件點可以直接 setter 注入其它擴充套件點。
根據小學語文的閱讀理解,不難概括出其實就是提高了效能
和增加了功能
.很多朋友都喜歡問,閱讀原始碼不如從何下手,要準備些什麼.如果只是粗略閱讀原始碼,掌握大體思路,其實具備小學語文的閱讀理解和看圖寫作業就差不多了(能看到本篇的均完全勝任這個條件,所以不要有任何恐懼心理).但是要領悟思想,對細節瞭如指掌,甚至寫出更優秀的框架,那麼就是四個字->終身學習
.(比如現在關注肥朝的公眾號,一起交流討論,終身學習即刻開啟)
對於閱讀原始碼,當然還是要有一些小技巧,俗話說得號,"技多不壓身",我們先看一個段子
某肥遇到了一個bug,需要引入多執行緒,結果引入多執行緒後,竟然出現了兩個bug
由此可見,多執行緒也確實是大家頭疼的問題,所以後面我也會演示一些小技巧,比如
-
如何除錯多執行緒程式碼(手把手實戰,不講理論)
-
如何檢視代理物件原始碼(手把手實戰,不講理論)
那dubbo這個改良後的spi
究竟怎麼提高效能,又增加了什麼功能,那就是本篇要講的.
插播面試題
-
既然你對spi有一定了解,那麼dubbo的spi和jdk的spi有區別嗎?有的話,究竟有什麼區別?
-
你說你看過Dubbo原始碼,那你簡單說下,他在設計上有哪些細節讓你覺得很巧妙?(
區分度高
)
概念鋪墊
dubbo的擴充點機制
涉及到眾多的知識點,也是dubbo中比較難的地方,和之前的叢集容錯有Cluster
、Directory
、Router
、LoadBalance
關鍵詞一樣,這個擴充點機制
也有幾個關鍵詞,SPI
、Adaptive
、Activate
.這些會陸續講解,最後總結.
直入主題
提升效能
提升效能,我們最容易想到的方式是什麼?其實這個和初高中政治答題一樣,有萬能公式的,那就是"快取".所以面試無論問你什麼(適用於Android,iOS,Web前端,Java等等...),只要和提升效能有關的,往快取**方向
答肯定沒錯(當然按照"按點給分"的套路,往快取方向答只是不至於讓你拿0分,但是僅僅
**答快取肯定拿不到滿分).所以如果與jdk的spi對比,那麼可以有以下幾個點
1.從"萬能公式"角度分析,增加快取
因為部分朋友反饋說喜歡貼程式碼的形式,所以講解在註釋中
public static <T> ExtensionLoader<T> getExtensionLoader(Class<T> type) {
if (type == null)//擴充點型別非空判斷
throw new IllegalArgumentException("Extension type == null");
if(!type.isInterface()) {//擴充點型別只能是介面
throw new IllegalArgumentException("Extension type(" + type + ") is not interface!");
}
if(!withExtensionAnnotation(type)) {//需要新增spi註解,否則拋異常
throw new IllegalArgumentException("Extension type(" + type +
") is not extension, because WITHOUT @" + SPI.class.getSimpleName() + " Annotation!");
}
//從快取EXTENSION_LOADERS中獲取,如果不存在則新建後加入快取
//對於每一個擴充,都會有且只有一個ExtensionLoader與其對應
ExtensionLoader<T> loader = (ExtensionLoader<T>) EXTENSION_LOADERS.get(type);
if (loader == null) {
EXTENSION_LOADERS.putIfAbsent(type, new ExtensionLoader<T>(type));
loader = (ExtensionLoader<T>) EXTENSION_LOADERS.get(type);
}
return loader;
}
private ExtensionLoader(Class<?> type) {
this.type = type;
//這裡會存在遞迴呼叫,ExtensionFactory的objectFactory為null,其他的均為AdaptiveExtensionFactory
//AdaptiveExtensionFactory的factories中有SpiExtensionFactory,SpringExtensionFactory
//getAdaptiveExtension()這個是獲取一個擴充裝飾類物件.細節在下篇講解
objectFactory = (type == ExtensionFactory.class ? null : ExtensionLoader.getExtensionLoader(ExtensionFactory.class).getAdaptiveExtension());
}
複製程式碼
以下是快取擴充點物件的
public T getExtension(String name) {
if (name == null || name.length() == 0)
throw new IllegalArgumentException("Extension name == null");
if ("true".equals(name)) {
return getDefaultExtension();
}
Holder<Object> holder = cachedInstances.get(name);
if (holder == null) {
cachedInstances.putIfAbsent(name, new Holder<Object>());
holder = cachedInstances.get(name);
}
Object instance = holder.get();
if (instance == null) {
synchronized (holder) {
instance = holder.get();
if (instance == null) {
instance = createExtension(name);
holder.set(instance);
}
}
}
return (T) instance;
}
複製程式碼
為什麼這個要單獨拿出來說呢?很多朋友容易產生大意心理,以為快取嘛,無非就是判斷一下是否存在,不存在則新增.dubbo也不過如此.我不看原始碼也懂.但是你如果稍加註意,就會發現它在細節方面做得很好.
敲黑板劃重點
在上一篇[從Dubbo核心聊聊雙親委派機制]中我就強調了,無論做什麼,都要形成差異性.在Java中,最容易形成差異性的知識點,就是JVM
和併發包
.既然上一篇中我們提了JVM
相關的內容(雙親委派機制),那麼本篇我們就說說併發包
的相關內容.我們仔細看上面的這段double-checked locking
程式碼
//double-checked locking
Object instance = holder.get();
if (instance == null) {
synchronized (holder) {
instance = holder.get();
if (instance == null) {
instance = createExtension(name);
holder.set(instance);
}
}
}
複製程式碼
public class Holder<T> {
private volatile T value;
public void set(T value) {
this.value = value;
}
public T get() {
return value;
}
}
複製程式碼
這裡為什麼用到了volatile
關鍵字呢?看原始碼更重要的是看到這些細節,魔鬼都藏在細節當中!本來肥朝想展開講一下這個volatile
秀一波操作的,但是無奈篇幅有限,那個我給你個關鍵詞.doubleCheck Singleton 重排序
,那把這個對著瀏覽器一搜.然後把搜尋到的第一頁結果都看完,你就知道這段程式碼並不是你想的只是判空加個快取這麼簡單.這也是我經常說,沒有做過中介軟體開發,很難對併發包有深入瞭解的一個原因.
2.從註解角度入手分析
既然是對比spi
區別,並且dubbo中有@spi
這個註解,那我們順著註解看看能有什麼線索.
如果在15年有用過dubbo,那麼就會留意到@Extension
這個註解,但是後來因為含義廣泛廢棄,換成了@SPI
.
@SPI("javassist")
public interface Compiler {
//省略...
}
複製程式碼
public Class<?> compile(String code, ClassLoader classLoader) {
Compiler compiler;
ExtensionLoader<Compiler> loader = ExtensionLoader.getExtensionLoader(Compiler.class);
String name = DEFAULT_COMPILER; // copy reference
if (name != null && name.length() > 0) {
compiler = loader.getExtension(name);
} else {
compiler = loader.getDefaultExtension();
}
return compiler.compile(code, classLoader);
}
複製程式碼
//com.alibaba.dubbo.common.compiler.Compiler 檔案配置如下
adaptive=com.alibaba.dubbo.common.compiler.support.AdaptiveCompiler
jdk=com.alibaba.dubbo.common.compiler.support.JdkCompiler
javassist=com.alibaba.dubbo.common.compiler.support.JavassistCompiler
複製程式碼
我們從上面這兩部分程式碼和配置檔案就不難分析出兩點(如果對spi不熟悉的請先把上一篇spi(一)看一遍,基礎不牢地動山搖的情況下沒法分析)
- JDK的
spi
要用for迴圈,然後if判斷才能獲取到指定的spi物件,dubbo用指定的key就可以獲取
//返回指定名字的擴充套件
public T getExtension(String name){}
複製程式碼
- JDK的
spi
不支援預設值,dubbo增加了預設值的設計
//@SPI("javassist")代表預設的spi物件,比如Compiler預設使用的是javassist,可通過
ExtensionLoader<Compiler> loader = ExtensionLoader.getExtensionLoader(Compiler.class);
compiler = loader.getDefaultExtension();
//方式獲取實現類,根據配置,即為
//com.alibaba.dubbo.common.compiler.support.JavassistCompiler
複製程式碼
增加功能
增加的功能,就如文件所說的,spi
增加了IoC
、AOP
AOP
這是個老生常談的話題了,談到AOP
大家最容易聯想到Spring
,甚至因為AOP
常用在事務的場景,甚至就有不少人認為AOP
就是事務.所以肥朝建議初學者
學習AOP
的路線大致如下:
// 這一步步演進的過程,才是最大的收穫
裝飾者設計模式->靜態代理->JDK、cglib、Javassist優缺點對比->AOP原始碼
複製程式碼
寫在最後
肥朝 是一個專注於 原理、原始碼、開發技巧的技術公眾號,號內原創專題式原始碼解析、真實場景原始碼原理實戰(重點)。掃描下面二維碼關注肥朝,讓本該造火箭的你,不再擰螺絲!