Dubbo SPI 使用源碼分析

本文通過(guò)分析 Dubbo 服務(wù)暴露過(guò)程中使用到的3個(gè) SPI 加載的類 ExtensionFactoryProxyFactory最冰、Protocol 來(lái)理解 SPI 的靈活加載邏輯洁灵。

以最簡(jiǎn)單的暴露服務(wù)到 jvm 為例,下面是位于 org.apache.dubbo.config.ServiceConfig 的關(guān)鍵源代碼

// PROTOCOL 的動(dòng)態(tài)類
private static final Protocol PROTOCOL = (Protocol)ExtensionLoader.getExtensionLoader(Protocol.class).getAdaptiveExtension();
// PROXY_FACTORY 的動(dòng)態(tài)類
private static final ProxyFactory PROXY_FACTORY = (ProxyFactory)ExtensionLoader.getExtensionLoader(ProxyFactory.class).getAdaptiveExtension();

// 暴露服務(wù)到本地 jvm 中
private void exportLocal(URL url) {
    URL local = URLBuilder.from(url).setProtocol("injvm").setHost("127.0.0.1").setPort(0).build();
    Exporter<?> exporter = PROTOCOL.export(PROXY_FACTORY.getInvoker(this.ref, this.interfaceClass, local));
    // ...
}

獲取 AdaptiveClass

類中兩個(gè)靜態(tài)變量 PROXY_FACTORYPROTOCOL 使用了相同的方法初始化柱锹。

  1. 獲取指定類的 ExtensionLoader 實(shí)例
  2. 通過(guò) ExtensionLoader 實(shí)例調(diào)用 getAdaptiveExtension() 方法獲取指定類的可擴(kuò)展類闽巩。

獲取指定 type 的 ExtensionLoader 方法如下:

public static <T> ExtensionLoader<T> getExtensionLoader(Class<T> type) {
    if (type == null) {
        throw new IllegalArgumentException("Extension type == null");
    } else if (!type.isInterface()) {
        throw new IllegalArgumentException("Extension type (" + type + ") is not an interface!");
    } else if (!withExtensionAnnotation(type)) {
        throw new IllegalArgumentException("Extension type (" + type + ") is not an extension, because it is NOT annotated with @" + SPI.class.getSimpleName() + "!");
    } else {
        // 先查看是否已經(jīng)生成過(guò)
        ExtensionLoader<T> loader = (ExtensionLoader)EXTENSION_LOADERS.get(type);
        if (loader == null) {
            // 當(dāng) type=ProxyFactory 時(shí),首次進(jìn)入這里會(huì)初始化 ProxyFactory 類的 ExtensionLoader 實(shí)例
            // 并把實(shí)例都會(huì)緩存到靜態(tài)變量中
            EXTENSION_LOADERS.putIfAbsent(type, new ExtensionLoader(type));
            loader = (ExtensionLoader)EXTENSION_LOADERS.get(type);
        }

        return loader;
    }
}

關(guān)鍵是使用 ExtensionLoader 類的構(gòu)造函數(shù)進(jìn)行實(shí)例化的流椒。

private ExtensionLoader(Class<?> type) {
    this.type = type;
    // 當(dāng)前 type=ProxyFactory署惯, 它的 objectFactory 為 ExtensionFactory 類的動(dòng)態(tài)類
    // 當(dāng) type=ExtensionFactory 時(shí),它的 objectFactory 為 null
    this.objectFactory = type == ExtensionFactory.class ? null : (ExtensionFactory)getExtensionLoader(ExtensionFactory.class).getAdaptiveExtension();
}

objectFactory 是用來(lái)在注入依賴的時(shí)候镣隶,獲取依賴屬性的對(duì)象工廠。它是 ExtensionFactory 類的 AdaptiveClass诡右。
獲取 ExtensionLoader的 AdaptiveClass安岂,它的流程比較長(zhǎng),如下圖:

ExtensionLoader 類加載.png

最終是在項(xiàng)目的 META-INF 指定文件夾中查找到 type 配置的所有實(shí)現(xiàn)類帆吻,獲取到的 AdaptiveClass 有兩種情況:

  1. Type 接口的實(shí)現(xiàn)類上有 @Adaptive 注解域那,則直接返回有此注解的類,如 type=ExtensionFactory 的實(shí)現(xiàn)類 AdaptiveExtensionFactory猜煮。
  2. Type 接口的實(shí)現(xiàn)類上均無(wú) @Adaptive 注解次员,但是實(shí)現(xiàn)類的方法上有此注解,則會(huì)在有注解的方法上動(dòng)態(tài)生成實(shí)現(xiàn)類王带,如 type=ProxyFactory 接口均有 @Adaptive 方法淑蔚,又 @SPI("javassist") 代表默認(rèn)獲取到的是 JavassistProxyFactory 類。
    (ProxyFactory)ExtensionLoader.getExtensionLoader(ProxyFactory.class).getAdaptiveExtension() 獲得的類如下:
package org.apache.dubbo.rpc;
import org.apache.dubbo.common.extension.ExtensionLoader;
public class ProxyFactory$Adaptive implements org.apache.dubbo.rpc.ProxyFactory {

    public java.lang.Object getProxy(org.apache.dubbo.rpc.Invoker arg0) throws org.apache.dubbo.rpc.RpcException {
        // 檢查是否參數(shù)異常
        if (arg0 == null) throw new IllegalArgumentException("org.apache.dubbo.rpc.Invoker argument == null");
        if (arg0.getUrl() == null) throw new IllegalArgumentException("org.apache.dubbo.rpc.Invoker argument getUrl() == null");

        org.apache.dubbo.common.URL url = arg0.getUrl();
        // 首先根據(jù) url 中指定的 proxy 名稱愕撰,為空的話默認(rèn)使用 javassist 類名對(duì)應(yīng)的 ProxyFactory 實(shí)現(xiàn)類
        String extName = url.getParameter("proxy", "javassist");
        if(extName == null) throw new IllegalStateException("Failed to get extension (org.apache.dubbo.rpc.ProxyFactory) name from url (" + url.toString() + ") use keys([proxy])");
        org.apache.dubbo.rpc.ProxyFactory extension = (org.apache.dubbo.rpc.ProxyFactory)ExtensionLoader.getExtensionLoader(org.apache.dubbo.rpc.ProxyFactory.class).getExtension(extName);
        return extension.getProxy(arg0);
    }

    public java.lang.Object getProxy(org.apache.dubbo.rpc.Invoker arg0, boolean arg1) throws org.apache.dubbo.rpc.RpcException {
        if (arg0 == null) throw new IllegalArgumentException("org.apache.dubbo.rpc.Invoker argument == null");
        if (arg0.getUrl() == null) throw new IllegalArgumentException("org.apache.dubbo.rpc.Invoker argument getUrl() == null");

        org.apache.dubbo.common.URL url = arg0.getUrl();
        String extName = url.getParameter("proxy", "javassist");
        if(extName == null) throw new IllegalStateException("Failed to get extension (org.apache.dubbo.rpc.ProxyFactory) name from url (" + url.toString() + ") use keys([proxy])");
        org.apache.dubbo.rpc.ProxyFactory extension = (org.apache.dubbo.rpc.ProxyFactory)ExtensionLoader.getExtensionLoader(org.apache.dubbo.rpc.ProxyFactory.class).getExtension(extName);
        return extension.getProxy(arg0, arg1);
    }

    public org.apache.dubbo.rpc.Invoker getInvoker(java.lang.Object arg0, java.lang.Class arg1, org.apache.dubbo.common.URL arg2) throws org.apache.dubbo.rpc.RpcException {
        if (arg2 == null) throw new IllegalArgumentException("url == null");

        org.apache.dubbo.common.URL url = arg2;
        String extName = url.getParameter("proxy", "javassist");
        if(extName == null) throw new IllegalStateException("Failed to get extension (org.apache.dubbo.rpc.ProxyFactory) name from url (" + url.toString() + ") use keys([proxy])");
        org.apache.dubbo.rpc.ProxyFactory extension = (org.apache.dubbo.rpc.ProxyFactory)ExtensionLoader.getExtensionLoader(org.apache.dubbo.rpc.ProxyFactory.class).getExtension(extName);
        return extension.getInvoker(arg0, arg1, arg2);
    }
}

(Protocol)ExtensionLoader.getExtensionLoader(Protocol.class).getAdaptiveExtension() 獲得的類如下:

package org.apache.dubbo.rpc;
import org.apache.dubbo.common.extension.ExtensionLoader;
public class Protocol$Adaptive implements org.apache.dubbo.rpc.Protocol {
    // 沒(méi)有 @Adaptive 注解的方法會(huì)直接拋出異常
    public void destroy()  {
        throw new UnsupportedOperationException("The method public abstract void org.apache.dubbo.rpc.Protocol.destroy() of interface org.apache.dubbo.rpc.Protocol is not adaptive method!");
    }

    public int getDefaultPort()  {
        throw new UnsupportedOperationException("The method public abstract int org.apache.dubbo.rpc.Protocol.getDefaultPort() of interface org.apache.dubbo.rpc.Protocol is not adaptive method!");
    }
    
    // 有 Adaptive 注解的方法都會(huì)生成動(dòng)態(tài)方法
    public org.apache.dubbo.rpc.Exporter export(org.apache.dubbo.rpc.Invoker arg0) throws org.apache.dubbo.rpc.RpcException {
        // 參數(shù)異常檢查
        if (arg0 == null) throw new IllegalArgumentException("org.apache.dubbo.rpc.Invoker argument == null");
        if (arg0.getUrl() == null) throw new IllegalArgumentException("org.apache.dubbo.rpc.Invoker argument getUrl() == null");
        
        // 這里使用的是 url 中的 protocol 作為動(dòng)態(tài)獲取的類名刹衫,如果 url 中沒(méi)有,則使用默認(rèn)的類名
        org.apache.dubbo.common.URL url = arg0.getUrl();
        String extName = ( url.getProtocol() == null ? "dubbo" : url.getProtocol() );
        if(extName == null) throw new IllegalStateException("Failed to get extension (org.apache.dubbo.rpc.Protocol) name from url (" + url.toString() + ") use keys([protocol])");

        org.apache.dubbo.rpc.Protocol extension = (org.apache.dubbo.rpc.Protocol)ExtensionLoader.getExtensionLoader(org.apache.dubbo.rpc.Protocol.class).getExtension(extName);
        return extension.export(arg0);
    }

    public org.apache.dubbo.rpc.Invoker refer(java.lang.Class arg0, org.apache.dubbo.common.URL arg1) throws org.apache.dubbo.rpc.RpcException {
        if (arg1 == null) throw new IllegalArgumentException("url == null");

        org.apache.dubbo.common.URL url = arg1;
        String extName = ( url.getProtocol() == null ? "dubbo" : url.getProtocol() );
        if(extName == null) throw new IllegalStateException("Failed to get extension (org.apache.dubbo.rpc.Protocol) name from url (" + url.toString() + ") use keys([protocol])");

        org.apache.dubbo.rpc.Protocol extension = (org.apache.dubbo.rpc.Protocol)ExtensionLoader.getExtensionLoader(org.apache.dubbo.rpc.Protocol.class).getExtension(extName);
        return extension.refer(arg0, arg1);
    }

    public java.util.List getServers()  {
        throw new UnsupportedOperationException("The method public default java.util.List org.apache.dubbo.rpc.Protocol.getServers() of interface org.apache.dubbo.rpc.Protocol is not adaptive method!");
    }
}

通過(guò) SPI 獲取到了 Type 接口的 AdaptiveClass搞挣,接下來(lái)看在使用的地方如何動(dòng)態(tài)獲取指定的類带迟。

暴露服務(wù)到 jvm 本地

代碼很短小,實(shí)際運(yùn)行的內(nèi)容卻不簡(jiǎn)單

private void exportLocal(URL url) {
    // 設(shè)置 local URL 屬性
    URL local = URLBuilder.from(url).setProtocol("injvm").setHost("127.0.0.1").setPort(0).build();
    Exporter<?> exporter = PROTOCOL.export(PROXY_FACTORY.getInvoker(this.ref, this.interfaceClass, local));
    // ...
}

通過(guò)前面已經(jīng)知道 PROXY_FACTORY 是動(dòng)態(tài)生成ProxyFactory$Adaptive 類的實(shí)例囱桨,
PROXY_FACTORY.getInvoker(this.ref, this.interfaceClass, local) 會(huì)調(diào)用ProxyFactory$Adaptive 類的下面幾行代碼

org.apache.dubbo.common.URL url = arg2;
// 為 local 的 URL 中 proxy 參數(shù)為空仓犬,會(huì)去獲取名為 javassist 的默認(rèn)實(shí)現(xiàn)類
String extName = url.getParameter("proxy", "javassist");
// 這里根據(jù)運(yùn)行時(shí)得到的名稱加載指定類實(shí)例
org.apache.dubbo.rpc.ProxyFactory extension = (org.apache.dubbo.rpc.ProxyFactory)ExtensionLoader.getExtensionLoader(org.apache.dubbo.rpc.ProxyFactory.class).getExtension(extName);
// 調(diào)用類實(shí)例的 getInvoker() 方法
return extension.getInvoker(arg0, arg1, arg2);

getExtension(extName) 方法調(diào)用邏輯如下


ExtensionLoader 類加載指定類.png

PROXY_FACTORY.getInvoker(this.ref, this.interfaceClass, local) 實(shí)際調(diào)用棧如下

ProxyFactory的getInvoker.png

接著是 PROTOCOL.export(invoker) 分析過(guò)程與 PROXY_FACTORY.getInvoker(...) 一致,它的調(diào)用棧邏輯如下

Protocal的export.png

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末舍肠,一起剝皮案震驚了整個(gè)濱河市搀继,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌貌夕,老刑警劉巖律歼,帶你破解...
    沈念sama閱讀 211,194評(píng)論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異啡专,居然都是意外死亡险毁,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,058評(píng)論 2 385
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)畔况,“玉大人鲸鹦,你說(shuō)我怎么就攤上這事□喂颍” “怎么了馋嗜?”我有些...
    開(kāi)封第一講書(shū)人閱讀 156,780評(píng)論 0 346
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)吵瞻。 經(jīng)常有香客問(wèn)我葛菇,道長(zhǎng),這世上最難降的妖魔是什么橡羞? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 56,388評(píng)論 1 283
  • 正文 為了忘掉前任眯停,我火速辦了婚禮,結(jié)果婚禮上卿泽,老公的妹妹穿的比我還像新娘莺债。我一直安慰自己,他們只是感情好签夭,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,430評(píng)論 5 384
  • 文/花漫 我一把揭開(kāi)白布齐邦。 她就那樣靜靜地躺著,像睡著了一般第租。 火紅的嫁衣襯著肌膚如雪措拇。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 49,764評(píng)論 1 290
  • 那天煌妈,我揣著相機(jī)與錄音儡羔,去河邊找鬼。 笑死璧诵,一個(gè)胖子當(dāng)著我的面吹牛汰蜘,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播之宿,決...
    沈念sama閱讀 38,907評(píng)論 3 406
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼族操,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了比被?” 一聲冷哼從身側(cè)響起色难,我...
    開(kāi)封第一講書(shū)人閱讀 37,679評(píng)論 0 266
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎等缀,沒(méi)想到半個(gè)月后枷莉,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,122評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡尺迂,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,459評(píng)論 2 325
  • 正文 我和宋清朗相戀三年笤妙,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了冒掌。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,605評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡蹲盘,死狀恐怖股毫,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情召衔,我是刑警寧澤铃诬,帶...
    沈念sama閱讀 34,270評(píng)論 4 329
  • 正文 年R本政府宣布,位于F島的核電站苍凛,受9級(jí)特大地震影響趣席,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜醇蝴,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,867評(píng)論 3 312
  • 文/蒙蒙 一吩坝、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧哑蔫,春花似錦、人聲如沸弧呐。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,734評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)俘枫。三九已至腥沽,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間鸠蚪,已是汗流浹背今阳。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 31,961評(píng)論 1 265
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留茅信,地道東北人盾舌。 一個(gè)月前我還...
    沈念sama閱讀 46,297評(píng)論 2 360
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像蘸鲸,于是被迫代替她去往敵國(guó)和親妖谴。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,472評(píng)論 2 348

推薦閱讀更多精彩內(nèi)容