Dubbo源碼分析之ExtensionLoader加載過程解析


ExtensionLoader加載機制閱讀:

 

Dubbo的類加載機制是模仿jdkspi加載機制;

 JdkSPI擴展加載機制:約定是當服務的提供者每增加一個接口的實現類時,需要在jar包的META-INF/service/目錄下同時創建一個以服務接口命名的具體實現類,該文件里面就是保存的實現該接口的具體實現類。而當外部程序裝配這個模塊的時候,就能通過該jar包META-INF/services/里的配置文件找到具體的實現類

名,並裝載實例化,完成模塊的注入。 基於這樣一個約定就能很好的找到服務接口的實現類,而不需要再代碼里制定。jdk提供服務實現查找的一個工具

類:java.util.ServiceLoader

但是原始的jdk擴展點加載機制有些缺陷:

·JDK標准的SPI會一次性實例化擴展點所有實現,如果有擴展實現初始化很耗時,但如果沒用上也加載,會很浪費資源。

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

·Dubbo增加了對擴展點IoC和AOP的支持,一個擴展點可以直接setter注入其它擴展點。

理解Dubbo的SPI機制之前的幾個概念:

·擴展點:Dubbo作為一個非常靈活的框架,並不會強制所有用戶必須使用Dubbo框架里自帶的某些架構,比如注冊中心的話,dubbo提供了zookeeper和redis,而開發者可以根據自己的需要使用自定的注冊中心,針對這種可靈活替換的技術我們就稱之為擴展點技術,類似擴展點在Dubbo中有很多,比如Protocol,Filter,LoadBlance,Cluster等等;

·Wrapper:Dubbo在加載某個接口的擴展類時候,如果發現某個實現中有一個拷貝構造函數,那么該接口實現是就是該接口的包裝類,此時dubbo會在真正的實現類上包裝一層wrapper,比如ProtocolFilterWrapper中含有Protocol引用,及構造函數,所以加載filter配置時會返回wrapper類;即這個時候從ExtensionLoader中返回的實際擴展類是被Wrapper包裝的接口實現類。

· Adaptive:這個自適應的擴展點比較難理解,所以這里直接以一個例子來講解:在RegistryProtocol中有一個屬性為Cluster,其中Protocol和Cluster都是Dubbo提供的擴展點,所以這時候當我們真正在操作中使用cluster的時候究竟使用的哪一個cluster的實現類呢?是FailbackCluster還是FailoverCluster?Dubbo在加載一個擴展點的時候如果發現其成員變量也是一個擴展點並且有相關的set方法,就會在這時候將該擴展點設置為一個自適應的擴展點,自適應擴展點(Adaptive)會在真正使用的時候從URL中獲取相關參數,來調用真正的擴展點實現類。它的用途主要是用於從 ExtensionLoader 返回擴展點時,包裝在真正的擴展點實現外。即從 ExtensionLoader 中返回的實際上是 Wrapper 類的實例,Wrapper 持有了實際的擴展點實現類。通過 Wrapper 類可以把所有擴展點公共邏輯移至 Wrapper 中。新加的 Wrapper 在所有的擴展點上添加了邏輯,有些類似 AOP,即 Wrapper 代理了擴展點。

擴展點自適應:ExtensionLoader 注入的依賴擴展點是一個 Adaptive 實例,直到擴展點方法執行時才決定調用是一個擴展點實現。Dubbo 使用 URL 對象(包含了Key-Value)傳遞配置信息。類似/context/path?version=1.0.0&application=morgan ,擴展點方法調用會有URL參數(或是參數有URL成員)注入的 Adaptive 實例可以提取約定 Key 來決定使用哪個接口的實現類來調用對應的實現的方法,比如提取協議為dubbo則會調用DubboProtocol進行實現。

·擴展點自動激活:可以理解為條件激活,比如Filter接口有很多擴展點實現類,當想簡化配置用到哪些過濾器時,可以@Activate自動激活,或者配置為@Activate(“xxx”)條件配置激活。

ExtensionLoader過程詳解:

Dubbo的擴展點加載機制中,ExtensionLoader是整個SPI加載的核心,而在DubboExtensionLoader的調用一般如下:

    private static final Cluster cluster = ExtensionLoader.getExtensionLoader(Cluster.class).getAdaptiveExtension();

 

接下來分析整個擴展點加載過程:

1.首先先從Dubbo的官方測試用例說起:

@Test

public void test_useAdaptiveClass() throws Exception {

 

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

    // 加載擴展類加載器

    ExtensionLoader<HasAdaptiveExt> loader = ExtensionLoader.getExtensionLoader(HasAdaptiveExt.class);

    // 獲取適配擴展點

    HasAdaptiveExt ext = loader.getAdaptiveExtension();

    // 判斷是否添加了HasAdaptiveExt_ManualAdaptive

    assertTrue(ext instanceof HasAdaptiveExt_ManualAdaptive);

}

 

1.對於一個新的擴展類接口HasAdaptiveExt,首先獲取它的擴展類加載器,過程如下:

 

 

2.首次創建沒有擴展類加載器則進行創建,而在新建ExtensionLoader時會首先判斷擴展工程類的擴展點是否已經加載,沒有的話,會先進行擴展工程類的擴展點實現的加載。

 

 

3.此時又回到第1步進行ExtensionFactory的擴展類加載器,進行ExtensionFactory類型的ExtensionLoader的實例化,實例化完成后則進行加載擴展點實現類;

 

 

3.1:擴展點實例創建過程a.首先獲取適配器類b.在適配器擴展點鍾注入其他依賴的擴展點

 

 

 

3.2.獲取適配擴展類的方式有兩種:a.在某個實現類上加@Adaptive注解b.如果沒有實現類被注解,則自動生成該接口的適配類擴展點

 

 

3.3加載指定目錄下的配置文件

 

 

 

 

 

 

3.4.資源定位獲取配置文件的內容進行具體擴展實現類的加載,里面配置key-value為類型和類路徑全名稱

例如:adaptive=org.apache.dubbo.common.extension.factory.AdaptiveExtensionFactory

 

 

 

 

 

 

 

 

3.5 然后進行指定擴展類實現的加載並進行緩存

 

 

 

3.5.1如果擴展類既不是有adaptive注解修飾,也不是包裝類:如果擴展點實例名稱長度為空

則自己手動獲取賦值給name,然后如果name以,分割的話則進行分割成數組處理,然后獲取該擴展點實例是否有Activate注解,有的話則進行激活點緩存處理,

 

 

 

 

findAnnotation實現:name為配置文件中的key

 

 

此時如果還沒找到適配的擴展點實例,則回到上面的3.2步驟會創建一個默認的擴展點;

 

 

 

3.6獲取code和類加載器,這樣通過編譯器生成一個class

private Class<?> createAdaptiveExtensionClass() {

    String code = createAdaptiveExtensionClassCode();

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

}

 

 

大概的邏輯流程如下:

1.為了獲得一個擴展點的適配類,首先會看緩存中有沒有已經加載過的適配類,如果有的話就直接返回,沒有的話就進入第2步。

2.加載所有的配置文件,將所有的配置類都load進內存並且在ExtensionLoader內部做好緩存,如果配置的文件中有適配類就緩存起來,如果沒有適配類就自行通過代碼自行創建適配類並且緩存起來(代碼之后給出樣例)。

3.在加載配置文件的時候,會依次將包裝類,自激活的類都進行緩存。

4.將獲取完適配類時候,如果適配類的set方法對應的屬性也是擴展點話,會依次注入對應的屬性的適配類(循環進行)。

 

下面看一下Dubbo自己生成的適配類代碼是怎樣的(以Protocol為例):

 

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

  }

}

本質上的做法就是通過方法的參數獲得URL信息,從URL中獲得對應的value對應值,然后從ExtensionLoader的緩存中找到value對應的具體實現類,然后用該實現類進行工作。可以看到上面的核心就是getExtension方法了,下面來看一下該方法的實現:

    public T getExtension(String name) {

        if (name == null || name.length() == 0)

            throw new IllegalArgumentException("Extension name == null");

        //DefaultExtension就是自適應的擴展類

        if ("true".equals(name)) {

            return getDefaultExtension();

        }

        //先從緩存中去取

        Holder<Object> holder = cachedInstances.get(name);

        if (holder == null) {

            //如果緩存中沒有的話在創建一個然后放進去,但是此時並沒有實際內容,只有一個空的容器Holder

            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;

    }

    

    private T createExtension(String name) {

        //獲取name類型對應的擴展類型,從cachedClasses根據key獲取對應的class,cachedClasses已經在load操作的時候初始化過了。

        Class<?> clazz = getExtensionClasses().get(name);

        if (clazz == null) {

            throw findException(name);

        }

        try {

            //獲得擴展類型對應的實例

            T instance = (T) EXTENSION_INSTANCES.get(clazz);

            if (instance == null) {

                //將實例放進緩存中

                EXTENSION_INSTANCES.putIfAbsent(clazz, (T) clazz.newInstance());

                instance = (T) EXTENSION_INSTANCES.get(clazz);

            }

            //injectExtension方法的作用就是通過set方法注入其他的屬性擴展點,上面已經講過

            injectExtension(instance);

            Set<Class<?>> wrapperClasses = cachedWrapperClasses;

            if (wrapperClasses != null && wrapperClasses.size() > 0) {

                for (Class<?> wrapperClass : wrapperClasses) {

                    //循環遍歷所有wrapper實現,實例化wrapper並進行擴展點注入

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

        }

    }

 

ExtensionLoader的源碼分析流程大概如上,在Dubbo源碼中這種使用是非常常見的,所以對源碼的閱讀很有必要了解Dubbo的類加載機制,如有疏漏之處還望多多指教,后面其他的Dubbo源碼還在路上,如感興趣可以關注一下
源碼分析的github:https://github.com/martixZero/dubbo-parent

參考文檔:
https://www.jianshu.com/p/a72856c77b6a
Dubbo官方開發者指南:http://dubbo.apache.org/zh-cn/docs/dev/implementation.html

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM