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)扩展机制的实现的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 企业Java应用服务器之JBoss7.1
- 下一篇: StringBuilder 、Strin