Dubbo 源碼分析之 SPI 詳解

前言

不得不說 Dubbo的自定義 spi 減輕了擴展者的負擔,但減輕負擔的代價是使用大量晦澀難懂的代碼,調用層次深,使閱讀者步步維艱。爲了避免後來者再像我當初那樣看源碼的吃力,特意把我的閱讀見解分享下來,希望大家一起學習。

我使用的是 maven 倉庫最新的 dubbo2.6.5 的源碼,在社區看到要 2.7 版本就要出了。爲了更方便的學習,我們可以等這個版本掌握差不多了再看更高的版本。

SPI

究竟什麼是 SPI 呢,SPI 的英文名爲 Service Provider Interface 顧名思義服務提供者接口,是面向開發者的。使用 SPI 我們可以把接口實現的全限定名放在文件中,通常在 META-INF/services 目錄下,文件名爲接口名,文件內容爲接口的實現類。由服務器加載讀取配置文件,加載實現類,這樣在運行時動態爲接口替換實現類。正因此特性,我們可以很容易的通過 SPI 機制爲我們的程序提供拓展功能。

其實 JDK 自帶了 SPI 功能。

JDK SPI

接口及其實現類

public interface Echo {

    void echo();
}
public class DubboEcho implements Echo {
    @Override
    public void echo() {
        System.out.println("I am dubbo");
    }
}
public class JdkEcho implements Echo {
    @Override
    public void echo() {
        System.out.println("I am jdk");
    }
}

資源文件目錄爲:META-INF/services/接口全限定名
比如我的:META-INF/services/com.dfire.spi.Echo

com.dfire.spi.DubboEcho
com.dfire.spi.JdkEcho

測試類:

public class App {

    public static void main(String[] args) {
        ServiceLoader<Echo> says = ServiceLoader.load(Echo.class);
        for (Echo echo : says) {
            echo.echo();
        }
    }
}

執行後輸出結果爲

I am dubbo
I am jdk

目錄結構如下
在這裏插入圖片描述


完成這個例子,大家就能簡單瞭解了 JDK SPI 。如果想要新增接口只需寫個實現類配置在文件中即可。那麼 Dubbo 爲什麼沒有使用JDK SPI而使用了自定義SPI呢?

首先說我們能看到的缺點

  • 一次性實例化所有的擴展點實現,即使該實現類沒有用上,浪費資源

看不到的缺點

  • 如果擴展點加載失敗,連擴展點的名稱都拿不到了。比如:JDK 標準的 ScriptEngine ,通過 getName 獲取腳本類型的名稱,但如果 RubyScriptEngine 因爲所依賴的 jruby.jar 不存在,導致 RubyScriptEngine 類加載失敗,這個失敗原因被吃掉了,和 ruby 對應不起來,當用戶執行 ruby 腳本時,會報不支持 ruby,而不是真正失敗的原因。

Dubbo SPI

Dubbo SPI除了修復 JDK SPI 的缺點外還增加了AOPIOC功能。


在提供者的入口類 ServiceConfig 中有這樣一行代碼

    private static final Protocol protocol = ExtensionLoader.getExtensionLoader(Protocol.class).getAdaptiveExtension();

其中 ExtensionLoader 類即是 Dubbo SPI 的實現類。
我們就從這行代碼逐步分析。

  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 interface!");
        }
        if(!withExtensionAnnotation(type)) {
            throw new IllegalArgumentException("Extension type(" + type + 
                    ") is not extension, because WITHOUT @" + SPI.class.getSimpleName() + " Annotation!");
        }
        
        //根據接口在緩存中獲得ExtensionLoader對象
        ExtensionLoader<T> loader = (ExtensionLoader<T>) EXTENSION_LOADERS.get(type);
        if (loader == null) {
        	//創建ExtensionLoader對象
            EXTENSION_LOADERS.putIfAbsent(type, new ExtensionLoader<T>(type));
            loader = (ExtensionLoader<T>) EXTENSION_LOADERS.get(type);
        }
        return loader;
    }
  • 在前兩行首先是進行判空和該類是否爲接口
  • 繼續從緩存中根據接口類型來獲得 ExtensionLoader 對象,如果爲空就創建
  • 進入 ExtensionLoader 構造方法
    private ExtensionLoader(Class<?> type) {
        this.type = type;
        objectFactory = (type == ExtensionFactory.class ? null : ExtensionLoader.getExtensionLoader(ExtensionFactory.class).getAdaptiveExtension());
    }

哇,好複雜。
簡單分析下
首先判斷 type 是否爲 ExtensionFactory類,如果是那麼 objectFactory 就爲null。如果不是就獲得 ExtensionFactory自適應擴展類ExtensionLoader.getExtensionLoader(T.class).getAdaptiveExtension() 這行代碼其實就是我們剛剛進入分析的入口。下面繼續分析 getAdaptiveExtension 方法。

獲得所有的擴展類

    public T getAdaptiveExtension() {
    	//檢測緩存中是否存在自適應擴展類實現
        Object instance = cachedAdaptiveInstance.get();
        //double check
        if (instance == null) {
            if(createAdaptiveInstanceError == null) {
                synchronized (cachedAdaptiveInstance) {
                    instance = cachedAdaptiveInstance.get();
                    if (instance == null) {
                        try {
                        	//新建
                            instance = createAdaptiveExtension();
                            //設置緩存
                            cachedAdaptiveInstance.set(instance);
                        } catch (Throwable t) {
                            createAdaptiveInstanceError = t;
                            throw new IllegalStateException("fail to create adaptive instance: " + t.toString(), t);
                        }
                    }
                }
            }
            else {
                throw new IllegalStateException("fail to create adaptive instance: " + createAdaptiveInstanceError.toString(), createAdaptiveInstanceError);
            }
        }

        return (T) instance;
    }

此方法,主要是爲了獲得自適應擴展類。首先從緩存的 cachedAdaptiveInstance 中檢測是否已經存在。

    private final Holder<Object> cachedAdaptiveInstance = new Holder<Object>();

cachedAdaptiveInstanceHolder 類創建的一個對象查看一下 Holder

public class Holder<T> {
    
    private volatile T value;
    
    public void set(T value) {
        this.value = value;
    }
    
    public T get() {
        return value;
    }

}

發現 Holder 類中持有一個 value 對象並且以 volatile 關鍵字修飾,保證其可見性,指令有序性。

繼續往下分析,常見的 double check ,如果不存在就進入 createAdaptiveExtension 方法進行創建。

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

createAdaptiveExtension 方法主要做了:調用getAdaptiveExtensionClass 方法獲得自適應擴展類,通過反射 newInstance 新建一個對象,然後使用 injectExtension 方法進入屬性注入操作。

根據調用順序,首先進入 getAdaptiveExtensionClass 方法

   private Class<?> getAdaptiveExtensionClass() {
   		//加載配置文件的所有的類
        getExtensionClasses();
        //如果cachedAdaptiveClass已經加載過,直接返回
        if (cachedAdaptiveClass != null) {
            return cachedAdaptiveClass;
        }
        //無自適應擴展類,動態創建自適應擴展類
        return cachedAdaptiveClass = createAdaptiveExtensionClass();
    }

getAdaptiveExtensionClass 方法中主要做了兩件事,首先調用 getExtensionClasses 方法,該方法會加載所有配置在文件中的類,其中如果有自適應擴展類,那麼就會爲 cachedAdaptiveClass 賦值。而 createAdaptiveExtensionClass 會在發現沒有自定義的自適應擴展類時進行動態生成。

那麼首先進入 getExtensionClasses 方法

	private Map<String, Class<?>> getExtensionClasses() {
		//首先從緩存中獲得所有的實現類,如果發現爲null 則進行初始化
        Map<String, Class<?>> classes = cachedClasses.get();
        //double check
        if (classes == null) {
            synchronized (cachedClasses) {
                classes = cachedClasses.get();
                if (classes == null) {
                	//爲空 則進行初始化加載類
                    classes = loadExtensionClasses();
                    cachedClasses.set(classes);
                }
            }
        }
        return classes;
	}

同樣是 double check ,首先根據緩存 map 判斷是否已經加載過,如果未加載過那麼進行第一次加載,並設置緩存。那麼就進入加載擴展類的方法中 loadExtensionClasses

    private Map<String, Class<?>> loadExtensionClasses() {
    	//首先獲得接口類的註解@SPI
        final SPI defaultAnnotation = type.getAnnotation(SPI.class);
        //如果接口類的@SPI註解不爲null
        if(defaultAnnotation != null) {
        	//獲得接口註解值
            String value = defaultAnnotation.value();
            if(value != null && (value = value.trim()).length() > 0) {
            	//只能有一個
                String[] names = NAME_SEPARATOR.split(value);
                if(names.length > 1) {
                    throw new IllegalStateException("more than 1 default extension name on extension " + type.getName()
                            + ": " + Arrays.toString(names));
                }
                //設置默認的實現類名稱爲接口類註釋@SPI的value 
                //tip:這裏要記住哦!
                if(names.length == 1) cachedDefaultName = names[0];
            }
        }
        
        Map<String, Class<?>> extensionClasses = new HashMap<String, Class<?>>();
        //從 "META-INF/dubbo/internal/" 目錄下解析實現類
        loadFile(extensionClasses, DUBBO_INTERNAL_DIRECTORY);
        //從 "META-INF/dubbo/" 目錄下解析實現類
        loadFile(extensionClasses, DUBBO_DIRECTORY);
        //從 "META-INF/services/" 目錄下解析實現類
        loadFile(extensionClasses, SERVICES_DIRECTORY);
        return extensionClasses;
    }

該方法大致內容是:

  • 查看該接口類是否具有 SPI 註解,有的話則獲得註解的值,並賦值給 cachedDefaultName 作爲自適應擴展實現的默認值
  • 去指定的文件路徑下解析該接口的實現類的配置信息

在看 loadFile 方法之前首先附上 Dubbo SPI 的一個資源文件 META-INF/dubbo/internal/com.alibaba.dubbo.rpc.Protocol,方便大家理解。

registry=com.alibaba.dubbo.registry.integration.RegistryProtocol
dubbo=com.alibaba.dubbo.rpc.protocol.dubbo.DubboProtocol
filter=com.alibaba.dubbo.rpc.protocol.ProtocolFilterWrapper
listener=com.alibaba.dubbo.rpc.protocol.ProtocolListenerWrapper
mock=com.alibaba.dubbo.rpc.support.MockProtocol
injvm=com.alibaba.dubbo.rpc.protocol.injvm.InjvmProtocol
rmi=com.alibaba.dubbo.rpc.protocol.rmi.RmiProtocol
hessian=com.alibaba.dubbo.rpc.protocol.hessian.HessianProtocol
com.alibaba.dubbo.rpc.protocol.http.HttpProtocol
com.alibaba.dubbo.rpc.protocol.webservice.WebServiceProtocol
thrift=com.alibaba.dubbo.rpc.protocol.thrift.ThriftProtocol
memcached=com.alibaba.dubbo.rpc.protocol.memcached.MemcachedProtocol
redis=com.alibaba.dubbo.rpc.protocol.redis.RedisProtocol

我們可以明顯發現和 Java SPI 的區別,Dubbo 是使用 name=classkey-value 模式存儲的實現類,至於這樣有什麼好處,我們繼續進入 loadFile 方法分析

    private void loadFile(Map<String, Class<?>> extensionClasses, String dir) {
        //獲得文件的路徑名,比如 type 爲Protocol.class,dir 爲 META-INF/dubbo/internal/ 
        //那麼fileName = "META-INF/dubbo/internal/com.alibaba.dubbo.rpc.Protocol"
        String fileName = dir + type.getName();
        try {
            Enumeration<java.net.URL> urls;
            ClassLoader classLoader = findClassLoader();
            //根據是否存在類加載器來使用不同的資源加載文件
            if (classLoader != null) {
                urls = classLoader.getResources(fileName);
            } else {
                urls = ClassLoader.getSystemResources(fileName);
            }
            if (urls != null) {
            	//遍歷文件
                while (urls.hasMoreElements()) {
                    java.net.URL url = urls.nextElement();
                    try {
                        BufferedReader reader = new BufferedReader(new InputStreamReader(url.openStream(), "utf-8"));
                        try {
                            String line = null;
                            //讀取文件內容
                            while ((line = reader.readLine()) != null) {
                            	//過濾註釋信息
                                final int ci = line.indexOf('#');
                                if (ci >= 0) line = line.substring(0, ci);
                                line = line.trim();
                                if (line.length() > 0) {
                                    try {
                                        String name = null;
                                        //根據 = 字符進行切分 左邊爲名稱,右邊爲類名
                                        int i = line.indexOf('=');
                                        if (i > 0) {
                                        	//名稱
                                            name = line.substring(0, i).trim();
                                            //類
                                            line = line.substring(i + 1).trim();
                                        }
                                       
                                        if (line.length() > 0) {
                                        	//加載類
                                            Class<?> clazz = Class.forName(line, true, classLoader);
                                            if (! type.isAssignableFrom(clazz)) {
                                                throw new IllegalStateException("Error when load extension class(interface: " +
                                                        type + ", class line: " + clazz.getName() + "), class " 
                                                        + clazz.getName() + "is not subtype of interface.");
                                            }
                                            //判斷該加載類是否具有 @Adaptive 註解
                                            if (clazz.isAnnotationPresent(Adaptive.class)) {
                                            	//如果當前加載類有 @Adaptive 註解並且緩存的自適應註解類爲空 那麼爲緩存設置
                                                if(cachedAdaptiveClass == null) {
                                                    cachedAdaptiveClass = clazz;
                                                    //如果 cachedAdaptiveClass 緩存已經設置 那麼拋出異常 @Adaptive 放在所有接口實現類的其中一個類上面  即:自適應擴展類只能有一個
                                                } else if (! cachedAdaptiveClass.equals(clazz)) {
                                                    throw new IllegalStateException("More than 1 adaptive class found: "
                                                            + cachedAdaptiveClass.getClass().getName()
                                                            + ", " + clazz.getClass().getName());
                                                }
                                                //如果沒有 @Adaptive 註解
                                            } else {
                                                try {
                                                	//首先判斷該加載類是否具一個參數爲自己的構造方法 如果不拋出 NoSuchMethodException 異常,那麼將其加入包裝(裝飾)類集合中
                                                	//tip:這裏要記住哦 
                          
                                                    clazz.getConstructor(type);
                                                    //可能有一個或多個
                                                    Set<Class<?>> wrappers = cachedWrapperClasses;
                                                    if (wrappers == null) {
                                                        cachedWrapperClasses = new ConcurrentHashSet<Class<?>>();
                                                        wrappers = cachedWrapperClasses;
                                                    }
                                                    wrappers.add(clazz);
                                                } catch (NoSuchMethodException e) {
                                                	//是否有默認(空)構造方法
                                                    clazz.getConstructor();
                                                    //判斷配置文件中 name 是否爲 null
                                                    if (name == null || name.length() == 0) {
                                                        //如果爲 null 就根據 type 自動生成一個
                                                        name = findAnnotationName(clazz);
                                                        if (name == null || name.length() == 0) {
                                                            if (clazz.getSimpleName().length() > type.getSimpleName().length()
                                                                    && clazz.getSimpleName().endsWith(type.getSimpleName())) {
                                                                name = clazz.getSimpleName().substring(0, clazz.getSimpleName().length() - type.getSimpleName().length()).toLowerCase();
                                                            } else {
                                                                throw new IllegalStateException("No such extension name for the class " + clazz.getName() + " in the config " + url);
                                                            }
                                                        }
                                                    }
                                                    //分割 name
                                                    String[] names = NAME_SEPARATOR.split(name);
                                                    if (names != null && names.length > 0) {
                                                       //判斷類上是否有 @Activate 註解 如果有則以第一個 name 作爲 key Activate 對象作爲val 放入緩存中
                                                        Activate activate = clazz.getAnnotation(Activate.class);
                                                        if (activate != null) {
                                                            cachedActivates.put(names[0], activate);
                                                        }
                                                        //遍歷所有 name
                                                        for (String n : names) {
                                                            //判斷緩存的class -> name 映射中是否存在  不存在則添加
                                                            if (! cachedNames.containsKey(clazz)) {
                                                                cachedNames.put(clazz, n);
                                                            }
                                                            Class<?> c = extensionClasses.get(n);
                                                            //判斷 name -> class 的映射 map 中是否已經添加過,進行添加/拋異常操作
                                                            //tip:extensionClasses 是我們在 loadExtensionClasses 方法中創建的 map, 用來存放我們解析的結果
                                                            if (c == null) {
                                                                extensionClasses.put(n, clazz);
                                                            } else if (c != clazz) {
                                                                throw new IllegalStateException("Duplicate extension " + type.getName() + " name " + n + " on " + c.getName() + " and " + clazz.getName());
                                                            }
                                                        }
                                                    }
                                                }
                                            }
                                        }
                                    } catch (Throwable t) {
                                        IllegalStateException e = new IllegalStateException("Failed to load extension class(interface: " + type + ", class line: " + line + ") in " + url + ", cause: " + t.getMessage(), t);
                                        exceptions.put(line, e);
                                    }
                                }
                            } // end of while read lines
                        } finally {
                            reader.close();
                        }
                    } catch (Throwable t) {
                        logger.error("Exception when load extension class(interface: " +
                                            type + ", class file: " + url + ") in " + url, t);
                    }
                } // end of while urls
            }
        } catch (Throwable t) {
            logger.error("Exception when load extension class(interface: " +
                    type + ", description file: " + fileName + ").", t);
        }
    }

代碼很長,需要大家仔細去看,具體的註釋我已經寫的很清楚。
總結一下這段代碼的作用

  • 解析在 "META-INF/dubbo/internal/","META-INF/dubbo/" ,"META-INF/services/" 等目錄下的 SPI 資源文件
  • 發現實現類具有 @Adaptive 註解,即自定義自適應擴展類,則爲緩存 cachedAdaptiveClass 賦值,很重要,別忘記我們爲什麼在執行到 getAdaptiveExtensionClass 方法時進入 getExtensionClasses 方法,就是爲了得到自適應擴展類
  • 查看實現類是否具有一個參數爲接口類的構造方法,有的話則把該實現類放入修飾類緩存中
  • 查看實現類是否具有 @Activate 註解,存在的話放入緩存
  • 將解析 SPI 資源文件 以 class -> name 的方式 放入緩存mapcachedNames

加載 SPI 資源這一部分算是結束了,那麼我們繼續回到 getAdaptiveExtensionClass 方法中。

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

我們通過上面分析知道 getExtensionClasses 方法會在存在 @Adaptive 註解的實現類時爲 cachedAdaptiveClass 賦值。在這裏如果發現 cachedAdaptiveClass != null 則直接返回,否則進入動態創建自適應擴展類方法 createAdaptiveExtensionClass

這裏纔是我們理解 Dubbo SPI 關鍵,大家要耐心、仔細往下看。

  private Class<?> createAdaptiveExtensionClass() {
  		//動態生成自適應擴展類代碼
        String code = createAdaptiveExtensionClassCode();
        //獲得類加載器
        ClassLoader classLoader = findClassLoader();
        //獲得 Compiler 自適應擴展類
        com.alibaba.dubbo.common.compiler.Compiler compiler = ExtensionLoader.getExtensionLoader(com.alibaba.dubbo.common.compiler.Compiler.class).getAdaptiveExtension();
        //編譯代碼生成 class
        return compiler.compile(code, classLoader);
    }

這個方法內大概內容就是動態生成自適應擴展類代碼,使用 Compiler 編譯該代碼。

我們主要看的在 createAdaptiveExtensionClassCode 方法內,先多嘴一句,動態生成自適應擴展類代碼需要保證接口內具有 @Adaptive 註解的方法

由於該方法內容較長,我決定一段一段分析,大家可以對照源碼閱讀。在下面文章中,如果存在 /* */ 註釋則表示省略了一段代碼,註釋內容爲省略代碼的主要功能,大家可以注意一下。

        Method[] methods = type.getMethods();
        //判斷方法是否具有 @Adaptive 註解
        boolean hasAdaptiveAnnotation = false;
        for(Method m : methods) {
            if(m.isAnnotationPresent(Adaptive.class)) {
                hasAdaptiveAnnotation = true;
                break;
            }
        }
        // 完全沒有 Adaptive 方法,則不需要生成 Adaptive 類
        if(! hasAdaptiveAnnotation)
            throw new IllegalStateException("No adaptive method on extension " + type.getName() + ", refuse to create the adaptive class!");

這部分主要是判斷接口內是否存在 @Adaptive 註解的方法,如果不存在,那麼直接拋異常。

		//生成包名
        codeBuidler.append("package " + type.getPackage().getName() + ";");
        //導入ExtensionLoader
        codeBuidler.append("\nimport " + ExtensionLoader.class.getName() + ";");
        //生成類名(比如: Protocol$Adpative) 並且 實現接口 該接口
        codeBuidler.append("\npublic class " + type.getSimpleName() + "$Adaptive" + " implements " + type.getCanonicalName() + " {");

很簡單,看註釋就好了

  
        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);
            //如果當前方法沒有 @Adaptive 註解,則在實現類的該方法體中拋出異常
            if (adaptiveAnnotation == null) {
                code.append("throw new UnsupportedOperationException(\"method ")
                        .append(method.toString()).append(" of interface ")
                        .append(type.getName()).append(" is not adaptive method!\");");
            } else {
            	//其它操作
            }

這段代碼主要是判斷當前接口方法是否存在 @Adaptive 註解,如果不存在,則在實現方法中添加拋異常代碼

   for (Method method : methods) {
             /* 參數獲取 */
            if (adaptiveAnnotation == null) {
          		/*不存在 @Adaptive 註解*/
            } else {
                int urlTypeIndex = -1;
                //判斷該方法的參數中是否存在 URL 類型的 有的話進行標記
                for (int i = 0; i < pts.length; ++i) {
                    if (pts[i].equals(URL.class)) {
                        urlTypeIndex = i;
                        break;
                    }
                }
                // 有類型爲URL的參數
                if (urlTypeIndex != -1) {
                    // Null Point check
                    String s = String.format("\nif (arg%d == null) throw new IllegalArgumentException(\"url == null\");",
                                    urlTypeIndex);
                    code.append(s);
                    
                    s = String.format("\n%s url = arg%d;", URL.class.getName(), urlTypeIndex); 
                    code.append(s);
                }
                // 參數沒有URL類型
                else {
                //...
                }
                //...
              }
                       //	其它
     }

這裏主要是檢測方法的參數中是否具有 URL 類型的參數。
繼續看參數沒有 URL 類型的時候處理方式

   for (Method method : methods) {
           /* 參數獲取 */
            if (adaptiveAnnotation == null) {
          		/*不存在 @Adaptive 註解*/
            } else {
           		/*獲得 URL 參數位置*/
                if (urlTypeIndex != -1) {
             		/* 參數有URL*/
                }
                // 參數沒有URL類型
                else {
              		String attribMethod = null;
                    
                    // 遍歷所有參數 從參數類型的所有方法中 查看 get* 方法並且返回類型爲 URL
                    LBL_PTS:
                    for (int i = 0; i < pts.length; ++i) {
                        Method[] ms = pts[i].getMethods();
                        for (Method m : ms) {
                            String name = m.getName();
                            if ((name.startsWith("get") || name.length() > 3)
                                    && Modifier.isPublic(m.getModifiers())
                                    && !Modifier.isStatic(m.getModifiers())
                                    && m.getParameterTypes().length == 0
                                    && m.getReturnType() == URL.class) {
                                //記錄 URL 參數的位置
                                urlTypeIndex = i;
                                 //記錄返回 URL 類型的方法名
                                attribMethod = name;
                                break LBL_PTS;
                            }
                        }
                    }
                    //如果爲空 則表示所有方法的參數類型中不具有 URL 參數
                    if(attribMethod == null) {
                        throw new IllegalStateException("fail to create adative class for interface " + type.getName()
                        		+ ": not found url parameter or url attribute in parameters of method " + method.getName());
                    }
                    
                    // 空指針檢測 生成的代碼如:if (arg0 == null) throw new IllegalArgumentException("com.alibaba.dubbo.rpc.Invoker argument == null");
                    String s = String.format("\nif (arg%d == null) throw new IllegalArgumentException(\"%s argument == null\");",
                                    urlTypeIndex, pts[urlTypeIndex].getName());
                    code.append(s);
                    //url參數判空,生成代碼如下:if (arg0.getUrl() == null) throw new IllegalArgumentException("com.alibaba.dubbo.rpc.Invoker argument getUrl() == null");
                    s = String.format("\nif (arg%d.%s() == null) throw new IllegalArgumentException(\"%s argument %s() == null\");",
                                    urlTypeIndex, attribMethod, pts[urlTypeIndex].getName(), attribMethod);
                    code.append(s);

					//獲得url參數,生成代碼如下:com.alibaba.dubbo.common.URL url = arg0.getUrl();
                    s = String.format("%s url = arg%d.%s();",URL.class.getName(), urlTypeIndex, attribMethod); 
                    code.append(s);
                }
              	//其它
           }
                    //	其它
     }

這部分代碼主要就是從接口的方法中遍歷所有參數,查看參數是否具有 URL 屬性。至於爲什麼 Dubbo 一直在查找URL 參數,是因爲我們的所有信息在 Dubbo 中都是以 URL 爲總線的,通過 URL 可以獲得我們需要的信息。

繼續往下分析

 for (Method method : methods) {
          	/* 參數獲取 */
            if (adaptiveAnnotation == null) {
          		/*不存在 @Adaptive 註解*/
            } else {
           		/* 獲得 URL 參數位置*/
                if (urlTypeIndex != -1) {
             		/* 參數有URL*/
                }
                // 參數沒有URL類型
                else {
              		/*從方法參數類型的方法中查找URL*/
                }
                	//獲得方法上具有 @Adaptive 註解的值
                    String[] value = adaptiveAnnotation.value();
                   // 沒有設置value,則使用“擴展點接口名的點分隔 作爲value
                	if(value.length == 0) {
                    char[] charArray = type.getSimpleName().toCharArray();
                    StringBuilder sb = new StringBuilder(128);
                    for (int i = 0; i < charArray.length; i++) {
                        if(Character.isUpperCase(charArray[i])) {
                            if(i != 0) {
                                sb.append(".");
                            }
                            sb.append(Character.toLowerCase(charArray[i]));
                        }
                        else {
                            sb.append(charArray[i]);
                        }
                    }
                    value = new String[] {sb.toString()};
                }
              	//其它
           }
                    //	其它
     }

這部分也很簡單,從方法註解上獲得註解的值,可能有多個。如果不存在的話,根據接口名獲得,遇見大寫改爲小寫如果不是第一個符號還需要加 . 符號。

 		for (Method method : methods) {
            /* 參數獲取 */
            if (adaptiveAnnotation == null) {
          		/* 不存在 @Adaptive 註解 */
            } else {
           		//獲得 URL 參數位置
                if (urlTypeIndex != -1) {
             		/* 參數有URL */
                }
                // 參數沒有URL類型
                else {
              		/*從方法參數類型的方法中查找URL*/
                }
             	/*獲得 @Adaptive 註解的值*/
                boolean hasInvocation = false;
                //判斷是否具有 Invocation 類型的參數 
                for (int i = 0; i < pts.length; ++i) {
                    if (pts[i].getName().equals("com.alibaba.dubbo.rpc.Invocation")) {
                        // 空指針檢測 生成的代碼如:if (arg0 == null) throw new IllegalArgumentException("invocation == null");"
                        String s = String.format("\nif (arg%d == null) throw new IllegalArgumentException(\"invocation == null\");", i);
                        code.append(s);
                        //獲得方法名 生成的代碼如:String methodName = arg0.getMethodName();
                        s = String.format("\nString methodName = arg%d.getMethodName();", i); 
                        code.append(s);
                        hasInvocation = true;
                        break;
                    }
                }
             	//cachedDefaultName  還記得上面我們會加載 SPI 資源文件時會設置該值嗎?忘了可以回去看看 該值是從接口類的 @Adaptive 註解中獲得的
			    String defaultExtName = cachedDefaultName;
			    //tip:這段代碼通過遞歸方式生成 仔細觀察下面的 for 循環語句 
                String getNameCode = null;
             	// 由於在方法上 @Adaptive 註解的值可能有多個,從後往前遍歷 以最左爲準,主要是爲了兼容老版本擴展名參數。越[老]的擴展名參數越靠右,會被新的覆蓋。默認擴展名爲defaultExtName
                for (int i = value.length - 1; i >= 0; --i) {
                    if(i == value.length - 1) {
                    	//如果默認擴展名不爲空
                        if(null != defaultExtName) {
                        	//至於爲什麼特判 protocol,是因爲 url 可以直接獲得
                            if(!"protocol".equals(value[i]))
                                if (hasInvocation) 
                                	//存在 Invocation 參數 生成的代碼如: url.getMethodParameter(methodName, "loadbalance", "random")
                                    getNameCode = String.format("url.getMethodParameter(methodName, \"%s\", \"%s\")", value[i], defaultExtName);
                                else
                                	//生成代碼如:url.getParameter("proxy", "javassist")
                                    getNameCode = String.format("url.getParameter(\"%s\", \"%s\")", value[i], defaultExtName);
                            else
                            	//生成代碼如:( url.getProtocol() == null ? "dubbo" : 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
                                    getNameCode = String.format("url.getParameter(\"%s\")", value[i]);
                            else
                                getNameCode = "url.getProtocol()";
                        }
                    }
                    else {
                        if(!"protocol".equals(value[i]))
                            if (hasInvocation) 
                                getNameCode = String.format("url.getMethodParameter(methodName, \"%s\", \"%s\")", value[i], defaultExtName);
                            else
                                getNameCode = String.format("url.getParameter(\"%s\", %s)", value[i], getNameCode);
                        else
                            getNameCode = String.format("url.getProtocol() == null ? (%s) : url.getProtocol()", getNameCode);
                    }
                }
                code.append("\nString extName = ").append(getNameCode).append(";");
                // check extName == null?
                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);
             	//其它
           }
                    //	其它
     }

代碼有些複雜,建議大家 debug 查看。主要是爲了獲得 extName 的參數。

 	for (Method method : methods) {
            /* 參數獲取 */
            if (adaptiveAnnotation == null) {
          		/* 不存在 @Adaptive 註解 */
            } else {
           		//獲得 URL 參數位置
                if (urlTypeIndex != -1) {
             		/* 參數有URL */
                }
                // 參數沒有URL類型
                else {
              		/*從方法參數類型的方法中查找URL*/
                }
             	/*獲得 @Adaptive 註解的值*/
              	/* 生成 extName 的代碼*/
				//根據 extName 從 SPI 中獲得實例,具體代碼如:com.alibaba.dubbo.rpc.Protocol extension = (com.alibaba.dubbo.rpc.Protocol)ExtensionLoader.getExtensionLoader(com.alibaba.dubbo.rpc.Protocol.class).getExtension(extName);
             	s = String.format("\n%s extension = (%<s)%s.getExtensionLoader(%s.class).getExtension(extName);",
                        type.getName(), ExtensionLoader.class.getSimpleName(), type.getName());
                code.append(s);
                
                // 如果非 void 返回類型則生成返回值語句
                if (!rt.equals(void.class)) {
                    code.append("\nreturn ");
                }

				//下面就是生成返回值,內容如: return extension.export(arg0);
                s = String.format("extension.%s(", method.getName());
                code.append(s);
                for (int i = 0; i < pts.length; i++) {
                    if (i != 0)
                        code.append(", ");
                    code.append("arg").append(i);
                }
                code.append(");");
           }
           //	其它
     }		 	

這部分代碼主要是爲了生成返回值。

繼續看最後一部分了

			// public + 返回值全限定名 + 方法名 + (
 			codeBuidler.append("\npublic " + rt.getCanonicalName() + " " + method.getName() + "(");
 			// 添加參數列表代碼
            for (int i = 0; i < pts.length; i ++) {
                if (i > 0) {
                    codeBuidler.append(", ");
                }
                codeBuidler.append(pts[i].getCanonicalName());
                codeBuidler.append(" ");
                codeBuidler.append("arg" + i);
            }
            codeBuidler.append(")");
            // 添加異常拋出代碼
            if (ets.length > 0) {
                codeBuidler.append(" throws ");
                for (int i = 0; i < ets.length; i ++) {
                    if (i > 0) {
                        codeBuidler.append(", ");
                    }
                    codeBuidler.append(pts[i].getCanonicalName());
                }
            }
            codeBuidler.append(" {");
            codeBuidler.append(code.toString());
            codeBuidler.append("\n}");

大致就到這裏了,動態生成自適應擴展類大概就這麼多,簡單舉個例子。
大家可以看com.alibaba.dubbo.rpc.Protocol接口,該 接口的實現類中不存在 @Adaptive 註解的實現類,所以在使用

Protocol protocol = ExtensionLoader.getExtensionLoader(Protocol.class).getAdaptiveExtension()

時會動態生成,還記得我們的生成的原則吧:

  • 判斷方法是否存在 @Adaptive 註解
    – 不存在:自適應類的實現方法中拋異常
    – 存在:判斷方法參數是否存在 URL 類的參數
package com.alibaba.dubbo.rpc;

import com.alibaba.dubbo.common.URL;
import com.alibaba.dubbo.common.extension.Adaptive;
import com.alibaba.dubbo.common.extension.SPI;

@SPI("dubbo")
public interface Protocol {

    int getDefaultPort();
    
    @Adaptive
    <T> Exporter<T> export(Invoker<T> invoker) throws RpcException;
 
    @Adaptive
    <T> Invoker<T> refer(Class<T> type, URL url) throws RpcException;

    void destroy();

}

通過查看 Protocol 接口我們發現,該類的默認 SPI 擴展名爲 dubbogetDefaultPortdestroy 方法無 @Adaptive 註解。export 和 refer 具有 @Adaptive 註解。自動生成的Protocol自適應類如下

package com.alibaba.dubbo.rpc;

import com.alibaba.dubbo.common.extension.ExtensionLoader;

public class Protocol$Adpative implements com.alibaba.dubbo.rpc.Protocol {
	//無 @Adaptive 接口 直接拋異常
    public void destroy() {
        throw new UnsupportedOperationException("method public abstract void com.alibaba.dubbo.rpc.Protocol.destroy() of interface com.alibaba.dubbo.rpc.Protocol is not adaptive method!");
    }
	//無 @Adaptive 接口 直接拋異常
    public int getDefaultPort() {
        throw new UnsupportedOperationException("method public abstract int com.alibaba.dubbo.rpc.Protocol.getDefaultPort() of interface com.alibaba.dubbo.rpc.Protocol is not adaptive method!");
    }

    public com.alibaba.dubbo.rpc.Exporter export(com.alibaba.dubbo.rpc.Invoker arg0) throws com.alibaba.dubbo.rpc.Invoker {
        if (arg0 == null) throw new IllegalArgumentException("com.alibaba.dubbo.rpc.Invoker argument == null");
        if (arg0.getUrl() == null)
            throw new IllegalArgumentException("com.alibaba.dubbo.rpc.Invoker argument getUrl() == null");
        com.alibaba.dubbo.common.URL url = arg0.getUrl();
        String extName = (url.getProtocol() == null ? "dubbo" : url.getProtocol());
        if (extName == null)
            throw new IllegalStateException("Fail to get extension(com.alibaba.dubbo.rpc.Protocol) name from url(" + url.toString() + ") use keys([protocol])");
        com.alibaba.dubbo.rpc.Protocol extension = (com.alibaba.dubbo.rpc.Protocol) ExtensionLoader.getExtensionLoader(com.alibaba.dubbo.rpc.Protocol.class).getExtension(extName);
        return extension.export(arg0);
    }

    public com.alibaba.dubbo.rpc.Invoker refer(java.lang.Class arg0, com.alibaba.dubbo.common.URL arg1) throws java.lang.Class {
        if (arg1 == null) throw new IllegalArgumentException("url == null");
        com.alibaba.dubbo.common.URL url = arg1;
        String extName = (url.getProtocol() == null ? "dubbo" : url.getProtocol());
        if (extName == null)
            throw new IllegalStateException("Fail to get extension(com.alibaba.dubbo.rpc.Protocol) name from url(" + url.toString() + ") use keys([protocol])");
        com.alibaba.dubbo.rpc.Protocol extension = (com.alibaba.dubbo.rpc.Protocol) ExtensionLoader.getExtensionLoader(com.alibaba.dubbo.rpc.Protocol.class).getExtension(extName);
        return extension.refer(arg0, arg1);
    }
}

然後就要回到 createAdaptiveExtension 方法了

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

getAdaptiveExtensionClass().newInstance() 通過反射創建一個實例就不用說了,就剩下 injectExtension 方法了,這個方法中也就是 dubboIOC 了主要是爲了給該實例注入值

  private T injectExtension(T instance) {
        try {
        	//判斷objectFactory是否爲null  通過構造方法我們可以知道主要是判斷 type 是否等於ExtensionFactory.class
            if (objectFactory != null) {
            	//遍歷所有方法查看是否具有 set 方法並且參數只有一個
                for (Method method : instance.getClass().getMethods()) {
                    if (method.getName().startsWith("set")
                            && method.getParameterTypes().length == 1
                            && Modifier.isPublic(method.getModifiers())) {
                        Class<?> pt = method.getParameterTypes()[0];
                        try {
                        	//獲得變量名
                            String property = method.getName().length() > 3 ? method.getName().substring(3, 4).toLowerCase() + method.getName().substring(4) : "";
                            //對象工廠新建值 這邊可以繼續看我就不往下分析了
                            Object object = objectFactory.getExtension(pt, property);
                            //反射注入
                            if (object != null) {
                                method.invoke(instance, object);
                            }
                        } catch (Exception e) {
                            logger.error("fail to inject via method " + method.getName()
                                    + " of interface " + type.getName() + ": " + e.getMessage(), e);
                        }
                    }
                }
            }
        } catch (Exception e) {
            logger.error(e.getMessage(), e);
        }
       

寫了很多,需要大家慢慢消化。其實我這裏不可能把 SPI 的全部源碼分析的,該類中還有一些其它方法,不過核心都是在這片文章裏面。希望大家能夠由點到面,逐個擊破。


最後還想再說一句,有一點需要注意的是在 loadFile 時,如果發現一個類無 @Adaptive 註解並且該類中具有接口類的構造方法,那麼則任務該類爲包裝類,則向 cachedWrapperClasses 變量中緩存可以包裝的類。

比如 ProtocolFilterWrapperProtocolListenerWrapper 均可以包裝 Protocol 的實現類。

public class ProtocolFilterWrapper implements Protocol {

    private final Protocol protocol;

    public ProtocolFilterWrapper(Protocol protocol){
        if (protocol == null) {
            throw new IllegalArgumentException("protocol == null");
        }
        this.protocol = protocol;
    }

   public ProtocolListenerWrapper(Protocol protocol){
        if (protocol == null) {
            throw new IllegalArgumentException("protocol == null");
        }
        this.protocol = protocol;
    }

具體的包裝實現是在ExtensionLoader類的createExtension方法中

    private T createExtension(String name) {
        Class<?> clazz = getExtensionClasses().get(name);
        if (clazz == null) {
            throw findException(name);
        }
        try {
            T instance = (T) EXTENSION_INSTANCES.get(clazz);
            if (instance == null) {
                EXTENSION_INSTANCES.putIfAbsent(clazz, (T) clazz.newInstance());
                instance = (T) EXTENSION_INSTANCES.get(clazz);
            }
            injectExtension(instance);
            Set<Class<?>> wrapperClasses = cachedWrapperClasses;
            //判斷是否存在包裝類
            if (wrapperClasses != null && wrapperClasses.size() > 0) {
                for (Class<?> wrapperClass : wrapperClasses) {
                	//對實例進行包裝
                    instance = injectExtension((T) wrapperClass.getConstructor(type).newInstance(instance));
                }
            }
            //返回包裝後的實例
            return instance;
        } catch (Throwable t) {
            throw new IllegalStateException("Extension instance(name: " + name + ", class: " +
                    type + ")  could not be instantiated: " + t.getMessage(), t);
        }
    }
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章