日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

dubbo源码分析系列(1)扩展机制的实现

發布時間:2024/9/21 编程问答 24 豆豆
生活随笔 收集整理的這篇文章主要介紹了 dubbo源码分析系列(1)扩展机制的实现 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

1 系列目錄

  • dubbo源碼分析系列(1)擴展機制的實現
  • dubbo源碼分析系列(2)服務的發布
  • dubbo源碼分析系列(3)服務的引用
  • dubbo源碼分析系列(4)dubbo通信設計

2 SPI擴展機制

站在一個框架作者的角度來說,定義一個接口,自己默認給出幾個接口的實現類,同時允許框架的使用者也能夠自定義接口的實現。現在一個簡單的問題就是:如何優雅的根據一個接口來獲取該接口的所有實現類呢?

這就需要引出java的SPI機制了

2.1 SPI介紹與demo

這些內容就不再多說了,網上搜一下,一大堆,具體可以參考這篇博客Java SPI機制簡介;

我這里給出一個簡單的demo:

定義一個接口:com.demo.dubbo.demo.spi.service.HelloService

接口的實現類:

com.demo.dubbo.demo.spi.service.impl.DefaultHelloService com.demo.dubbo.demo.spi.service.impl.CustomHelloService

然后在類路徑下,創建META-INF/services/com.demo.dubbo.demo.spi.service.HelloService文件,內容如下:

com.demo.dubbo.demo.spi.service.impl.DefaultHelloService com.demo.dubbo.demo.spi.service.impl.CustomHelloService

整體結構如下圖所示:

使用方式如下:

ServiceLoader<HelloService> helloServiceLoader=ServiceLoader.load(HelloService.class); for(HelloService item:helloServiceLoader){item.hello(); }

2.2 ServiceLoader的源碼分析

從上面可以看到,先根據ServiceLoader的load靜態方法根據目標接口加載出一個ServiceLoader實例,然后可以遍歷這個實例(實現了Iterable接口),獲取到接口的所有實現類

來看下ServiceLoader的幾個重要屬性:

要加載的接口 private Class<S> service;// The class loader used to locate, load, and instantiate providers private ClassLoader loader;// 用于緩存已經加載的接口實現類,其中key為實現類的完整類名 private LinkedHashMap<String,S> providers = new LinkedHashMap<>();// 用于延遲加載接口的實現類 private LazyIterator lookupIterator;

首先第一步:獲取一個ServiceLoader<HelloService> helloServiceLoader實例,此時還沒有進行任何接口實現類的加載操作,屬于延遲加載類型的。只是創建了LazyIterator lookupIterator對象而已。

第二步:ServiceLoader實現了Iterable接口,即實現了該接口的iterator()方法,實現內容如下:

public Iterator<S> iterator() {return new Iterator<S>() {Iterator<Map.Entry<String,S>> knownProviders= providers.entrySet().iterator();public boolean hasNext() {if (knownProviders.hasNext())return true;return lookupIterator.hasNext();}public S next() {if (knownProviders.hasNext())return knownProviders.next().getValue();return lookupIterator.next();}public void remove() {throw new UnsupportedOperationException();}}; }

for循環遍歷ServiceLoader的過程其實就是調用上述hasNext()和next()方法的過程

第一次循環遍歷會使用lookupIterator去查找,之后就緩存到providers中。LazyIterator會去加載類路徑下/META-INF/services/接口全稱 文件的url地址,使用如下代碼來加載:

String fullName = "META-INF/services/" + service.getName(); loader.getResources(fullName)

文件加載并解析完成之后,得到一系列的接口實現類的完整類名,調用next()方法時才回去真正執行接口實現類的加載操作,并根據無參構造器創建出一個實例,存到providers中;

之后再次遍歷ServiceLoader,就直接遍歷providers中的數據

2.3 ServiceLoader缺點分析

  • 雖然ServiceLoader也算是使用的延遲加載,但是基本只能通過遍歷全部獲取,也就是接口的實現類全部加載并實例化一遍。如果你并不想用某些實現類,它也被加載并實例化了,這就造成了浪費。

  • 獲取某個實現類的方式不夠靈活,只能通過Iterator形式獲取,不能根據某個參數來獲取對應的實現類

3 dubbo的擴展機制

3.1 簡單功能介紹

dubbo的擴展機制和java的SPI機制非常相似,但是又增加了如下功能:

  • 1 可以方便的獲取某一個想要的擴展實現,java的SPI機制就沒有提供這樣的功能
  • 2 對于擴展實現IOC依賴注入功能:

    舉例來說:接口A,實現者A1、A2。接口B,實現者B1、B2。

    現在實現者A1含有setB()方法,會自動注入一個接口B的實現者,此時注入B1還是B2呢?都不是,而是注入一個動態生成的接口B的實現者B$Adpative,該實現者能夠根據參數的不同,自動引用B1或者B2來完成相應的功能

  • 3 對擴展采用裝飾器模式進行功能增強,類似AOP實現的功能

    還是以上面的例子,接口A的另一個實現者AWrapper1。大體內容如下:

    private A a; AWrapper1(A a){

    this.a=a;

    }

    因此,我們在獲取某一個接口A的實現者A1的時候,已經自動被AWrapper1包裝了。

3.2 dubbo的ExtensionLoader解析擴展過程

以下面的例子為例來分析下:

ExtensionLoader<Protocol> protocolLoader=ExtensionLoader.getExtensionLoader(Protocol.class); Protocol protocol=protocolLoader.getAdaptiveExtension();

其中Protocol接口定義如下:

@Extension("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();}

對應的實現者如下:

第一步:根據要加載的接口創建出一個ExtensionLoader實例

ExtensionLoader中含有一個靜態屬性:

ConcurrentMap<Class<?>, ExtensionLoader<?>> EXTENSION_LOADERS = new ConcurrentHashMap<Class<?>, ExtensionLoader<?>>();

用于緩存所有的擴展加載實例,這里加載Protocol.class,就以Protocol.class為key,創建的ExtensionLoader為value存儲到上述EXTENSION_LOADERS中

這里沒有進行任何的加載操作。

我們先來看下,ExtensionLoader實例是如何來加載Protocol的實現類的:

  • 1 先解析Protocol上的Extension注解的name,存至String cachedDefaultName屬性中,作為默認的實現
  • 2 到類路徑下的加載 META-INF/services/com.alibaba.dubbo.rpc.Protocol文件

    該文件的內容如下:

    com.alibaba.dubbo.registry.support.RegistryProtocol com.alibaba.dubbo.rpc.protocol.ProtocolFilterWrapper com.alibaba.dubbo.rpc.protocol.ProtocolListenerWrapper com.alibaba.dubbo.rpc.protocol.dubbo.DubboProtocol com.alibaba.dubbo.rpc.protocol.injvm.InjvmProtocol com.alibaba.dubbo.rpc.protocol.rmi.RmiProtocol com.alibaba.dubbo.rpc.protocol.hessian.HessianProtocol

    然后就是讀取每一行內容,加載對應的class。

  • 3 對于上述class分成三種情況來處理

    對于一個接口的實現者,ExtensionLoader分三種情況來分別存儲對應的實現者,屬性分別如下:

    Class<?> cachedAdaptiveClass; Set<Class<?>> cachedWrapperClasses; Reference<Map<String, Class<?>>> cachedClasses;

    情況1: 如果這個class含有Adaptive注解,則將這個class設置為Class<?> cachedAdaptiveClass。

    情況2: 嘗試獲取帶對應接口參數的構造器,如果能夠獲取到,則說明這個class是一個裝飾類即,需要存到Set<Class<?>> cachedWrapperClasses中

    情況3: 如果沒有上述構造器。則獲取class上的Extension注解,根據該注解的定義的name作為key,存至Reference<Map<String, Class<?>>> cachedClasses結構中

至此,解析文件過程結束。

以Protocol為例來詳細介紹下整個過程:

  • 1 解析Protocol上的Extension注解的name

    @Extension("dubbo") public interface Protocol{

    //略

    }

    所以cachedDefaultName值為dubbo。

  • 2 解析類路徑下的加載 META-INF/services/com.alibaba.dubbo.rpc.Protocol文件

    如DubboProtocol:

    @Extension(DubboProtocol.NAME) public class DubboProtocol extends AbstractProtocol {

    //略

    }

    沒有Adaptive注解,同時只有無參構造器,所以只能存放到Reference<Map<String, Class<?>>> cachedClasses中,key就是上述DubboProtocol.NAME即dubbo。

    如ProtocolFilterWrapper:

    public class ProtocolFilterWrapper implements Protocol {

    private final Protocol protocol;public ProtocolFilterWrapper(Protocol protocol){if (protocol == null) {throw new IllegalArgumentException("protocol == null");}this.protocol = protocol;}

    }

    含有Protocol參數的構造器,作為一個裝飾類,存放至Set<Class<?>> cachedWrapperClasses中

    同理ProtocolListenerWrapper:

    public class ProtocolListenerWrapper implements Protocol {

    private final Protocol protocol;public ProtocolListenerWrapper(Protocol protocol){if (protocol == null) {throw new IllegalArgumentException("protocol == null");}this.protocol = protocol;}

    }

    含有Protocol參數的構造器,作為一個裝飾類,存放至Set<Class<?>> cachedWrapperClasses中。

3.3 dubbo的ExtensionLoader獲取擴展的過程

以獲取DubboProtocol為例

ExtensionLoader<Protocol> protocolLoader=ExtensionLoader.getExtensionLoader(Protocol.class); Protocol dubboProtocol=protocolLoader.getExtension(DubboProtocol.NAME);

獲取過程如下:

private T createExtension(String name) {Class<?> clazz = getExtensionClasses().get(name); if (clazz == null) { throw findException(name);}try {T instance = injectExtension((T) clazz.newInstance());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) { throw new IllegalStateException("Extension instance(name: " + name + ", class: " +type + ") could not be instantiated: " + t.getMessage(), t);} }

大致分成4步:

  • 1 根據name獲取對應的class

    首先獲取ExtensionLoader<Protocol>對象的Reference<Map<String, Class<?>>> cachedClasses屬性,如果為空則表示還沒有進行解析,則開始進行上面的解析。解析完成之后,根據name獲取對應的class,這里便獲取到了DubboProtocol.class

  • 2 根據獲取到的class創建一個實例

  • 3 對獲取到的實例,進行依賴注入

  • 4 對于上述經過依賴注入的實例,再次進行包裝。即遍歷Set<Class<?>> cachedWrapperClasses中每一個包裝類,分別調用帶Protocol參數的構造函數創建出實例,然后同樣進行依賴注入

    以Protocol為例,cachedWrapperClasses中存著上述提到過的ProtocolFilterWrapper、ProtocolListenerWrapper。分別會對DubboProtocol實例進行包裝,這個比較好理解的

下面對于這個依賴注入的過程就要詳細的說明下,來看下這個過程:

private T injectExtension(T instance) {try {for (Method method : instance.getClass().getMethods()) {if (method.getName().startsWith("set")&& method.getParameterTypes().length == 1&& Modifier.isPublic(method.getModifiers())) {Class<?> pt = method.getParameterTypes()[0];if (pt.isInterface() && getExtensionLoader(pt).getSupportedExtensions().size() > 0) {try {Object adaptive = getExtensionLoader(pt).getAdaptiveExtension();method.invoke(instance, adaptive);} catch (Exception e) {logger.error("fail to inject via method " + method.getName()+ " of interface " + type.getName() + ": " + e.getMessage(), e);}}}}} catch (Exception e) {logger.error(e.getMessage(), e);}return instance; }

從上面可以看到,進行注入的條件如下:

  • set開頭的方法
  • 方法的參數只有一個
  • 方法必須是public
  • 方法的參數必須是接口,并且是ExtensionLoader能夠獲取其擴展類

我們知道一個接口的實現者可能有多個,此時到底注入哪一個呢?

此時采取的策略是,并不去注入一個具體的實現者,而是注入一個動態生成的實現者,這個動態生成的實現者的邏輯是確定的,能夠根據不同的參數來使用不同的實現者實現相應的方法。這個動態生成的實現者的class就是ExtensionLoader的Class<?> cachedAdaptiveClass

以Protocol為例,動態生成的Protocol實現者大概如下:

class Protocol$Adpative implements Protocol{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)com.alibaba.dubbo.common.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)com.alibaba.dubbo.common.ExtensionLoader.getExtensionLoader(com.alibaba.dubbo.rpc.Protocol.class).getExtension(extName);return extension.refer(arg0, arg1);}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!");} }

從上面的代碼中可以看到,Protocol$Adpative是根據URL參數中protocol屬性的值來選擇具體的實現類的。

如值為dubbo,則從ExtensionLoader<Protocol>中獲取dubbo對應的實例,即DubboProtocol實例

如值為hessian,則從ExtensionLoader<Protocol>中獲取hessian對應的實例,即HessianProtocol實例

也就是說Protocol$Adpative能夠根據url中的protocol屬性值動態的采用對應的實現。

對于上述獲取動態實現者即Protocol$Adpative的過程還需要補充一些細節內容:

  • 1 要求對應的接口中的某些方法必須含有Adaptive注解,沒有Adaptive注解,則表示不需要生成動態類
  • 2 對于接口的方法中不含Adaptive注解的,全部是不可調用的,如上述的destroy()方法
  • 3 含有Adaptive注解的方法必須含有URL類型的參數,或者能夠獲取到URL,分別如上述的refer方法和export方法
  • 4 從URL中根據什么參數來獲取實現者信息呢?以Protocol為例,參數就為"protocol",默認是接口簡單名稱首字母小寫或者接口中指定的默認實現,對于別的接口,我們從url的哪個參數中獲取對應的實現者呢?這就可以從Adpative注解中給出,下面給出一個Transporter例子

Transporter接口內容如下:

@Extension("netty") public interface Transporter {@Adaptive({Constants.SERVER_KEY, Constants.TRANSPORTER_KEY})Server bind(URL url, ChannelHandler handler) throws RemotingException;@Adaptive({Constants.CLIENT_KEY, Constants.TRANSPORTER_KEY})Client connect(URL url, ChannelHandler handler) throws RemotingException;}

接口Transporter指定的默認實現是"netty",同時@Adaptive注解中又給出了"client"和"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)com.alibaba.dubbo.common.ExtensionLoader.getExtensionLoader(com.alibaba.dubbo.remoting.Transporter.class).getExtension(extName);return extension.connect(arg0, arg1); }

String extName = url.getParameter("client", url.getParameter("transporter", "netty"));

先根據client來獲取,如果獲取不到再根據transporter來獲取,如果還獲取不到,則直接使用Transporter默認指定的netty。

至此,dubbo的ExtensionLoader的內容大概就說完了。

4 結束語

下一篇文章就開始介紹下,服務器端暴漏服務和向注冊中心注冊服務的過程

與50位技術專家面對面20年技術見證,附贈技術全景圖

總結

以上是生活随笔為你收集整理的dubbo源码分析系列(1)扩展机制的实现的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。