本文分析Dubbo服務暴露的實現原理,併進行詳細的代碼跟蹤與解析。 ...
一、框架設計
在官方《Dubbo 用戶指南》架構部分,給出了服務調用的整體架構和流程:
另外,在官方《Dubbo 開髮指南》框架設計部分,給出了整體設計:
以及暴露服務時序圖:
本文將根據以上幾張圖,分析服務暴露的實現原理,併進行詳細的代碼跟蹤與解析。
二、原理和源碼解析
2.1 標簽解析
從文章《Dubbo原理和源碼解析之標簽解析》中我們知道,<dubbo:service> 標簽會被解析成 ServiceBean。
ServiceBean 實現了 InitializingBean,在類載入完成之後會調用 afterPropertiesSet() 方法。在 afterPropertiesSet() 方法中,依次解析以下標簽信息:
- <dubbo:provider>
- <dubbo:application>
- <dubbo:module>
- <dubbo:registry>
- <dubbo:monitor>
- <dubbo:protocol>
ServiceBean 還實現了 ApplicationListener,在 Spring 容器初始化的時候會調用 onApplicationEvent 方法。ServiceBean 重寫了 onApplicationEvent 方法,實現了服務暴露的功能。
ServiceBean.java
public void onApplicationEvent(ApplicationEvent event) { if (ContextRefreshedEvent.class.getName().equals(event.getClass().getName())) { if (isDelay() && ! isExported() && ! isUnexported()) { if (logger.isInfoEnabled()) { logger.info("The service ready on spring started. service: " + getInterface()); } export(); } } }
2.2 延遲暴露
ServiceBean 擴展了 ServiceConfig,調用 export() 方法時由 ServiceConfig 完成服務暴露的功能實現。
ServiceConfig.java
public synchronized void export() { if (provider != null) { if (export == null) { export = provider.getExport(); } if (delay == null) { delay = provider.getDelay(); } } if (export != null && ! export.booleanValue()) { return; } if (delay != null && delay > 0) { Thread thread = new Thread(new Runnable() { public void run() { try { Thread.sleep(delay); } catch (Throwable e) { } doExport(); } }); thread.setDaemon(true); thread.setName("DelayExportServiceThread"); thread.start(); } else { doExport(); } }
由上面代碼可知,如果設置了 delay 參數,Dubbo 的處理方式是啟動一個守護線程在 sleep 指定時間後再 doExport。
2.3 參數檢查
在 ServiceConfig 的 doExport() 方法中會進行參數檢查和設置,包括:
- 泛化調用
- 本地實現
- 本地存根
- 本地偽裝
- 配置(application、registry、protocol等)
ServiceConfig.java
protected synchronized void doExport() { if (unexported) { throw new IllegalStateException("Already unexported!"); } if (exported) { return; } exported = true; if (interfaceName == null || interfaceName.length() == 0) { throw new IllegalStateException("<dubbo:service interface=\"\" /> interface not allow null!"); } checkDefault(); //省略 if (ref instanceof GenericService) { interfaceClass = GenericService.class; generic = true; } else { try { interfaceClass = Class.forName(interfaceName, true, Thread.currentThread() .getContextClassLoader()); } catch (ClassNotFoundException e) { throw new IllegalStateException(e.getMessage(), e); } checkInterfaceAndMethods(interfaceClass, methods); checkRef(); generic = false; } if(local !=null){ if(local=="true"){ local=interfaceName+"Local"; } Class<?> localClass; try { localClass = ClassHelper.forNameWithThreadContextClassLoader(local); } catch (ClassNotFoundException e) { throw new IllegalStateException(e.getMessage(), e); } if(!interfaceClass.isAssignableFrom(localClass)){ throw new IllegalStateException("The local implemention class " + localClass.getName() + " not implement interface " + interfaceName); } } if(stub !=null){ if(stub=="true"){ stub=interfaceName+"Stub"; } Class<?> stubClass; try { stubClass = ClassHelper.forNameWithThreadContextClassLoader(stub); } catch (ClassNotFoundException e) { throw new IllegalStateException(e.getMessage(), e); } if(!interfaceClass.isAssignableFrom(stubClass)){ throw new IllegalStateException("The stub implemention class " + stubClass.getName() + " not implement interface " + interfaceName); } } //此處省略:檢查並設置相關參數 doExportUrls(); }
2.4 多協議、多註冊中心
在檢查完參數之後,開始暴露服務。Dubbo 支持多協議和多註冊中心:
ServiceConfig.java
private void doExportUrls() { List<URL> registryURLs = loadRegistries(true); for (ProtocolConfig protocolConfig : protocols) { doExportUrlsFor1Protocol(protocolConfig, registryURLs); } }
2.5 組裝URL
針對每個協議、每個註冊中心,開始組裝 URL。
ServiceConfig.java
private void doExportUrlsFor1Protocol(ProtocolConfig protocolConfig, List<URL> registryURLs) { String name = protocolConfig.getName(); if (name == null || name.length() == 0) { name = "dubbo"; } //處理host //處理port Map<String, String> map = new HashMap<String, String>(); //設置參數到map // 導出服務 String contextPath = protocolConfig.getContextpath(); if ((contextPath == null || contextPath.length() == 0) && provider != null) { contextPath = provider.getContextpath(); } URL url = new URL(name, host, port, (contextPath == null || contextPath.length() == 0 ? "" : contextPath + "/") + path, map); if (ExtensionLoader.getExtensionLoader(ConfiguratorFactory.class) .hasExtension(url.getProtocol())) { url = ExtensionLoader.getExtensionLoader(ConfiguratorFactory.class) .getExtension(url.getProtocol()).getConfigurator(url).configure(url); } //此處省略:服務暴露(詳見 2.6 和 2.7) this.urls.add(url); }
2.6 本地暴露
如果配置 scope=none, 則不會進行服務暴露;如果沒有配置 scope 或者 scope=local,則會進行本地暴露。
ServiceConfig.java
//public static final String LOCAL_PROTOCOL = "injvm"; //public static final String LOCALHOST = "127.0.0.1"; private void doExportUrlsFor1Protocol(ProtocolConfig protocolConfig, List<URL> registryURLs) { //...... String scope = url.getParameter(Constants.SCOPE_KEY); //配置為none不暴露 if (! Constants.SCOPE_NONE.toString().equalsIgnoreCase(scope)) { //配置不是remote的情況下做本地暴露 (配置為remote,則表示只暴露遠程服務) if (!Constants.SCOPE_REMOTE.toString().equalsIgnoreCase(scope)) { exportLocal(url); } //...... } //...... } private void exportLocal(URL url) { if (!Constants.LOCAL_PROTOCOL.equalsIgnoreCase(url.getProtocol())) { URL local = URL.valueOf(url.toFullString()) .setProtocol(Constants.LOCAL_PROTOCOL) .setHost(NetUtils.LOCALHOST) .setPort(0); Exporter<?> exporter = protocol.export( proxyFactory.getInvoker(ref, (Class) interfaceClass, local)); exporters.add(exporter); logger.info("Export dubbo service " + interfaceClass.getName() +" to local registry"); } }
1. 暴露服務的時候,會通過代理創建 Invoker;
2. 本地暴露時使用 injvm 協議,injvm 協議是一個偽協議,它不開啟埠,不能被遠程調用,只在 JVM 內直接關聯,但執行 Dubbo 的 Filter 鏈。