【Dubbo源碼閱讀系列】之 Dubbo SPI 機制

最近抽空開始了 Dubbo 源碼的閱讀之旅,希望可以通過寫文章的方式記錄和分享自己對 Dubbo 的理解。如果在本文出現一些紕漏或者錯誤之處,也希望大家不吝指出。

Dubbo SPI 介紹

Java SPI

在閱讀本文之前可能需要你對 Java SPI(Service Provider Interface) 機制有過簡單的瞭解。這裏簡單介紹下:在面向對象的設計中,我們提倡模塊之間基於接口編程。不同模塊可能會有不同的具體實現,但是爲了避免模塊的之間的耦合過大,我們需要一種有效的服務(服務實現)發現機制來選擇具體模塊。SPI 就是這樣一種基於接口編程+策略模式+配置文件,同時可供使用者根據自己的實際需要啓用/替換模塊具體實現的方案。

Dubbo SPI 的改進點

以下內容摘錄自 https://dubbo.gitbooks.io/dub...
Dubbo 的擴展點加載從 JDK 標準的 SPI (Service Provider Interface) 擴展點發現機制加強而來。
JDK 標準的 SPI 會一次性實例化擴展點所有實現,如果有擴展實現初始化很耗時,但如果沒用上也加載,會很浪費資源。
如果擴展點加載失敗,連擴展點的名稱都拿不到了。比如:JDK 標準的 ScriptEngine,通過 getName() 獲取腳本類型的名稱,但如果 RubyScriptEngine 因爲所依賴的 jruby.jar 不存在,導致 RubyScriptEngine 類加載失敗,這個失敗原因被吃掉了,和 ruby 對應不起來,當用戶執行 ruby 腳本時,會報不支持 ruby,而不是真正失敗的原因。
增加了對擴展點 IoC 和 AOP 的支持,一個擴展點可以直接 setter 注入其它擴展點

在 Dubbo 中,如果某個 interface 接口標記了 @SPI 註解,那麼我們認爲它是 Dubbo 中的一個擴展點。擴展點是 Dubbo SPI 的核心,下面我們就擴展點加載、擴展點自動包裝、擴展點自動裝配幾方面來聊聊具體實現。

Dubbo SPI 機制詳解

Dubbo 擴展點的加載

在閱讀本文前,如果你閱讀過Java SPI 相關內容,大概能回憶起來有 /META-INF/services 這樣一個目錄。在這個目錄下有一個以接口命名的文件,文件的內容爲接口具體實現類的全限定名。在 Dubbo 中我們也能找到類似的設計。

  • META-INF/services/(兼容JAVA SPI)
  • META-INF/dubbo/(自定義擴展點實現)
  • META-INF/dubbo/internal/(Dubbo內部擴展點實現)

非常好~我們現在已經知道了從哪裏加載擴展點了,再回憶一下,JAVA SPI是如何加載的。

ServiceLoader<DubboService> spiLoader = ServiceLoader.load(XXX.class);

類似的,在 Dubbo 中也有這樣一個用於加載擴展點的類 ExtensionLoader。這一章節,我們會着重瞭解一下這個類到底是如何幫助我們加載擴展點的。我們先來看一段簡短的代碼。

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

在 Dubbo 的實現裏面用到了大量類似的代碼片段,我們只需要提供一個 type ,即可獲取該 type 的自適應(關於自適應的理解在後文會提到)擴展類。在獲取對應自適應擴展類時,我們首先獲取該類型的 ExtensionLoader。看到這裏我們應該下意識的感覺到對於每個 type 來說,都應該有一個對應的 ExtensionLoader 對象。我們先來看看 ExtensionLoader 是如何獲取的。

getExtensionLoader()

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!");
    }
    // 是否被 SPI 註解標識 
    if (!withExtensionAnnotation(type)) {
        throw new IllegalArgumentException("Extension type(" + type +
                ") is not extension, because WITHOUT @" + SPI.class.getSimpleName() + " Annotation!");
    }
    //EXTENSION_LOADERS 爲一個 ConcurrentMap集合,key 爲 Class 對象,value 爲ExtenLoader 對象
    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;
}

private ExtensionLoader(Class<?> type) {
    this.type = type;
    objectFactory = (type == ExtensionFactory.class ? null : ExtensionLoader.getExtensionLoader(ExtensionFactory.class).getAdaptiveExtension());
}

上面這一段的代碼比較簡單,根據 type 從 EXTENSION_LOADERS 集合中獲取 loader ,如果返回的值爲 null 則新建一個 ExtensionLoader 對象。這裏的 objectFactory 獲取也用到了類似的方法,獲取到了 ExtensionFactory 的擴展自適應類。

getAdaptiveExtension()

public T getAdaptiveExtension() {
    //cachedAdaptiveInstance用於緩存自適應擴展類實例
    Object instance = cachedAdaptiveInstance.get();
    if (instance == null) {
        if (createAdaptiveInstanceError == null) {
            synchronized (cachedAdaptiveInstance) {
                instance = cachedAdaptiveInstance.get();
                if (instance == null) {
                    try {
                        instance = createAdaptiveExtension();
                        cachedAdaptiveInstance.set(instance);
                    } catch (Throwable t) {
                        // ...
                    }
                }
            }
    }

    return (T) instance;
}

getAdaptiveExtension()方法用於獲取當前自適應擴展類實例,首先會從 cachedAdaptiveInstance 對象中獲取,如果值爲 null 同時 createAdaptiveInstanceError 爲空,則調用 createAdaptiveExtension 方法創建擴展類實例。創建完後更新 cachedAdaptiveInstance 。

createAdaptiveExtension()

private T createAdaptiveExtension() {
    try {
        return injectExtension((T) getAdaptiveExtensionClass().newInstance());
    } catch (Exception e) {
        // 省略異常
    }
}

這裏有兩個方法值得我們關注,injectExtension() 和 getAdaptiveExtensionClass()。injectExtension() 看名字像是一個實現了注入功能的方法,而 getAdaptiveExtensionClass() 則用於獲取具體的自適應擴展類。我們依次看下這兩個方法。

injectExtension()

private T injectExtension(T instance) {
    try {
        if (objectFactory != null) {
            for (Method method : instance.getClass().getMethods()) {
                if (method.getName().startsWith("set")
                        && method.getParameterTypes().length == 1
                        && Modifier.isPublic(method.getModifiers())) {
                    //如果存在 DisableInject 註解則跳過
                    if (method.getAnnotation(DisableInject.class) != null) {
                        continue;
                    }
                    //獲取 method 第一個參數的類型
                    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);
    }
    return instance;
}

簡單的總結下這個方法做了什麼:遍歷當前實例的 set 方法,以 set 方法第四位開始至末尾的字符串爲關鍵字,嘗試通過 objectFactory 來獲取對應的 擴展類實現。如果存在對應擴展類,通過反射注入到當前實例中。這個方法相當於完成了一個簡單的依賴注入功能,我們常說 Dubbo 中的 IOC 實際上也是在這裏體現的。

getAdaptiveExtensionClass()

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

接着看 getAdaptiveExtensionClass() 方法。首先調用 getExtensionClasses() 方法,如果 cachedAdaptiveClass() 不爲 null 則返回,如果爲 null 則調用 createAdaptiveExtensionClass() 方法。依次看下這兩個方法。

getExtensionClasses()

private Map<String, Class<?>> getExtensionClasses() {
    Map<String, Class<?>> classes = cachedClasses.get();
    if (classes == null) {
        synchronized (cachedClasses) {
            classes = cachedClasses.get();
            if (classes == null) {
                classes = loadExtensionClasses();
                cachedClasses.set(classes);
            }
        }
    }
    return classes;
}

內容比較簡單,我們直接看 loadExtensionClasses() 方法。

private Map<String, Class<?>> loadExtensionClasses() {
    final SPI defaultAnnotation = type.getAnnotation(SPI.class);
    if (defaultAnnotation != null) {
        String value = defaultAnnotation.value();
        if ((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));
            }
            if (names.length == 1) {
                cachedDefaultName = names[0];
            }
        }
    }

    Map<String, Class<?>> extensionClasses = new HashMap<String, Class<?>>();
    loadDirectory(extensionClasses, DUBBO_INTERNAL_DIRECTORY, type.getName());
    loadDirectory(extensionClasses, DUBBO_INTERNAL_DIRECTORY, type.getName().replace("org.apache", "com.alibaba"));
    loadDirectory(extensionClasses, DUBBO_DIRECTORY, type.getName());
    loadDirectory(extensionClasses, DUBBO_DIRECTORY, type.getName().replace("org.apache", "com.alibaba"));
    loadDirectory(extensionClasses, SERVICES_DIRECTORY, type.getName());
    loadDirectory(extensionClasses, SERVICES_DIRECTORY, type.getName().replace("org.apache", "com.alibaba"));
    return extensionClasses;
}

繞來繞去這麼久,終於要進入主題了。爲什麼說進入主題了呢?看看這幾個變量的值

  • DUBBO_INTERNAL_DIRECTORY:META-INF/dubbo/internal/
  • DUBBO_DIRECTORY:META-INF/dubbo/
  • SERVICES_DIRECTORY:META-INF/services/

熟悉的配方熟悉的料。。沒錯了,我們馬上就要開始讀取這三個目錄下的文件,然後開始加載我們的擴展點了。

loadDirectory()

private void loadDirectory(Map<String, Class<?>> extensionClasses, String dir, String type) {
    String fileName = dir + type;
    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 resourceURL = urls.nextElement();
                loadResource(extensionClasses, classLoader, resourceURL);
            }
        }
    } catch (Throwable t) {
        // ...
    }
}
private void loadResource(Map<String, Class<?>> extensionClasses, ClassLoader classLoader, java.net.URL resourceURL) {
    try {
        BufferedReader reader = new BufferedReader(new InputStreamReader(resourceURL.openStream(), "utf-8"));
        try {
            String line;
            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;
                        //文件中的內容以 key=value 的形式保存,拆分 key 和 vlaue
                        int i = line.indexOf('=');
                        if (i > 0) {
                            name = line.substring(0, i).trim();
                            line = line.substring(i + 1).trim();
                        }
                        if (line.length() > 0) {
                            loadClass(extensionClasses, resourceURL, Class.forName(line, true, classLoader), name);
                        }
                    } catch (Throwable t) {
                        // ...
                    }
                }
            }
        } finally {
            reader.close();
        }
    } catch (Throwable t) {
        // ...
    }
}
private void loadClass(Map<String, Class<?>> extensionClasses, java.net.URL resourceURL, Class<?> clazz, String name) throws NoSuchMethodException {
    // 用於判斷 class 是不是 type 接口的實現類
    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.");
    }
    // 如果當前 class 被 @Adaptive 註解標記,更新 cachedAdaptiveClass 緩存對象
    if (clazz.isAnnotationPresent(Adaptive.class)) {
        if (cachedAdaptiveClass == null) {
            cachedAdaptiveClass = clazz;
        } else if (!cachedAdaptiveClass.equals(clazz)) {
            // 省略異常
        }
    } else if (isWrapperClass(clazz)) {
    // 這裏涉及到了 Dubbo 擴展點的另一個機制:包裝,在後文介紹
        Set<Class<?>> wrappers = cachedWrapperClasses;
        if (wrappers == null) {
            cachedWrapperClasses = new ConcurrentHashSet<Class<?>>();
            wrappers = cachedWrapperClasses;
        }
        wrappers.add(clazz);
    } else {
        clazz.getConstructor();
        // 如果 name 爲空,調用 findAnnotationName() 方法。如果當前類有 @Extension 註解,直接返回 @Extension 註解value;
        // 若沒有 @Extension 註解,但是類名類似 xxxType(Type 代表 type 的類名),返回值爲小寫的 xxx
        if (name == null || name.length() == 0) {
            name = findAnnotationName(clazz);
            if (name.length() == 0) {
                throw new IllegalStateException("No such extension name for the class " + clazz.getName() + " in the config " + resourceURL);
            }
        }
        String[] names = NAME_SEPARATOR.split(name);
        if (names != null && names.length > 0) {
            // @Activate 註解用於配置擴展被自動激活條件
            // 如果當前 class 包含 @Activate ,加入到緩存中
            Activate activate = clazz.getAnnotation(Activate.class);
            if (activate != null) {
                cachedActivates.put(names[0], activate);
            } else {
                // support com.alibaba.dubbo.common.extension.Activate
                com.alibaba.dubbo.common.extension.Activate oldActivate = clazz.getAnnotation(com.alibaba.dubbo.common.extension.Activate.class);
                if (oldActivate != null) {
                    cachedActivates.put(names[0], oldActivate);
                }
            }
            for (String n : names) {
                if (!cachedNames.containsKey(clazz)) {
                    cachedNames.put(clazz, n);
                }
                Class<?> c = extensionClasses.get(n);
                if (c == null) {
                    // 還記得文件內容長啥樣嗎?(name = calssvalue),我們最後將其保存到了 extensionClasses 集合中
                    extensionClasses.put(n, clazz);
                } else if (c != clazz) {
                    // ...
                }
            }
        }
    }
}

這一段代碼真的相當長啊。。梳理下之後發現其實他做的事情也很簡單:

  1. 拼接生成文件名:dir + type,讀取該文件
  2. 讀取文件內容,將文件內容拆分爲 name 和 class 字符串
  3. 如果 clazz 類中包含 @Adaptive 註解,將其加入到 cachedAdaptiveClass 緩存中
    如果 clazz 類中爲包裝類,添加到 wrappers 中
    如果文件不爲 key=class 形式,會嘗試通過 @Extension 註解獲取 name
    如果 clazz 包含 @Activate 註解(兼容 com.alibaba.dubbo.common.extension.Activate 註解),將其添加到 cachedActivates 緩存中
  4. 最後以 name 爲 key ,clazz 爲 vlaue,將其添加到 extensionClasses 集合中並返回

獲取自適應擴展類

getAdaptiveExtensionClass()

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

Ok,我們已經分析了 getExtensionClasses 方法,並且已經將擴展點實現加載到了緩存中。這個方法是由 getAdaptiveExtensionClass() 方法引出來的,它看起來是像是創建自適應擴展類的。這裏會先判斷緩存對象 cachedAdaptiveClass 是否會空,cachedAdaptiveClass 是什麼時候被初始化的呢?回顧一下之前的代碼:

private void loadClass(Map<String, Class<?>> extensionClasses, java.net.URL resourceURL, Class<?> clazz, String name) throws NoSuchMethodException {
    // 省略...
    if (clazz.isAnnotationPresent(Adaptive.class)) {
        if (cachedAdaptiveClass == null) {
            cachedAdaptiveClass = clazz;
        } else if (!cachedAdaptiveClass.equals(clazz)) {
            // 省略...
        }
    }
}

在 loadClass() 方法中如果發現當前 clazz 包含 @Adaptive 註解,則將當前 clazz 作爲緩存自適應類保存。例如在 AdaptiveExtensionFactory 類中就有這麼用,我們會將 AdaptiveExtensionFactory 類作爲 ExtensionFactory 類型的自適應類緩存起來。

@Adaptive
public class AdaptiveExtensionFactory implements ExtensionFactory

我們繼續分析該方法的後部分。如果 cachedAdaptiveClass 爲 null,則會調用 createAdaptiveExtensionClass() 方法動態生成一個自適應擴展類。

private Class<?> createAdaptiveExtensionClass() {
    String code = createAdaptiveExtensionClassCode();
    System.out.println(code);
    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);
}

這一段代碼在本次分享中不打算重點敘述,可以簡單的理解爲 dubbo 幫我生成了一個自適應類。我摘取了生成的一段代碼,如下所示:

package org.apache.dubbo.rpc;
import org.apache.dubbo.common.extension.ExtensionLoader;
public class ProxyFactory$Adaptive implements org.apache.dubbo.rpc.ProxyFactory {
    private static final org.apache.dubbo.common.logger.Logger logger = org.apache.dubbo.common.logger.LoggerFactory.getLogger(ExtensionLoader.class);
    private java.util.concurrent.atomic.AtomicInteger count = new java.util.concurrent.atomic.AtomicInteger(0);

    public org.apache.dubbo.rpc.Invoker getInvoker(java.lang.Object arg0, java.lang.Class arg1, org.apache.dubbo.common.URL arg2) throws org.apache.dubbo.rpc.RpcException {
        if (arg2 == null) throw new IllegalArgumentException("url == null");
        org.apache.dubbo.common.URL url = arg2;
        String extName = url.getParameter("proxy", "javassist");
        if(extName == null) throw new IllegalStateException("Fail to get extension(org.apache.dubbo.rpc.ProxyFactory) name from url(" + url.toString() + ") use keys([proxy])");
        org.apache.dubbo.rpc.ProxyFactory extension = null;
        try {
            extension = (org.apache.dubbo.rpc.ProxyFactory)ExtensionLoader.getExtensionLoader(org.apache.dubbo.rpc.ProxyFactory.class).getExtension(extName);
        }catch(Exception e){
            if (count.incrementAndGet() == 1) {
                logger.warn("Failed to find extension named " + extName + " for type org.apache.dubbo.rpc.ProxyFactory, will use default extension javassist instead.", e);
            }
            extension = (org.apache.dubbo.rpc.ProxyFactory)ExtensionLoader.getExtensionLoader(org.apache.dubbo.rpc.ProxyFactory.class).getExtension("javassist");
        }
        return extension.getInvoker(arg0, arg1, arg2);
    }
    public java.lang.Object getProxy(org.apache.dubbo.rpc.Invoker arg0, boolean arg1) throws org.apache.dubbo.rpc.RpcException {
        if (arg0 == null) throw new IllegalArgumentException("org.apache.dubbo.rpc.Invoker argument == null");
        if (arg0.getUrl() == null) throw new IllegalArgumentException("org.apache.dubbo.rpc.Invoker argument getUrl() == null");org.apache.dubbo.common.URL url = arg0.getUrl();
        String extName = url.getParameter("proxy", "javassist");
        if(extName == null) throw new IllegalStateException("Fail to get extension(org.apache.dubbo.rpc.ProxyFactory) name from url(" + url.toString() + ") use keys([proxy])");
        org.apache.dubbo.rpc.ProxyFactory extension = null;
        try {
            extension = (org.apache.dubbo.rpc.ProxyFactory)ExtensionLoader.getExtensionLoader(org.apache.dubbo.rpc.ProxyFactory.class).getExtension(extName);
        }catch(Exception e){
            if (count.incrementAndGet() == 1) {
                logger.warn("Failed to find extension named " + extName + " for type org.apache.dubbo.rpc.ProxyFactory, will use default extension javassist instead.", e);
            }
            extension = (org.apache.dubbo.rpc.ProxyFactory)ExtensionLoader.getExtensionLoader(org.apache.dubbo.rpc.ProxyFactory.class).getExtension("javassist");
        }
        return extension.getProxy(arg0, arg1);
    }
    public java.lang.Object getProxy(org.apache.dubbo.rpc.Invoker arg0) throws org.apache.dubbo.rpc.RpcException {
        if (arg0 == null) throw new IllegalArgumentException("org.apache.dubbo.rpc.Invoker argument == null");
        if (arg0.getUrl() == null) throw new IllegalArgumentException("org.apache.dubbo.rpc.Invoker argument getUrl() == null");org.apache.dubbo.common.URL url = arg0.getUrl();
        String extName = url.getParameter("proxy", "javassist");
        if(extName == null) throw new IllegalStateException("Fail to get extension(org.apache.dubbo.rpc.ProxyFactory) name from url(" + url.toString() + ") use keys([proxy])");
        org.apache.dubbo.rpc.ProxyFactory extension = null;
        try {
            extension = (org.apache.dubbo.rpc.ProxyFactory)ExtensionLoader.getExtensionLoader(org.apache.dubbo.rpc.ProxyFactory.class).getExtension(extName);
        }catch(Exception e){
            if (count.incrementAndGet() == 1) {
                logger.warn("Failed to find extension named " + extName + " for type org.apache.dubbo.rpc.ProxyFactory, will use default extension javassist instead.", e);
            }
            extension = (org.apache.dubbo.rpc.ProxyFactory)ExtensionLoader.getExtensionLoader(org.apache.dubbo.rpc.ProxyFactory.class).getExtension("javassist");
        }
        return extension.getProxy(arg0);
    }
}
extension = (org.apache.dubbo.rpc.ProxyFactory)ExtensionLoader.getExtensionLoader(org.apache.dubbo.rpc.ProxyFactory.class).getExtension(extName);

這一段代碼實際上纔是自適應適配類的精髓,看看 extName 是怎麼來的?

String extName = url.getParameter("proxy", "javassist");

extName 又是從 url 中取得的,實際上 url 對於 Dubbo 來說是一種非常重要的上下文傳輸載體,在後續系列文章中大家會逐步感受到。

public T getExtension(String name) {
    if (name == null || name.length() == 0) {
        throw new IllegalArgumentException("Extension name == null");
    }
    if ("true".equals(name)) {
        return getDefaultExtension();
    }
    // 從緩存中讀取擴展實現類
    Holder<Object> holder = cachedInstances.get(name);
    if (holder == null) {
        cachedInstances.putIfAbsent(name, new Holder<Object>());
        holder = cachedInstances.get(name);
    }
    Object instance = holder.get();
    if (instance == null) {
        synchronized (holder) {
            instance = holder.get();
            if (instance == null) {
                instance = createExtension(name);
                holder.set(instance);
            }
        }
    }
    return (T) instance;
}

上面的邏輯比較簡單,這裏也不贅述了,直接看 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, clazz.newInstance());
            instance = (T) EXTENSION_INSTANCES.get(clazz);
        }
        injectExtension(instance);
        Set<Class<?>> wrapperClasses = cachedWrapperClasses;
        if (wrapperClasses != null && !wrapperClasses.isEmpty()) {
            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);
    }
}

getExtensionClasses() 方法在前文已經分析過了,但是需要注意的是:getExtensionClasses 返回給我們的不過是使用 Class.forName() 加載過的類而已,充其量執行了裏面的靜態代碼段,而並非得到了真正的實例。真正的實例對象仍需要調用 class.newInstance() 方法才能獲取。
瞭解了這些之後我們繼續看,我們通過 getExtensionClasses() 嘗試獲取系統已經加載的 class 對象,通過 class 對象再去擴展實例緩存中取。如果擴展實例爲 null,調用 newInstance() 方法初始化實例,並放到 EXTENSION_INSTANCES 緩存中。之後再調用 injectExtension() 方法進行依賴注入。最後一段涉及到包裝類的用法,下一個章節進行介紹。

擴展類的包裝

在 createExtension() 方法中有如下一段代碼:

private T createExtension(String name) {
    // ···省略···
    Set<Class<?>> wrapperClasses = cachedWrapperClasses;
    if (wrapperClasses != null && !wrapperClasses.isEmpty()) {
        for (Class<?> wrapperClass : wrapperClasses) {
            instance = injectExtension((T) wrapperClass.getConstructor(type).newInstance(instance));
        }
    }
    return instance;
    // ···省略···
}

還記得 wrapperClasses 在什麼地方被初始化的嗎?在前文中的 loadClass() 方法中我們已經有介紹過。再回顧一下:

private void loadClass(Map<String, Class<?>> extensionClasses, java.net.URL resourceURL, Class<?> clazz, String name) throws NoSuchMethodException {
    // ···省略···
    if (isWrapperClass(clazz)) {
        Set<Class<?>> wrappers = cachedWrapperClasses;
        if (wrappers == null) {
            cachedWrapperClasses = new ConcurrentHashSet<Class<?>>();
            wrappers = cachedWrapperClasses;
        }
        wrappers.add(clazz);
    }
    // ···省略···
}

private boolean isWrapperClass(Class<?> clazz) {
    try {
        clazz.getConstructor(type);
        return true;
    } catch (NoSuchMethodException e) {
        return false;
    }
}

在看這個方法前我們先了解下 Dubbo 中 wrapper 類的定義。
舉個例子:

class A {
    private A a;
    public A(A a){
        this.a = a;
    }
}

我們可以看到 A 類有一個以 A 爲參數的構造方法,我們稱它爲複製構造方法。有這樣構造方法的類在 Dubbo 中我們稱它爲 Wrapper 類。
繼續看 isWrapperClass() 方法,這個方法比較簡單,嘗試獲取 clazz 中以 type 爲參數的構造方法,如果可以獲取到,則認爲 clazz 則是當前 type 類的包裝類。再結合上面的代碼,我們會發現在加載擴展點時,我們將對應 type 的包裝類緩存起來。

private T createExtension(String name) {
    // ···省略···
    T instance = (T) EXTENSION_INSTANCES.get(clazz);
    if (instance == null) {
        EXTENSION_INSTANCES.putIfAbsent(clazz, clazz.newInstance());
        instance = (T) EXTENSION_INSTANCES.get(clazz);
    }
    injectExtension(instance);
    Set<Class<?>> wrapperClasses = cachedWrapperClasses;
    if (wrapperClasses != null && !wrapperClasses.isEmpty()) {
        for (Class<?> wrapperClass : wrapperClasses) {
            instance = injectExtension((T) wrapperClass.getConstructor(type).newInstance(instance));
        }
    }
    return instance;
    // ···省略···
}

爲了更好的理解這段代碼,我們假設當前 type 值爲 Protocol.class ,我們可以在 org.apache.dubbo.rpc.Protocol 文件中找到 Protocol 接口的包裝類 ProtocolFilterWrapper 和 ProtocolListenerWrapper,他們會依次被添加到 cachedWrapperClasses 集合中。依次遍歷 cachedWrapperClasses 集合,比如第一次取到的是 ProtocolFilterWrapper 類,則會以調用 ProtocolFilterWrapper 的複製構造方法將 instance 包裝起來。創建完 ProtocolFilterWrapper 對象實例後,調用 injectExtension() 進行依賴注入。此時 instance 已經爲 ProtocolFilterWrapper 的實例,繼續循環,會將 ProtocolFilterWrapper 類包裝在 ProtocolListenerWrapper 類中。因此我們最後返回的是一個 ProtocolListenerWrapper 實例。最後調用時,仍會通過一層一層的調用,最後調用原始 instance 的方法。
這裏的包裝類有點類似 AOP 思想,我們可以通過一層一層的包裝,在調用擴展實現之前添加一些日誌打印、監控等自定義的操作。

Dubbo 中的 IOC 機制

上文中我們已經討論過 Dubbo 中利用反射機制實現一個類 IOC 功能。在這一章節中,我們再回顧一下 injectExtension() 方法,仔細的來看看 Dubbo 中 IOC 功能的實現。

createExtension()
instance = injectExtension((T) wrapperClass.getConstructor(type).newInstance(instance));

private T injectExtension(T instance) {
    // ···
    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);
        }
    }
    // ···
}

public class StubProxyFactoryWrapper implements ProxyFactory {
    // ...
    private Protocol protocol;
    public void setProtocol(Protocol protocol) {
        this.protocol = protocol;
    }
    //...
}

在上一章節中我們已經講過 wrapper 類,在這裏我們舉個例子說明一下。比如我們當前的 wrapperClass 類爲 StubProxyFactoryWrapper,那麼代碼執行邏輯大致如下所示:

  1. 創建 StubProxyFactoryWrapper 實例;
  2. 獲取流程1創建的實例作爲 injectExtension() 的參數,執行;
  3. injectExtension() 方法循環遍歷到 StubProxyFactoryWrapper 的 setProtocol()方法(此時 pt=Protocol.class,property=protocol),執行 objectFactory.getExtension(pt,property) 方法。objectFactory 在 ExtensionLoader 的構造方法中被初始化,在這裏獲取到自適應擴展類爲 AdaptiveExtensionFactory。
  4. 執行 AdaptiveExtensionFactory.getExtension()。AdaptiveExtensionFactory 類中有一個集合變量 factories。factories 在 AdaptiveExtensionFactory 的構造方法中被初始化,包含了兩個工廠類:SpiExtensionFactory、SpringExtensionFactory。執行 AdaptiveExtensionFactory 類的 getExtension() 方法會依次調用 SpiExtensionFactory 和 SpringExtensionFactory 類的 getExtension() 方法。
  5. 執行 SpiExtensionFactory 的 getExtension() 方法。上面有說到此時的 type=Procotol.class,property=protocol,從下面的代碼我們可以發現 Protocol 是一個接口類,同時標註了 @SPI 註解,此時會獲取 Protocol 類型的 ExtensionLoader 對象,最後又去調用 loader 的 getAdaptiveExtension() 方法。最終獲取到的自適應類爲 Protocol$Adaptive 動態類。
  6. objectFactory.getExtension(pt, property); 最後得到的類爲 Protocol$Adaptive 類,最後利用反射機制將其注入到 StubProxyFactoryWrapper 實例中。
@SPI("dubbo")
public interface Protocol {
}
public class SpiExtensionFactory implements ExtensionFactory {
    @Override
    public <T> T getExtension(Class<T> type, String name) {
        if (type.isInterface() && type.isAnnotationPresent(SPI.class)) {
            ExtensionLoader<T> loader = ExtensionLoader.getExtensionLoader(type);
            if (!loader.getSupportedExtensions().isEmpty()) {
                return loader.getAdaptiveExtension();
            }
        }
        return null;
    }
}

END

在最後,我們再回顧下開頭關於 Dubbo SPI 基於 JAVA SPI 改進的那段話:

Dubbo 的擴展點加載從 JDK 標準的 SPI (Service Provider Interface) 擴展點發現機制加強而來。
JDK 標準的 SPI 會一次性實例化擴展點所有實現,如果有擴展實現初始化很耗時,但如果沒用上也加載,會很浪費資源。
如果擴展點加載失敗,連擴展點的名稱都拿不到了。比如:JDK 標準的 ScriptEngine,通過 getName() 獲取腳本類型的名稱,但如果 RubyScriptEngine 因爲所依賴的 jruby.jar 不存在,導致 RubyScriptEngine 類加載失敗,這個失敗原因被吃掉了,和 ruby 對應不起來,當用戶執行 ruby 腳本時,會報不支持 ruby,而不是真正失敗的原因。增加了對擴展點 IoC 和 AOP 的支持,一個擴展點可以直接 setter 注入其它擴展點

總結如下:

  1. Dubbo SPI 在加載擴展點,會以 key-value 的形式將擴展類保存在緩存中,但此時的擴展類只是調用 Class.forName() 加載的類,並沒有實例化。擴展類會在調用 getExtension() 方法時被實例化。
  2. Dubbo 通過工廠模式和反射機制實現了依賴注入功能。
  3. Dubbo 中通過包裝類實現了 AOP 機制,方便我們添加監控和打印日誌。
本BLOG上原創文章未經本人許可,不得用於商業用途及傳統媒體。網絡媒體轉載請註明出處,否則屬於侵權行爲。https://juejin.im/post/5c0cd7...
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章