擴展點配置:
約定:
在擴展類的jar包內,放置擴展點配置文件:META-INF/dubbo/接口全限定名,內容為:配置名=擴展實現類全限定名,多個實現類用換行符分隔。(摘自dubbo文檔)
示例:
假如我現在想使用自己定義的協議Myprotocol,在resources目錄下新建META-INF/dubbo/com.alibaba.dubbo.rpc.Protocol目錄文件,文件內容定義:
myprotocol=com.selrain.MyProtocol
實現類內容:
public class MyProtocol implements Protocol { @Override public int getDefaultPort() { return 1111111; } ... }
在我們的xml配置protocol屬性時就可以配置myprotocol啦,現在為了測試我們手動獲取下:
@SpringBootApplication public class Chapter2Application { public static void main(String[] args) { SpringApplication.run(Chapter2Application.class, args); Protocol p= ExtensionLoader.getExtensionLoader(Protocol.class).getExtension("myprotocol"); System.out.print(p.getDefaultPort()); } } 住:為了少寫點代碼直接上spring boot了
看看自己的控制台輸出吧,是不是調用了我們自定義的類了?
擴展點自動包裝:
自動Wrap擴展點的Wrapper類
ExtensionLoader會把加載擴展點時(通過擴展點配置文件中內容),如果該實現有拷貝構造函數,則判定為擴展點Wrapper類。
Wrapper類同樣實現了擴展點接口(摘自dubbo文檔)
什么意思呢?比方說Dubbo里面定義了ProtocolListenerWrapper、ProtocolFilterWrapper2各Wrapper類:
public class ProtocolListenerWrapper implements Protocol { private final Protocol protocol; public ProtocolListenerWrapper(Protocol protocol){ if (protocol == null) { throw new IllegalArgumentException("protocol == null"); } this.protocol = protocol; } ... public <T> Exporter<T> export(Invoker<T> invoker) throws RpcException { if (Constants.REGISTRY_PROTOCOL.equals(invoker.getUrl().getProtocol())) { return protocol.export(invoker); } return new ListenerExporterWrapper<T>(protocol.export(invoker), Collections.unmodifiableList(ExtensionLoader.getExtensionLoader(ExporterListener.class) .getActivateExtension(invoker.getUrl(), Constants.EXPORTER_LISTENER_KEY))); } public <T> Invoker<T> refer(Class<T> type, URL url) throws RpcException { if (Constants.REGISTRY_PROTOCOL.equals(url.getProtocol())) { return protocol.refer(type, url); } return new ListenerInvokerWrapper<T>(protocol.refer(type, url), Collections.unmodifiableList( ExtensionLoader.getExtensionLoader(InvokerListener.class) .getActivateExtension(url, Constants.INVOKER_LISTENER_KEY))); } ... } public class ProtocolFilterWrapper implements Protocol { private final Protocol protocol; public ProtocolFilterWrapper(Protocol protocol){ if (protocol == null) { throw new IllegalArgumentException("protocol == null"); } this.protocol = protocol; } public <T> Exporter<T> export(Invoker<T> invoker) throws RpcException { ... return protocol.export(invoker); } public <T> Invoker<T> refer(Class<T> type, URL url) throws RpcException { ... return protocol.refer(type, url); } public void destroy() { protocol.destroy(); } ... }
2個Wrapper類共同點就是都實現了Protocol接口,構造函數也都時Protocol參數,這樣做的好處是可以通過層層包裝來使各個類的邏輯處理分開;
ProtocolListenerWrapper===》ProtocolFilterWrapper===》DubboProtocol,Protocol的調用過程就是這樣的。
現在來測試一下:
@SpringBootApplication public class Chapter2Application { public static void main(String[] args) { SpringApplication.run(Chapter2Application.class, args); Protocol p= ExtensionLoader.getExtensionLoader(Protocol.class).getExtension("myprotocol"); System.out.print(p.getClass().getSimpleName()); } }
控制台輸出了ProtocolListenerWrapper,也就是說當我們調用myprotocol的方法時,會先經過層層包裝,處理,最后才調用myprotocol的方法,當然這個Wrapper我們自己也可以定義,當我們需要額外的做一些邏輯處理的時候,和上面的擴展點配置一樣。
擴展點自動裝配:
加載擴展點時,擴展點實現類的成員如果為其它擴展點類型,ExtensionLoader在會自動注入依賴的擴展點
對應文章底部圖中的injectExtension方法
private T injectExtension(T instance) { try { if (objectFactory != null) { for (Method method : instance.getClass().getMethods()) { if (method.getName().startsWith("set") && method.getParameterTypes().length == 1 && Modifier.isPublic(method.getModifiers())) { Class<?> pt = method.getParameterTypes()[0]; try { String property = method.getName().length() > 3 ? method.getName().substring(3, 4).toLowerCase() + method.getName().substring(4) : ""; Object object = objectFactory.getExtension(pt, property); if (object != null) { method.invoke(instance, object); } ... }
objectFactory 也是基於dubbo的spi擴展機制獲取,它有3個實現類,分別是AdaptiveExtensionFactory、SpiExtensionFactory、SpringExtensionFactory
objectFactory 在我們初始化ExtensionLoader類的時候已經初始化:(因為AdaptiveExtensionFactory打上了Adaptive注解,所以值為AdaptiveExtensionFactory)
private ExtensionLoader(Class<?> type) { this.type = type; objectFactory = (type == ExtensionFactory.class ? null : ExtensionLoader.getExtensionLoader(ExtensionFactory.class).getAdaptiveExtension()); }
AdaptiveExtensionFactory持有所有ExtensionFactory對象的集合,dubbo內部默認實現的對象工廠是SpiExtensionFactory和SpringExtensionFactory,他們經過TreeMap排好序的查找順序是優先先從SpiExtensionFactory獲取,如果返回空在從SpringExtensionFactory獲取。
1) SpiExtensionFactory工廠獲取要被注入的對象,就是要獲取dubbo spi擴展的實現,所以傳入的參數類型必須是接口類型並且接口上打上了@SPI注解,返回的是一個設配類對象。
public class SpiExtensionFactory implements ExtensionFactory {
public <T> T getExtension(Class<T> type, String name) {
if (type.isInterface() && type.isAnnotationPresent(SPI.class)) {
ExtensionLoader<T> loader = ExtensionLoader.getExtensionLoader(type);
if (loader.getSupportedExtensions().size() > 0) {
return loader.getAdaptiveExtension();
}
}
return null;
}
}
2)SpringExtensionFactory,Dubbo利用spring的擴展機制跟spring做了很好的融合。在發布或者去引用一個服務的時候,會把spring的容器添加到SpringExtensionFactory工廠集合中去, 當SpiExtensionFactory沒有獲取到對象的時候會遍歷SpringExtensionFactory中的spring容器來獲取要注入的對象
public class SpringExtensionFactory implements ExtensionFactory { ... @SuppressWarnings("unchecked") public <T> T getExtension(Class<T> type, String name) { for (ApplicationContext context : contexts) { if (context.containsBean(name)) { Object bean = context.getBean(name); if (type.isInstance(bean)) { return (T) bean; } } } return null; }
擴展點自適應
擴展點的Adaptive實例
ExtensionLoader注入的依賴擴展點是一個Adaptive實例,直到擴展點方法執行時才決定調用是一個擴展點實現。
Dubbo使用URL對象(包含了Key-Value)傳遞配置信息。
擴展點方法調用會有URL參數(或是參數有URL成員)
這樣依賴的擴展點也可以從URL拿到配置信息,所有的擴展點自己定好配置的Key后,配置信息從URL上從最外層傳入。URL在配置傳遞上即是一條總線(摘自Dubbo官方文檔)
比如我們的Protocol接口,由於沒有實現類打上Adaptive注解,所以生成javassist字節碼的方式創建的實現類:
package com.alibaba.dubbo.rpc; import com.alibaba.dubbo.common.extension.ExtensionLoader; public class Protocol$Adpative implements com.alibaba.dubbo.rpc.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)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)ExtensionLoader.getExtensionLoader(com.alibaba.dubbo.rpc.Protocol.class).getExtension(extName); return extension.refer(arg0, arg1); } }
可以看到通過url傳播,在url中拿到最終擴展點名字,最終調用擴展點的export方法
內部實現(重要方法):

參考:
http://blog.csdn.net/quhongwei_zhanqiu/article/details/41577235
