7.2 服務本地暴露
服務暴露的流程其實就是下邊這樣(圖片來自:http://www.iteye.com/topic/1123039)
簡單看一下服務暴露的偽代碼:
1 /** 2 * dubbo 服務暴露為代碼 3 */ 4 public class DubboProviderSimpleCode { 5 private final List<Exporter<?>> exporters = new ArrayList<Exporter<?>>(); 6 /** 7 * 獲取註冊中心url列表 8 * [ registry://10.211.55.5:2181/com.alibaba.dubbo.registry.RegistryService?application=demo-provider&client=curator&dubbo=2.0.0&pid=2956®istry=zookeeper×tamp=1507004600231 ]9 */ 10 List<URL> registryURLs = loadRegistries();//獲取註冊中心url列表 11 for (ProtocolConfig protocolConfig : protocols) { 12 /** 13 * 創建協議url 14 * dubbo://10.243.62.133:20880/com.alibaba.dubbo.demo.DemoService?anyhost=true&application=demo-provider&dubbo=2.0.0&generic=false&interface=com.alibaba.dubbo.demo.DemoService&methods=sayHello&pid=2956&side=provider×tamp=150700462595715 */ 16 URL url = new URL(name, host, port, path, map); 17 /** 18 * 本地暴露 19 */ 20 if (<dubbo:service scope!=remote>) { 21 /** 22 * 構造injvm協議的url 23 * injvm://127.0.0.1/com.alibaba.dubbo.demo.DemoService?anyhost=true&application=demo-provider&dubbo=2.0.0&generic=false&interface=com.alibaba.dubbo.demo.DemoService&methods=sayHello&pid=2956&side=provider×tamp=150700462595724 */ 25 URL local = URL.valueOf(url.toFullString()) 26 .setProtocol(Constants.LOCAL_PROTOCOL) 27 .setHost(NetUtils.LOCALHOST) 28 .setPort(0); 29 Invoker<?> invoker = proxyFactory.getInvoker(ref, (Class) interfaceClass, local); 30 Exporter<?> exporter = protocol.export(invoker); 31 exporters.add(exporter); 32 } 33 /** 34 * 遠程暴露 35 */ 36 if (<dubbo:service scope!=local>) { 37 for (URL registryURL : registryURLs) { 38 Invoker<?> invoker = proxyFactory.getInvoker(ref, (Class) interfaceClass, registryURL.addParameterAndEncoded(Constants.EXPORT_KEY, url.toFullString())); 39 Exporter<?> exporter = protocol.export(invoker); 40 exporters.add(exporter); 41 } 42 } 43 } 44 }
本地暴露:
- 通過JavassistProxyFactory(默認)將具體的實現類包裝成AbstractProxyInvoker實例
- InjvmProtocol將上述的AbstractProxyInvoker實例轉換成Exporter
遠程暴露:
- 通過JavassistProxyFactory(默認)將具體的實現類包裝成AbstractProxyInvoker實例
- DubboProtocol將上述的AbstractProxyInvoker實例轉換成Exporter
本節來看本地暴露。首先給出整個本地服務暴露的調用鏈。
1 ServiceBean.onApplicationEvent(ApplicationEvent event) 2 -->ServiceConfig.export() 3 -->doExport() 4 -->doExportUrls() 5 -->loadRegistries(boolean provider) // 6 -->doExportUrlsFor1Protocol(ProtocolConfig protocolConfig, List<URL> registryURLs) 7 -->Wrapper getWrapper(Class<?> c) 8 -->makeWrapper(Class<?> c) 9 -->new URL(name, host, port, (contextPath == null || contextPath.length() == 0 ? "" : contextPath + "/") + path, map) 10 11 <!-- 本地暴露 --> 12 -->exportLocal(url) 13 -->構造injvm協議的url:injvmUrl 14 <!-- 1 將ref包裝成Invoker --> 15 -->ProxyFactory$Adaptive.getInvoker(ref實例, interfaceClass, injvmUrl) 16 -->ExtensionLoader.getExtensionLoader(com.alibaba.dubbo.rpc.ProxyFactory.class).getExtension("javassist") 17 -->StubProxyFactoryWrapper.getInvoker(T proxy, Class<T> type, URL url) 18 -->JavassistProxyFactory.getInvoker(T proxy, Class<T> type, URL url) 19 -->Wrapper getWrapper(Class<?> c) 20 -->makeWrapper(Class<?> c) 21 -->new AbstractProxyInvoker<T>(ref實例, interfaceClass, injvmUrl) 22 <!-- 2 將Invoker暴露為Exporter --> 23 -->Protocol$Adaptive.export(Invoker<T> invoker) 24 -->ExtensionLoader.getExtensionLoader(com.alibaba.dubbo.rpc.Protocol.class).getExtension("injvm") 25 -->ProtocolListenerWrapper.export(Invoker<T> invoker) 26 -->ProtocolFilterWrapper.export(Invoker<T> invoker) 27 -->buildInvokerChain(final Invoker<T> invoker, key:"service.filter", group:"provider") 28 -->ExtensionLoader.getExtensionLoader(Filter.class).getActivateExtension(injvmUrl, "service.filter", "provider") 29 -->InjvmProtocol.export(Invoker<T> invoker) 30 -->new InjvmExporter(Invoker<T> invoker, key:"com.alibaba.dubbo.demo.DemoService", Map<String, Exporter<?>> exporterMap) 31 -->InjvmExporter.exporterMap({"com.alibaba.dubbo.demo.DemoService" -> "injvm://127.0.0.1/com.alibaba.dubbo.demo.DemoService?anyhost=true&application=demo-provider&dubbo=2.0.0&generic=false&interface=com.alibaba.dubbo.demo.DemoService&methods=sayHello&pid=3207&side=provider×tamp=1507009133930"}) 32 -->new ListenerExporterWrapper(Exporter<T> exporter, List<ExporterListener> listeners) : 使用listener包裝invoker 33 <!-- 3 將Invoker暴露為Exporter --> 34 -->ServiceConfig#exporters.add(exporter)
本地暴露的代碼如下:
1 /** 2 * 本地暴露 3 * 1 根據傳進來的url(例如dubbo協議的url)構造injvm協議的url 4 * injvm://127.0.0.1/com.alibaba.dubbo.demo.DemoService?anyhost=true&application=demo-provider&dubbo=2.0.0&generic=false&interface=com.alibaba.dubbo.demo.DemoService&methods=sayHello&pid=2999&side=provider×tamp=1507005507343 5 * 6 * 2 將ref包裝為AbstractProxyInvoker實例 7 * 3 將AbstractProxyInvoker實例轉換為InjvmExporter 8 * 9 * @param url dubbo://10.243.62.133:20880/com.alibaba.dubbo.demo.DemoService?anyhost=true&application=demo-provider&dubbo=2.0.0&generic=false&interface=com.alibaba.dubbo.demo.DemoService&methods=sayHello&pid=2999&side=provider×tamp=1507005507343 10 */ 11 @SuppressWarnings({"unchecked", "rawtypes"}) 12 private void exportLocal(URL url) { 13 if (!Constants.LOCAL_PROTOCOL.equalsIgnoreCase(url.getProtocol())) { 14 URL local = URL.valueOf(url.toFullString()).setProtocol(Constants.LOCAL_PROTOCOL).setHost(NetUtils.LOCALHOST).setPort(0); 15 Invoker<?> invoker = proxyFactory.getInvoker(ref, (Class) interfaceClass, local); 16 Exporter<?> exporter = protocol.export(invoker); 17 exporters.add(exporter); 18 logger.info("Export dubbo service " + interfaceClass.getName() + " to local registry"); 19 } 20 }
為了清晰,這裏對exportLocal(URL url)做了稍稍的改動。整體流程如下:
1 首先將dubbo協議的url,改成了injvm協議的url:local;
2 將具體服務類ref通過proxyFactory包裝成AbstractProxyInvoker實例;
3 將AbstractProxyInvoker實例轉化為Exporter實例;
4 最後將生成的Exporter實例存放在ServiceConfig的List<Exporter> exporters中。
一 具體服務包裝成AbstractProxyInvoker實例
Invoker<?> invoker = proxyFactory.getInvoker(ref, (Class) interfaceClass, local);
具體服務:com.alibaba.dubbo.demo.provider.DemoServiceImpl。調用鏈如下:
1 <!-- 1 將ref包裝成Invoker --> 2 -->ProxyFactory$Adaptive.getInvoker(ref實例, interfaceClass, injvmUrl) 3 -->ExtensionLoader.getExtensionLoader(com.alibaba.dubbo.rpc.ProxyFactory.class).getExtension("javassist") 4 -->StubProxyFactoryWrapper.getInvoker(T proxy, Class<T> type, URL url) 5 -->JavassistProxyFactory.getInvoker(T proxy, Class<T> type, URL url) 6 -->Wrapper getWrapper(Class<?> c) 7 -->makeWrapper(Class<?> c) 8 -->new AbstractProxyInvoker<T>(ref實例, interfaceClass, injvmUrl)
1 ProxyFactory$Adaptive.getInvoker(java.lang.Object arg0, java.lang.Class arg1, com.alibaba.dubbo.common.URL arg2)
1 public com.alibaba.dubbo.rpc.Invoker getInvoker(java.lang.Object arg0, java.lang.Class arg1, com.alibaba.dubbo.common.URL arg2) throws com.alibaba.dubbo.rpc.RpcException { 2 if (arg2 == null) 3 throw new IllegalArgumentException("url == null"); 4 com.alibaba.dubbo.common.URL url = arg2; 5 String extName = url.getParameter("proxy", "javassist"); 6 if(extName == null) 7 throw new IllegalStateException("Fail to get extension(com.alibaba.dubbo.rpc.ProxyFactory) name from url(" + url.toString() + ") use keys([proxy])"); 8 com.alibaba.dubbo.rpc.ProxyFactory extension = (com.alibaba.dubbo.rpc.ProxyFactory)ExtensionLoader.getExtensionLoader(com.alibaba.dubbo.rpc.ProxyFactory.class).getExtension(extName); 9 return extension.getInvoker(arg0, arg1, arg2); 10 }
- arg0: com.alibaba.dubbo.demo.provider.DemoServiceImpl 實例
- arg1: interface com.alibaba.dubbo.demo.DemoService Class對象
- arg2: injvm://127.0.0.1/com.alibaba.dubbo.demo.DemoService?anyhost=true&application=demo-provider&dubbo=2.0.0&generic=false&interface=com.alibaba.dubbo.demo.DemoService&methods=sayHello&pid=3275&side=provider×tamp=1507010529605
這裏,首先是參數檢查和賦值。之後獲取key為javassist的ProxyFactory實現類:JavassistProxyFactory,該類會在spi進行aop的時候包裹在StubProxyFactoryWrapper中,最終調用鏈為:
ProxyFactory$Adaptive -> StubProxyFactoryWrapper -> JavassistProxyFactory
2 StubProxyFactoryWrapper.getInvoker(T proxy, Class<T> type, URL url)
1 public <T> Invoker<T> getInvoker(T proxy, Class<T> type, URL url) throws RpcException { 2 return proxyFactory.getInvoker(proxy, type, url); 3 }
這裏的proxyFactory就是JavassistProxyFactory實例了。
3 JavassistProxyFactory.getInvoker(T proxy, Class<T> type, URL url)
1 public <T> Invoker<T> getInvoker(T proxy, Class<T> type, URL url) { 2 // TODO Wrapper類不能正確處理帶$的類名 3 final Wrapper wrapper = Wrapper.getWrapper(proxy.getClass().getName().indexOf(‘$‘) < 0 ? proxy.getClass() : type); 4 return new AbstractProxyInvoker<T>(proxy, type, url) { 5 @Override 6 protected Object doInvoke(T proxy, String methodName, 7 Class<?>[] parameterTypes, 8 Object[] arguments) throws Throwable { 9 return wrapper.invokeMethod(proxy, methodName, parameterTypes, arguments); 10 } 11 }; 12 }
這裏首先會創建一個class com.alibaba.dubbo.demo.provider.DemoServiceImpl的包裝類Wrapper(該類後續去講)。之後創建一個AbstractProxyInvoker實例。
1 package com.alibaba.dubbo.rpc.proxy; 2 3 import com.alibaba.dubbo.common.URL; 4 import com.alibaba.dubbo.rpc.Invocation; 5 import com.alibaba.dubbo.rpc.Invoker; 6 import com.alibaba.dubbo.rpc.Result; 7 import com.alibaba.dubbo.rpc.RpcException; 8 import com.alibaba.dubbo.rpc.RpcResult; 9 10 import java.lang.reflect.InvocationTargetException; 11 12 public abstract class AbstractProxyInvoker<T> implements Invoker<T> { 13 private final T proxy; 14 private final Class<T> type; 15 private final URL url; 16 17 public AbstractProxyInvoker(T proxy, Class<T> type, URL url) { 18 if (proxy == null) { 19 throw new IllegalArgumentException("proxy == null"); 20 } 21 if (type == null) { 22 throw new IllegalArgumentException("interface == null"); 23 } 24 if (!type.isInstance(proxy)) { 25 throw new IllegalArgumentException(proxy.getClass().getName() + " not implement interface " + type); 26 } 27 this.proxy = proxy; 28 this.type = type; 29 this.url = url; 30 } 31 32 public Class<T> getInterface() { 33 return type; 34 } 35 36 public URL getUrl() { 37 return url; 38 } 39 40 public boolean isAvailable() { 41 return true; 42 } 43 44 public void destroy() { 45 } 46 47 public Result invoke(Invocation invocation) throws RpcException { 48 try { 49 return new RpcResult(doInvoke(proxy, invocation.getMethodName(), invocation.getParameterTypes(), invocation.getArguments())); 50 } catch (InvocationTargetException e) { 51 return new RpcResult(e.getTargetException()); 52 } catch (Throwable e) { 53 throw new RpcException("Failed to invoke remote proxy method " + invocation.getMethodName() + " to " + getUrl() + ", cause: " + e.getMessage(), e); 54 } 55 } 56 57 /** 58 * 由創建的實例來復寫 59 * @param proxy 60 * @param methodName 61 * @param parameterTypes 62 * @param arguments 63 * @return 64 * @throws Throwable 65 */ 66 protected abstract Object doInvoke(T proxy, String methodName, Class<?>[] parameterTypes, Object[] arguments) throws Throwable; 67 68 @Override 69 public String toString() { 70 return getInterface() + " -> " + getUrl() == null ? " " : getUrl().toString(); 71 } 72 }
其中:
- proxy: com.alibaba.dubbo.demo.provider.DemoServiceImpl 實例
- type: interface com.alibaba.dubbo.demo.DemoService Class對象
- url: injvm://127.0.0.1/com.alibaba.dubbo.demo.DemoService?anyhost=true&application=demo-provider&dubbo=2.0.0&generic=false&interface=com.alibaba.dubbo.demo.DemoService&methods=sayHello&pid=3275&side=provider×tamp=1507010529605
這樣,具體服務就被包裝成AbstractProxyInvoker實例了。
二 AbstractProxyInvoker實例轉換為Exporter
Exporter<?> exporter = protocol.export(invoker);
調用鏈如下:
1 <!-- 2 將Invoker暴露為Exporter --> 2 -->Protocol$Adaptive.export(Invoker<T> invoker) 3 -->ExtensionLoader.getExtensionLoader(com.alibaba.dubbo.rpc.Protocol.class).getExtension("injvm") 4 -->ProtocolListenerWrapper.export(Invoker<T> invoker) 5 -->ProtocolFilterWrapper.export(Invoker<T> invoker) 6 -->buildInvokerChain(final Invoker<T> invoker, key:"service.filter", group:"provider") 7 -->ExtensionLoader.getExtensionLoader(Filter.class).getActivateExtension(injvmUrl, "service.filter", "provider") 8 -->InjvmProtocol.export(Invoker<T> invoker) 9 -->new InjvmExporter(Invoker<T> invoker, key:"com.alibaba.dubbo.demo.DemoService", Map<String, Exporter<?>> exporterMap) 10 -->InjvmExporter.exporterMap({"com.alibaba.dubbo.demo.DemoService" -> "injvm://127.0.0.1/com.alibaba.dubbo.demo.DemoService?anyhost=true&application=demo-provider&dubbo=2.0.0&generic=false&interface=com.alibaba.dubbo.demo.DemoService&methods=sayHello&pid=3207&side=provider×tamp=1507009133930"}) 11 -->new ListenerExporterWrapper(Exporter<T> exporter, List<ExporterListener> listeners) : 使用listener包裝invoker
1 Protocol$Adaptive.export(Invoker<T> invoker)
1 public com.alibaba.dubbo.rpc.Exporter export(com.alibaba.dubbo.rpc.Invoker arg0) throws com.alibaba.dubbo.rpc.RpcException { 2 if (arg0 == null) 3 throw new IllegalArgumentException("com.alibaba.dubbo.rpc.Invoker argument == null"); 4 if (arg0.getUrl() == null) 5 throw new IllegalArgumentException("com.alibaba.dubbo.rpc.Invoker argument getUrl() == null"); 6 com.alibaba.dubbo.common.URL url = arg0.getUrl(); 7 String extName = (url.getProtocol() == null ? "dubbo" : url.getProtocol()); 8 if(extName == null) 9 throw new IllegalStateException("Fail to get extension(com.alibaba.dubbo.rpc.Protocol) name from url(" + url.toString() + ") use keys([protocol])"); 10 com.alibaba.dubbo.rpc.Protocol extension = (com.alibaba.dubbo.rpc.Protocol)ExtensionLoader.getExtensionLoader(com.alibaba.dubbo.rpc.Protocol.class).getExtension(extName); 11 return extension.export(arg0); 12 }
- arg0:上邊創建出來的AbstractProxyInvoker實例。
這裏,首先是參數檢查和賦值。之後獲取key為injvm的Protocol實現類:InjvmProtocol,該類會在spi進行aop的時候被ProtocolFilterWrapper和ProtocolListenerWrapper遞歸包裹,最終調用鏈為:
ProxyFactory$Adaptive -> ProtocolListenerWrapper -> ProtocolFilterWrapper -> InjvmProtocol
2 ProtocolListenerWrapper.export(Invoker<T> invoker)
1 public <T> Exporter<T> export(Invoker<T> invoker) throws RpcException { 2 if (Constants.REGISTRY_PROTOCOL.equals(invoker.getUrl().getProtocol())) { 3 return protocol.export(invoker); 4 } 5 return new ListenerExporterWrapper<T>(protocol.export(invoker), 6 Collections.unmodifiableList(ExtensionLoader.getExtensionLoader(ExporterListener.class).getActivateExtension(invoker.getUrl(), Constants.EXPORTER_LISTENER_KEY))); 7 }
這裏先調用ProtocolFilterWrapper.export(Invoker<T> invoker),之後獲取listener,最後進行遞歸包裹。(這裏沒有listener)
3 ProtocolFilterWrapper.export(Invoker<T> invoker)
1 public <T> Exporter<T> export(Invoker<T> invoker) throws RpcException { 2 if (Constants.REGISTRY_PROTOCOL.equals(invoker.getUrl().getProtocol())) { 3 return protocol.export(invoker); 4 } 5 return protocol.export(buildInvokerChain(invoker, Constants.SERVICE_FILTER_KEY, Constants.PROVIDER)); 6 }
這裏首先使用filter對invoker進行了遞歸包裹,之後使用InjvmProtocol將包裹後的invoker轉化為InjvmExporter。
buildInvokerChain(final Invoker<T> invoker, String key, String group)
1 /** 2 * 1 根據key從url中獲取相應的filter的values,再根據這個values和group去獲取類上帶有@Active註解的filter集合 3 * 2 之後將這些filter對傳入的invoker進行遞歸包裝層invoker(就是一個鏈表) 4 */ 5 private static <T> Invoker<T> buildInvokerChain(final Invoker<T> invoker, String key, String group) { 6 Invoker<T> last = invoker; 7 List<Filter> filters = ExtensionLoader.getExtensionLoader(Filter.class).getActivateExtension(invoker.getUrl(), key, group); 8 if (filters.size() > 0) { 9 for (int i = filters.size() - 1; i >= 0; i--) { 10 final Filter filter = filters.get(i); 11 final Invoker<T> next = last; 12 last = new Invoker<T>() { 13 14 public Class<T> getInterface() { 15 return invoker.getInterface(); 16 } 17 18 public URL getUrl() { 19 return invoker.getUrl(); 20 } 21 22 public boolean isAvailable() { 23 return invoker.isAvailable(); 24 } 25 26 public Result invoke(Invocation invocation) throws RpcException { 27 return filter.invoke(next, invocation); 28 } 29 30 public void destroy() { 31 invoker.destroy(); 32 } 33 34 @Override 35 public String toString() { 36 return invoker.toString(); 37 } 38 }; 39 } 40 } 41 return last; 42 }
這裏:
- invoker:之前創建出來的AbstractProxyInvoker實例;
- key:service.filter
- group:provider
List<Filter> filters = ExtensionLoader.getExtensionLoader(Filter.class).getActivateExtension(invoker.getUrl(), key, group);
這句代碼,是:根據key從url中獲取相應的filter的values,再根據這個values和group去獲取類上帶有@Active註解的filter集合。這一塊兒具體的代碼可以查看講解spi中的loadFile方法。最終會獲取到8個filter,關於filter,後續會說。
4 InjvmProtocol.export(Invoker<T> invoker)
1 public <T> Exporter<T> export(Invoker<T> invoker) throws RpcException { 2 return new InjvmExporter<T>(invoker, invoker.getUrl().getServiceKey(), exporterMap); 3 }
1 class InjvmExporter<T> extends AbstractExporter<T> { 2 private final String key; 3 private final Map<String, Exporter<?>> exporterMap; 4 5 InjvmExporter(Invoker<T> invoker, String key, Map<String, Exporter<?>> exporterMap) { 6 super(invoker); 7 this.key = key; 8 this.exporterMap = exporterMap; 9 exporterMap.put(key, this); 10 } 11 12 public void unexport() { 13 super.unexport(); 14 exporterMap.remove(key); 15 } 16 }
最終的InjvmExporter實例:
- key = "com.alibaba.dubbo.demo.DemoService"
- exporterMap: { "com.alibaba.dubbo.demo.DemoService" -> 當前的InjvmExporter實例 }
- Invoker<T> invoker = 被filter進行遞歸包裹後的Invoker
最終的ServiceConfig的exporters列表:
- List<Exporter<?>> exporters = [ 上邊的injvmExporter實例 ]
為什麽要有本地暴露?
同一個jvm中的服務,相互調用不需要通過遠程註冊中心,但是又想使用filter鏈,可以使用本地暴露。
https://dubbo.gitbooks.io/dubbo-user-book/demos/local-call.html:
“本地調用使用了 injvm 協議,是一個偽協議,它不開啟端口,不發起遠程調用,只在 JVM 內直接關聯,但執行 Dubbo 的 Filter 鏈。”
7.2 服務本地暴露