dubbo源碼導讀一 dubboSPI自適應擴展

dubbo SPI

dubbo自適應擴展機制

自適應拓展類加載過程

  • 加載入口方法

  • 加載@SPI接口所有的實現類,如果有實現類有@Adaptive註解,則直接返回此類。
  • 判斷@SPI接口中的方法是否至少有一個方法具有@Adaptive註解,如果沒有,則拋異常。
  • 通過反射獲取接口的方法數組,然後遍歷數組生成代理類內部代碼。
  • 對於沒有@Adaptive註解的方法,生成的代理方法內部直接拋出異常。
  • 代理方法體核心邏輯就是要獲取代理類的在配置文件中對應的名稱,即extName,然後加載對應的實現類。

  • 獲取URL參數

以Protocol爲例

  • 獲取 Adaptive 註解值,註解值是一個String數組 values,如果註解沒有值則把SPI接口名的轉成小寫字母放入values。根據這些值從URL中獲取的值就是extName。

  • 檢測 Invocation 參數

for (Method method : methods) {
    Class<?> rt = method.getReturnType();
    Class<?>[] pts = method.getParameterTypes();    // 獲取參數類型列表
    Class<?>[] ets = method.getExceptionTypes();

    Adaptive adaptiveAnnotation = method.getAnnotation(Adaptive.class);
    StringBuilder code = new StringBuilder(512);
    if (adaptiveAnnotation == null) {
        // ${無 Adaptive 註解方法代碼生成邏輯}
    } else {
        // ${獲取 URL 數據}
        
        // ${獲取 Adaptive 註解值}
        
        boolean hasInvocation = false;
        // 遍歷參數類型列表
        for (int i = 0; i < pts.length; ++i) {
            // 判斷當前參數名稱是否等於 com.alibaba.dubbo.rpc.Invocation
            if (pts[i].getName().equals("com.alibaba.dubbo.rpc.Invocation")) {
                // 爲 Invocation 類型參數生成判空代碼
                String s = String.format("\nif (arg%d == null) throw new IllegalArgumentException(\"invocation == null\");", i);
                code.append(s);
                // 生成 getMethodName 方法調用代碼,格式爲:
                //    String methodName = argN.getMethodName();
                s = String.format("\nString methodName = arg%d.getMethodName();", i);
                code.append(s);
                
                // 設置 hasInvocation 爲 true
                hasInvocation = true;
                break;
            }
        }
    }
    
    // 省略無關邏輯
}
  • extName獲取邏輯,從後至前遍歷values,從URL中獲取對應的值,最終values數組中第一個值對應URL中的值就是最後的extName。

for (Method method : methods) {
    Class<?> rt = method.getReturnType();
    Class<?>[] pts = method.getParameterTypes();
    Class<?>[] ets = method.getExceptionTypes();

    Adaptive adaptiveAnnotation = method.getAnnotation(Adaptive.class);
    StringBuilder code = new StringBuilder(512);
    if (adaptiveAnnotation == null) {
        // $無 Adaptive 註解方法代碼生成邏輯}
    } else {
        // ${獲取 URL 數據}
        
        // ${獲取 Adaptive 註解值}
        
        // ${檢測 Invocation 參數}
        
        // 設置默認拓展名,cachedDefaultName 源於 SPI 註解值,默認情況下,
        // SPI 註解值爲空串,此時 cachedDefaultName = null
        String defaultExtName = cachedDefaultName;
        String getNameCode = null;
        
        // 遍歷 value,這裏的 value 是 Adaptive 的註解值,2.2.3.3 節分析過 value 變量的獲取過程。
        // 此處循環目的是生成從 URL 中獲取拓展名的代碼,生成的代碼會賦值給 getNameCode 變量。注意這
        // 個循環的遍歷順序是由後向前遍歷的。
        for (int i = value.length - 1; i >= 0; --i) {
            // 當 i 爲最後一個元素的座標時
            if (i == value.length - 1) {
                // 默認拓展名非空
                if (null != defaultExtName) {
                    // protocol 是 url 的一部分,可通過 getProtocol 方法獲取,其他的則是從
                    // URL 參數中獲取。因爲獲取方式不同,所以這裏要判斷 value[i] 是否爲 protocol
                    if (!"protocol".equals(value[i]))
                    	// hasInvocation 用於標識方法參數列表中是否有 Invocation 類型參數
                        if (hasInvocation)
                            // 生成的代碼功能等價於下面的代碼:
                            //   url.getMethodParameter(methodName, value[i], defaultExtName)
                            // 以 LoadBalance 接口的 select 方法爲例,最終生成的代碼如下:
                            //   url.getMethodParameter(methodName, "loadbalance", "random")
                            getNameCode = String.format("url.getMethodParameter(methodName, \"%s\", \"%s\")", value[i], defaultExtName);
                    	else
                    		// 生成的代碼功能等價於下面的代碼:
	                        //   url.getParameter(value[i], defaultExtName)
	                        getNameCode = String.format("url.getParameter(\"%s\", \"%s\")", value[i], defaultExtName);
                    else
                    	// 生成的代碼功能等價於下面的代碼:
                        //   ( url.getProtocol() == null ? defaultExtName : url.getProtocol() )
                        getNameCode = String.format("( url.getProtocol() == null ? \"%s\" : url.getProtocol() )", defaultExtName);
                    
                // 默認拓展名爲空
                } else {
                    if (!"protocol".equals(value[i]))
                        if (hasInvocation)
                        	// 生成代碼格式同上
                            getNameCode = String.format("url.getMethodParameter(methodName, \"%s\", \"%s\")", value[i], defaultExtName);
	                    else
	                    	// 生成的代碼功能等價於下面的代碼:
	                        //   url.getParameter(value[i])
	                        getNameCode = String.format("url.getParameter(\"%s\")", value[i]);
                    else
                    	// 生成從 url 中獲取協議的代碼,比如 "dubbo"
                        getNameCode = "url.getProtocol()";
                }
            } else {
                if (!"protocol".equals(value[i]))
                    if (hasInvocation)
                        // 生成代碼格式同上
                        getNameCode = String.format("url.getMethodParameter(methodName, \"%s\", \"%s\")", value[i], defaultExtName);
	                else
	                	// 生成的代碼功能等價於下面的代碼:
	                    //   url.getParameter(value[i], getNameCode)
	                    // 以 Transporter 接口的 connect 方法爲例,最終生成的代碼如下:
	                    //   url.getParameter("client", url.getParameter("transporter", "netty"))
	                    getNameCode = String.format("url.getParameter(\"%s\", %s)", value[i], getNameCode);
                else
                    // 生成的代碼功能等價於下面的代碼:
                    //   url.getProtocol() == null ? getNameCode : url.getProtocol()
                    // 以 Protocol 接口的 connect 方法爲例,最終生成的代碼如下:
                    //   url.getProtocol() == null ? "dubbo" : url.getProtocol()
                    getNameCode = String.format("url.getProtocol() == null ? (%s) : url.getProtocol()", getNameCode);
            }
        }
        // 生成 extName 賦值代碼
        code.append("\nString extName = ").append(getNameCode).append(";");
        // 生成 extName 判空代碼
        String s = String.format("\nif(extName == null) " +
                                 "throw new IllegalStateException(\"Fail to get extension(%s) name from url(\" + url.toString() + \") use keys(%s)\");",
                                 type.getName(), Arrays.toString(value));
        code.append(s);
    }
    
    // 省略無關邏輯
}

  • 使用extName加載拓展

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章