欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

java開(kāi)發(fā)Dubbo注解Adaptive實(shí)現(xiàn)原理

 更新時(shí)間:2022年09月07日 11:24:52   作者:居然遇見(jiàn)不如同行  
這篇文章主要為大家介紹了java開(kāi)發(fā)Dubbo注解Adaptive實(shí)現(xiàn)原理詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪

前言

前面我們已經(jīng)分析Dubbo SPI相關(guān)的源碼,看過(guò)的小伙伴相信已經(jīng)知曉整個(gè)加載過(guò)程,我們也留下兩個(gè)問(wèn)題,今天我們先來(lái)處理下其中關(guān)于注解Adaptive的原理。

什么是@Adaptive

對(duì)應(yīng)于Adaptive機(jī)制,Dubbo提供了一個(gè)注解@Adaptive,該注解可以用于接口的某個(gè)子類(lèi)上,也可以用于接口方法上。

如果用在接口的子類(lèi)上,則表示Adaptive機(jī)制的實(shí)現(xiàn)會(huì)按照該子類(lèi)的方式進(jìn)行自定義實(shí)現(xiàn);如果用在方法上,則表示Dubbo會(huì)為該接口自動(dòng)生成一個(gè)子類(lèi),并且重寫(xiě)該方法,沒(méi)有標(biāo)注@Adaptive注解的方法將會(huì)默認(rèn)拋出異常。對(duì)于第一種Adaptive的使用方式,Dubbo里只有ExtensionFactory接口使用,AdaptiveExte

nsionFactory的實(shí)現(xiàn)就使用了@Adaptive注解進(jìn)行了標(biāo)注,主要作用就是在獲取目標(biāo)對(duì)象時(shí),分別通過(guò)ExtensionLoader和Spring容器兩種方式獲取,該類(lèi)的實(shí)現(xiàn)已經(jīng)在Dubbo SPI機(jī)制分析過(guò),此篇文章關(guān)注的重點(diǎn)是關(guān)于@Adaptive注解修飾在接口方法的實(shí)現(xiàn)原理,也就是關(guān)于Dubbo SPI動(dòng)態(tài)的加載擴(kuò)展類(lèi)能力如何實(shí)現(xiàn),搞清楚Dubbo是如何在運(yùn)行時(shí)動(dòng)態(tài)的選擇對(duì)應(yīng)的擴(kuò)展類(lèi)來(lái)提供服務(wù)。

簡(jiǎn)單一點(diǎn)說(shuō)就是一個(gè)代理層,通過(guò)對(duì)應(yīng)的參數(shù)返回對(duì)應(yīng)的類(lèi)的實(shí)現(xiàn),運(yùn)行時(shí)編譯。為了更好的理解我們來(lái)寫(xiě)個(gè)案例:

@SPI("china")
public interface PersonService {
    @Adaptive
    String queryCountry(URL url);
}
public class ChinaPersonServiceImpl implements PersonService {
    @Override
    public String queryCountry(URL url) {
        System.out.println("中國(guó)人");
        return "中國(guó)人";
    }
}
public class EnglandPersonServiceImpl implements PersonService{
    @Override
    public String queryCountry(URL url) {
        System.out.println("英國(guó)人");
        return "英國(guó)人";
    }
}
public class Test {
    public static void main(String[] args) {
        URL url = URL.valueOf("dubbo://192.168.0.101:20880?person.service=china");
        PersonService service = ExtensionLoader.getExtensionLoader(PersonService.class)
                .getAdaptiveExtension();
        service.queryCountry(url);
    }
}
china=org.dubbo.spi.example.ChinaPersonServiceImpl
england=org.dubbo.spi.example.EnglandPersonServiceImpl

該案例中首先構(gòu)造了一個(gè)URL對(duì)象,這個(gè)URL對(duì)象是Dubbo中進(jìn)行參數(shù)傳遞所使用的一個(gè)基礎(chǔ)類(lèi),在配置文件中配置的屬性都會(huì)被封裝到該對(duì)象中。

這里我們需要注意的是我們的對(duì)象是通過(guò)一個(gè)url構(gòu)造的,并且在url的最后有一個(gè)參數(shù)person.service=china,這里也就是我們所指定的使用哪種基礎(chǔ)服務(wù)類(lèi)的參數(shù),通過(guò)指向不同的對(duì)象就可以生成對(duì)應(yīng)不同的實(shí)現(xiàn)。關(guān)于URL部分的介紹我們?cè)谙乱黄恼陆榻B,聊聊Dubbo中URL的使用場(chǎng)景有哪些。

在構(gòu)造一個(gè)URL對(duì)象之后,通過(guò)getExtensionLoader(PersonService.class)方法獲取了一個(gè)PersonService對(duì)應(yīng)的ExtensionLoader對(duì)象,然后調(diào)用其getAdaptiveExtension()方法獲取PersonService接口構(gòu)造的子類(lèi)實(shí)例,這里的子類(lèi)實(shí)際上就是ExtensionLoader通過(guò)一定的規(guī)則為PersonService接口編寫(xiě)的子類(lèi)代碼,然后通過(guò)javassist或jdk編譯加載這段代碼,加載完成之后通過(guò)反射構(gòu)造其實(shí)例,最后將其實(shí)例返回。

當(dāng)發(fā)生調(diào)用的時(shí)候,方法內(nèi)部就會(huì)通過(guò)url對(duì)象指定的參數(shù)來(lái)選擇具體的實(shí)例,從而將真正的工作交給該實(shí)例進(jìn)行。通過(guò)這種方式,Dubbo SPI就實(shí)現(xiàn)了根據(jù)傳入?yún)?shù)動(dòng)態(tài)的選用具體的實(shí)例來(lái)提供服務(wù)的功能。以下代碼就是動(dòng)態(tài)生成以后的代碼:

public class PersonService$Adaptive implements org.dubbo.spi.example.PersonService {
    public java.lang.String queryCountry(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("person.service", "china");
        if (extName == null)
            throw new IllegalStateException("Failed to get extension (org.dubbo.spi.example.PersonService) name from url (" + url.toString() + ") use keys([person.service])");
        org.dubbo.spi.example.PersonService extension = (org.dubbo.spi.example.PersonService) ExtensionLoader.getExtensionLoader(org.dubbo.spi.example.PersonService.class).getExtension(extName);
        return extension.queryCountry(arg0);
    }
}

關(guān)于使用我們需要注意以下兩個(gè)問(wèn)題:

  • 要使用Dubbo的SPI的支持,必須在目標(biāo)接口上使用@SPI注解進(jìn)行標(biāo)注,后面的值提供了一個(gè)默認(rèn)值,此處可以理解為這是一種規(guī)范,如果在接口的@SPI注解中指定了默認(rèn)值,那么在使用URL對(duì)象獲取參數(shù)值時(shí),如果沒(méi)有取到,就會(huì)使用該默認(rèn)值;
  • @Adaptive注解標(biāo)注的方法中,其參數(shù)中必須有一個(gè)參數(shù)類(lèi)型為URL,或者其某個(gè)參數(shù)提供了某個(gè)方法,該方法可以返回一個(gè)URL對(duì)象,此處我們可以再看源碼的時(shí)候給大家標(biāo)注一下,面試的時(shí)候防止大佬問(wèn):是不是一定要 @Adaptive 實(shí)現(xiàn)的方法的中必須有URL對(duì)象;

實(shí)現(xiàn)原理

getAdaptiveExtension

關(guān)于getAdaptiveExtension方法我們?cè)谏掀恼乱呀?jīng)講過(guò),此方法就是通過(guò)雙檢查法來(lái)從緩存中獲取Adaptive實(shí)例,如果沒(méi)獲取到,則創(chuàng)建一個(gè)。

    public T getAdaptiveExtension() {
        //從裝載適配器實(shí)例緩存里面找
        Object instance = cachedAdaptiveInstance.get();
        if (instance == null) {
            //創(chuàng)建cachedAdaptiveInstance異常
            if (createAdaptiveInstanceError != null) {
                throw new IllegalStateException("Failed to create adaptive instance: " +
                        createAdaptiveInstanceError.toString(),
                        createAdaptiveInstanceError);
            }
            synchronized (cachedAdaptiveInstance) {
                instance = cachedAdaptiveInstance.get();
                if (instance == null) {
                    try {
                        //創(chuàng)建對(duì)應(yīng)的適配器類(lèi)
                        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

在getAdaptiveExtensionClass方法中有兩個(gè)分支,如果某個(gè)子類(lèi)標(biāo)注了@Adaptive注解,那么就會(huì)使用該子類(lèi)所自定義的Adaptive機(jī)制,如果沒(méi)有子類(lèi)標(biāo)注該注解,那么就會(huì)使用下面的createAdaptiveExtensionClass()方式來(lái)創(chuàng)建一個(gè)目標(biāo)類(lèi)class對(duì)象。

整個(gè)過(guò)程通過(guò)AdaptiveClassCodeGenerator來(lái)為目標(biāo)類(lèi)生成子類(lèi)代碼,并以字符串的形式返回,最后通過(guò)javassist或jdk的方式進(jìn)行編譯然后返回class對(duì)象。

    private Class<?> getAdaptiveExtensionClass() {
        //獲取所有的擴(kuò)展類(lèi)
        getExtensionClasses();
        //如果可以適配
        if (cachedAdaptiveClass != null) {
            return cachedAdaptiveClass;
        }
        //如果沒(méi)有適配擴(kuò)展類(lèi)就創(chuàng)建
        return cachedAdaptiveClass = createAdaptiveExtensionClass();
    }
    private Class<?> createAdaptiveExtensionClass() {
        //生成代碼片段
        String code = new AdaptiveClassCodeGenerator(type, cachedDefaultName).generate();
        //獲取ClassLoader
        ClassLoader classLoader = findClassLoader();
        //通過(guò)jdk或者javassist的方式編譯生成的子類(lèi)字符串,從而得到一個(gè)class對(duì)象
        org.apache.dubbo.common.compiler.Compiler compiler =
                ExtensionLoader.getExtensionLoader(org.apache.dubbo.common.compiler.Compiler.class).getAdaptiveExtension();
        //編譯
        return compiler.compile(code, classLoader);
    }

generate

generate方法是生成目標(biāo)類(lèi)的方法,其實(shí)和創(chuàng)建一個(gè)類(lèi)一樣,其主要四個(gè)步驟:

  • 生成package信息;
  • 生成import信息;
  • 生成類(lèi)聲明信息;
  • 生成各個(gè)方法的實(shí)現(xiàn);
    public String generate() {
        // 判斷目標(biāo)接口是否有方法標(biāo)注了@Adaptive注解,如果沒(méi)有則拋出異常
        if (!hasAdaptiveMethod()) {
            throw new IllegalStateException("No adaptive method exist on extension " + type.getName() + ", refuse to create the adaptive class!");
        }
        StringBuilder code = new StringBuilder();
        //生成package
        code.append(generatePackageInfo());
        //生成import信息 只導(dǎo)入了ExtensionLoader類(lèi),其余的類(lèi)都通過(guò)全限定名的方式來(lái)使用
        code.append(generateImports());
        //生成類(lèi)聲明信息
        code.append(generateClassDeclaration());
        Method[] methods = type.getMethods();
        //為各個(gè)方法生成實(shí)現(xiàn)方法信息
        for (Method method : methods) {
            code.append(generateMethod(method));
        }
        code.append("}");
        if (logger.isDebugEnabled()) {
            logger.debug(code.toString());
        }
        //返回class代碼
        return code.toString();
    }

接下來(lái)主要看方法實(shí)現(xiàn)的生成,對(duì)于包路徑、類(lèi)的生成的代碼相對(duì)比較簡(jiǎn)單,這里進(jìn)行忽略,對(duì)于方法生成主要包含以下幾個(gè)步驟:

  • 獲取返回值信息;
  • 獲取方法名信息;
  • 獲取方法體內(nèi)容;
  • 獲取方法參數(shù);
  • 獲取異常信息;
  • 格式化
    private String generateMethod(Method method) {
        //獲取方法返回值
        String methodReturnType = method.getReturnType().getCanonicalName();
        //獲取方法名稱(chēng)
        String methodName = method.getName();
        //獲取方法體內(nèi)容
        String methodContent = generateMethodContent(method);
        //獲取方法參數(shù)
        String methodArgs = generateMethodArguments(method);
        //生成異常信息
        String methodThrows = generateMethodThrows(method);
        //格式化
        return String.format(CODE_METHOD_DECLARATION, methodReturnType, methodName, methodArgs, methodThrows, methodContent);
    }

需要注意的是,這里所使用的所有類(lèi)都是使用的其全限定類(lèi)名,在上面生成的代碼中也可以看到,在方法生成的整個(gè)過(guò)程中,方法的返回值,方法名,方法參數(shù)以及異常信息都可以通過(guò)反射的信息獲取到,而方法體則需要根據(jù)一定規(guī)則來(lái)生成,這里我們要看一下方法體是如何生成的;

    private String generateMethodContent(Method method) {
        //獲取Adaptive的注解信息
        Adaptive adaptiveAnnotation = method.getAnnotation(Adaptive.class);
        StringBuilder code = new StringBuilder(512);
        if (adaptiveAnnotation == null) {
            //如果當(dāng)前方法沒(méi)有被Adaptive修飾則需要拋出異常
            return generateUnsupported(method);
        } else {
            //獲取參數(shù)中類(lèi)型為URL的參數(shù)所在的參數(shù)索引位 通過(guò)下標(biāo)獲取對(duì)應(yīng)的參數(shù)值信息
            int urlTypeIndex = getUrlTypeIndex(method);
            if (urlTypeIndex != -1) {
                //如果參數(shù)中存在URL類(lèi)型的參數(shù),那么就為該參數(shù)進(jìn)行空值檢查,如果為空,則拋出異常
                code.append(generateUrlNullCheck(urlTypeIndex));
            } else {
                //如果參數(shù)中不存在URL類(lèi)型的參數(shù),則會(huì)檢查每個(gè)參數(shù),判斷是否有某個(gè)方法的返回類(lèi)型是URL類(lèi)型,
                //如果存在該方法,首先對(duì)該參數(shù)進(jìn)行空指針檢查,如果為空則拋出異常。如果不為空則調(diào)用該對(duì)象的目標(biāo)方法,
                //獲取URL對(duì)象,然后對(duì)獲取到的URL對(duì)象進(jìn)行空值檢查,為空拋出異常。
                code.append(generateUrlAssignmentIndirectly(method));
            }
            //獲取@Adaptive注解的參數(shù),如果沒(méi)有配置,就會(huì)使用目標(biāo)接口的類(lèi)型由駝峰形式轉(zhuǎn)換為點(diǎn)分形式
            //的名稱(chēng)作為將要獲取的參數(shù)值的key名稱(chēng)
            String[] value = getMethodAdaptiveValue(adaptiveAnnotation);
            //判斷是否存在Invocation類(lèi)型的參數(shù) 關(guān)于這個(gè)對(duì)象我們?cè)诤罄m(xù)章節(jié)在進(jìn)行講解
            boolean hasInvocation = hasInvocationArgument(method);
            //為Invocation類(lèi)型的參數(shù)添加空值檢查的邏輯
            code.append(generateInvocationArgumentNullCheck(method));
            //生成獲取extName的邏輯,獲取用戶配置的擴(kuò)展的名稱(chēng)
            code.append(generateExtNameAssignment(value, hasInvocation));
            //extName空值檢查代碼
            code.append(generateExtNameNullCheck(value));
            //通過(guò)extName在ExtensionLoader中獲取其對(duì)應(yīng)的基礎(chǔ)服務(wù)類(lèi)
            code.append(generateExtensionAssignment());
            //生成實(shí)例的當(dāng)前方法的調(diào)用邏輯,然后將結(jié)果返回
            code.append(generateReturnAndInvocation(method));
        }
        return code.toString();
    }

上面整體的邏輯還是比較清楚的,通過(guò)對(duì)比PersonService$Adaptive生成我們可以更容易理解改代碼生成的過(guò)程,整體的邏輯可以分為四步:

  • 判斷當(dāng)前方法是否標(biāo)注了@Adaptive注解,如果沒(méi)有標(biāo)注,則為其生成默認(rèn)拋出異常的方法,只有使用@Adaptive注解標(biāo)注的方法才是作為自適應(yīng)機(jī)制的方法;
  • 獲取方法參數(shù)中類(lèi)型為URL的參數(shù),如果不存在,則獲取參數(shù)中存在URL類(lèi)型的參數(shù),如果不存在拋出異常,如果存在獲取URL參數(shù)類(lèi)型;
  • 通過(guò)@Adaptive注解的配置獲取目標(biāo)參數(shù)的key值,然后通過(guò)URL參數(shù)獲取該key對(duì)應(yīng)的參數(shù)值,得到了基礎(chǔ)服務(wù)類(lèi)對(duì)應(yīng)的名稱(chēng);
  • 通過(guò)ExtensionLoader獲取該名稱(chēng)對(duì)應(yīng)的基礎(chǔ)服務(wù)類(lèi)實(shí)例,最終調(diào)用該服務(wù)的方法來(lái)進(jìn)行實(shí)現(xiàn);

以上就是java開(kāi)發(fā)Dubbo注解Adaptive實(shí)現(xiàn)原理的詳細(xì)內(nèi)容,更多關(guān)于java Dubbo注解Adaptive的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

  • Java獲取時(shí)間如何將當(dāng)前時(shí)間減一天、一月、一年、并格式化

    Java獲取時(shí)間如何將當(dāng)前時(shí)間減一天、一月、一年、并格式化

    這篇文章主要介紹了Java獲取時(shí)間,將當(dāng)前時(shí)間減一天、一月、一年,并加以格式化,本文通過(guò)實(shí)例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下
    2023-09-09
  • Java通過(guò)URL類(lèi)下載圖片的實(shí)例代碼

    Java通過(guò)URL類(lèi)下載圖片的實(shí)例代碼

    這篇文章主要介紹了Java通過(guò)URL類(lèi)下載圖片,文中結(jié)合實(shí)例代碼補(bǔ)充介紹了java通過(guò)url獲取圖片文件的相關(guān)知識(shí),代碼簡(jiǎn)單易懂,對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下
    2023-02-02
  • java實(shí)現(xiàn)監(jiān)聽(tīng)u盤(pán)示例分享

    java實(shí)現(xiàn)監(jiān)聽(tīng)u盤(pán)示例分享

    這篇文章主要介紹了java實(shí)現(xiàn)監(jiān)聽(tīng)u盤(pán)示例,需要的朋友可以參考下
    2014-03-03
  • Java中一些關(guān)鍵字的使用技巧總結(jié)

    Java中一些關(guān)鍵字的使用技巧總結(jié)

    這篇文章主要介紹了Java中一些關(guān)鍵字的使用技巧總結(jié),其中重點(diǎn)講述了this和super兩個(gè)關(guān)鍵字的用法,需要的朋友可以參考下
    2015-09-09
  • java中extends與implements的區(qū)別淺談

    java中extends與implements的區(qū)別淺談

    java中extends與implements的區(qū)別淺談,需要的朋友可以參考一下
    2013-03-03
  • 使用Spring開(kāi)啟@Async異步方式(javaconfig配置)

    使用Spring開(kāi)啟@Async異步方式(javaconfig配置)

    這篇文章主要介紹了使用Spring開(kāi)啟@Async異步方式(javaconfig配置),具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2021-08-08
  • 一篇文章帶你認(rèn)識(shí)Java8接口的默認(rèn)方法

    一篇文章帶你認(rèn)識(shí)Java8接口的默認(rèn)方法

    這篇文章主要給大家介紹了如何通過(guò)一篇文章帶你認(rèn)識(shí)Java8接口的默認(rèn)方法的相關(guān)資料,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家學(xué)習(xí)或者使用Java8具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2019-05-05
  • Springboot如何使用@Async實(shí)現(xiàn)異步任務(wù)

    Springboot如何使用@Async實(shí)現(xiàn)異步任務(wù)

    這篇文章主要介紹了Springboot如何使用@Async實(shí)現(xiàn)異步任務(wù)問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2023-09-09
  • Java8并發(fā)新特性CompletableFuture

    Java8并發(fā)新特性CompletableFuture

    這篇文章主要介紹了Java8并發(fā)新特性CompletableFuture,CompletableFuture針對(duì)Future接口做了改進(jìn),相比Callable/Runnable接口它支持多任務(wù)進(jìn)行鏈?zhǔn)秸{(diào)用、組合、多任務(wù)并發(fā)處理,下面文章更多相關(guān)內(nèi)容得介紹,需要的小伙伴可以參考一下
    2022-06-06
  • java實(shí)現(xiàn)圖片任意角度旋轉(zhuǎn)

    java實(shí)現(xiàn)圖片任意角度旋轉(zhuǎn)

    這篇文章主要為大家詳細(xì)介紹了java實(shí)現(xiàn)圖片任意角度旋轉(zhuǎn),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2019-04-04

最新評(píng)論