Dubbo中SPI擴展機制詳解

前面我們了解過了Java的SPI擴展機制,對于Java擴展機制的原理以及優缺點也有了大概的了解,這里繼續深入一下Dubbo的擴展點加載機制。

Dubbo擴展點加載的功能

Dubbo的擴展點加載機制類似于Java的SPI,我們知道Java的SPI在使用的時候,只能通過遍歷來進行實現的查找和實例化,有可能會一次性把所有的實現都實例化,這樣會造成有些不使用的擴展實現也會被實例化,這就會造成一定的資源浪費。有關Dubbo的改進,參照文檔上的說明:

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

關于第一點,通過和Java的SPI對比,就能明白;第二點還未做測試,不太清楚其中的緣由;第三點對于IOC和AOP的支持下面簡單介紹下。

擴展點自動裝配功能(IOC)

就是當加載一個擴展點時,會自動的注入這個擴展點所依賴的其他擴展點,如果描述不清楚的話,可以看下下面的例子:

接口A,實現類A1,A2
接口B,實現類B1,B2

其中實現類A1含有setB()方法,當通過擴展機制加載A的實現的時候,會自動的注入一個B的實現類,但是,此時不是注入B1,也不是注入B2,而是注入一個自適應的B的實現類:B$Adpative,該實現類是動態生成的,能夠根據參數的不同,自動選擇B1或者B2來進行調用。

擴展點自適應

上面我們說,在自動裝配的時候,并不是注入一個真正的實現,而是注入一個自適應的擴展點實現,其實就是動態的生成的代碼,也就是手動拼裝的代碼,這段代碼里會根據SPI上配置的信息來加入對于具體實現的選擇功能。生成的代碼類似于下面的,代碼做了一下精簡,把包都去掉了:

import com.alibaba.dubbo.common.extension.ExtensionLoader;
public class Protocol$Adpative implements Protocol {
  public Invoker refer(Class arg0, URL arg1) throws Class {
    if (arg1 == null) throw new IllegalArgumentException("url == null");

    URL url = arg1;
    String extName = ( url.getProtocol() == null ? "dubbo" : url.getProtocol() );

    if(extName == null) throw new IllegalStateException("Fail to get extension(Protocol) name from url(" + url.toString() + ") use keys([protocol])");
    
    Protocol extension = (Protocol)ExtensionLoader.getExtensionLoader(Protocol.class).getExtension(extName);
    
    return extension.refer(arg0, arg1);
  }
  
  public Exporter export(Invoker arg0) throws Invoker {
    if (arg0 == null) throw new IllegalArgumentException("Invoker argument == null");
    
    if (arg0.getUrl() == null) throw new IllegalArgumentException("Invoker argument getUrl() == null");URL url = arg0.getUrl();
    //這里會根據url中的信息獲取具體的實現類名
    String extName = ( url.getProtocol() == null ? "dubbo" : url.getProtocol() );
    
    if(extName == null) throw new IllegalStateException("Fail to get extension(Protocol) name from url(" + url.toString() + ") use keys([protocol])");
    //根據上面的實現類名,會在運行時,通過Dubbo的擴展機制加載具體實現類
    Protocol extension = (Protocol)ExtensionLoader.getExtensionLoader(Protocol.class).getExtension(extName);
    
    return extension.export(arg0);
  }
  
  public void destroy() {
    throw new UnsupportedOperationException("method public abstract void Protocol.destroy() of interface Protocol is not adaptive method!");
  }
  
  public int getDefaultPort() {
    throw new UnsupportedOperationException("method public abstract int Protocol.getDefaultPort() of interface Protocol is not adaptive method!");
  }
}

使用這種方式的原因也很容易能想到,在我們加載擴展點實現的時候,并沒有調用實現的具體邏輯,那我們注入一個擴展點,也就不知道這個擴展點的實現具體是什么,所以要注入一個自適應的實現。等到運行時候,才根據自適應實現,來調用真正實現。

擴展點自動包裝功能(AOP)

先看下下面的示例,假如接口A還有另外一個實現者:AWrapper1:

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

AWrapper1相當于A的包裝類,類似于AOP的功能,AWrapper1增加了A的功能。當我們獲取接口A的實現類的時候,得到的就是包裝過的類。

Dubbo擴展點加載的實現

首先還是定義接口,然后是接口的具體實現類,配置文件類似于Java的SPI配置文件,Dubbo的配置文件放在META-INF/dubbo/目錄下,配置文件名為接口的全限定名,配置文件內容是配置名=擴展實現類的全限定名,加載實現類的功能是通過ExtensionLoader來實現,類似于Java中的ServiceLoader的作用。

另外,擴展點使用單一實例加載,需要確保線程安全性。

Dubbo擴展點加載的一些定義

  • @SPI注解,被此注解標記的接口,就表示是一個可擴展的接口。

  • @Adaptive注解,有兩種注解方式:一種是注解在類上,一種是注解在方法上。

    • 注解在類上,而且是注解在實現類上,目前dubbo只有AdaptiveCompiler和AdaptiveExtensionFactory類上標注了此注解,這是些特殊的類,ExtensionLoader需要依賴他們工作,所以得使用此方式。
    • 注解在方法上,注解在接口的方法上,除了上面兩個類之外,所有的都是注解在方法上。ExtensionLoader根據接口定義動態的生成適配器代碼,并實例化這個生成的動態類。被Adaptive注解的方法會生成具體的方法實現。沒有注解的方法生成的實現都是拋不支持的操作異常UnsupportedOperationException。被注解的方法在生成的動態類中,會根據url里的參數信息,來決定實際調用哪個擴展。

    比如說這段代碼:

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

    當上面代碼執行的時候,我們其實還不知道要真正使用的Protocol是什么,可能是具體的實現DubboProtocol,也可能是其他的具體實現的Protocol,那么這時候refprotocol到底是什么呢?refprotocol其實是在調用getAdaptiveExtension()方法時候,自動生成的一個類,代碼如下:

    import com.alibaba.dubbo.common.extension.ExtensionLoader;
    public class Protocol$Adpative implements Protocol {
      public Invoker refer(Class arg0, URL arg1) throws Class {
        if (arg1 == null) throw new IllegalArgumentException("url == null");
    
        URL url = arg1;
        String extName = ( url.getProtocol() == null ? "dubbo" : url.getProtocol() );
    
        if(extName == null) throw new IllegalStateException("Fail to get extension(Protocol) name from url(" + url.toString() + ") use keys([protocol])");
    
        Protocol extension = (Protocol)ExtensionLoader.getExtensionLoader(Protocol.class).getExtension(extName);
    
        return extension.refer(arg0, arg1);
      }
    
      public Exporter export(Invoker arg0) throws Invoker {
        if (arg0 == null) throw new IllegalArgumentException("Invoker argument == null");
    
        if (arg0.getUrl() == null) throw new IllegalArgumentException("Invoker argument getUrl() == null");URL url = arg0.getUrl();
    
        String extName = ( url.getProtocol() == null ? "dubbo" : url.getProtocol() );
    
        if(extName == null) throw new IllegalStateException("Fail to get extension(Protocol) name from url(" + url.toString() + ") use keys([protocol])");
    
        Protocol extension = (Protocol)ExtensionLoader.getExtensionLoader(Protocol.class).getExtension(extName);
    
        return extension.export(arg0);
      }
    
      public void destroy() {
        throw new UnsupportedOperationException("method public abstract void Protocol.destroy() of interface Protocol is not adaptive method!");
      }
    
      public int getDefaultPort() {
        throw new UnsupportedOperationException("method public abstract int Protocol.getDefaultPort() of interface Protocol is not adaptive method!");
      }
    }
    

    可以看到被@Adaptive注解的方法都生成了具體的實現,并且實現邏輯都相同。而沒有被注解的方法直接拋出不支持操作的異常。

    當我們使用refprotocol調用方法的時候,其實是調用生成的類Protocol$Adpative中的方法,這里面的方法根據url中的參數配置來找到具體的實現類,找具體實現類的方式還是通過dubbo的擴展機制。比如url中可能會有protocol=dubbo,此時就可以根據這個dubbo來確定我們要找的類是DubboProtocol。可以查看下生成的代碼中getExtension(extName)這里是根據具體的名字去查找實現類。

  • @Activate注解,此注解需要注解在類上或者方法上,并注明被激活的條件,以及所有的被激活實現類中的排序信息。

  • ExtensionLoader,是dubbo的SPI機制的查找服務實現的工具類,類似與Java的ServiceLoader,可做類比。dubbo約定擴展點配置文件放在classpath下的/META-INF/dubbo,/META-INF/dubbo/internal,/META-INF/services目錄下,配置文件名為接口的全限定名,配置文件內容為配置名=擴展實現類的全限定名

Dubbo擴展點加載的源碼解析

重點解析下ExtensionLoader這個類。Dubbo的擴展點使用單一實例去加載,緩存在ExtensionLoader中。每一個ExtensionLoader實例僅負責加載特定SPI擴展的實現,想要獲得某個擴展的實現,首先要獲得該擴展對應的ExtensionLoader實例。

以Protocol為例進行分析擴展點的加載:

//這樣使用,先獲取ExtensionLoader實例,然后加載自適應的Protocol擴展點
Protocol protocol = ExtensionLoader.getExtensionLoader(Protocol.class).getAdaptiveExtension();
//使用
protocol.refer(Class<T> type, URL url));

可以看到,使用擴展點加載的步驟大概有三步:

  1. 獲取ExtensionLoader實例。
  2. 獲取自適應實現。
  3. 使用獲取到的實現。

下面我們就以這三步作為分界,來深入源碼的解析。

獲取ExtensionLoader實例

第一步,getExtensionLoader(Protocol.class),根據要加載的接口Protocol,創建出一個ExtensionLoader實例,加載完的實例會被緩存起來,下次再加載Protocol的ExtensionLoader的時候,會使用已經緩存的這個,不會再新建一個實例:

public static <T> ExtensionLoader<T> getExtensionLoader(Class<T> type) {
    //擴展點類型不能為空
    if (type == null)
        throw new IllegalArgumentException();
    //擴展點類型只能是接口類型的
    if(!type.isInterface()) {
        throw new IllegalArgumentException();
    }
    //沒有添加@SPI注解,只有注解了@SPI的才會解析
    if(!withExtensionAnnotation(type)) {
        throw new IllegalArgumentException();
    }
    //先從緩存中獲取指定類型的ExtensionLoader
    //EXTENSION_LOADERS是一個ConcurrentHashMap,緩存了所有已經加載的ExtensionLoader的實例
    //比如這里加載Protocol.class,就以Protocol.class作為key,以新創建的ExtensionLoader作為value
    //每一個要加載的擴展點只會對應一個ExtensionLoader實例,也就是只會存在一個Protocol.class在緩存中
    ExtensionLoader<T> loader = (ExtensionLoader<T>) EXTENSION_LOADERS.get(type);
    //緩存中不存在
    if (loader == null) {
        //創建一個新的ExtensionLoader實例,放到緩存中去
        //對于每一個擴展,dubbo中只有一個對應的ExtensionLoader實例
        EXTENSION_LOADERS.putIfAbsent(type, new ExtensionLoader<T>(type));
        loader = (ExtensionLoader<T>) EXTENSION_LOADERS.get(type);
    }
    return loader;
}

上面代碼返回一個ExtensionLoader實例,getExtensionLoader(Protocol.class)這一步沒有進行任何的加載工作,只是獲得了一個ExtensionLoader的實例。

ExtensionLoader的構造方法

上面獲取的是一個ExtensionLoader實例,接著看下構造實例的時候到底做了什么,我們發現在ExtensionLoader中只有一個私有的構造方法:

private ExtensionLoader(Class<?> type) {
    //接口類型
    this.type = type;
    //對于擴展類型是ExtensionFactory的,設置為null
    //getAdaptiveExtension方法獲取一個運行時自適應的擴展類型
    //每個Extension只能有一個@Adaptive類型的實現,如果么有,dubbo會自動生成一個類
    //objectFactory是一個ExtensionFactory類型的屬性,主要用于加載需要注入的類型的實現
    //objectFactory主要用在注入那一步,詳細說明見注入時候的說明
    //這里記住非ExtensionFactory類型的返回的都是一個AdaptiveExtensionFactory
    objectFactory = (type == ExtensionFactory.class ? null : ExtensionLoader.getExtensionLoader(ExtensionFactory.class).getAdaptiveExtension());
}

不難理解,ExtensionFactory是主要是用來加載被注入的類的實現,分為SpiExtensionFactory和SpringExtensionFactory兩個,分別用來加載SPI擴展實現和Spring中bean的實現。

獲取自適應實現

上面返回一個ExtensionLoader的實例之后,開始加載自適應實現,加載是在調用getAdaptiveExtension()方法中進行的:

getAdaptiveExtension()-->
                createAdaptiveExtension()-->
                                getAdaptiveExtensionClass()-->
                                                getExtensionClasses()-->
                                                                loadExtensionClasses()

先看下getAdaptiveExtension()方法,用來獲取一個擴展的自適應實現類,最后返回的自適應實現類是一個類名為Protocol$Adaptive的類,并且這個類實現了Protocol接口:

public T getAdaptiveExtension() {
    //先從實例緩存中查找實例對象
    //private final Holder<Object> cachedAdaptiveInstance = new Holder<Object>();
    //在當前的ExtensionLoader中保存著一個Holder實例,用來緩存自適應實現類的實例
    Object instance = cachedAdaptiveInstance.get();
    if (instance == null) {//緩存中不存在
        if(createAdaptiveInstanceError == null) {
            synchronized (cachedAdaptiveInstance) {
                //獲取鎖之后再檢查一次緩存中是不是已經存在
                instance = cachedAdaptiveInstance.get();
                if (instance == null) {
                    try {
                        //緩存中沒有,就創建新的AdaptiveExtension實例
                        instance = createAdaptiveExtension();
                        //新實例加入緩存
                        cachedAdaptiveInstance.set(instance);
                    } catch (Throwable t) {createAdaptiveInstanceError = t; }
                }
            }
        }
    }

    return (T) instance;
}

創建自適應擴展

緩存中不存在自適應擴展的實例,表示還沒有創建過自適應擴展的實例,接下來就是創建自適應擴展實現,createAdaptiveExtension()方法,用來創建自適應擴展類的實例:

private T createAdaptiveExtension() {
    try {
        //先通過getAdaptiveExtensionClass獲取AdaptiveExtensionClass
        //然后獲取其實例
        //最后進行注入處理
        return injectExtension((T) getAdaptiveExtensionClass().newInstance());
    } catch (Exception e) {}
}

獲取自適應擴展類

接著查看getAdaptiveExtensionClass()方法,用來獲取一個自適應擴展的Class,這個Class將會在下一步被實例化:

private Class<?> getAdaptiveExtensionClass() {
    //加載當前Extension的所有實現(這里舉例是Protocol,只會加載Protocol的所有實現類),如果有@Adaptive類型的實現類,會賦值給cachedAdaptiveClass
    //目前只有AdaptiveExtensionFactory和AdaptiveCompiler兩個實現類是被注解了@Adaptive
    //除了ExtensionFactory和Compiler類型的擴展之外,其他類型的擴展都是下面動態創建的的實現
    getExtensionClasses();
    //加載完所有的實現之后,發現有cachedAdaptiveClass不為空
    //也就是說當前獲取的自適應實現類是AdaptiveExtensionFactory或者是AdaptiveCompiler,就直接返回,這兩個類是特殊用處的,不用代碼生成,而是現成的代碼
    if (cachedAdaptiveClass != null) {
        return cachedAdaptiveClass;
    }
    //沒有找到Adaptive類型的實現,動態創建一個
    //比如Protocol的實現類,沒有任何一個實現是用@Adaptive來注解的,只有Protocol接口的方法是有注解的
    //這時候就需要來動態的生成了,也就是生成Protocol$Adaptive
    return cachedAdaptiveClass = createAdaptiveExtensionClass();
}

加載擴展類實現

先看下getExtensionClasses()這個方法,加載所有的擴展類的實現:

private Map<String, Class<?>> getExtensionClasses() {
    //從緩存中獲取,cachedClasses也是一個Holder,Holder這里持有的是一個Map,key是擴展點實現名,value是擴展點實現類
    //這里會存放當前擴展點類型的所有的擴展點的實現類
    //這里以Protocol為例,就是會存放Protocol的所有實現類
    //比如key為dubbo,value為com.alibaba.dubbo.rpc.protocol.dubbo.DubboProtocol
    //cachedClasses擴展點實現名稱對應的實現類
    Map<String, Class<?>> classes = cachedClasses.get();
    //如果為null,說明沒有被加載過,就會進行加載,而且加載就只會進行這一次
    if (classes == null) {
        synchronized (cachedClasses) {
            classes = cachedClasses.get();
            if (classes == null) {
                //如果沒有加載過Extension的實現,進行掃描加載,完成后緩存起來
                //每個擴展點,其實現的加載只會這執行一次
                classes = loadExtensionClasses();
                cachedClasses.set(classes);
            }
        }
    }
    return classes;
}

看下loadExtensionClasses()方法,這個方法中加載擴展點的實現類:

private Map<String, Class<?>> loadExtensionClasses() {
    final SPI defaultAnnotation = type.getAnnotation(SPI.class);
    if(defaultAnnotation != null) {
        //當前Extension的默認實現名字
        //比如說Protocol接口,注解是@SPI("dubbo")
        //這里dubbo就是默認的值
        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();
            }
            //默認的名字保存起來
            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;
}

從各個位置的配置文件中加載實現類,對于Protocol來說加載的文件是以com.alibaba.dubbo.rpc.Protocol為名稱的文件,文件的內容是(有好幾個同名的配置文件,這里直接把內容全部寫在了一起):

registry=com.alibaba.dubbo.registry.integration.RegistryProtocol

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

dubbo=com.alibaba.dubbo.rpc.protocol.dubbo.DubboProtocol

hessian=com.alibaba.dubbo.rpc.protocol.hessian.HessianProtocol

com.alibaba.dubbo.rpc.protocol.http.HttpProtocol

injvm=com.alibaba.dubbo.rpc.protocol.injvm.InjvmProtocol

memcached=memcom.alibaba.dubbo.rpc.protocol.memcached.MemcachedProtocol

redis=com.alibaba.dubbo.rpc.protocol.redis.RedisProtocol

rmi=com.alibaba.dubbo.rpc.protocol.rmi.RmiProtocol

thrift=com.alibaba.dubbo.rpc.protocol.thrift.ThriftProtocol

com.alibaba.dubbo.rpc.protocol.webservice.WebServiceProtocol

看下loadFile()方法:

private void loadFile(Map<String, Class<?>> extensionClasses, String dir) {
    //配置文件的名稱
    //這里type是擴展類,比如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);
                                        //查看類型是否匹配
                                        //type是Protocol接口
                                        //clazz就是Protocol的各個實現類
                                        if (! type.isAssignableFrom(clazz)) {
                                            throw new IllegalStateException();
                                        }
                                        //如果實現類是@Adaptive類型的,會賦值給cachedAdaptiveClass,這個用來存放被@Adaptive注解的實現類
                                        if (clazz.isAnnotationPresent(Adaptive.class)) {
                                            if(cachedAdaptiveClass == null) {
                                                cachedAdaptiveClass = clazz;
                                            } else if (! cachedAdaptiveClass.equals(clazz)) {
                                                throw new IllegalStateException();
                                            }
                                        } else {//不是@Adaptice類型的類,就是沒有注解@Adaptive的實現類
                                            try {//判斷是否是wrapper類型
                                                //如果得到的實現類的構造方法中的參數是擴展點類型的,就是一個Wrapper類
                                                //比如ProtocolFilterWrapper,實現了Protocol類,
                                                //而它的構造方法是這樣public ProtocolFilterWrapper(Protocol protocol)
                                                //就說明這個類是一個包裝類
                                                clazz.getConstructor(type);
                                                //cachedWrapperClasses用來存放當前擴展點實現類中的包裝類
                                                Set<Class<?>> wrappers = cachedWrapperClasses;
                                                if (wrappers == null) {
                                                    cachedWrapperClasses = new ConcurrentHashSet<Class<?>>();
                                                    wrappers = cachedWrapperClasses;
                                                }
                                                wrappers.add(clazz);
                                            } catch (NoSuchMethodException e) {
                                            //沒有上面提到的構造器,則說明不是wrapper類型
                                                //獲取無參構造
                                                clazz.getConstructor();
                                                //沒有名字,就是配置文件中沒有xxx=xxxx.com.xxx這種
                                                if (name == null || name.length() == 0) {
                                                    //去找@Extension注解中配置的值
                                                    name = findAnnotationName(clazz);
                                                    //如果還沒找到名字,從類名中獲取
                                                    if (name == null || name.length() == 0) {
                                                        //比如clazz是DubboProtocol,type是Protocol
                                                        //這里得到的name就是dubbo
                                                        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(");
                                                        }
                                                    }
                                                }
                                                //有可能配置了多個名字
                                                String[] names = NAME_SEPARATOR.split(name);
                                                if (names != null && names.length > 0) {
                                                    //是否是Active類型的類
                                                    Activate activate = clazz.getAnnotation(Activate.class);
                                                    if (activate != null) {
                                                        //第一個名字作為鍵,放進cachedActivates這個map中緩存
                                                        cachedActivates.put(names[0], activate);
                                                    }
                                                    for (String n : names) {
                                                        if (! cachedNames.containsKey(clazz)) {
                                                            //放入Extension實現類與名稱映射的緩存中去,每個class只對應第一個名稱有效
                                                            cachedNames.put(clazz, n);
                                                        }
                                                        Class<?> c = extensionClasses.get(n);
                                                        if (c == null) {
                                                            //放入到extensionClasses緩存中去,多個name可能對應一份extensionClasses
                                                            extensionClasses.put(n, clazz);
                                                        } else if (c != clazz) {
                                                            throw new IllegalStateException();
                                                        }
                                                    }
                                                }
                                            }
                                        }
                                    }
                                } catch (Throwable t) { }
                            }
                        } // end of while read lines
                    } finally {
                        reader.close();
                    }
                } catch (Throwable t) { }
            } // end of while urls
        }
    } catch (Throwable t) { }
}

到這里加載當前Extension的所有實現就已經完成了,繼續返回getAdaptiveExtensionClass中,在調用完getExtensionClasses()之后,會首先檢查是不是已經有@Adaptive注解的類被解析并加入到緩存中了,如果有就直接返回,這里的cachedAdaptiveClass中現在只能是AdaptiveExtensionFactory或者AdaptiveCompiler中的一個,如果沒有,說明是一個普通擴展點,就動態創建一個,比如會創建一個Protocol$Adaptive

創建自適應擴展類的代碼

看下createAdaptiveExtensionClass()這個方法,用來動態的創建自適應擴展類:

private Class<?> createAdaptiveExtensionClass() {
    //組裝自適應擴展點類的代碼
    String code = createAdaptiveExtensionClassCode();
    //獲取到應用的類加載器
    ClassLoader classLoader = findClassLoader();
    //獲取編譯器
    //dubbo默認使用javassist
    //這里還是使用擴展點機制來找具體的Compiler的實現
    //現在就知道cachedAdaptiveClass是啥意思了,如果沒有AdaptiveExtensionFactory和AdaptiveCompiler這兩個類,這里又要去走加載流程然后來生成擴展點類的代碼,不就死循環了么。
    //這里解析Compiler的實現類的時候,會在getAdaptiveExtensionClass中直接返回
    //可以查看下AdaptiveCompiler這個類,如果我們沒有指定,默認使用javassist
    //這里Compiler是JavassistCompiler實例
    com.alibaba.dubbo.common.compiler.Compiler compiler = ExtensionLoader.getExtensionLoader(com.alibaba.dubbo.common.compiler.Compiler.class).getAdaptiveExtension();
    //將代碼轉換成Class
    return compiler.compile(code, classLoader);
}

接著看下createAdaptiveExtensionClassCode()方法,用來組裝自適應擴展類的代碼(拼寫源碼,代碼比較長不在列出),這里列出生成的Protocol$Adaptive

import com.alibaba.dubbo.common.extension.ExtensionLoader;
public class Protocol$Adpative implements com.alibaba.dubbo.rpc.Protocol {
  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);
  }
  
  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 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!");
  }
}

其他具體的擴展點的生成也類似。在生成完代碼之后,是找到ClassLoader,然后獲取到Compiler的自適應實現,這里得到的就是AdaptiveCompiler,最后調用compiler.compile(code, classLoader);來編譯上面生成的類并返回,先進入AdaptiveCompiler的compile方法:

public Class<?> compile(String code, ClassLoader classLoader) {
    Compiler compiler;
    //得到一個ExtensionLoader
    ExtensionLoader<Compiler> loader = ExtensionLoader.getExtensionLoader(Compiler.class);
    //默認的Compiler名字
    String name = DEFAULT_COMPILER; // copy reference
    //有指定了Compiler名字,就使用指定的名字來找到Compiler實現類
    if (name != null && name.length() > 0) {
        compiler = loader.getExtension(name);
    } else {//沒有指定Compiler名字,就查找默認的Compiler的實現類
        compiler = loader.getDefaultExtension();
    }
    //調用具體的實現類來進行編譯
    return compiler.compile(code, classLoader);
}

獲取指定名字的擴展

先看下根據具體的名字來獲取擴展的實現類loader.getExtension(name);,loader是ExtensionLoader<Compiler>類型的。這里就是比Java的SPI要方便的地方,Java的SPI只能通過遍歷所有的實現類來查找,而dubbo能夠指定一個名字查找。代碼如下:

public T getExtension(String name) {
    if (name == null || name.length() == 0)
        throw new IllegalArgumentException("Extension name == null");
    //如果name指定為true,則獲取默認實現
    if ("true".equals(name)) {
        //默認實現查找在下面解析
        return getDefaultExtension();
    }
    //先從緩存獲取Holder,cachedInstance是一個ConcurrentHashMap,鍵是擴展的name,值是一個持有name對應的實現類實例的Holder。
    Holder<Object> holder = cachedInstances.get(name);
    //如果當前name對應的Holder不存在,就創建一個,添加進map中
    if (holder == null) {
        cachedInstances.putIfAbsent(name, new Holder<Object>());
        holder = cachedInstances.get(name);
    }
    //從Holder中獲取保存的實例
    Object instance = holder.get();
    //不存在,就需要根據這個name找到實現類,實例化一個
    if (instance == null) {
        synchronized (holder) {
            instance = holder.get();
            if (instance == null) {
                //緩存不存在,創建實例
                instance = createExtension(name);
                //加入緩存
                holder.set(instance);
            }
        }
    }
    //存在,就直接返回
    return (T) instance;
}

創建擴展實例,createExtension(name);

private T createExtension(String name) {
    //getExtensionClasses加載當前Extension的所有實現
    //上面已經解析過,返回的是一個Map,鍵是name,值是name對應的Class
    //根據name查找對應的Class
    Class<?> clazz = getExtensionClasses().get(name);
    //如果這時候class還不存在,說明在所有的配置文件中都沒找到定義,拋異常
    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);
        //Wrapper的包裝
        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) { }
}

有關屬性注入和Wrapper的包裝,下面再講。到這里Compiler就能獲得到一個指定name的具體實現類的實例了,然后就是調用實例的compile()方法對生成的代碼進行編譯。

獲取默認擴展實現

如果在AdaptiveCompiler中沒有找到指定的名字,就會找默認的擴展實現loader.getDefaultExtension();

public T getDefaultExtension() {
    //首先還是先去加載所有的擴展實現
    //加載的時候會設置默認的名字cachedDefaultName,這個名字是在@SPI中指定的,比如Compiler就指定了@SPI("javassist"),所以這里是javassist
    getExtensionClasses();
    if(null == cachedDefaultName || cachedDefaultName.length() == 0
            || "true".equals(cachedDefaultName)) {
        return null;
    }
    //根據javassist這個名字去查找擴展實現
    //具體的過程上面已經解析過了
    return getExtension(cachedDefaultName);
}

關于javassist編譯Class的過程暫先不說明。我們接著流程看:

 private T createAdaptiveExtension() {
    try {
        //先通過getAdaptiveExtensionClass獲取AdaptiveExtensionClass(在上面這一步已經解析了,獲得到了一個自適應實現類的Class)
        //然后獲取其實例,newInstance進行實例
        //最后進行注入處理injectExtension
        return injectExtension((T) getAdaptiveExtensionClass().newInstance());
    } catch (Exception e) { }
}

擴展點注入

接下來就是有關擴展點的注入的問題了,injectExtension,關于注入的解釋查看最上面擴展點自動裝配(IOC)的說明,injectExtension方法:

//這里的實例是Xxxx$Adaptive
private T injectExtension(T instance) {
    try {
        //關于objectFactory的來路,先看下面的解析
        //這里的objectFactory是AdaptiveExtensionFactory
        if (objectFactory != null) {
            //遍歷擴展實現類實例的方法
            for (Method method : instance.getClass().getMethods()) {
                //只處理set方法
                //set開頭,只有一個參數,public
                if (method.getName().startsWith("set")
                        && method.getParameterTypes().length == 1
                        && Modifier.isPublic(method.getModifiers())) {
                    //set方法參數類型
                    Class<?> pt = method.getParameterTypes()[0];
                    try {
                        //setter方法對應的屬性名
                        String property = method.getName().length() > 3 ? method.getName().substring(3, 4).toLowerCase() + method.getName().substring(4) : "";
                        //根據類型和名稱信息從ExtensionFactory中獲取
                        //比如在某個擴展實現類中會有setProtocol(Protocol protocol)這樣的set方法
                        //這里pt就是Protocol,property就是protocol
                        //AdaptiveExtensionFactory就會根據這兩個參數去查找對應的擴展實現類
                        //這里就會返回Protocol$Adaptive
                        Object object = objectFactory.getExtension(pt, property);
                        if (object != null) {//說明set方法的參數是擴展點類型,進行注入
                            //為set方法注入一個自適應的實現類
                            method.invoke(instance, object);
                        }
                    } catch (Exception e) { }
                }
            }
        }
    } catch (Exception e) {}
    return instance;
}

有關AdaptiveExtensionFactory中獲取Extension的過程,會首先在實例化的時候得到ExtensionFactory的具體實現類,然后遍歷每個ExtensionFactory的實現類,分別在每個ExtensionFactory的實現類中獲取Extension。

這里使用SpiExtensionFactory的獲取擴展的方法為例,getExtension,也是先判斷給定類是否是注解了@SPI的接口,然后根據類去獲取ExtensionLoader,在使用得到的ExtensionLoader去加載自適應擴展。

objectFactory的來歷

objectFactory的來路,在ExtensionLoader中有個私有構造器:

//當我們調用getExtensionLoader這個靜態方法的時候,會觸發ExtensionLoader類的實例化,會先初始化靜態變量和靜態塊,然后是構造代碼塊,最后是構造器的初始化
private ExtensionLoader(Class<?> type) {
    this.type = type;
    //這里會獲得一個AdaptiveExtensionFactory
    //根據類型和名稱信息從ExtensionFactory中獲取
    //獲取實現
    //為什么要使用對象工廠來獲取setter方法中對應的實現?
    //不能通過spi直接獲取自適應實現嗎?比如ExtensionLoader.getExtension(pt);
    //因為setter方法中有可能是一個spi,也有可能是普通的bean
    //所以此時不能寫死通過spi獲取,還需要有其他方式來獲取實現進行注入
    // dubbo中有兩個實現,一個是spi的ExtensionFactory,一個是spring的ExtensionFactory
    //如果還有其他的,我們可以自定義ExtensionFactory
    //objectFactory是AdaptiveExtensionFactory實例
    objectFactory = (type == ExtensionFactory.class ? null : ExtensionLoader.getExtensionLoader(ExtensionFactory.class).getAdaptiveExtension());
}

到此為止createAdaptiveExtension方法解析完成,接著返回上層getAdaptiveExtension()方法中,發現創建完自適應擴展實例之后,就會加入到cachedAdaptiveInstance緩存起來,然后就會返回給調用的地方一個Xxx$Adaptive實例。

走到這里,下面的代碼就解析完了:

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

得到擴展之后的使用

我們得到了一個Protocol$Adaptive實例,接著就是調用了,比如說我們要調用refprotocol.refer(Class<T> type, URL url))方法,由于這里refprotocol是一個Protocol$Adaptive實例,所以就先調用這個實例的refer方法,這里的實例的代碼在最上面:

//這里為了好看,代碼做了精簡,包名都去掉了
public Invoker refer(Class arg0, URL arg1) throws Class {
    if (arg1 == null) throw new IllegalArgumentException();

    URL url = arg1;
    String extName = ( url.getProtocol() == null ? "dubbo" : url.getProtocol() );

    if(extName == null) throw new IllegalStateException();
    
    Protocol extension = (Protocol)ExtensionLoader.getExtensionLoader(Protocol.class).getExtension(extName);
    
    return extension.refer(arg0, arg1);
  }

可以看到這里首先根據url中的參數獲取擴展名字,如果url中沒有就使用默認的擴展名,然后根據擴展名去獲取具體的實現。關于getExtension(String name)上面已經解析過一次,這里再次列出:

public T getExtension(String name) {
    if (name == null || name.length() == 0)
        throw new IllegalArgumentException("Extension name == null");
    //如果name指定為true,則獲取默認實現
    if ("true".equals(name)) {
        //默認實現查找在下面解析
        return getDefaultExtension();
    }
    //先從緩存獲取Holder,cachedInstance是一個ConcurrentHashMap,鍵是擴展的name,值是一個持有name對應的實現類實例的Holder。
    Holder<Object> holder = cachedInstances.get(name);
    //如果當前name對應的Holder不存在,就創建一個,添加進map中
    if (holder == null) {
        cachedInstances.putIfAbsent(name, new Holder<Object>());
        holder = cachedInstances.get(name);
    }
    //從Holder中獲取保存的實例
    Object instance = holder.get();
    //不存在,就需要根據這個name找到實現類,實例化一個
    if (instance == null) {
        synchronized (holder) {
            instance = holder.get();
            if (instance == null) {
                //緩存不存在,創建實例
                instance = createExtension(name);
                //加入緩存
                holder.set(instance);
            }
        }
    }
    //存在,就直接返回
    return (T) instance;
}

創建擴展實例,createExtension(name);

private T createExtension(String name) {
    //getExtensionClasses加載當前Extension的所有實現
    //上面已經解析過,返回的是一個Map,鍵是name,值是name對應的Class
    //根據name查找對應的Class
    //比如name是dubbo,Class就是com.alibaba.dubbo.rpc.protocol.dubbo.DubboProtocol
    Class<?> clazz = getExtensionClasses().get(name);
    //如果這時候class還不存在,說明在所有的配置文件中都沒找到定義,拋異常
    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);
        }
        //這里實例就是具體實現的實例了比如是DubboProtocol的實例
        //屬性注入,在上面已經解析過了,根據實例中的setXxx方法進行注入
        injectExtension(instance);
        //Wrapper的包裝
        //cachedWrapperClasses存放著所有的Wrapper類
        //cachedWrapperClasses是在加載擴展實現類的時候放進去的
        //Wrapper類的說明在最上面擴展點自動包裝(AOP)
        Set<Class<?>> wrapperClasses = cachedWrapperClasses;
        if (wrapperClasses != null && wrapperClasses.size() > 0) {
            for (Class<?> wrapperClass : wrapperClasses) {
                //比如在包裝之前的instance是DubboProtocol實例
                //先使用構造器來實例化當前的包裝類
                //包裝類中就已經包含了我們的DubboProtocol實例
                //然后對包裝類進行injectExtension注入,注入過程在上面
                //最后返回的Instance就是包裝類的實例。
                instance = injectExtension((T) wrapperClass.getConstructor(type).newInstance(instance));
            }
        }
        //這里返回的是經過所有的包裝類包裝之后的實例
        return instance;
    } catch (Throwable t) { }
}

獲取的Extension是經過層層包裝的擴展實現,然后就是調用經過包裝的refer方法了,這就到了具體的實現中的方法了。

到此為止調用refprotocol.refer(Class<T> type, URL url))方法的過程也解析完了。

關于getActivateExtension方法的解析,等下再添加。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 229,565評論 6 539
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 99,115評論 3 423
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 177,577評論 0 382
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,514評論 1 316
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,234評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,621評論 1 326
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,641評論 3 444
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,822評論 0 289
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,380評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,128評論 3 356
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,319評論 1 371
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,879評論 5 362
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,548評論 3 348
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,970評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,229評論 1 291
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,048評論 3 397
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,285評論 2 376

推薦閱讀更多精彩內容