1. 程式人生 > >dubbo源碼解析-spi(二)

dubbo源碼解析-spi(二)

gets tile load type rec nfa 收獲 shm 介紹

前言

上一篇簡單的介紹了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中比較難的地方,和之前的集群容錯有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也不過如此.我不看源碼也懂.但是你如果稍加註意,就會發現它在線程安全細節方面做得很好,比如

private final ConcurrentMap<String, Holder<Object>> cachedInstances = new ConcurrentHashMap<String, Holder<Object>>();

現在知道為什麽面試問完HashMap喜歡問ConcurrentHashMap了吧?這還不是個關鍵,我們繼續看還有個很重要的類Holder,這個類用於保存一個值,並且給值添加volatile來保證線程的可見性.

public class Holder<T> {
    
    private volatile T value;
    
    public void set(T value) {
        this.value = value;
    }
    
    public T get() {
        return value;
    }

}

看源碼更重要的是看到這些細節,魔鬼都藏在細節當中!

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動態代理->AOP

這裏說的dubbo增加的IoC和AOP因為涉及到一個重要註解@Adaptive,後面會有專門一篇來詳細講,敬請期待.

寫在最後

本篇以spi為引線,主要想表達的是分析問題的思路,新技術雖然層出不窮,但是只要掌握原理和分析問題的思路,自然能應對千變萬化.鑒於肥朝才疏學淺,分析問題的思路仍有很多缺陷和不足,不足之處還望你不吝斧正.期待下周與你相遇,

Ref:

https://www.jianshu.com/p/e7446cdc7161

dubbo源碼解析-spi(二)