<<dubbo系列>> 神器 ExtensionLoader

概述

ExtensionLoader 是dubbo項目的核心組件,正是有了它,使得dubbo的可擴展性得到了極大的提升.dubbo設計之精良,從這點可見一斑.
dubbo的擴展機制,類似于jdk所提供的spi機制. 但是dubbo的更加強大,體現在:

  • jdk的spi默認會加載并實例化所有擴展點類,如果對應擴展點沒用到且實例化消耗大,則是個耗資源的點。
  • 不支持依賴注入,即如果在擴展點類a中,有b擴展點屬性實例,則在加載擴展點類a時不會自動注入b。但是dubbo支持依賴注入(根據目標類的setter方法注入
  • 加載擴展點失敗會吞掉異常,比如加載一個擴展類a,如果其所依賴的jar找不到,不會報這個錯誤,反而會報不支持。

首先,先手寫一個demo利用dubbo的擴展機制,實現一個自己的服務.初步體驗一下ExtensionLoader的用法.

首先定義一個(SPI)接口

//必須有 @spi注解 沒有就報錯
@SPI
public interface BaseExt {
    @Adaptive
    String echo(String str, URL url);
}

然后定義2個實現類

@Adaptive
public class ExtOne implements BaseExt {
    @Override
    public String echo(String str, URL url) {
        System.out.println("one ... " + str);
        return null;
    }
}
public class ExtTwo implements BaseExt {
    @Override
    public String echo(String str, URL url) {
        System.out.println("two ... " + str);
        return null;
    }
}

最后要在 classpath(src/main/resources)下創(chuàng)建一個META-INF/dubbo/internal的目錄,創(chuàng)建一個名字為spi接口全路徑(com.xx.BaseExt)的文件里面寫入一下內容(key=value)

one=com.xx.ExtOne
two=com.xx.ExtTwo

搞定,最后寫一個測試類

public class Main {
    public static void main(String[] args) {
        //
        ExtensionLoader<BaseExt> loader = ExtensionLoader.getExtensionLoader(BaseExt.class);
        BaseExt adaptiveExtension = loader.getAdaptiveExtension();
        URL url = URL.valueOf("test://localhost/test");
        adaptiveExtension.echo("d", url);
    }

得到以下輸出

one ... d

入門非常簡單,例子更簡單.從上面能得到一下總結: 用法其實和jdk的ServiceLoader 一個思想, (接口+實現類+配置文件) . 其實dubbo的擴展一直要遵守一下的約定

  • Dubbo約定,需要通過SPI加載的接口,在接口上必須添加注解@SPI。
  • Dubbo自己實現了加載器(ExtensionLoader)而非使用JDK自帶的(ServiceLoader)。
  • 實現類的配置文件名稱還是以接口全路徑命名約定不變,但是內容以key=value格式存在,key表示實現類的別名,value表示實現類全路徑(JDK自帶的SPI約定內容是實現類全路徑)。
  • ExtensionLoader分別從依賴的JAR包中的,META-INF/dubbo/internal/目錄、META-INF/dubbo/目錄、META-INF/services/目錄,這個3個目錄中掃描尋找實現類配置文件。
  • 所有SPI接口,都會有一個要求有一個適配類(即在實現類打上@Adaptive注解就被認定為適配類,如果掃描后所有實現類都沒有該注解,系統(tǒng)會生成一個)。在系統(tǒng)里面使用時,一般都先獲取接口的適配類實例,然后再由入參配置來決定選擇哪個具體實現類調用。實現了可配置動態(tài)切換實現類。
  • 在實現類里面,可以通過提供setter方法來達到依賴注入。

其實dubbo一個spi服務一般會有多種實現 ,具體使用哪一個實現呢? 這個是憂優(yōu)先級的 .

  • 首先,在實現類(方法上無效)上面加 @Adaptive 注解,優(yōu)先級會最高
  • 如果所有實現類都沒有這個注解(接口方法上的不算),回去尋找url路徑中的指明的那個注解 ,比如上面mian函數中的 ,spi接口的分段形式(XxxYyy類的分段形式 : xxxx.yyy)作為key,配置文件中的key作為value, 根據這個value也可以找到具體實現類,比如上面如果URL中這樣寫:
    URL url = URL.valueOf("test://localhost/test?base.ext=one");
    就表示 spi接口BaseExt 要求使用OneExt作為實現類
  • 如果URL中也沒有指定 ,還有次次優(yōu)先級的方式: spi注解中加入默認值
    比如 BaseExt的spi注解中這樣寫: @SPI("two")此時,TwoExt 就作為默認實現類提供服務了 .注意這個優(yōu)先級哦

知道了上面的用法和各種玩法. 下面是時候再探究竟了 .我們看下源代碼吧 .

ExtensionLoader<BaseExt> loader = ExtensionLoader.getExtensionLoader(BaseExt.class);
引出

 @SuppressWarnings("unchecked")
    public static <T> ExtensionLoader<T> getExtensionLoader(Class<T> type) {
        if (type == null)
            throw new IllegalArgumentException("Extension type == null");
        //說明SPI獲取的必須是接口類型
        if(!type.isInterface()) {
            throw new IllegalArgumentException("Extension type(" + type + ") is not interface!");
        }
        // 說明SPI服務的接口必須有`@SPI`這個注解
        if(!withExtensionAnnotation(type)) {
            throw new IllegalArgumentException("Extension type(" + type + 
                    ") is not extension, because WITHOUT @" + SPI.class.getSimpleName() + " Annotation!");
        }
        //第一次獲取必定拿到null
        ExtensionLoader<T> loader = (ExtensionLoader<T>) EXTENSION_LOADERS.get(type);
        if (loader == null) {
            //new一個擴展器處理這個type,new操作的特別之處在于會創(chuàng)建一個factory來獲取目標type,從而得到擴展工廠,
            EXTENSION_LOADERS.putIfAbsent(type, new ExtensionLoader<T>(type));
            loader = (ExtensionLoader<T>) EXTENSION_LOADERS.get(type);
        }
        return loader;
    }

創(chuàng)建擴展工廠需要ExtensionFactory

  private ExtensionLoader(Class<?> type) {
        this.type = type;
        objectFactory = (type == ExtensionFactory.class ? null : ExtensionLoader.getExtensionLoader(ExtensionFactory.class).getAdaptiveExtension());
    }

從上面可以看到 ,拿到 ExtensionFactory之后,會放入 ExtensionLoader的內部類變量 private static final ConcurrentMap<Class<?>, ExtensionLoader<?>> EXTENSION_LOADERS = new ConcurrentHashMap<Class<?>, ExtensionLoader<?>>(); 里面 .這個玩意就是緩存著了. 所有獲取過的擴展對象都會在這里存下來. 后面再拿就走這個緩存了. 我們再來看main中下一句引子BaseExt adaptiveExtension = loader.getAdaptiveExtension();

public T getAdaptiveExtension() {
        Object instance = cachedAdaptiveInstance.get();
        if (instance == null) {
            if(createAdaptiveInstanceError == null) {
                synchronized (cachedAdaptiveInstance) {
                    instance = cachedAdaptiveInstance.get();
                    if (instance == null) {
                        try {
                            //首次拿擴展實例會需要先創(chuàng)建 ,
                            //創(chuàng)建好了 才回緩存這個實例
                            //下面這句是核心邏輯 ,
                            instance = createAdaptiveExtension();
                            cachedAdaptiveInstance.set(instance);
                        } catch (Throwable t) {
                            createAdaptiveInstanceError = t;
                            throw new IllegalStateException("fail to create adaptive instance: " + t.toString(), t);
                        }
                    }
                }
            }
            else {
                throw new IllegalStateException("fail to create adaptive instance: " + createAdaptiveInstanceError.toString(), createAdaptiveInstanceError);
            }
        }

        return (T) instance;
    }

調用創(chuàng)建擴展類函數 ExtensionLoader.createAdaptiveExtension

private T createAdaptiveExtension() {
        try {
            //創(chuàng)建擴展執(zhí)行依賴插入檢查和依賴插入
            return injectExtension((T) getAdaptiveExtensionClass().newInstance());
        } catch (Exception e) {
            throw new IllegalStateException("Can not create adaptive extenstion " + type + ", cause: " + e.getMessage(), e);
        }
    }

繼續(xù)看獲取的邏輯 getAdaptiveExtensionClass

// 開始獲取擴展
   private Class<?> getAdaptiveExtensionClass() {
        //這里如果獲取下面步驟
        getExtensionClasses();
        if (cachedAdaptiveClass != null) {
            return cachedAdaptiveClass;
        }
        return cachedAdaptiveClass = createAdaptiveExtensionClass();
    }
//下面先沿著 getExtensionClasses 這條線 ,嘗試獲取擴展實現類
//下面是獲取擴展的
private Map<String, Class<?>> getExtensionClasses() {
        Map<String, Class<?>> classes = cachedClasses.get();
        if (classes == null) {
            synchronized (cachedClasses) {
                classes = cachedClasses.get();
                if (classes == null) {
                    classes = loadExtensionClasses();
                    cachedClasses.set(classes);
                }
            }
        }
        return classes;
    }
  // 這個方法就是 核心邏輯了 ,會從文件中掃描指定接口的實現類
  // 此方法已經getExtensionClasses方法同步過。
    private Map<String, Class<?>> loadExtensionClasses() {
        final SPI defaultAnnotation = type.getAnnotation(SPI.class);
        if(defaultAnnotation != null) {
            String value = defaultAnnotation.value();
            if(value != null && (value = value.trim()).length() > 0) {
                String[] names = NAME_SEPARATOR.split(value);
                if(names.length > 1) {
                    throw new IllegalStateException("more than 1 default extension name on extension " + type.getName()
                            + ": " + Arrays.toString(names));
                }
                if(names.length == 1) cachedDefaultName = names[0];
            }
        }
        
        Map<String, Class<?>> extensionClasses = new HashMap<String, Class<?>>();
        //從 META-INF/dubbo/internal 目錄讀取
        loadFile(extensionClasses, DUBBO_INTERNAL_DIRECTORY);
        // 從 META-INF/dubbo/ 目錄讀取
        loadFile(extensionClasses, DUBBO_DIRECTORY);
        // 從 META-INF/services/ 目錄讀取
        loadFile(extensionClasses, SERVICES_DIRECTORY);
        return extensionClasses;
    }
//讀取邏輯不在貼代碼了 ,就是獲取文件 按照key=value的配置文件的形式解析服務配置文件
//但是有一段代碼貼下 ,就是在解析的過程中,發(fā)現實現類頭上有 @Adaptive注解的 ,就把他設置到當前的 cachedAdaptiveClass 屬性中去.(摘自方法 loadFile)
  //
 if (clazz.isAnnotationPresent(Adaptive.class)) {
    if(cachedAdaptiveClass == null) {
        cachedAdaptiveClass = clazz;
    } else if (! cachedAdaptiveClass.equals(clazz)) {
        throw new IllegalStateException("More than 1 adaptive class found: "
                + cachedAdaptiveClass.getClass().getName()
                + ", " + clazz.getClass().getName());
    }
} 
// ..................

到此為止,這條線已經走完,完成之后(看下面),如果在load的過程中 找到了自動適配的類(@adaptive注解的實現類) ,則直接返回cachedAdaptiveClass完成任務 .否則開始走另一條線createAdaptiveExtensionClass

// 這塊代碼是上面開始的時候代碼,
  private Class<?> getAdaptiveExtensionClass() {
        //這里如果獲取下面步驟
        getExtensionClasses();
        if (cachedAdaptiveClass != null) {
            return cachedAdaptiveClass;
        }
        return cachedAdaptiveClass = createAdaptiveExtensionClass();
    }

接下來看看另一條線吧.

 private Class<?> createAdaptiveExtensionClass() {
//拼接代碼的方式
        String code = createAdaptiveExtensionClassCode();
        ClassLoader classLoader = findClassLoader();
        com.alibaba.dubbo.common.compiler.Compiler compiler = ExtensionLoader.getExtensionLoader(com.alibaba.dubbo.common.compiler.Compiler.class).getAdaptiveExtension();
        return compiler.compile(code, classLoader);
    }
//不全部粘貼了 ,詳細的自己看源碼了 ,篇幅有限
private String createAdaptiveExtensionClassCode() {
        StringBuilder codeBuidler = new StringBuilder();
        Method[] methods = type.getMethods();
        boolean hasAdaptiveAnnotation = false;
        for(Method m : methods) {
            if(m.isAnnotationPresent(Adaptive.class)) {
                hasAdaptiveAnnotation = true;
                break;
            }
        }
        // 完全沒有Adaptive方法,則不需要生成Adaptive類
        if(! hasAdaptiveAnnotation)
            throw new IllegalStateException("No adaptive method on extension " + type.getName() + ", refuse to create the adaptive class!");
        
        codeBuidler.append("package " + type.getPackage().getName() + ";");
        codeBuidler.append("\nimport " + ExtensionLoader.class.getName() + ";");
        codeBuidler.append("\npublic class " + type.getSimpleName() + "$Adpative" + " implements " + type.getCanonicalName() + " {");
        
        for (Method method : methods) {
            Class<?> rt = method.getReturnType();
            Class<?>[] pts = method.getParameterTypes();
            Class<?>[] ets = method.getExceptionTypes();
//.......................省略 ,

可以看出,上面就是要通過代碼創(chuàng)建一個java文件,并用compile進行編譯
下面是一個通過編譯得到的例子 (可讀性比較差,但是能看出大概意思),

package com.xxx.dubbo;
import com.alibaba.dubbo.common.extension.ExtensionLoader;
public class BaseExt$Adpative implements com.zuosh.dts2018.dubbo.BaseExt {
public java.lang.String echo(java.lang.String arg0, com.alibaba.dubbo.common.URL arg1) {
if (arg1 == null) throw new IllegalArgumentException("url == null");
com.alibaba.dubbo.common.URL url = arg1;
String extName = url.getParameter("base.ext");
if(extName == null) throw new IllegalStateException("Fail to get extension(com.xxx.dubbo.BaseExt) name from url(" + url.toString() + ") use keys([base.ext])");
com.xxx.dubbo.BaseExt extension = (com.xxx.dubbo.BaseExt)ExtensionLoader.getExtensionLoader(com.xxx.dubbo.BaseExt.class).getExtension(extName);
return extension.echo(arg0, arg1);
}
}

上面就是獲取自適應擴展實現類的全部過程. 我們從一個方法逐漸深入,看了擴展器的其中一個功能 (獲取自適應擴展類) ,其他各種五花八門的獲取實現類的方式(如下圖)都是差不多同樣的道理.都值得我們一一學習和參考.


image.png

后面的其他擴展原理都是大同小異,但是重載的很多方法給我們日常編碼提供了很好的例子,讓我們知道怎么樣寫一個好的擴展,而不是僅僅實現功能.

?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
【社區(qū)內容提示】社區(qū)部分內容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內容

友情鏈接更多精彩內容