溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

如何理解Dubbo的SPI自適應

發布時間:2021-10-19 16:49:11 來源:億速云 閱讀:155 作者:iii 欄目:編程語言

這篇文章主要介紹“如何理解Dubbo的SPI自適應”,在日常操作中,相信很多人在如何理解Dubbo的SPI自適應問題上存在疑惑,小編查閱了各式資料,整理出簡單好用的操作方法,希望對大家解答”如何理解Dubbo的SPI自適應”的疑惑有所幫助!接下來,請跟著小編一起來學習吧!

先定義一個SPI接口(被@SPI標注):

import org.apache.dubbo.common.URL;
import org.apache.dubbo.common.extension.Adaptive;
import org.apache.dubbo.common.extension.SPI;

@SPI
public interface SpiIf {
    @Adaptive
    void test1(URL url);

    @Adaptive
    void test2(ObjHasUrl ohu);

    void test3(URL url);

    void test4(String name);
}

這里的ObjHasUrl是一個內部有URL屬性的對象,為什么要有URL屬性的原因下面會說到。

下一步,定義兩個實現類Spi1和Spi2:

public class Spi1 implements SpiIf {
    @Override
    public void test1(URL url) {
        System.out.println("This is Spi1:test1");
    }

    @Override
    public void test2(ObjHasUrl ohu) {
        System.out.println("This is Spi1:test2");
    }

    @Override
    public void test3(URL url) {
        System.out.println("This is Spi1:test3");
    }

    @Override
    public void test4(String name) {
        System.out.println("This is Spi1:test4");
    }
}


public class Spi2 implements SpiIf {
    @Override
    public void test1(URL url) {
        System.out.println("This is Spi2:test1");
    }

    @Override
    public void test2(ObjHasUrl ohu) {
        System.out.println("This is Spi2:test2");
    }

    @Override
    public void test3(URL url) {
        System.out.println("This is Spi2:test3");
    }

    @Override
    public void test4(String name) {
        System.out.println("This is Spi2:test4");
    }
}

最后一步,定義一個Runner測試啟動器:

public class Runner {
    public static void main(String[] args) {
        URL url = new URL("dubbo", "123", 999);
        url = url.addParameter("spi.if", "S2"); //設置url值,來獲取 SpiTest的自適應擴展 S2。
        SpiIf spiIf = ExtensionLoader.getExtensionLoader(SpiIf.class).getAdaptiveExtension();
        spiIf.test1(url);
        ObjHasUrl ohu = new ObjHasUrl(url);
        spiIf.test2(ohu);

        
        url = url.addParameter("spi.if", "S1");
        SpiIf spiIf2 = ExtensionLoader.getExtensionLoader(SpiIf.class).getAdaptiveExtension();
        spiIf.test2(ohu);
    }
}

以上類可以直接拷貝到自己的本地工程DEBUG用。

當我們啟功Runner的時候,第一步先看getExtensionLoader方法,這里開始進入Dubbo的代碼:

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 an interface!");
        }
//驗證是否有SPI注解
        if (!withExtensionAnnotation(type)) {
            throw new IllegalArgumentException("Extension type (" + type +
                    ") is not an extension, because it is NOT annotated with @" + SPI.class.getSimpleName() + "!");
        }

        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;
    }

這一步主要是獲取ExtensionLoader對象,主要是對接口類做一些驗證,確認是擴展點(有SPI注解)。

第二步,進入ExtensionLoader的getAdaptiveExtension方法:

public T getAdaptiveExtension() {
//先找緩存
        Object instance = cachedAdaptiveInstance.get();
        if (instance == null) {
            if (createAdaptiveInstanceError != null) {
                throw new IllegalStateException("Failed to create adaptive instance: " +
                        createAdaptiveInstanceError.toString(),
                        createAdaptiveInstanceError);
            }
//緩存沒有,則開始創建
            synchronized (cachedAdaptiveInstance) {
                instance = cachedAdaptiveInstance.get();
                if (instance == null) {
                    try {
                        instance = createAdaptiveExtension();
                        cachedAdaptiveInstance.set(instance);
                    } catch (Throwable t) {
                        createAdaptiveInstanceError = t;
                        throw new IllegalStateException("Failed to create adaptive instance: " + t.toString(), t);
                    }
                }
            }
        }

        return (T) instance;
    }

這一步主要是獲取接口的實現對象實列,繼續分析實際創建擴展點的方法:

 private T createAdaptiveExtension() {
        try {
            return injectExtension((T) getAdaptiveExtensionClass().newInstance());
        } catch (Exception e) {
            throw new IllegalStateException("Can't create adaptive extension " + type + ", cause: " + e.getMessage(), e);
        }
    }

這一步拆分成兩步,第一步:getAdaptiveExtensionClass,第二步:injectExtension

第一步:

private Class<?> getAdaptiveExtensionClass() {
        getExtensionClasses();
        if (cachedAdaptiveClass != null) {
            return cachedAdaptiveClass;
        }
        return cachedAdaptiveClass = createAdaptiveExtensionClass();
    }

    private Class<?> createAdaptiveExtensionClass() {
        String code = new AdaptiveClassCodeGenerator(type, cachedDefaultName).generate();
        ClassLoader classLoader = findClassLoader();
        org.apache.dubbo.common.compiler.Compiler compiler = ExtensionLoader.getExtensionLoader(org.apache.dubbo.common.compiler.Compiler.class).getAdaptiveExtension();
        return compiler.compile(code, classLoader);
    }

重點來了,創建class的時候會生成一個code(實際是SpiIf$Adaptive->SpiIf一個實現類):

import org.apache.dubbo.common.extension.ExtensionLoader;
public class SpiIf$Adaptive implements SpiIf {
    public void test2(com.zf.server.authserver.spi.dubbospitest2.ObjHasUrl arg0)  {
        if (arg0 == null) throw new IllegalArgumentException("com.zf.server.authserver.spi.dubbospitest2.ObjHasUrl argument == null");
        if (arg0.getUrl() == null) throw new IllegalArgumentException("com.zf.server.authserver.spi.dubbospitest2.ObjHasUrl argument getUrl() == null");
        org.apache.dubbo.common.URL url = arg0.getUrl();
        String extName = url.getParameter("spi.if");
        if(extName == null) throw new IllegalStateException("Failed to get extension (com.zf.server.authserver.spi.dubbospitest2.SpiIf) name from url (" + url.toString() + ") use keys([spi.if])");
        com.zf.server.authserver.spi.dubbospitest2.SpiIf extension = (com.zf.server.authserver.spi.dubbospitest2.SpiIf)ExtensionLoader.getExtensionLoader(com.zf.server.authserver.spi.dubbospitest2.SpiIf.class).getExtension(extName);
        extension.test2(arg0);
    }
    public void test1(org.apache.dubbo.common.URL arg0)  {
        if (arg0 == null) throw new IllegalArgumentException("url == null");
        org.apache.dubbo.common.URL url = arg0;
        String extName = url.getParameter("spi.if");
        if(extName == null) throw new IllegalStateException("Failed to get extension (com.zf.server.authserver.spi.dubbospitest2.SpiIf) name from url (" + url.toString() + ") use keys([spi.if])");
        com.zf.server.authserver.spi.dubbospitest2.SpiIf extension = (com.zf.server.authserver.spi.dubbospitest2.SpiIf)ExtensionLoader.getExtensionLoader(com.zf.server.authserver.spi.dubbospitest2.SpiIf.class).getExtension(extName);
        extension.test1(arg0);
    }
    public void test3(org.apache.dubbo.common.URL arg0)  {
        throw new UnsupportedOperationException("The method public abstract void com.zf.server.authserver.spi.dubbospitest2.SpiIf.test3(org.apache.dubbo.common.URL) of interface com.zf.server.authserver.spi.dubbospitest2.SpiIf is not adaptive method!");
    }
    public void test4(java.lang.String arg0)  {
        throw new UnsupportedOperationException("The method public abstract void com.zf.server.authserver.spi.dubbospitest2.SpiIf.test4(java.lang.String) of interface com.zf.server.authserver.spi.dubbospitest2.SpiIf is not adaptive method!");
    }
}

仔細一看,其實就是為我們的接口生成了一個實現類。然后為Adaptive注解標注的方法生成了實際的內容(就是根據URL參數來獲取實際的擴展類),這也解釋的Adaptive注解的實際作用。

還有一點需要注意:test1提供的是URL的參數,test2提供的是包含URL屬性的對象。它們的共同點就是都包含了一個URL,如果不提供會提示沒有URL異常。具體原因可以自行分析以下方法:

new AdaptiveClassCodeGenerator(type, cachedDefaultName).generate();

最終在加載生成的實現類。

第二步:injectExtension

private T injectExtension(T instance) {

        if (objectFactory == null) {
            return instance;
        }

        try {
            for (Method method : instance.getClass().getMethods()) {
                if (!isSetter(method)) {
                    continue;
                }
           
                if (method.getAnnotation(DisableInject.class) != null) {
                    continue;
                }
                Class<?> pt = method.getParameterTypes()[0];
                if (ReflectUtils.isPrimitives(pt)) {
                    continue;
                }

                try {
                    String property = getSetterProperty(method);
                    Object object = objectFactory.getExtension(pt, property);
                    if (object != null) {
                        method.invoke(instance, object);
                    }
                } catch (Exception e) {
                    logger.error("Failed to inject via method " + method.getName()
                            + " of interface " + type.getName() + ": " + e.getMessage(), e);
                }

            }
        } catch (Exception e) {
            logger.error(e.getMessage(), e);
        }
        return instance;
    }

而這一步干啥呢?簡單說就是循環set方法,如果參數類型也是一個自適應擴展點的話,繼續上面的步驟拿到擴展點對象并反射注入,實現了Dubbo版的依賴注入。

至此,返回最終生成的對象-> SpiIf$Adaptive的實例并緩存在cachedAdaptiveInstance中,在Runner中就會根據url對應的參數值來獲取對應的擴展類。

總結:

1、自適應擴展接口需要 SPI注解,方法需要Adaptive注解,Adaptive方法需要URL參數或者是有URL屬性的對象參數;

2、最終會返回接口實現類對象 SpiIf$Adaptive,里面封裝了根據url參數來獲取擴展對象的方法;

到此,關于“如何理解Dubbo的SPI自適應”的學習就結束了,希望能夠解決大家的疑惑。理論與實踐的搭配能更好的幫助大家學習,快去試試吧!若想繼續學習更多相關知識,請繼續關注億速云網站,小編會繼續努力為大家帶來更多實用的文章!

向AI問一下細節

免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。

AI

亚洲午夜精品一区二区_中文无码日韩欧免_久久香蕉精品视频_欧美主播一区二区三区美女