Dubbo和JDK的SPI究竟有何區別?

前言

上一篇簡單的介紹了spi的基本一些概念,可是其實Dubbo對jdk的spi進行了一些改進,具體改進了什麼,來看看文檔的描述前端

  • JDK 標準的 SPI 會一次性實例化擴展點全部實現,若是有擴展實現初始化很耗時,但若是沒用上也加載,會很浪費資源。java

  • 若是擴展點加載失敗,連擴展點的名稱都拿不到了。好比:JDK 標準的 ScriptEngine,經過 getName() 獲取腳本類型的名稱,但若是 RubyScriptEngine 由於所依賴的 jruby.jar 不存在,致使 RubyScriptEngine 類加載失敗,這個失敗緣由被吃掉了,和 ruby 對應不起來,當用戶執行 ruby 腳本時,會報不支持 ruby,而不是真正失敗的緣由。面試

  • 增長了對擴展點 IoC 和 AOP 的支持,一個擴展點能夠直接 setter 注入其它擴展點。設計模式

根據小學語文的閱讀理解,不難歸納出其實就是提升了性能增長了功能.不少朋友都喜歡問,閱讀源碼不如從何下手,要準備些什麼.若是隻是粗略閱讀源碼,掌握大致思路,其實具有小學語文的閱讀理解和看圖寫做業就差很少了(能看到本篇的均徹底勝任這個條件,因此不要有任何恐懼心理).可是要領悟思想,對細節瞭如指掌,甚至寫出更優秀的框架,那麼就是四個字->終身學習.(好比如今關注肥朝公衆號,一塊兒交流討論,終身學習即刻開啓)瀏覽器

那dubbo這個改良後的spi究竟怎麼提升性能,又增長了什麼功能,那就是本篇要講的.緩存

圖片

插播面試題

  • 既然你對spi有必定了解,那麼Dubbo的spi和jdk的spi有區別嗎?有的話,究竟有什麼區別?ruby

  • 你說你看過Dubbo源碼,那你簡單說下,他在設計上有哪些細節讓你以爲很巧妙?(區分度高)併發

概念鋪墊

Dubbo的拓展點機制涉及到衆多的知識點,也是Dubbo中比較難的地方,和以前的集羣容錯有ClusterDirectoryRouterLoadBalance關鍵詞同樣,這個拓展點機制也有幾個關鍵詞,SPIAdaptiveActivate.這些會陸續講解,最後總結.框架

直入主題

提高性能

提高性能,咱們最容易想到的方式是什麼?其實這個和初高中政治答題同樣,有萬能公式的,那就是"緩存".因此面試不管問你什麼(適用於Android,iOS,Web前端,Java等等…),只要和提高性能有關的,往緩存方向答確定沒錯(固然按照"按點給分"的套路,往緩存方向答只是不至於讓你拿0分,可是僅僅答緩存確定拿不到滿分).因此若是與jdk的spi對比,那麼能夠有如下幾個點ide

1.從"萬能公式"角度分析,增長緩存

由於部分朋友反饋說喜歡貼代碼的形式,因此講解在註釋中

 1public static <T> ExtensionLoader<T> getExtensionLoader(Class<T> type) {
2    if (type == null)//拓展點類型非空判斷
3        throw new IllegalArgumentException("Extension type == null");
4    if(!type.isInterface()) {//拓展點類型只能是接口
5        throw new IllegalArgumentException("Extension type(" + type + ") is not interface!");
6    }
7    if(!withExtensionAnnotation(type)) {//須要添加spi註解,不然拋異常
8        throw new IllegalArgumentException("Extension type(" + type + 
9                ") is not extension, because WITHOUT @" + SPI.class.getSimpleName() + " Annotation!");
10    }
11    //從緩存EXTENSION_LOADERS中獲取,若是不存在則新建後加入緩存
12    //對於每個拓展,都會有且只有一個ExtensionLoader與其對應
13    ExtensionLoader<T> loader = (ExtensionLoader<T>) EXTENSION_LOADERS.get(type);
14    if (loader == null) {
15        EXTENSION_LOADERS.putIfAbsent(type, new ExtensionLoader<T>(type));
16        loader = (ExtensionLoader<T>) EXTENSION_LOADERS.get(type);
17    }
18    return loader;
19}
20
21private ExtensionLoader(Class<?> type) {
22    this.type = type;
23    //這裏會存在遞歸調用,ExtensionFactory的objectFactory爲null,其餘的均爲AdaptiveExtensionFactory
24    //AdaptiveExtensionFactory的factories中有SpiExtensionFactory,SpringExtensionFactory
25    //getAdaptiveExtension()這個是獲取一個拓展裝飾類對象.細節在下篇講解
26    objectFactory = (type == ExtensionFactory.class ? null : ExtensionLoader.getExtensionLoader(ExtensionFactory.class).getAdaptiveExtension());
27}

如下是緩存拓展點對象的

 1public T getExtension(String name) {
2    if (name == null || name.length() == 0)
3        throw new IllegalArgumentException("Extension name == null");
4    if ("true".equals(name)) {
5        return getDefaultExtension();
6    }
7    Holder<Object> holder = cachedInstances.get(name);
8    if (holder == null) {
9        cachedInstances.putIfAbsent(name, new Holder<Object>());
10        holder = cachedInstances.get(name);
11    }
12    Object instance = holder.get();
13    if (instance == null) {
14        synchronized (holder) {
15            instance = holder.get();
16            if (instance == null) {
17                instance = createExtension(name);
18                holder.set(instance);
19            }
20        }
21    }
22    return (T) instance;
23}

爲何這個要單獨拿出來講呢?不少朋友容易產生大意心理,覺得緩存嘛,無非就是判斷一下是否存在,不存在則添加.dubbo也不過如此.我不看源碼也懂.可是你若是稍加註意,就會發現它在細節方面作得很好.

敲黑板劃重點

在上一篇從Dubbo內核聊聊雙親委派機制中我就強調了,不管作什麼,都要造成差別性.在Java中,最容易造成差別性的知識點,就是JVM併發包.既然上一篇中咱們提了JVM相關的內容(雙親委派機制),那麼本篇咱們就說說併發包的相關內容.咱們仔細看上面的這段double-checked locking代碼

 1//double-checked locking
2Object instance = holder.get();
3if (instance == null) {
4    synchronized (holder) {
5        instance = holder.get();
6        if (instance == null) {
7            instance = createExtension(name);
8            holder.set(instance);
9        }
10    }
11}
 1public class Holder<T{
2
3    private volatile T value;
4
5    public void set(T value) {
6        this.value = value;
7    }
8
9    public T get() {
10        return value;
11    }
12
13}

這裏爲何用到了volatile關鍵字呢?看源碼更重要的是看到這些細節,魔鬼都藏在細節當中!原本肥朝想展開講一下這個volatile秀一波操做的,可是無奈篇幅有限,那個我給你個關鍵詞.doubleCheck Singleton 重排序,你把這個關鍵詞對着瀏覽器一搜.而後把搜索到的第一頁結果都看完,你就知道這段代碼並非你想的只是判空加個緩存這麼簡單.他在併發細節上是有考慮的

2.從註解角度入手分析

既然是對比spi區別,而且dubbo中有@spi這個註解,那咱們順着註解看看能有什麼線索.

若是在15年有用過dubbo,那麼就會留意到@Extension這個註解,可是後來由於含義普遍廢棄,換成了@SPI.

1@SPI("javassist")
2public interface Compiler {
3    //省略...
4}
 1public Class<?> compile(String code, ClassLoader classLoader) {
2    Compiler compiler;
3    ExtensionLoader<Compiler> loader = ExtensionLoader.getExtensionLoader(Compiler.class);
4    String name = DEFAULT_COMPILER; // copy reference
5    if (name != null && name.length() > 0) {
6        compiler = loader.getExtension(name);
7    } else {
8        compiler = loader.getDefaultExtension();
9    }
10    return compiler.compile(code, classLoader);
11}
1//com.alibaba.dubbo.common.compiler.Compiler 文件配置以下
2adaptive=com.alibaba.dubbo.common.compiler.support.AdaptiveCompiler
3jdk=com.alibaba.dubbo.common.compiler.support.JdkCompiler
4javassist=com.alibaba.dubbo.common.compiler.support.JavassistCompiler

咱們從上面這兩部分代碼和配置文件就不難分析出兩點(若是對spi不熟悉的請先把上一篇spi(一)看一遍,基礎不牢地動山搖的狀況下無法分析)

  • JDK的spi要用for循環,而後if判斷才能獲取到指定的spi對象,dubbo用指定的key就能夠獲取

1//返回指定名字的擴展
2public T getExtension(String name){}
  • JDK的spi不支持默認值,dubbo增長了默認值的設計

1//@SPI("javassist")表明默認的spi對象,好比Compiler默認使用的是javassist,可經過
2ExtensionLoader<Compiler> loader = ExtensionLoader.getExtensionLoader(Compiler.class);
3compiler = loader.getDefaultExtension();
4//方式獲取實現類,根據配置,即爲
5//com.alibaba.dubbo.common.compiler.support.JavassistCompiler

增長功能

增長的功能,就如文檔所說的,spi增長了IoCAOP

AOP這是個老生常談的話題了,談到AOP你們最容易聯想到Spring,甚至由於AOP經常使用在事務的場景,甚至就有很多人認爲AOP就是事務.因此肥朝建議初學者學習AOP的路線大體以下:

1// 這一步步演進的過程,纔是最大的收穫
2裝飾者設計模式->靜態代理->JDK、cglib、Javassist優缺點對比->AOP源碼

寫在最後

關注肥朝公衆號,後續還會有更多奇巧淫技,真實企業場景源碼級實戰和你們分享.讓"原理"再也不只是面試裝逼.也歡迎你們留言一塊兒交流精進

相關文章
相關標籤/搜索