溫馨提示×

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

密碼登錄×
登錄注冊(cè)×
其他方式登錄
點(diǎn)擊 登錄注冊(cè) 即表示同意《億速云用戶(hù)服務(wù)條款》

如何理解Dubbo的SPI自適應(yīng)

發(fā)布時(shí)間:2021-10-19 16:49:11 來(lái)源:億速云 閱讀:146 作者:iii 欄目:編程語(yǔ)言

這篇文章主要介紹“如何理解Dubbo的SPI自適應(yīng)”,在日常操作中,相信很多人在如何理解Dubbo的SPI自適應(yīng)問(wèn)題上存在疑惑,小編查閱了各式資料,整理出簡(jiǎn)單好用的操作方法,希望對(duì)大家解答”如何理解Dubbo的SPI自適應(yīng)”的疑惑有所幫助!接下來(lái),請(qǐng)跟著小編一起來(lái)學(xué)習(xí)吧!

先定義一個(gè)SPI接口(被@SPI標(biāo)注):

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是一個(gè)內(nèi)部有URL屬性的對(duì)象,為什么要有URL屬性的原因下面會(huì)說(shuō)到。

下一步,定義兩個(gè)實(shí)現(xiàn)類(lèi)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");
    }
}

最后一步,定義一個(gè)Runner測(cè)試啟動(dòng)器:

public class Runner {
    public static void main(String[] args) {
        URL url = new URL("dubbo", "123", 999);
        url = url.addParameter("spi.if", "S2"); //設(shè)置url值,來(lái)獲取 SpiTest的自適應(yīng)擴(kuò)展 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);
    }
}

以上類(lèi)可以直接拷貝到自己的本地工程DEBUG用。

當(dāng)我們啟功Runner的時(shí)候,第一步先看getExtensionLoader方法,這里開(kāi)始進(jìn)入Dubbo的代碼:

public static <T> ExtensionLoader<T> getExtensionLoader(Class<T> type) {

if (type == null) {
            throw new IllegalArgumentException("Extension type == null");
        }
//驗(yàn)證是否是接口
        if (!type.isInterface()) {
            throw new IllegalArgumentException("Extension type (" + type + ") is not an interface!");
        }
//驗(yàn)證是否有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對(duì)象,主要是對(duì)接口類(lèi)做一些驗(yàn)證,確認(rèn)是擴(kuò)展點(diǎn)(有SPI注解)。

第二步,進(jìn)入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);
            }
//緩存沒(méi)有,則開(kāi)始創(chuàng)建
            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;
    }

這一步主要是獲取接口的實(shí)現(xiàn)對(duì)象實(shí)列,繼續(xù)分析實(shí)際創(chuàng)建擴(kuò)展點(diǎn)的方法:

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

重點(diǎn)來(lái)了,創(chuàng)建class的時(shí)候會(huì)生成一個(gè)code(實(shí)際是SpiIf$Adaptive->SpiIf一個(gè)實(shí)現(xiàn)類(lèi)):

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!");
    }
}

仔細(xì)一看,其實(shí)就是為我們的接口生成了一個(gè)實(shí)現(xiàn)類(lèi)。然后為Adaptive注解標(biāo)注的方法生成了實(shí)際的內(nèi)容(就是根據(jù)URL參數(shù)來(lái)獲取實(shí)際的擴(kuò)展類(lèi)),這也解釋的Adaptive注解的實(shí)際作用。

還有一點(diǎn)需要注意:test1提供的是URL的參數(shù),test2提供的是包含URL屬性的對(duì)象。它們的共同點(diǎn)就是都包含了一個(gè)URL,如果不提供會(huì)提示沒(méi)有URL異常。具體原因可以自行分析以下方法:

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

最終在加載生成的實(shí)現(xiàn)類(lèi)。

第二步: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;
    }

而這一步干啥呢?簡(jiǎn)單說(shuō)就是循環(huán)set方法,如果參數(shù)類(lèi)型也是一個(gè)自適應(yīng)擴(kuò)展點(diǎn)的話,繼續(xù)上面的步驟拿到擴(kuò)展點(diǎn)對(duì)象并反射注入,實(shí)現(xiàn)了Dubbo版的依賴(lài)注入。

至此,返回最終生成的對(duì)象-> SpiIf$Adaptive的實(shí)例并緩存在cachedAdaptiveInstance中,在Runner中就會(huì)根據(jù)url對(duì)應(yīng)的參數(shù)值來(lái)獲取對(duì)應(yīng)的擴(kuò)展類(lèi)。

總結(jié):

1、自適應(yīng)擴(kuò)展接口需要 SPI注解,方法需要Adaptive注解,Adaptive方法需要URL參數(shù)或者是有URL屬性的對(duì)象參數(shù);

2、最終會(huì)返回接口實(shí)現(xiàn)類(lèi)對(duì)象 SpiIf$Adaptive,里面封裝了根據(jù)url參數(shù)來(lái)獲取擴(kuò)展對(duì)象的方法;

到此,關(guān)于“如何理解Dubbo的SPI自適應(yīng)”的學(xué)習(xí)就結(jié)束了,希望能夠解決大家的疑惑。理論與實(shí)踐的搭配能更好的幫助大家學(xué)習(xí),快去試試吧!若想繼續(xù)學(xué)習(xí)更多相關(guān)知識(shí),請(qǐng)繼續(xù)關(guān)注億速云網(wǎng)站,小編會(huì)繼續(xù)努力為大家?guī)?lái)更多實(shí)用的文章!

向AI問(wèn)一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI