聊聊Dubbo - Dubbo可擴展機制源碼解析

摘要: 在Dubbo可擴展機制實戰中,咱們瞭解了Dubbo擴展機制的一些概念,初探了Dubbo中LoadBalance的實現,並本身實現了一個LoadBalance。是否是以爲Dubbo的擴展機制很不錯呀,接下來,咱們就深刻Dubbo的源碼,一睹廬山真面目。java

Dubbo可擴展機制實戰中,咱們瞭解了Dubbo擴展機制的一些概念,初探了Dubbo中LoadBalance的實現,並本身實現了一個LoadBalance。是否是以爲Dubbo的擴展機制很不錯呀,接下來,咱們就深刻Dubbo的源碼,一睹廬山真面目。api

ExtensionLoader

ExtentionLoader是最核心的類,負責擴展點的加載和生命週期管理。咱們就以這個類開始吧。
Extension的方法比較多,比較經常使用的方法有:緩存

  • public static <T> ExtensionLoader<T> getExtensionLoader(Class<T> type)
  • public T getExtension(String name)
  • public T getAdaptiveExtension()

比較常見的用法有:app

  • LoadBalance lb = ExtensionLoader.getExtensionLoader(LoadBalance.class).getExtension(loadbalanceName)
  • RouterFactory routerFactory = ExtensionLoader.getExtensionLoader(RouterFactory.class).getAdaptiveExtension()

說明:在接下來展現的源碼中,我會將無關的代碼(好比日誌,異常捕獲等)去掉,方便你們閱讀和理解。框架

*1. getExtensionLoader方法
這是一個靜態工廠方法,入參是一個可擴展的接口,返回一個該接口的ExtensionLoader實體類。經過這個實體類,能夠根據name得到具體的擴展,也能夠得到一個自適應擴展。dom

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方法jvm

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文件夾下讀取擴展點配置文件。ui

*2. 使用反射建立一個擴展類的實例this

*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這兩個方法有具體的實現,其他方法都是拋出異常。

原文連接

本文爲雲棲社區原創內容,未經容許不得轉載。

相關文章
相關標籤/搜索