從ExtensionLoader看Dubbo插件化

歡迎加入 DUBBO交流羣:259566260

以前不少人問我Dubbo插件化是怎麼實現的,我都是簡單回答SPI。瞭解SPI的人知道,它只是提供一種協議,並無提供相關插件化實施的接口,不像OSGI那樣有一成套實施插件化API。它只是規定在META-INF目錄下提供接口的實現描述文件,框架自己定義接口、規範,第三方只須要將本身實如今META-INF下描述清楚,那麼框架就會自動加載你的實現,至於怎麼加載,JDK並無提供相關API,而是框架設計者須要考慮和實現的,而且在META-INF下面對實現描述規則,也是須要框架設計者來規定。好比Dubbo的規則是在META-INF/dubbo、META-INF/dubbo/internal或者META-INF/services下面以須要實現的接口全面去建立一個文件,而且在文件中以properties規則同樣配置實現類的全面以及分配實現一個名稱。圖23是對Cluster接口擴展實現的描述 java


上面對Dubbo的插件實現方式和規則進行了簡單介紹,以及提到SPI是一種插件化的規範,並無提供實施的API,是須要框架本身去實現的。Dubbo對這一塊的實現所有都集中在類ExtensionLoader中,那麼接下來將圍繞這個類來介紹Dubbo插件化的實現,在介紹Dubbo插件化實施以前,須要知道Dubbo框架是以URL爲總線的模式,即運行過程當中全部的狀態數據信息均可以經過URL來獲取,好比當前系統採用什麼序列化,採用什麼通訊,採用什麼負載均衡等信息,都是經過URL的參數來呈現的,因此在框架運行過程當中,運行到某個階段須要相應的數據,均可以經過對應的Key從URL的參數列表中獲取,好比在cluster模塊,到服務調用觸發到該模塊,則會從URL中獲取當前調用服務的負載均衡策略,以及mock信息等。 負載均衡

       ExtensionLoader是一個單例工廠類,它對外暴露getExtensionLoader靜態方法返回一個ExtensionLoader實體,這個方法的入參是一個Class類型,這個方法的意思是返回某個接口的ExtensionLoader。那麼對於某一個接口,只會有一個ExtensionLoader實體。ExtensionLoader實體對外暴露了圖24中的一些接口來獲取擴展實現。 框架

上圖的方法歸爲幾類,分別是activate extension、adaptive extension、default extension、get extension by name以及supported extension。經過圖24發現activate extension都須要傳入url參數,這裏涉及到Activate註解,這個註解主要用處是標註在插件接口實現類上,用來配置該擴展實現類激活條件。在Dubbo框架裏面的Filter的各類實現類都經過Activate標註,用來描述該Filter何時生效。好比MonitorFilter經過Activate標註用來告訴Dubbo框架這個Filter是在服務提供端和消費端會生效的;而TimeoutFilter則是隻在服務提供端生效,消費端是不會調用該Filter;ValidationFilter要激活的條件除了在消費端和服務提供端激活,它還配置了value,這個表述另外一個激活條件,上面介紹要獲取activate extension都須要傳入URL對象,那麼這個value配置的值則表述URL必須有指定的參數才能夠激活這個擴展。例如ValidationFilter則表示URL中必須包含參數validation(Constants.VALIDATION_KEY常量的值就是validation),不然即便是消費端和服務端都不會激活這個擴展實現,仔細的同窗還會發如今ValidationFilter中的Activate註解還有一個參數order,這是表示一種排序規則。由於一個接口的實現有多種,返回的結果是一個列表,若是不指定排序規則,那麼可能列表的排序不可控,爲了實現這個因此添加了order屬性用來控制排序,其中order的值越大,那麼該擴展實現排序就越靠前。除了經過order來控制排序,還有before和after來配置當前擴展的位置,before和after配置的值是擴展的別名(擴展實現的別名是在圖23中等號左邊內容,下面出現的別名均是此內容)。 ide

@Activate(group = {Constants.PROVIDER, Constants.CONSUMER})
public class MonitorFilter implements Filter {……}
@Activate(group = Constants.PROVIDER)
public class TimeoutFilter implements Filter {……}
@Activate(group = { Constants.CONSUMER, Constants.PROVIDER }, value = Constants.VALIDATION_KEY, order = 10000)
public class ValidationFilter implements Filter {……}



上面基本對activate介紹的差很少了,在Dubbo框架中對這個用的最多的就是Filter的各類實現,由於Dubbo的調用會通過一個過濾器鏈,哪些Filter這個鏈中是經過各類Filter實現類的Activate註解來控制的。包括上面說的排序,也能夠理解爲過濾器鏈中各個Filter的先後順序。這裏的順序須要注意一個地方,這裏的排序均是框架自己實現擴展的進行排序,用戶自定義的擴展默認是追加在列表後面。說到這裏具體例子: 測試

<dubbo:reference id=」fooRef」 interface=」com.foo.Foo」 ….. filter=」A,B,C」/> 編碼

假設上面是一個有效的消費端服務引用,其中配置了一個filter屬性,而且經過逗號隔開配置了三個過濾器A,B,C(A,B,C均爲Filter實現的別名),那麼對於接口Foo調用的過濾器鏈是怎麼樣的呢?首先Dubbo會加載默認的過濾器(通常消費端有三個ConsumerContextFilter,MonitorFilter,FutureFilter),而且對這些默認的過濾器實現進行排序(ActivateComparator實現排序邏輯),這寫默認過濾器實現會在過濾器鏈前面,後面緊接着的纔是A,B,C三個自定義過濾器。 url

上面介紹了activate extension,下面介紹ExtensionLoader另外一個重要模塊adaptive extension。Dubbo框架提供的各類接口均有不少種類的實現,在引用具體實現的時候不可能經過硬編碼制定引用哪一個實現,這樣整個框架的靈活性嚴重下降。因此爲了可以適配一個接口的各類實現,便有了adaptive extension這一說。對一個接口實現的適配器Dubbo提供兩種途徑,第一種途徑是對某個接口實現對應的適配器,第二種是Dubbo框架動態生成適配器類。先對第一種途徑進行介紹,這種途徑也最好理解,對於這種途徑Dubbo也提供了一個註解—Adaptive,他用來標註在接口的某個實現上,表示這個實現並非提供具體業務支持,而是做爲該接口的適配器。對於這種途徑的使用在Dubbo框架中ExtensionFactory的實現類AdaptiveExtensionFactory就是實現適配的功能,它的類被Adaptive進行了標註,那麼當調用ExtensionLoader.getExtensionLoader(ExtensionFactory.class).getAdaptiveExtension()的時候將會返回AdaptiveExtensionFactory實體,用來適配ExtensionFactory接口的SPIExtensionFactory和SpringExtensionFactory兩種實現,在AdaptiveExtensionFactory將會根據運行時的狀態來肯定具體調用ExtensionFactory的哪一個實現。 spa

而第二種相對於第一種來講就隱晦一點,是ExtensionLoader經過分析接口配置的adaptive規則動態生成adaptive類而且加載到ClassLoader中,來實現動態適配。配置adaptive的規則也是經過Adaptive註解來設置,該註解有一個value屬性,經過設置這個屬性即可以設置該接口的Adaptive的規則,上面說過服務調用的全部數據都可以從URL獲取(Dubbo的URL總線模式),那麼須要Dubbo幫咱們動態生成adaptive的擴展接口的方法入參必須包含URL,這樣才能根據運行狀態動態選擇具體實現。這裏列舉一下Transporter接口中配置adaptive規則。 .net

@SPI("netty")
public interface Transporter {
    /**
     * Bind a server.
     * 
     * @see com.alibaba.dubbo.remoting.Transporters#bind(URL, Receiver, ChannelHandler)
     * @param url server url
     * @param handler
     * @return server
     * @throws RemotingException 
     */
    @Adaptive({Constants.SERVER_KEY, Constants.TRANSPORTER_KEY})
    Server bind(URL url, ChannelHandler handler) throws RemotingException;
    /**
     * Connect to a server.
     * 
     * @see com.alibaba.dubbo.remoting.Transporters#connect(URL, Receiver, ChannelListener)
     * @param url server url
     * @param handler
     * @return client
     * @throws RemotingException 
     */
    @Adaptive({Constants.CLIENT_KEY, Constants.TRANSPORTER_KEY})
    Client connect(URL url, ChannelHandler handler) throws RemotingException;
}



Transporter接口提供了兩個方法,一個是connect(用來建立客戶端鏈接),另外一個是bind(用來綁定服務端端口提供服務),而且這兩個方法上面均經過Adaptive註解配置了value屬性,bind配置的是server和transporter,connect配置的是client和transporter。那麼配置這些值有什麼用呢?下面看看ExtensionLoader根據這些生成了什麼樣的adaptive代碼。 插件

package com.alibaba.dubbo.remoting;
import com.alibaba.dubbo.common.extension.ExtensionLoader;
public class Transporter$Adpative implements com.alibaba.dubbo.remoting.Transporter{
public com.alibaba.dubbo.remoting.Client connect(
com.alibaba.dubbo.common.URL arg0, com.alibaba.dubbo.remoting.ChannelHandler arg1) 
throws com.alibaba.dubbo.remoting.RemotingException {
if (arg0 == null) 
throw new IllegalArgumentException("url == null");
com.alibaba.dubbo.common.URL url = arg0;
String extName = url.getParameter("client", url.getParameter("transporter", "netty"));
if(extName == null) 
throw new IllegalStateException("Fail to get extension(com.alibaba.dubbo.remoting.Transporter) name from url(" + url.toString() + ") use keys([client, transporter])");
com.alibaba.dubbo.remoting.Transporter extension = (com.alibaba.dubbo.remoting.Transporter)ExtensionLoader.getExtensionLoader
(com.alibaba.dubbo.remoting.Transporter.class).getExtension(extName);
return extension.connect(arg0, arg1);
}
public com.alibaba.dubbo.remoting.Server bind(
com.alibaba.dubbo.common.URL arg0, com.alibaba.dubbo.remoting.ChannelHandler arg1) 
throws com.alibaba.dubbo.remoting.RemotingException {
if (arg0 == null) 
throw new IllegalArgumentException("url == null");
com.alibaba.dubbo.common.URL url = arg0;
String extName = url.getParameter("server", url.getParameter("transporter", "netty"));
if(extName == null) 
throw new IllegalStateException("Fail to get extension(com.alibaba.dubbo.remoting.Transporter) name from url(" + url.toString() + ") use keys([server, transporter])");
com.alibaba.dubbo.remoting.Transporter extension = (com.alibaba.dubbo.remoting.Transporter)ExtensionLoader.getExtensionLoader
(com.alibaba.dubbo.remoting.Transporter.class).getExtension(extName);
return extension.bind(arg0, arg1);
}
}



上面是ExtensionLoader自動生成的Transporter$Adpative類,而且實現了Transporter接口,下面咱們分別看看在它connect和bind中分別作了哪些邏輯。先看看bind方法代碼段:

public com.alibaba.dubbo.remoting.Server bind(
com.alibaba.dubbo.common.URL arg0, com.alibaba.dubbo.remoting.ChannelHandler arg1) 
throws com.alibaba.dubbo.remoting.RemotingException {
if (arg0 == null) 
throw new IllegalArgumentException("url == null");
com.alibaba.dubbo.common.URL url = arg0;
String extName = url.getParameter("server", url.getParameter("transporter", "netty"));
if(extName == null) 
throw new IllegalStateException("Fail to get extension(com.alibaba.dubbo.remoting.Transporter) name from url(" + url.toString() + ") use keys([server, transporter])");
com.alibaba.dubbo.remoting.Transporter extension = (com.alibaba.dubbo.remoting.Transporter)ExtensionLoader.getExtensionLoader
(com.alibaba.dubbo.remoting.Transporter.class).getExtension(extName);
return extension.bind(arg0, arg1);
}



能夠看到bind方法先對url參數(arg0)進行了非空判斷,而後即是調用url.getParameter方法,首先是獲取server參數,若是沒有邊獲取transporter參數,最後若是兩個參數均沒有,extName即是netty。獲取完參數以後,緊接着對extName進行非空判斷,接下來即是獲取Transporter的ExtensionLoader,而且獲取別名爲extName的Transporter實現,並調用對應的bind,進行綁定服務端口操做。connect也是相似,只是它首先是從url中獲取client參數,在獲取transporter參數,一樣若是最後兩個參數都沒有,那麼extName也是netty,也依據extName獲取對已的接口擴展實現,調用connect方法。

到這裏或許你已經明白了ExtensionLoader是怎麼動態生成adaptive,上面從url中獲取server,client仍是transporter參數均是在Transporter接口的方法經過Adaptive註解配置的value屬性。其中netty是經過註解SPI制定當前接口的一種默認實現。這即是Dubbo經過ExtensionLoader動態生成adaptive類來動態適配接口的全部實現。

上面對activate和adaptive進行了詳細的介紹,這兩部分對已ExtensionLoader的實現分別是方法getActivateExtension(URL url, String[] values, String group) 和createAdaptiveExtensionClassCode(),若是感興趣,能夠去查看ExtensionLoader源碼。接下來對get extension by name和default extension介紹一下,get extension by name這個沒什麼好介紹的,就是經過接口實現的別名來獲取某一個具體的服務。而default extension須要坐一下詳細介紹,Dubbo的SPI規範除了上面說的在制定文件夾下面描述服務的實現信息以外,在被實現的接口必須標註SPI註解,用來告訴Dubbo這個接口是經過SPI來進行擴展實現的,不然ExtensionLoader則不會對這個接口建立ExtensionLoader實體,而且調用ExtensionLoader.getExtensionLoader方法會出現IllegalArgumentException異常。那說這些和默認擴展實現有什麼關係呢?在接口上標註SPI註解的時候能夠配置一個value屬性用來描述這個接口的默認實現別名,例如上面Transporter的@SPI(「netty」)就是指定Transporter默認實現是NettyTransporter,由於NettyTransporter的別名是netty。這裏再對服務別名補充有點,別名是站在某一個接口的維度來區分不一樣實現的,因此一個接口的實現不能有相同的別名,不然Dubbo框架將啓動失敗,固然不一樣接口的各自實現別名能夠相同。到此ExtensionLoader的實現原則和基本原理介紹完了,接下來咱們來看看怎麼基於Dubbo的ExtensionLoader來實施咱們本身的插件化。一樣仍是dubbo-demo項目中進行演示,在其中建立了一個demo-extension模塊。

插件化的第一步是抽象一個接口,從定義了插件的規範,那麼咱們先建立一個MyFirstExtension接口,而且標註了SPI註解,同時制定默認實現是別名爲default的擴展實現。

@SPI("default")
public interface MyFirstExtension {
    public String sayHello(String name,ExtensionType type);
}



那麼接下來就是對這個插件接口提供不一樣的實現,能夠看到我上面接口方法sayHello方法入參中並無URL類型,因此不能經過Dubbo動態給我生成adaptive類,須要我本身來實現一個適配類。個人適配類以下:

@Adaptive
public class AdaptiveExtension implements MyFirstExtension {
    @Override
    public String sayHello(String name,ExtensionType type) {
        ExtensionLoader extensionLoader = ExtensionLoader.getExtensionLoader(MyFirst Extension.class);
        MyFirstExtension extension= (MyFirstExtension) extensionLoader.getDefaultExtension();
        switch (type){
            case DEFAULT:
                extension= (MyFirstExtension) extensionLoader.getExtension("default");
                break;
            case OTHER:
                extension= (MyFirstExtension) extensionLoader.getExtension("other");
                break;
        }
        return extension.sayHello(name,type);
    }
}



可見在AdaptiveExtension中將會根據ExtensionType分發擴展的具體實現,並觸發sayHello方法。我在demo-extension模塊中對MyFirstExtension接口提供了兩種實現,它們如圖25所示,而且在META-INF/dubbo下面建立了文件com.bieber.dubbo.extension.MyFirstExtension其中內容如圖26所示。



到此插件的定義以及插件的實現都已經完畢,下面咱們來驗證一下是否成功依託Dubbo的插件機制管理了咱們的插件。我建立了一個測試類ExtensionTest來驗證,運行該測試類便會等到圖27所示的結果,若是調整ExtensionType便會獲得不一樣的記過,說明咱們的插件已經成功整合到了Dubbo框架裏面。

public class ExtensionTest {

    public static void main(String[] args){
       ExtensionLoader extensionLoader = ExtensionLoader.getExtensionLoader(MyFirstExtension.class);
        MyFirstExtension myFirstExtension = (MyFirstExtension) extensionLoader.getAdaptiveExtension();
        System.out.println(myFirstExtension.sayHello("bieber",ExtensionType.DEFAULT));
    }
}



到此關於Dubbo插件化的內容介紹完了,其實能夠把ExtensionLoader看成是Spring的IOC容器,只不過IOC容器裏面作的事情是幫咱們初始化和管理bean,咱們能夠根據咱們須要的bean類型或者bean的id來獲取對應的bean實體,而Dubbo裏面的ExtensionLoader豈不是同樣,只不過它管理的是插件,一樣咱們能夠根據具體插件實現別名和插件接口來獲取咱們想要的插件實現。另外一個不一樣點是Spring是經過XML的方式告訴Spring個人bean的實現類全路徑,而Dubbo則是經過SPI的方式告訴ExtensionLoader具體實現類信息。若是你理解了這個,那麼你就理解ExtensionLoader了。

相關文章
相關標籤/搜索