聊聊Dubbo – Dubbo可擴充套件機制原始碼解析

貓耳發表於2019-01-19

摘要: 在Dubbo可擴充套件機制實戰中,我們瞭解了Dubbo擴充套件機制的一些概念,初探了Dubbo中LoadBalance的實現,並自己實現了一個LoadBalance。是不是覺得Dubbo的擴充套件機制很不錯呀,接下來,我們就深入Dubbo的原始碼,一睹廬山真面目。

在Dubbo可擴充套件機制實戰中,我們瞭解了Dubbo擴充套件機制的一些概念,初探了Dubbo中LoadBalance的實現,並自己實現了一個LoadBalance。是不是覺得Dubbo的擴充套件機制很不錯呀,接下來,我們就深入Dubbo的原始碼,一睹廬山真面目。

ExtensionLoader
ExtentionLoader是最核心的類,負責擴充套件點的載入和生命週期管理。我們就以這個類開始吧。
Extension的方法比較多,比較常用的方法有:

public static <T> ExtensionLoader<T> getExtensionLoader(Class<T> type)
public T getExtension(String name)
public T getAdaptiveExtension()
比較常見的用法有:

LoadBalance lb = ExtensionLoader.getExtensionLoader(LoadBalance.class).getExtension(loadbalanceName)
RouterFactory routerFactory = ExtensionLoader.getExtensionLoader(RouterFactory.class).getAdaptiveExtension()
說明:在接下來展示的原始碼中,我會將無關的程式碼(比如日誌,異常捕獲等)去掉,方便大家閱讀和理解。

*1. getExtensionLoader方法
這是一個靜態工廠方法,入參是一個可擴充套件的介面,返回一個該介面的ExtensionLoader實體類。通過這個實體類,可以根據name獲得具體的擴充套件,也可以獲得一個自適應擴充套件。

public static <T> ExtensionLoader<T> getExtensionLoader(Class<T> type) {

    // 擴充套件點必須是介面
    if (!type.isInterface()) {
        throw new IllegalArgumentException("Extension type(" + type + ") is not interface!");
    }
    // 必須要有@SPI註解
    if (!withExtensionAnnotation(type)) {
        throw new IllegalArgumentException("Extension type without @SPI Annotation!");
    }
    // 從快取中根據介面獲取對應的ExtensionLoader
    // 每個擴充套件只會被載入一次
    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());
}

*2. getExtension方法

public T getExtension(String name) {

    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;
}

getExtention方法中做了一些判斷和快取,主要的邏輯在createExtension方法中。我們繼續看createExtention方法。

private T createExtension(String name) {

    // 根據擴充套件點名稱得到擴充套件類,比如對於LoadBalance,根據random得到RandomLoadBalance類
    Class<?> clazz = getExtensionClasses().get(name);
    
    T instance = (T) EXTENSION_INSTANCES.get(clazz);
    if (instance == null) {
          // 使用反射呼叫nesInstance來建立擴充套件類的一個示例
        EXTENSION_INSTANCES.putIfAbsent(clazz, (T) clazz.newInstance());
        instance = (T) EXTENSION_INSTANCES.get(clazz);
    }
    // 對擴充套件類示例進行依賴注入
    injectExtension(instance);
    // 如果有wrapper,新增wrapper
    Set<Class<?>> wrapperClasses = cachedWrapperClasses;
    if (wrapperClasses != null && !wrapperClasses.isEmpty()) {
        for (Class<?> wrapperClass : wrapperClasses) {
            instance = injectExtension((T) wrapperClass.getConstructor(type).newInstance(instance));
        }
    }
    return instance;

}

createExtension方法做了以下事情:
*1. 先根據name來得到對應的擴充套件類。從ClassPath下META-INF資料夾下讀取擴充套件點配置檔案。

*2. 使用反射建立一個擴充套件類的例項

*3. 對擴充套件類例項的屬性進行依賴注入,即IoC。

*4. 如果有wrapper,新增wrapper,即AoP。

下面我們來重點看下這4個過程
*1. 根據name獲取對應的擴充套件類
先看程式碼:

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;
}

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

    Map<String, Class<?>> extensionClasses = new HashMap<String, Class<?>>();
    loadFile(extensionClasses, DUBBO_INTERNAL_DIRECTORY);
    loadFile(extensionClasses, DUBBO_DIRECTORY);
    loadFile(extensionClasses, SERVICES_DIRECTORY);
    return extensionClasses;
}

過程很簡單,先從快取中獲取,如果沒有,就從配置檔案中載入。配置檔案的路徑就是之前提到的:

META-INF/dubbo/internal
META-INF/dubbo
META-INF/services
*2. 使用反射建立擴充套件例項
這個過程很簡單,使用clazz.newInstance())來完成。建立的擴充套件例項的屬性都是空值。

*3. 擴充套件例項自動裝配
在實際的場景中,類之間都是有依賴的。擴充套件例項中也會引用一些依賴,比如簡單的Java類,另一個Dubbo的擴充套件或一個Spring Bean等。依賴的情況很複雜,Dubbo的處理也相對複雜些。我們稍後會有專門的章節對其進行說明,現在,我們只需要知道,Dubbo可以正確的注入擴充套件點中的普通依賴,Dubbo擴充套件依賴或Spring依賴等。

*4. 擴充套件例項自動包裝
自動包裝就是要實現類似於Spring的AOP功能。Dubbo利用它在內部實現一些通用的功能,比如日誌,監控等。關於擴充套件例項自動包裝的內容,也會在後面單獨講解。

經過上面的4步,Dubbo就建立並初始化了一個擴充套件例項。這個例項的依賴被注入了,也根據需要被包裝了。到此為止,這個擴充套件例項就可以被使用了。

Dubbo SPI高階用法之自動裝配
自動裝配的相關程式碼在injectExtension方法中:

private T injectExtension(T instance) {

for (Method method : instance.getClass().getMethods()) {
    if (method.getName().startsWith("set")
            && method.getParameterTypes().length == 1
            && Modifier.isPublic(method.getModifiers())) {
        Class<?> pt = method.getParameterTypes()[0];
      
        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);
        }
    }
}
return instance;

}

要實現對擴充套件例項的依賴的自動裝配,首先需要知道有哪些依賴,這些依賴的型別是什麼。Dubbo的方案是查詢Java標準的setter方法。即方法名以set開始,只有一個引數。如果擴充套件類中有這樣的set方法,Dubbo會對其進行依賴注入,類似於Spring的set方法注入。
但是Dubbo中的依賴注入比Spring要複雜,因為Spring注入的都是Spring bean,都是由Spring容器來管理的。而Dubbo的依賴注入中,需要注入的可能是另一個Dubbo的擴充套件,也可能是一個Spring Bean,或是Google guice的元件,或其他任何一個框架中的元件。Dubbo需要能夠從任何一個場景中載入擴充套件。在injectExtension方法中,是用Object object = objectFactory.getExtension(pt, property)來實現的。objectFactory是ExtensionFactory型別的,在建立ExtensionLoader時被初始化:

private ExtensionLoader(Class<?> type) {

    this.type = type;
    objectFactory = (type == ExtensionFactory.class ? null : ExtensionLoader.getExtensionLoader(ExtensionFactory.class).getAdaptiveExtension());
}

objectFacory本身也是一個擴充套件,通過ExtensionLoader.getExtensionLoader(ExtensionFactory.class).getAdaptiveExtension())來獲取。

ExtensionLoader有三個實現:
*1. SpiExtensionLoader:Dubbo自己的Spi去載入Extension

*2. SpringExtensionLoader:從Spring容器中去載入Extension

*3. AdaptiveExtensionLoader: 自適應的AdaptiveExtensionLoader

這裡要注意AdaptiveExtensionLoader,原始碼如下:

@Adaptive
public class AdaptiveExtensionFactory implements ExtensionFactory {

private final List<ExtensionFactory> factories;

public AdaptiveExtensionFactory() {
    ExtensionLoader<ExtensionFactory> loader = ExtensionLoader.getExtensionLoader(ExtensionFactory.class);
    List<ExtensionFactory> list = new ArrayList<ExtensionFactory>();
    for (String name : loader.getSupportedExtensions()) {
        list.add(loader.getExtension(name));
    }
    factories = Collections.unmodifiableList(list);
}

public <T> T getExtension(Class<T> type, String name) {
    for (ExtensionFactory factory : factories) {
        T extension = factory.getExtension(type, name);
        if (extension != null) {
            return extension;
        }
    }
    return null;
}

}

AdaptiveExtensionLoader類有@Adaptive註解。前面提到了,Dubbo會為每一個擴充套件建立一個自適應例項。如果擴充套件類上有@Adaptive,會使用該類作為自適應類。如果沒有,Dubbo會為我們建立一個。所以ExtensionLoader.getExtensionLoader(ExtensionFactory.class).getAdaptiveExtension())會返回一個AdaptiveExtensionLoader例項,作為自適應擴充套件例項。
AdaptiveExtentionLoader會遍歷所有的ExtensionFactory實現,嘗試著去載入擴充套件。如果找到了,返回。如果沒有,在下一個ExtensionFactory中繼續找。Dubbo內建了兩個ExtensionFactory,分別從Dubbo自身的擴充套件機制和Spring容器中去尋找。由於ExtensionFactory本身也是一個擴充套件點,我們可以實現自己的ExtensionFactory,讓Dubbo的自動裝配支援我們自定義的元件。比如,我們在專案中使用了Google的guice這個IoC容器。我們可以實現自己的GuiceExtensionFactory,讓Dubbo支援從guice容器中載入擴充套件。

Dubbo SPI高階用法之AoP
在用Spring的時候,我們經常會用到AOP功能。在目標類的方法前後插入其他邏輯。比如通常使用Spring AOP來實現日誌,監控和鑑權等功能。
Dubbo的擴充套件機制,是否也支援類似的功能呢?答案是yes。在Dubbo中,有一種特殊的類,被稱為Wrapper類。通過裝飾者模式,使用包裝類包裝原始的擴充套件點例項。在原始擴充套件點實現前後插入其他邏輯,實現AOP功能。

什麼是Wrapper類
那什麼樣類的才是Dubbo擴充套件機制中的Wrapper類呢?Wrapper類是一個有複製建構函式的類,也是典型的裝飾者模式。下面就是一個Wrapper類:

class A{

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

}

類A有一個建構函式public A(A a),建構函式的引數是A本身。這樣的類就可以成為Dubbo擴充套件機制中的一個Wrapper類。Dubbo中這樣的Wrapper類有ProtocolFilterWrapper, ProtocolListenerWrapper等, 大家可以檢視原始碼加深理解。

怎麼配置Wrapper類
在Dubbo中Wrapper類也是一個擴充套件點,和其他的擴充套件點一樣,也是在META-INF資料夾中配置的。比如前面舉例的ProtocolFilterWrapper和ProtocolListenerWrapper就是在路徑dubbo-rpc/dubbo-rpc-api/src/main/resources/META-INF/dubbo/internal/com.alibaba.dubbo.rpc.Protocol中配置的:

filter=com.alibaba.dubbo.rpc.protocol.ProtocolFilterWrapper
listener=com.alibaba.dubbo.rpc.protocol.ProtocolListenerWrapper
mock=com.alibaba.dubbo.rpc.support.MockProtocol

在Dubbo載入擴充套件配置檔案時,有一段如下的程式碼:

try {
clazz.getConstructor(type);
Set<Class<?>> wrappers = cachedWrapperClasses;
if (wrappers == null) {

cachedWrapperClasses = new ConcurrentHashSet<Class<?>>();
wrappers = cachedWrapperClasses;

}
wrappers.add(clazz);
} catch (NoSuchMethodException e) {}

這段程式碼的意思是,如果擴充套件類有複製建構函式,就把該類存起來,供以後使用。有複製建構函式的類就是Wrapper類。通過clazz.getConstructor(type)來獲取引數是擴充套件點介面的建構函式。注意建構函式的引數型別是擴充套件點介面,而不是擴充套件類。
以Protocol為例。配置檔案dubbo-rpc/dubbo-rpc-api/src/main/resources/META-INF/dubbo/internal/com.alibaba.dubbo.rpc.Protocol中定義了filter=com.alibaba.dubbo.rpc.protocol.ProtocolFilterWrapper。
ProtocolFilterWrapper程式碼如下:

public class ProtocolFilterWrapper implements Protocol {

private final Protocol protocol;

// 有一個引數是Protocol的複製建構函式
public ProtocolFilterWrapper(Protocol protocol) {
    if (protocol == null) {
        throw new IllegalArgumentException("protocol == null");
    }
    this.protocol = protocol;
}

ProtocolFilterWrapper有一個建構函式public ProtocolFilterWrapper(Protocol protocol),引數是擴充套件點Protocol,所以它是一個Dubbo擴充套件機制中的Wrapper類。ExtensionLoader會把它快取起來,供以後建立Extension例項的時候,使用這些包裝類依次包裝原始擴充套件點。

擴充套件點自適應
前面講到過,Dubbo需要在執行時根據方法引數來決定該使用哪個擴充套件,所以有了擴充套件點自適應例項。其實是一個擴充套件點的代理,將擴充套件的選擇從Dubbo啟動時,延遲到RPC呼叫時。Dubbo中每一個擴充套件點都有一個自適應類,如果沒有顯式提供,Dubbo會自動為我們建立一個,預設使用Javaassist。
先來看下建立自適應擴充套件類的程式碼:

public T getAdaptiveExtension() {

Object instance = cachedAdaptiveInstance.get();
if (instance == null) {
        synchronized (cachedAdaptiveInstance) {
            instance = cachedAdaptiveInstance.get();
            if (instance == null) {
                  instance = createAdaptiveExtension();
                  cachedAdaptiveInstance.set(instance); 
            }
        }        
}

return (T) instance;

}

繼續看createAdaptiveExtension方法

private T createAdaptiveExtension() {

return injectExtension((T) getAdaptiveExtensionClass().newInstance());

}

繼續看getAdaptiveExtensionClass方法

private Class<?> getAdaptiveExtensionClass() {

    getExtensionClasses();
    if (cachedAdaptiveClass != null) {
        return cachedAdaptiveClass;
    }
    return cachedAdaptiveClass = createAdaptiveExtensionClass();
}

繼續看createAdaptiveExtensionClass方法,繞了一大圈,終於來到了具體的實現了。看這個createAdaptiveExtensionClass方法,它首先會生成自適應類的Java原始碼,然後再將原始碼編譯成Java的位元組碼,載入到JVM中。

private Class<?> createAdaptiveExtensionClass() {

    String code = createAdaptiveExtensionClassCode();
    ClassLoader classLoader = findClassLoader();
    com.alibaba.dubbo.common.compiler.Compiler compiler = ExtensionLoader.getExtensionLoader(com.alibaba.dubbo.common.compiler.Compiler.class).getAdaptiveExtension();
    return compiler.compile(code, classLoader);
}

Compiler的程式碼,預設實現是javassist。

@SPI(“javassist”)
public interface Compiler {

Class<?> compile(String code, ClassLoader classLoader);

}

createAdaptiveExtensionClassCode()方法中使用一個StringBuilder來構建自適應類的Java原始碼。方法實現比較長,這裡就不貼程式碼了。這種生成位元組碼的方式也挺有意思的,先生成Java原始碼,然後編譯,載入到jvm中。通過這種方式,可以更好的控制生成的Java類。而且這樣也不用care各個位元組碼生成框架的api等。因為xxx.java檔案是Java通用的,也是我們最熟悉的。只是程式碼的可讀性不強,需要一點一點構建xx.java的內容。
下面是使用createAdaptiveExtensionClassCode方法為Protocol建立的自適應類的Java程式碼範例:

package com.alibaba.dubbo.rpc;

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

public class Protocol$Adpative implements com.alibaba.dubbo.rpc.Protocol {

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!");
}

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.RpcException {
    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 com.alibaba.dubbo.rpc.RpcException {
    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);
}

}

大致的邏輯和開始說的一樣,通過url解析出引數,解析的邏輯由@Adaptive的value引數控制,然後再根據得到的擴充套件點名獲取擴充套件點實現,然後進行呼叫。如果大家想知道具體的構建.java程式碼的邏輯,可以看createAdaptiveExtensionClassCode的完整實現。
在生成的Protocol$Adpative中,發現getDefaultPort和destroy方法都是直接丟擲異常的,這是為什麼呢?來看看Protocol的原始碼:

@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介面中有4個方法,但只有export和refer兩個方法使用了@Adaptive註解。Dubbo自動生成的自適應例項,只有@Adaptive修飾的方法才有具體的實現。所以,Protocol$Adpative類中,也只有export和refer這兩個方法有具體的實現,其餘方法都是丟擲異常。

原文連結

相關文章