#1 系列目錄java
#2 SPI擴展機制git
站在一個框架做者的角度來講,定義一個接口,本身默認給出幾個接口的實現類,同時容許框架的使用者也可以自定義接口的實現。如今一個簡單的問題就是:如何優雅的根據一個接口來獲取該接口的全部實現類呢?github
這就須要引出java的SPI機制了緩存
##2.1 SPI介紹與demo服務器
這些內容就再也不多說了,網上搜一下,一大堆,具體能夠參考這篇博客Java SPI機制簡介;app
我這裏給出一個簡單的demo:框架
定義一個接口:com.demo.dubbo.demo.spi.service.HelloServicejvm
接口的實現類:ide
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,實現者A一、A2。接口B,實現者B一、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; }
從上面能夠看到,進行注入的條件以下:
咱們知道一個接口的實現者可能有多個,此時到底注入哪個呢?
此時採起的策略是,並不去注入一個具體的實現者,而是注入一個動態生成的實現者,這個動態生成的實現者的邏輯是肯定的,可以根據不一樣的參數來使用不一樣的實現者實現相應的方法。這個動態生成的實現者的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的過程還須要補充一些細節內容:
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 結束語
下一篇文章就開始介紹下,服務器端暴漏服務和向註冊中心註冊服務的過程