最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

java開發(fā)Dubbo注解Adaptive實現原理

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

前言

前面我們已經分析Dubbo SPI相關的源碼,看過的小伙伴相信已經知曉整個加載過程,我們也留下兩個問題,今天我們先來處理下其中關于注解Adaptive的原理。

什么是@Adaptive

對應于Adaptive機制,Dubbo提供了一個注解@Adaptive,該注解可以用于接口的某個子類上,也可以用于接口方法上。

如果用在接口的子類上,則表示Adaptive機制的實現會按照該子類的方式進行自定義實現;如果用在方法上,則表示Dubbo會為該接口自動生成一個子類,并且重寫該方法,沒有標注@Adaptive注解的方法將會默認拋出異常。對于第一種Adaptive的使用方式,Dubbo里只有ExtensionFactory接口使用,AdaptiveExte

nsionFactory的實現就使用了@Adaptive注解進行了標注,主要作用就是在獲取目標對象時,分別通過ExtensionLoader和Spring容器兩種方式獲取,該類的實現已經在Dubbo SPI機制分析過,此篇文章關注的重點是關于@Adaptive注解修飾在接口方法的實現原理,也就是關于Dubbo SPI動態(tài)的加載擴展類能力如何實現,搞清楚Dubbo是如何在運行時動態(tài)的選擇對應的擴展類來提供服務。

簡單一點說就是一個代理層,通過對應的參數返回對應的類的實現,運行時編譯。為了更好的理解我們來寫個案例:

@SPI("china")
public interface PersonService {
    @Adaptive
    String queryCountry(URL url);
}
public class ChinaPersonServiceImpl implements PersonService {
    @Override
    public String queryCountry(URL url) {
        System.out.println("中國人");
        return "中國人";
    }
}
public class EnglandPersonServiceImpl implements PersonService{
    @Override
    public String queryCountry(URL url) {
        System.out.println("英國人");
        return "英國人";
    }
}
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

該案例中首先構造了一個URL對象,這個URL對象是Dubbo中進行參數傳遞所使用的一個基礎類,在配置文件中配置的屬性都會被封裝到該對象中。

這里我們需要注意的是我們的對象是通過一個url構造的,并且在url的最后有一個參數person.service=china,這里也就是我們所指定的使用哪種基礎服務類的參數,通過指向不同的對象就可以生成對應不同的實現。關于URL部分的介紹我們在下一篇文章介紹,聊聊Dubbo中URL的使用場景有哪些。

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

當發(fā)生調用的時候,方法內部就會通過url對象指定的參數來選擇具體的實例,從而將真正的工作交給該實例進行。通過這種方式,Dubbo SPI就實現了根據傳入參數動態(tài)的選用具體的實例來提供服務的功能。以下代碼就是動態(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);
    }
}

關于使用我們需要注意以下兩個問題:

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

實現原理

getAdaptiveExtension

關于getAdaptiveExtension方法我們在上篇文章已經講過,此方法就是通過雙檢查法來從緩存中獲取Adaptive實例,如果沒獲取到,則創(chuàng)建一個。

    public T getAdaptiveExtension() {
        //從裝載適配器實例緩存里面找
        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)建對應的適配器類
                        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方法中有兩個分支,如果某個子類標注了@Adaptive注解,那么就會使用該子類所自定義的Adaptive機制,如果沒有子類標注該注解,那么就會使用下面的createAdaptiveExtensionClass()方式來創(chuàng)建一個目標類class對象。

整個過程通過AdaptiveClassCodeGenerator來為目標類生成子類代碼,并以字符串的形式返回,最后通過javassist或jdk的方式進行編譯然后返回class對象。

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

generate

generate方法是生成目標類的方法,其實和創(chuàng)建一個類一樣,其主要四個步驟:

  • 生成package信息;
  • 生成import信息;
  • 生成類聲明信息;
  • 生成各個方法的實現;
    public String generate() {
        // 判斷目標接口是否有方法標注了@Adaptive注解,如果沒有則拋出異常
        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信息 只導入了ExtensionLoader類,其余的類都通過全限定名的方式來使用
        code.append(generateImports());
        //生成類聲明信息
        code.append(generateClassDeclaration());
        Method[] methods = type.getMethods();
        //為各個方法生成實現方法信息
        for (Method method : methods) {
            code.append(generateMethod(method));
        }
        code.append("}");
        if (logger.isDebugEnabled()) {
            logger.debug(code.toString());
        }
        //返回class代碼
        return code.toString();
    }

接下來主要看方法實現的生成,對于包路徑、類的生成的代碼相對比較簡單,這里進行忽略,對于方法生成主要包含以下幾個步驟:

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

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

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

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

  • 判斷當前方法是否標注了@Adaptive注解,如果沒有標注,則為其生成默認拋出異常的方法,只有使用@Adaptive注解標注的方法才是作為自適應機制的方法;
  • 獲取方法參數中類型為URL的參數,如果不存在,則獲取參數中存在URL類型的參數,如果不存在拋出異常,如果存在獲取URL參數類型;
  • 通過@Adaptive注解的配置獲取目標參數的key值,然后通過URL參數獲取該key對應的參數值,得到了基礎服務類對應的名稱;
  • 通過ExtensionLoader獲取該名稱對應的基礎服務類實例,最終調用該服務的方法來進行實現;

以上就是java開發(fā)Dubbo注解Adaptive實現原理的詳細內容,更多關于java Dubbo注解Adaptive的資料請關注腳本之家其它相關文章!

相關文章

  • JDK8配置環(huán)境變量的bat文件的詳細教程

    JDK8配置環(huán)境變量的bat文件的詳細教程

    這篇文章主要介紹了JDK8配置環(huán)境變量的bat文件,本文通過圖文并茂的形式給大家介紹的非常詳細,對大家的學習或工作具有一定的參考借鑒價值,需要的朋友可以參考下
    2020-07-07
  • 關于jackson序列化和feign返回值的問題

    關于jackson序列化和feign返回值的問題

    這篇文章主要介紹了關于jackson序列化和feign返回值的問題,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2022-03-03
  • spring boot配置MySQL數據庫連接、Hikari連接池和Mybatis的簡單配置方法

    spring boot配置MySQL數據庫連接、Hikari連接池和Mybatis的簡單配置方法

    這篇文章主要介紹了spring boot配置MySQL數據庫連接、Hikari連接池和Mybatis的簡單配置方法,需要的朋友可以參考下
    2018-03-03
  • springboot?通過博途獲取plc點位的數據代碼實現

    springboot?通過博途獲取plc點位的數據代碼實現

    這篇文章主要介紹了springboot?通過博途獲取plc點位的數據的代碼實現,本文通過實例代碼給大家介紹的非常詳細,對大家的學習或工作具有一定的參考借鑒價值,需要的朋友可以參考下
    2023-08-08
  • Java實現對華北、華南、華東和華中四個區(qū)域的劃分

    Java實現對華北、華南、華東和華中四個區(qū)域的劃分

    在Java中,通過定義枚舉類、編寫主程序和進行測試,本文詳細介紹了如何劃分華北、華南、華東和華中四個區(qū)域,首先定義枚舉類標識區(qū)域,然后通過主程序接收用戶輸入并返回相應區(qū)域,最后通過測試用例確保正確性,文章還介紹了甘特圖和餅狀圖的使用
    2024-09-09
  • java如何獲取兩個日期的時間差

    java如何獲取兩個日期的時間差

    這篇文章主要為大家詳細介紹了java獲取兩個日期時間差的方法,文中示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2020-07-07
  • java識別一篇文章中某單詞出現個數的方法

    java識別一篇文章中某單詞出現個數的方法

    這篇文章主要介紹了java識別一篇文章中某單詞出現個數的方法,涉及java字符解析操作的相關技巧,具有一定參考借鑒價值,需要的朋友可以參考下
    2015-10-10
  • Java動態(tài)修改配置即時生效的方式WatchService

    Java動態(tài)修改配置即時生效的方式WatchService

    這篇文章給大家分享了Java動態(tài)修改配置即時生效的方式WatchService的相關知識點內容,有興趣的朋友可以參考學習下。
    2018-06-06
  • Java19新特性虛擬線程的具體使用

    Java19新特性虛擬線程的具體使用

    Java 19 引入了虛擬線程,這是 JDK Project Loom 項目中的重要新特性,目的是簡化 Java 中的并發(fā)編程,并提高線程管理的效率和性能,下面就來具體介紹下
    2024-09-09
  • spring security 5.x實現兼容多種密碼的加密方式

    spring security 5.x實現兼容多種密碼的加密方式

    spring security針對該功能有兩種實現方式,一種是簡單的使用加密來保證基于 cookie 的 token 的安全,另一種是通過數據庫或其它持久化存儲機制來保存生成的 token。這篇文章主要給大家介紹了關于spring security 5.x實現兼容多種密碼的加密方式,需要的朋友可以參考下。
    2018-01-01

最新評論

六枝特区| 白水县| 钦州市| 大田县| 泰安市| 南城县| 松桃| 邻水| 东莞市| 中宁县| 柯坪县| 华容县| 阜新市| 灵寿县| 石城县| 长顺县| 苗栗县| 荣成市| 鱼台县| 仪征市| 虹口区| 富源县| 彩票| 仪征市| 许昌市| 太谷县| 嫩江县| 龙泉市| 开江县| 舞钢市| 江油市| 哈尔滨市| 安阳市| 越西县| 张掖市| 元江| 安阳市| 鄂温| 安庆市| 尚义县| 定州市|