dubbo源碼解析(四十四)服務暴露過程

dubbo服務暴露過程

目標:從源碼的角度分析服務暴露過程。

前言

原本這一篇一個寫異步化改造的內容,可是最近我一直在想,某一部分的優化改造該怎麼去撰寫才能更加的讓讀者理解。我以爲仍是須要先從整個調用鏈入手,先弄清楚了該功能在哪個時機發生的,說通俗一點,這塊代碼是何時或者什麼場景被執行的,而後再去分析內部是如何實現,最後闡述這樣改造的好處。html

我在前面的文章都不多說起各個調用鏈的關係,各模塊之間也沒有串起來,而且要講解異步化改造我認爲先弄懂服務的暴露和引用過程是很是有必要的,因此我將用兩片文章來說解服務暴露和服務引用的過程。java

服務暴露過程

服務暴露過程大體可分爲三個部分:正則表達式

  1. 前置工做,主要用於檢查參數,組裝 URL。
  2. 導出服務,包含暴露服務到本地 (JVM),和暴露服務到遠程兩個過程。
  3. 向註冊中心註冊服務,用於服務發現。

暴露起點

Spring中有一個ApplicationListener接口,其中定義了一個onApplicationEvent()方法,在當容器內發生任何事件時,此方法都會被觸發。spring

Dubbo中ServiceBean類實現了該接口,而且實現了onApplicationEvent方法:apache

@Override
public void onApplicationEvent(ContextRefreshedEvent event) {
      // 若是服務沒有被暴露而且服務沒有被取消暴露,則打印日誌
    if (!isExported() && !isUnexported()) {
        if (logger.isInfoEnabled()) {
            logger.info("The service ready on spring started. service: " + getInterface());
        }
          // 導出
        export();
    }
}

只要服務沒有被暴露而且服務沒有被取消暴露,就暴露服務。執行export方法。接下來就是跟官網的時序圖有關,對照着時序圖來看下面的過程。segmentfault

dubbo-export

我會在下面的小標題加上時序圖的每一步操做範圍,好比下面要講到的前置工做其實就是時序圖中的1:export(),那我會在標題括號裏面寫1。數組

其中服務暴露的第八步已經沒有了。緩存

前置工做(1)

前置工做主要包含兩個部分,分別是配置檢查,以及 URL 裝配。在暴露服務以前,Dubbo 須要檢查用戶的配置是否合理,或者爲用戶補充缺省配置。配置檢查完成後,接下來須要根據這些配置組裝 URL。在 Dubbo 中,URL 的做用十分重要。Dubbo 使用 URL 做爲配置載體,全部的拓展點都是經過 URL 獲取配置。服務器

配置檢查

在調用export方法以後,執行的是ServiceConfig中的export方法。app

public synchronized void export() {
    //檢查而且更新配置
    checkAndUpdateSubConfigs();

    // 若是不該該暴露,則直接結束
    if (!shouldExport()) {
        return;
    }

    // 若是使用延遲加載,則延遲delay時間後暴露服務
    if (shouldDelay()) {
        delayExportExecutor.schedule(this::doExport, delay, TimeUnit.MILLISECONDS);
    } else {
        // 暴露服務
        doExport();
    }
}

能夠看到首先作的就是對配置的檢查和更新,執行的是ServiceConfig中的checkAndUpdateSubConfigs方法。而後檢測是否應該暴露,若是不該該暴露,則直接結束,而後檢測是否配置了延遲加載,若是是,則使用定時器來實現延遲加載的目的。

checkAndUpdateSubConfigs()
public void checkAndUpdateSubConfigs() {
    // Use default configs defined explicitly on global configs
    // 用於檢測 provider、application 等核心配置類對象是否爲空,
    // 若爲空,則嘗試從其餘配置類對象中獲取相應的實例。
    completeCompoundConfigs();
    // Config Center should always being started first.
    // 開啓配置中心
    startConfigCenter();
    // 檢測 provider 是否爲空,爲空則新建一個,並經過系統變量爲其初始化
    checkDefault();
    // 檢查application是否爲空
    checkApplication();
    // 檢查註冊中心是否爲空
    checkRegistry();
    // 檢查protocols是否爲空
    checkProtocol();
    this.refresh();
    // 覈對元數據中心配置是否爲空
    checkMetadataReport();

    // 服務接口名不能爲空,不然拋出異常
    if (StringUtils.isEmpty(interfaceName)) {
        throw new IllegalStateException("<dubbo:service interface=\"\" /> interface not allow null!");
    }

    // 檢測 ref 是否爲泛化服務類型
    if (ref instanceof GenericService) {
        // 設置interfaceClass爲GenericService
        interfaceClass = GenericService.class;
        if (StringUtils.isEmpty(generic)) {
            // 設置generic = true
            generic = Boolean.TRUE.toString();
        }
    } else {
        try {
            // 得到接口類型
            interfaceClass = Class.forName(interfaceName, true, Thread.currentThread()
                    .getContextClassLoader());
        } catch (ClassNotFoundException e) {
            throw new IllegalStateException(e.getMessage(), e);
        }
        // 對 interfaceClass,以及 <dubbo:method> 標籤中的必要字段進行檢查
        checkInterfaceAndMethods(interfaceClass, methods);
        // 對 ref 合法性進行檢測
        checkRef();
        generic = Boolean.FALSE.toString();
    }
    // stub local同樣都是配置本地存根
    if (local != null) {
        if ("true".equals(local)) {
            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 implementation class " + localClass.getName() + " not implement interface " + interfaceName);
        }
    }
    if (stub != null) {
        if ("true".equals(stub)) {
            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 implementation class " + stubClass.getName() + " not implement interface " + interfaceName);
        }
    }
    // 本地存根合法性校驗
    checkStubAndLocal(interfaceClass);
    // mock合法性校驗
    checkMock(interfaceClass);
}

能夠看到,該方法中是對各種配置的校驗,而且更新部分配置。其中檢查的細節我就不展開,由於服務暴露整個過程纔是本文重點。

在通過shouldExport()和shouldDelay()兩個方法檢測後,會執行ServiceConfig的doExport()方法

doExport()
protected synchronized void doExport() {
    // 若是調用不暴露的方法,則unexported值爲true
    if (unexported) {
        throw new IllegalStateException("The service " + interfaceClass.getName() + " has already unexported!");
    }
    // 若是服務已經暴露了,則直接結束
    if (exported) {
        return;
    }
    // 設置已經暴露
    exported = true;

    // 若是path爲空,則賦值接口名稱
    if (StringUtils.isEmpty(path)) {
        path = interfaceName;
    }
    // 多協議多註冊中心暴露服務
    doExportUrls();
}

該方法就是對於服務是否暴露在一次校驗,而後會執行ServiceConfig的doExportUrls()方法,對於多協議多註冊中心暴露服務進行支持。

doExportUrls()
private void doExportUrls() {
    // 加載註冊中心連接
    List<URL> registryURLs = loadRegistries(true);
    // 遍歷 protocols,並在每一個協議下暴露服務
    for (ProtocolConfig protocolConfig : protocols) {
        // 以path、group、version來做爲服務惟一性肯定的key
        String pathKey = URL.buildKey(getContextPath(protocolConfig).map(p -> p + "/" + path).orElse(path), group, version);
        ProviderModel providerModel = new ProviderModel(pathKey, ref, interfaceClass);
        ApplicationModel.initProviderModel(pathKey, providerModel);
        // 組裝 URL
        doExportUrlsFor1Protocol(protocolConfig, registryURLs);
    }
}

從該方法能夠看到:

  • loadRegistries()方法是加載註冊中心連接。
  • 服務的惟一性是經過path、group、version一塊兒肯定的。
  • doExportUrlsFor1Protocol()方法開始組裝URL。
loadRegistries()
protected List<URL> loadRegistries(boolean provider) {
    // check && override if necessary
    List<URL> registryList = new ArrayList<URL>();
    // 若是registries爲空,直接返回空集合
    if (CollectionUtils.isNotEmpty(registries)) {
        // 遍歷註冊中心配置集合registries
        for (RegistryConfig config : registries) {
            // 得到地址
            String address = config.getAddress();
            // 若地址爲空,則設置爲0.0.0.0
            if (StringUtils.isEmpty(address)) {
                address = Constants.ANYHOST_VALUE;
            }
            // 若是地址爲N/A,則跳過
            if (!RegistryConfig.NO_AVAILABLE.equalsIgnoreCase(address)) {
                Map<String, String> map = new HashMap<String, String>();
                // 添加 ApplicationConfig 中的字段信息到 map 中
                appendParameters(map, application);
                // 添加 RegistryConfig 字段信息到 map 中
                appendParameters(map, config);
                // 添加path
                map.put(Constants.PATH_KEY, RegistryService.class.getName());
                // 添加 協議版本、發佈版本,時間戳 等信息到 map 中
                appendRuntimeParameters(map);
                // 若是map中沒有protocol,則默認爲使用dubbo協議
                if (!map.containsKey(Constants.PROTOCOL_KEY)) {
                    map.put(Constants.PROTOCOL_KEY, Constants.DUBBO_PROTOCOL);
                }
                // 解析獲得 URL 列表,address 可能包含多個註冊中心 ip,所以解析獲得的是一個 URL 列表
                List<URL> urls = UrlUtils.parseURLs(address, map);

                // 遍歷URL 列表
                for (URL url : urls) {
                    // 將 URL 協議頭設置爲 registry
                    url = URLBuilder.from(url)
                            .addParameter(Constants.REGISTRY_KEY, url.getProtocol())
                            .setProtocol(Constants.REGISTRY_PROTOCOL)
                            .build();
                    // 經過判斷條件,決定是否添加 url 到 registryList 中,條件以下:
                    // 若是是服務提供者,而且是註冊中心服務   或者   是消費者端,而且是訂閱服務
                    // 則加入到registryList
                    if ((provider && url.getParameter(Constants.REGISTER_KEY, true))
                            || (!provider && url.getParameter(Constants.SUBSCRIBE_KEY, true))) {
                        registryList.add(url);
                    }
                }
            }
        }
    }
    return registryList;
}

組裝URL

我在之前的文章也提到過,dubbo內部用URL來攜帶各種配置,貫穿整個調用鏈,它就是配置的載體。服務的配置被組裝到URL中就是從這裏開始,上面提到遍歷每一個協議配置,在每一個協議下都暴露服務,就會執行ServiceConfig的doExportUrlsFor1Protocol()方法,該方法前半部分實現了組裝URL的邏輯,後半部分實現了暴露dubbo服務等邏輯,其中爲用分割線分隔了。

doExportUrlsFor1Protocol()
private void doExportUrlsFor1Protocol(ProtocolConfig protocolConfig, List<URL> registryURLs) {
    // 獲取協議名
    String name = protocolConfig.getName();
    // 若是爲空,則是默認的dubbo
    if (StringUtils.isEmpty(name)) {
        name = Constants.DUBBO;
    }

    Map<String, String> map = new HashMap<String, String>();
    // 設置服務提供者冊
    map.put(Constants.SIDE_KEY, Constants.PROVIDER_SIDE);

    // 添加 協議版本、發佈版本,時間戳 等信息到 map 中
    appendRuntimeParameters(map);
    // 添加metrics、application、module、provider、protocol的全部信息到map
    appendParameters(map, metrics);
    appendParameters(map, application);
    appendParameters(map, module);
    appendParameters(map, provider, Constants.DEFAULT_KEY);
    appendParameters(map, protocolConfig);
    appendParameters(map, this);
    // 若是method的配置列表不爲空
    if (CollectionUtils.isNotEmpty(methods)) {
        // 遍歷method配置列表
        for (MethodConfig method : methods) {
            // 把方法名加入map
            appendParameters(map, method, method.getName());
            // 添加 MethodConfig 對象的字段信息到 map 中,鍵 = 方法名.屬性名。
            // 好比存儲 <dubbo:method name="sayHello" retries="2"> 對應的 MethodConfig,
            // 鍵 = sayHello.retries,map = {"sayHello.retries": 2, "xxx": "yyy"}
            String retryKey = method.getName() + ".retry";
            if (map.containsKey(retryKey)) {
                String retryValue = map.remove(retryKey);
                // 若是retryValue爲false,則不重試,設置值爲0
                if ("false".equals(retryValue)) {
                    map.put(method.getName() + ".retries", "0");
                }
            }
            // 得到ArgumentConfig列表
            List<ArgumentConfig> arguments = method.getArguments();
            if (CollectionUtils.isNotEmpty(arguments)) {
                // 遍歷ArgumentConfig列表
                for (ArgumentConfig argument : arguments) {
                    // convert argument type
                    // // 檢測 type 屬性是否爲空,或者空串
                    if (argument.getType() != null && argument.getType().length() > 0) {
                        // 利用反射獲取該服務的全部方法集合
                        Method[] methods = interfaceClass.getMethods();
                        // visit all methods
                        if (methods != null && methods.length > 0) {
                            // 遍歷全部方法
                            for (int i = 0; i < methods.length; i++) {
                                // 得到方法名
                                String methodName = methods[i].getName();
                                // target the method, and get its signature
                                // 找到目標方法
                                if (methodName.equals(method.getName())) {
                                    // 經過反射獲取目標方法的參數類型數組 argtypes
                                    Class<?>[] argtypes = methods[i].getParameterTypes();
                                    // one callback in the method
                                    // 若是下標爲-1
                                    if (argument.getIndex() != -1) {
                                        // 檢測 argType 的名稱與 ArgumentConfig 中的 type 屬性是否一致
                                        if (argtypes[argument.getIndex()].getName().equals(argument.getType())) {
                                            //  添加 ArgumentConfig 字段信息到 map 中
                                            appendParameters(map, argument, method.getName() + "." + argument.getIndex());
                                        } else {
                                            // 不一致,則拋出異常
                                            throw new IllegalArgumentException("Argument config error : the index attribute and type attribute not match :index :" + argument.getIndex() + ", type:" + argument.getType());
                                        }
                                    } else {
                                        // multiple callbacks in the method
                                        // 遍歷參數類型數組 argtypes,查找 argument.type 類型的參數
                                        for (int j = 0; j < argtypes.length; j++) {
                                            Class<?> argclazz = argtypes[j];
                                            if (argclazz.getName().equals(argument.getType())) {
                                                // 若是找到,則添加 ArgumentConfig 字段信息到 map 中
                                                appendParameters(map, argument, method.getName() + "." + j);
                                                if (argument.getIndex() != -1 && argument.getIndex() != j) {
                                                    throw new IllegalArgumentException("Argument config error : the index attribute and type attribute not match :index :" + argument.getIndex() + ", type:" + argument.getType());
                                                }
                                            }
                                        }
                                    }
                                }
                            }
                        }
                    } else if (argument.getIndex() != -1) {
                        // 用戶未配置 type 屬性,但配置了 index 屬性,且 index != -1,則直接添加到map
                        appendParameters(map, argument, method.getName() + "." + argument.getIndex());
                    } else {
                        // 拋出異常
                        throw new IllegalArgumentException("Argument config must set index or type attribute.eg: <dubbo:argument index='0' .../> or <dubbo:argument type=xxx .../>");
                    }

                }
            }
        } // end of methods for
    }

    // 若是是泛化調用,則在map中設置generic和methods
    if (ProtocolUtils.isGeneric(generic)) {
        map.put(Constants.GENERIC_KEY, generic);
        map.put(Constants.METHODS_KEY, Constants.ANY_VALUE);
    } else {
        // 得到版本號
        String revision = Version.getVersion(interfaceClass, version);
        // 放入map
        if (revision != null && revision.length() > 0) {
            map.put(Constants.REVISION_KEY, revision);
        }

        // 得到方法集合
        String[] methods = Wrapper.getWrapper(interfaceClass).getMethodNames();
        // 若是爲空,則告警
        if (methods.length == 0) {
            logger.warn("No method found in service interface " + interfaceClass.getName());
            // 設置method爲*
            map.put(Constants.METHODS_KEY, Constants.ANY_VALUE);
        } else {
            // 不然加入方法集合
            map.put(Constants.METHODS_KEY, StringUtils.join(new HashSet<String>(Arrays.asList(methods)), ","));
        }
    }
    // 把token 的值加入到map中
    if (!ConfigUtils.isEmpty(token)) {
        if (ConfigUtils.isDefault(token)) {
            map.put(Constants.TOKEN_KEY, UUID.randomUUID().toString());
        } else {
            map.put(Constants.TOKEN_KEY, token);
        }
    }
    // export service
    // 得到地址
    String host = this.findConfigedHosts(protocolConfig, registryURLs, map);
    // 得到端口號
    Integer port = this.findConfigedPorts(protocolConfig, name, map);
    // 生成 URL
    URL url = new URL(name, host, port, getContextPath(protocolConfig).map(p -> p + "/" + path).orElse(path), map);

    // —————————————————————————————————————分割線———————————————————————————————————————


    // 加載 ConfiguratorFactory,並生成 Configurator 實例,判斷是否有該協議的實現存在
    if (ExtensionLoader.getExtensionLoader(ConfiguratorFactory.class)
            .hasExtension(url.getProtocol())) {
        // 經過實例配置 url
        url = ExtensionLoader.getExtensionLoader(ConfiguratorFactory.class)
                .getExtension(url.getProtocol()).getConfigurator(url).configure(url);
    }

    String scope = url.getParameter(Constants.SCOPE_KEY);
    // don't export when none is configured
    // // 若是 scope = none,則什麼都不作
    if (!Constants.SCOPE_NONE.equalsIgnoreCase(scope)) {

        // export to local if the config is not remote (export to remote only when config is remote)
        // // scope != remote,暴露到本地
        if (!Constants.SCOPE_REMOTE.equalsIgnoreCase(scope)) {
            // 暴露到本地
            exportLocal(url);
        }
        // export to remote if the config is not local (export to local only when config is local)
        // // scope != local,導出到遠程
        if (!Constants.SCOPE_LOCAL.equalsIgnoreCase(scope)) {
            if (logger.isInfoEnabled()) {
                logger.info("Export dubbo service " + interfaceClass.getName() + " to url " + url);
            }
            // 若是註冊中心連接集合不爲空
            if (CollectionUtils.isNotEmpty(registryURLs)) {
                // 遍歷註冊中心
                for (URL registryURL : registryURLs) {
                    // 添加dynamic配置
                    url = url.addParameterIfAbsent(Constants.DYNAMIC_KEY, registryURL.getParameter(Constants.DYNAMIC_KEY));
                    // 加載監視器連接
                    URL monitorUrl = loadMonitor(registryURL);
                    if (monitorUrl != null) {
                        // 添加監視器配置
                        url = url.addParameterAndEncoded(Constants.MONITOR_KEY, monitorUrl.toFullString());
                    }
                    if (logger.isInfoEnabled()) {
                        logger.info("Register dubbo service " + interfaceClass.getName() + " url " + url + " to registry " + registryURL);
                    }

                    // For providers, this is used to enable custom proxy to generate invoker
                    // 得到代理方式
                    String proxy = url.getParameter(Constants.PROXY_KEY);
                    if (StringUtils.isNotEmpty(proxy)) {
                        // 添加代理方式到註冊中心到url
                        registryURL = registryURL.addParameter(Constants.PROXY_KEY, proxy);
                    }

                    // 爲服務提供類(ref)生成 Invoker
                    Invoker<?> invoker = proxyFactory.getInvoker(ref, (Class) interfaceClass, registryURL.addParameterAndEncoded(Constants.EXPORT_KEY, url.toFullString()));
                    // DelegateProviderMetaDataInvoker 用於持有 Invoker 和 ServiceConfig
                    DelegateProviderMetaDataInvoker wrapperInvoker = new DelegateProviderMetaDataInvoker(invoker, this);

                    // 暴露服務,而且生成Exporter
                    Exporter<?> exporter = protocol.export(wrapperInvoker);
                    // 加入到暴露者集合中
                    exporters.add(exporter);
                }
            } else {
                // 不存在註冊中心,則僅僅暴露服務,不會記錄暴露到地址
                Invoker<?> invoker = proxyFactory.getInvoker(ref, (Class) interfaceClass, url);
                DelegateProviderMetaDataInvoker wrapperInvoker = new DelegateProviderMetaDataInvoker(invoker, this);

                Exporter<?> exporter = protocol.export(wrapperInvoker);
                exporters.add(exporter);
            }
            /**
             * @since 2.7.0
             * ServiceData Store
             */
            MetadataReportService metadataReportService = null;
            // 若是元數據中心服務不爲空,則發佈該服務,也就是在元數據中心記錄url中到部分配置
            if ((metadataReportService = getMetadataReportService()) != null) {
                metadataReportService.publishProvider(url);
            }
        }
    }
    this.urls.add(url);
}

先看分割線上面部分,就是組裝URL的全過程,我以爲大體能夠分爲一下步驟:

  1. 它把metrics、application、module、provider、protocol等全部配置都放入map中,
  2. 針對method都配置,先作簽名校驗,先找到該服務是否有配置的方法存在,而後該方法簽名是否有這個參數存在,都覈對成功纔將method的配置加入map。
  3. 將泛化調用、版本號、method或者methods、token等信息加入map
  4. 得到服務暴露地址和端口號,利用map內數據組裝成URL。

建立invoker(2,3)

暴露到遠程的源碼直接看doExportUrlsFor1Protocol()方法分割線下半部分。當生成暴露者的時候,服務已經暴露,接下來會細緻的分析這暴露內部的過程。能夠發現不管暴露到本地仍是遠程,都會經過代理工廠建立invoker。這個時候就走到了上述時序圖的ProxyFactory。我在這篇文章中有講到invoker:dubbo源碼解析(十九)遠程調用——開篇,首先咱們來看看invoker如何誕生的。Invoker 是由 ProxyFactory 建立而來,Dubbo 默認的 ProxyFactory 實現類是 JavassistProxyFactory。JavassistProxyFactory中有一個getInvoker()方法。

獲取invoker方法

getInvoker()
public <T> Invoker<T> getInvoker(T proxy, Class<T> type, URL url) {
    // TODO Wrapper cannot handle this scenario correctly: the classname contains '$'
    // // 爲目標類建立 Wrapper
    final Wrapper wrapper = Wrapper.getWrapper(proxy.getClass().getName().indexOf('$') < 0 ? proxy.getClass() : type);
    // 建立匿名 Invoker 類對象,並實現 doInvoke 方法。
    return new AbstractProxyInvoker<T>(proxy, type, url) {
        @Override
        protected Object doInvoke(T proxy, String methodName,
                                  Class<?>[] parameterTypes,
                                  Object[] arguments) throws Throwable {
            // 調用 Wrapper 的 invokeMethod 方法,invokeMethod 最終會調用目標方法
            return wrapper.invokeMethod(proxy, methodName, parameterTypes, arguments);
        }
    };
}

能夠看到,該方法就是建立了一個匿名的Invoker類對象,在doInvoke()方法中調用wrapper.invokeMethod()方法。Wrapper 是一個抽象類,僅可經過 getWrapper(Class) 方法建立子類。在建立 Wrapper 子類的過程當中,子類代碼生成邏輯會對 getWrapper 方法傳入的 Class 對象進行解析,拿到諸如類方法,類成員變量等信息。以及生成 invokeMethod 方法代碼和其餘一些方法代碼。代碼生成完畢後,經過 Javassist 生成 Class 對象,最後再經過反射建立 Wrapper 實例。那麼咱們先來看看getWrapper()方法:

getWrapper()
public static Wrapper getWrapper(Class<?> c) {
    while (ClassGenerator.isDynamicClass(c)) // can not wrapper on dynamic class.
    {
        // 返回該對象的超類
        c = c.getSuperclass();
    }

    // 若是超類就是Object,則返回子類Wrapper
    if (c == Object.class) {
        return OBJECT_WRAPPER;
    }

    // 從緩存中獲取 Wrapper 實例
    Wrapper ret = WRAPPER_MAP.get(c);
    // 若是沒有命中,則建立 Wrapper
    if (ret == null) {
        // 建立Wrapper
        ret = makeWrapper(c);
        // 寫入緩存
        WRAPPER_MAP.put(c, ret);
    }
    return ret;
}

該方法只是對Wrapper 作了緩存。主要的邏輯在makeWrapper()。

makeWrapper()
// 檢測 c 是否爲基本類型,如果則拋出異常
    if (c.isPrimitive()) {
        throw new IllegalArgumentException("Can not create wrapper for primitive type: " + c);
    }

    // 得到類名
    String name = c.getName();
    // 得到類加載器
    ClassLoader cl = ClassHelper.getClassLoader(c);

    // c1 用於存儲 setPropertyValue 方法代碼
    StringBuilder c1 = new StringBuilder("public void setPropertyValue(Object o, String n, Object v){ ");
    // c2 用於存儲 getPropertyValue 方法代碼
    StringBuilder c2 = new StringBuilder("public Object getPropertyValue(Object o, String n){ ");
    // c3 用於存儲 invokeMethod 方法代碼
    StringBuilder c3 = new StringBuilder("public Object invokeMethod(Object o, String n, Class[] p, Object[] v) throws " + InvocationTargetException.class.getName() + "{ ");

    // 生成類型轉換代碼及異常捕捉代碼,好比:
    //   DemoService w; try { w = ((DemoServcie) $1); }}catch(Throwable e){ throw new IllegalArgumentException(e); }
    c1.append(name).append(" w; try{ w = ((").append(name).append(")$1); }catch(Throwable e){ throw new IllegalArgumentException(e); }");
    c2.append(name).append(" w; try{ w = ((").append(name).append(")$1); }catch(Throwable e){ throw new IllegalArgumentException(e); }");
    c3.append(name).append(" w; try{ w = ((").append(name).append(")$1); }catch(Throwable e){ throw new IllegalArgumentException(e); }");

    // pts 用於存儲成員變量名和類型
    Map<String, Class<?>> pts = new HashMap<>(); // <property name, property types>
    // ms 用於存儲方法描述信息(可理解爲方法簽名)及 Method 實例
    Map<String, Method> ms = new LinkedHashMap<>(); // <method desc, Method instance>
    // mns 爲方法名列表
    List<String> mns = new ArrayList<>(); // method names.
    // dmns 用於存儲「定義在當前類中的方法」的名稱
    List<String> dmns = new ArrayList<>(); // declaring method names.

    // get all public field.
    // 獲取 public 訪問級別的字段,併爲全部字段生成條件判斷語句
    for (Field f : c.getFields()) {
        String fn = f.getName();
        Class<?> ft = f.getType();
        if (Modifier.isStatic(f.getModifiers()) || Modifier.isTransient(f.getModifiers())) {
            // 忽略關鍵字 static 或 transient 修飾的變量
            continue;
        }
        // 生成條件判斷及賦值語句,好比:
        // if( $2.equals("name") ) { w.name = (java.lang.String) $3; return;}
        // if( $2.equals("age") ) { w.age = ((Number) $3).intValue(); return;}
        c1.append(" if( $2.equals(\"").append(fn).append("\") ){ w.").append(fn).append("=").append(arg(ft, "$3")).append("; return; }");
        // 生成條件判斷及返回語句,好比:
        // if( $2.equals("name") ) { return ($w)w.name; }
        c2.append(" if( $2.equals(\"").append(fn).append("\") ){ return ($w)w.").append(fn).append("; }");
        // 存儲 <字段名, 字段類型> 鍵值對到 pts 中
        pts.put(fn, ft);
    }

    // 得到c類的全部方法
    Method[] methods = c.getMethods();
    // get all public method.
    // 檢測 c 中是否包含在當前類中聲明的方法
    boolean hasMethod = hasMethods(methods);
    // 若是包含
    if (hasMethod) {
        c3.append(" try{");
        for (Method m : methods) {
            //ignore Object's method.
            // 忽略 Object 中定義的方法
            if (m.getDeclaringClass() == Object.class) {
                continue;
            }

            // 得到方法的名稱
            String mn = m.getName();
            // 生成方法名判斷語句,好比:
            // if ( "sayHello".equals( $2 )
            c3.append(" if( \"").append(mn).append("\".equals( $2 ) ");
            int len = m.getParameterTypes().length;
            // 生成「運行時傳入的參數數量與方法參數列表長度」判斷語句,好比:
            // && $3.length == 2
            c3.append(" && ").append(" $3.length == ").append(len);

            boolean override = false;
            for (Method m2 : methods) {
                // 檢測方法是否存在重載狀況,條件爲:方法對象不一樣 && 方法名相同
                if (m != m2 && m.getName().equals(m2.getName())) {
                    override = true;
                    break;
                }
            }
            // 對重載方法進行處理,考慮下面的方法:
            //    1. void sayHello(Integer, String)
            //    2. void sayHello(Integer, Integer)
            // 方法名相同,參數列表長度也相同,所以不能僅經過這兩項判斷兩個方法是否相等。
            // 須要進一步判斷方法的參數類型
            if (override) {
                if (len > 0) {
                    for (int l = 0; l < len; l++) {
                        c3.append(" && ").append(" $3[").append(l).append("].getName().equals(\"")
                                .append(m.getParameterTypes()[l].getName()).append("\")");
                    }
                }
            }
            // 添加 ) {,完成方法判斷語句,此時生成的代碼可能以下(已格式化):
            // if ("sayHello".equals($2)
            //     && $3.length == 2
            //     && $3[0].getName().equals("java.lang.Integer")
            //     && $3[1].getName().equals("java.lang.String")) {
            c3.append(" ) { ");

            // 根據返回值類型生成目標方法調用語句
            if (m.getReturnType() == Void.TYPE) {
                // w.sayHello((java.lang.Integer)$4[0], (java.lang.String)$4[1]); return null;
                c3.append(" w.").append(mn).append('(').append(args(m.getParameterTypes(), "$4")).append(");").append(" return null;");
            } else {
                // return w.sayHello((java.lang.Integer)$4[0], (java.lang.String)$4[1]);
                c3.append(" return ($w)w.").append(mn).append('(').append(args(m.getParameterTypes(), "$4")).append(");");
            }

            c3.append(" }");

            // 添加方法名到 mns 集合中
            mns.add(mn);
            // 檢測當前方法是否在 c 中被聲明的
            if (m.getDeclaringClass() == c) {
                // 如果,則將當前方法名添加到 dmns 中
                dmns.add(mn);
            }
            ms.put(ReflectUtils.getDesc(m), m);
        }
        // 添加異常捕捉語句
        c3.append(" } catch(Throwable e) { ");
        c3.append("     throw new java.lang.reflect.InvocationTargetException(e); ");
        c3.append(" }");
    }

    // 添加NoSuchMethodException異常
    c3.append(" throw new " + NoSuchMethodException.class.getName() + "(\"Not found method \\\"\"+$2+\"\\\" in class " + c.getName() + ".\"); }");

    // deal with get/set method.
    Matcher matcher;
    // 處理 get/set 方法
    for (Map.Entry<String, Method> entry : ms.entrySet()) {
        // 得到方法名稱
        String md = entry.getKey();
        // 得到Method方法
        Method method = entry.getValue();
        // 若是是get開頭的方法
        if ((matcher = ReflectUtils.GETTER_METHOD_DESC_PATTERN.matcher(md)).matches()) {
            // 獲取屬性名
            String pn = propertyName(matcher.group(1));
            // 生成屬性判斷以及返回語句,示例以下:
            // if( $2.equals("name") ) { return ($w).w.getName(); }
            c2.append(" if( $2.equals(\"").append(pn).append("\") ){ return ($w)w.").append(method.getName()).append("(); }");
            pts.put(pn, method.getReturnType());
        } else if ((matcher = ReflectUtils.IS_HAS_CAN_METHOD_DESC_PATTERN.matcher(md)).matches()) {
            String pn = propertyName(matcher.group(1));
            c2.append(" if( $2.equals(\"").append(pn).append("\") ){ return ($w)w.").append(method.getName()).append("(); }");
            // 存儲屬性名和返回類型到pts
            pts.put(pn, method.getReturnType());
        } else if ((matcher = ReflectUtils.SETTER_METHOD_DESC_PATTERN.matcher(md)).matches()) {
            // 若是是set開頭的方法
            // 得到參數類型
            Class<?> pt = method.getParameterTypes()[0];
            // 得到屬性名
            String pn = propertyName(matcher.group(1));
            // 生成屬性判斷以及 setter 調用語句,示例以下:
            // if( $2.equals("name") ) { w.setName((java.lang.String)$3); return; }
            c1.append(" if( $2.equals(\"").append(pn).append("\") ){ w.").append(method.getName()).append("(").append(arg(pt, "$3")).append("); return; }");
            pts.put(pn, pt);
        }
    }
    // 添加 NoSuchPropertyException 異常拋出代碼
    c1.append(" throw new " + NoSuchPropertyException.class.getName() + "(\"Not found property \\\"\"+$2+\"\\\" field or setter method in class " + c.getName() + ".\"); }");
    c2.append(" throw new " + NoSuchPropertyException.class.getName() + "(\"Not found property \\\"\"+$2+\"\\\" field or setter method in class " + c.getName() + ".\"); }");

    // make class
    long id = WRAPPER_CLASS_COUNTER.getAndIncrement();
    // 建立類生成器
    ClassGenerator cc = ClassGenerator.newInstance(cl);
    // 設置類名及超類
    cc.setClassName((Modifier.isPublic(c.getModifiers()) ? Wrapper.class.getName() : c.getName() + "$sw") + id);
    cc.setSuperClass(Wrapper.class);

    // 添加默認的構造函數
    cc.addDefaultConstructor();
    // 添加字段
    cc.addField("public static String[] pns;"); // property name array.
    cc.addField("public static " + Map.class.getName() + " pts;"); // property type map.
    cc.addField("public static String[] mns;"); // all method name array.
    cc.addField("public static String[] dmns;"); // declared method name array.
    for (int i = 0, len = ms.size(); i < len; i++) {
        cc.addField("public static Class[] mts" + i + ";");
    }

    // 添加方法
    cc.addMethod("public String[] getPropertyNames(){ return pns; }");
    cc.addMethod("public boolean hasProperty(String n){ return pts.containsKey($1); }");
    cc.addMethod("public Class getPropertyType(String n){ return (Class)pts.get($1); }");
    cc.addMethod("public String[] getMethodNames(){ return mns; }");
    cc.addMethod("public String[] getDeclaredMethodNames(){ return dmns; }");
    cc.addMethod(c1.toString());
    cc.addMethod(c2.toString());
    cc.addMethod(c3.toString());

    try {
        // 生成類
        Class<?> wc = cc.toClass();
        // setup static field.
        // 設置字段值
        wc.getField("pts").set(null, pts);
        wc.getField("pns").set(null, pts.keySet().toArray(new String[0]));
        wc.getField("mns").set(null, mns.toArray(new String[0]));
        wc.getField("dmns").set(null, dmns.toArray(new String[0]));
        int ix = 0;
        for (Method m : ms.values()) {
            wc.getField("mts" + ix++).set(null, m.getParameterTypes());
        }
        // 建立 Wrapper 實例
        return (Wrapper) wc.newInstance();
    } catch (RuntimeException e) {
        throw e;
    } catch (Throwable e) {
        throw new RuntimeException(e.getMessage(), e);
    } finally {
        cc.release();
        ms.clear();
        mns.clear();
        dmns.clear();
    }
}

該方法有點長,大體能夠分爲幾個步驟:

  1. 初始化了c一、c二、c三、pts、ms、mns、dmns變量,向 c一、c二、c3 中添加方法定義和類型轉換代碼。
  2. 爲 public 級別的字段生成條件判斷取值與賦值代碼
  3. 爲定義在當前類中的方法生成判斷語句,和方法調用語句。
  4. 處理 getter、setter 以及以 is/has/can 開頭的方法。處理方式是經過正則表達式獲取方法類型(get/set/is/...),以及屬性名。以後爲屬性名生成判斷語句,而後爲方法生成調用語句。
  5. 經過 ClassGenerator 爲剛剛生成的代碼構建 Class 類,並經過反射建立對象。ClassGenerator 是 Dubbo 本身封裝的,該類的核心是 toClass() 的重載方法 toClass(ClassLoader, ProtectionDomain),該方法經過 javassist 構建 Class。

服務暴露

服務暴露分爲暴露到本地 (JVM),和暴露到遠程。doExportUrlsFor1Protocol()方法分割線下半部分就是服務暴露的邏輯。根據scope的配置分爲:

  • scope = none,不暴露服務
  • scope != remote,暴露到本地
  • scope != local,暴露到遠程

暴露到本地

導出本地執行的是ServiceConfig中的exportLocal()方法。

exportLocal()(4)
private void exportLocal(URL url) {
    // 若是協議不是injvm
    if (!Constants.LOCAL_PROTOCOL.equalsIgnoreCase(url.getProtocol())) {
        // 生成本地的url,分別把協議改成injvm,設置host和port
        URL local = URLBuilder.from(url)
                .setProtocol(Constants.LOCAL_PROTOCOL)
                .setHost(LOCALHOST_VALUE)
                .setPort(0)
                .build();
        // 經過代理工程建立invoker
        // 再調用export方法進行暴露服務,生成Exporter
        Exporter<?> exporter = protocol.export(
                proxyFactory.getInvoker(ref, (Class) interfaceClass, local));
        // 把生成的暴露者加入集合
        exporters.add(exporter);
        logger.info("Export dubbo service " + interfaceClass.getName() + " to local registry");
    }
}

本地暴露調用的是injvm協議方法,也就是InjvmProtocol 的 export()方法。

export()(5)
public <T> Exporter<T> export(Invoker<T> invoker) throws RpcException {
    return new InjvmExporter<T>(invoker, invoker.getUrl().getServiceKey(), exporterMap);
}

該方法只是建立了一個,由於暴露到本地,因此在同一個jvm中。因此不須要其餘操做。

暴露到遠程

暴露到遠程的邏輯要比本地複雜的多,它大體能夠分爲服務暴露和服務註冊兩個過程。先來看看服務暴露。咱們知道dubbo有不少協議實現,在doExportUrlsFor1Protocol()方法分割線下半部分中,生成了Invoker後,就須要調用protocol 的 export()方法,不少人會認爲這裏的export()就是配置中指定的協議實現中的方法,但這裏是不對的。由於暴露到遠程後須要進行服務註冊,而RegistryProtocol的 export()方法就是實現了服務暴露和服務註冊兩個過程。因此這裏的export()調用的是RegistryProtocol的 export()。

export()
public <T> Exporter<T> export(final Invoker<T> originInvoker) throws RpcException {
    // 得到註冊中心的url
    URL registryUrl = getRegistryUrl(originInvoker);
    // url to export locally
    //得到已經註冊的服務提供者url
    URL providerUrl = getProviderUrl(originInvoker);

    // Subscribe the override data
    // FIXME When the provider subscribes, it will affect the scene : a certain JVM exposes the service and call
    //  the same service. Because the subscribed is cached key with the name of the service, it causes the
    //  subscription information to cover.
    // 獲取override訂閱 URL
    final URL overrideSubscribeUrl = getSubscribedOverrideUrl(providerUrl);
    // 建立override的監聽器
    final OverrideListener overrideSubscribeListener = new OverrideListener(overrideSubscribeUrl, originInvoker);
    // 把監聽器添加到集合
    overrideListeners.put(overrideSubscribeUrl, overrideSubscribeListener);

    // 根據override的配置來覆蓋原來的url,使得配置是最新的。
    providerUrl = overrideUrlWithConfig(providerUrl, overrideSubscribeListener);
    //export invoker
    // 服務暴露
    final ExporterChangeableWrapper<T> exporter = doLocalExport(originInvoker, providerUrl);

    // url to registry
    // 根據 URL 加載 Registry 實現類,好比ZookeeperRegistry
    final Registry registry = getRegistry(originInvoker);
    // 返回註冊到註冊表的url並過濾url參數一次
    final URL registeredProviderUrl = getRegisteredProviderUrl(providerUrl, registryUrl);
    // 生成ProviderInvokerWrapper,它會保存服務提供方和消費方的調用地址和代理對象
    ProviderInvokerWrapper<T> providerInvokerWrapper = ProviderConsumerRegTable.registerProvider(originInvoker,
            registryUrl, registeredProviderUrl);


    // ————————————————————————————————分割線——————————————————————————————————————
    //to judge if we need to delay publish
    // 獲取 register 參數
    boolean register = registeredProviderUrl.getParameter("register", true);
    // 若是須要註冊服務
    if (register) {
        // 向註冊中心註冊服務
        register(registryUrl, registeredProviderUrl);
        // 設置reg爲true,表示服務註冊
        providerInvokerWrapper.setReg(true);
    }

    // Deprecated! Subscribe to override rules in 2.6.x or before.
    // 向註冊中心進行訂閱 override 數據
    registry.subscribe(overrideSubscribeUrl, overrideSubscribeListener);

    // 設置註冊中心url
    exporter.setRegisterUrl(registeredProviderUrl);
    // 設置override數據訂閱的url
    exporter.setSubscribeUrl(overrideSubscribeUrl);
    //Ensure that a new exporter instance is returned every time export
    // 建立並返回 DestroyableExporter
    return new DestroyableExporter<>(exporter);
}

從代碼上看,我用分割線分紅兩部分,分別是服務暴露和服務註冊。該方法的邏輯大體分爲如下幾個步驟:

  1. 得到服務提供者的url,再經過override數據從新配置url,而後執行doLocalExport()進行服務暴露。
  2. 加載註冊中心實現類,向註冊中心註冊服務。
  3. 向註冊中心進行訂閱 override 數據。
  4. 建立並返回 DestroyableExporter

服務暴露先調用的是RegistryProtocol的doLocalExport()方法

private <T> ExporterChangeableWrapper<T> doLocalExport(final Invoker<T> originInvoker, URL providerUrl) {
    String key = getCacheKey(originInvoker);

    // 加入緩存
    return (ExporterChangeableWrapper<T>) bounds.computeIfAbsent(key, s -> {
        // 建立 Invoker 爲委託類對象
        Invoker<?> invokerDelegate = new InvokerDelegate<>(originInvoker, providerUrl);
        // 調用 protocol 的 export 方法暴露服務
        return new ExporterChangeableWrapper<>((Exporter<T>) protocol.export(invokerDelegate), originInvoker);
    });
}

這裏的邏輯比較簡單,主要是在這裏根據不一樣的協議配置,調用不一樣的protocol實現。跟暴露到本地的時候實現InjvmProtocol同樣。我這裏假設配置選用的是dubbo協議,來繼續下面的介紹。

DubboProtocol的export()

《dubbo源碼解析(二十四)遠程調用——dubbo協議》中的(三)DubboProtocol有export()相關的源碼分析, 從源碼中能夠看出作了一些本地存根的處理,關鍵的就是openServer,來啓動服務器。

DubboProtocol的openServer()

《dubbo源碼解析(二十四)遠程調用——dubbo協議》中的(三)DubboProtocol有openServer()相關的源碼分析, 不過該文章中的代碼是2.6.x的代碼,最新的版本中加入了 DCL。其中reset方法則是重置服務器的一些配置。例如在同一臺機器上(單網卡),同一個端口上僅容許啓動一個服務器實例。若某個端口上已有服務器實例,此時則調用 reset 方法重置服務器的一些配置。主要來看其中的createServer()方法。

DubboProtocol的createServer()

《dubbo源碼解析(二十四)遠程調用——dubbo協議》中的(三)DubboProtocol有createServer()相關的源碼分析,其中最新版本的默認遠程通信服務端實現方式已經改成netty4。該方法大體能夠分爲如下幾個步驟:

  1. 對服務端遠程通信服務端實現方式配置是否支持的檢測
  2. 建立服務器實例,也就是調用bind()方法
  3. 對服務端遠程通信客戶端實現方式配置是否支持的檢測
Exchangers的bind()

能夠參考《dubbo源碼解析(十)遠程通訊——Exchange層》中的(二十一)Exchangers。

public static ExchangeServer bind(URL url, ExchangeHandler handler) throws RemotingException {
    if (url == null) {
        throw new IllegalArgumentException("url == null");
    }
    if (handler == null) {
        throw new IllegalArgumentException("handler == null");
    }
    url = url.addParameterIfAbsent(Constants.CODEC_KEY, "exchange");
    // 獲取 Exchanger,默認爲 HeaderExchanger。
    // 緊接着調用 HeaderExchanger 的 bind 方法建立 ExchangeServer 實例
    return getExchanger(url).bind(url, handler);
}
HeaderExchanger的bind()

能夠參考《dubbo源碼解析(十)遠程通訊——Exchange層》(十六)HeaderExchanger,其中bind()方法作了大體如下步驟:

  1. 建立HeaderExchangeHandler
  2. 建立DecodeHandler
  3. Transporters.bind(),建立服務器實例。
  4. 建立HeaderExchangeServer

其中HeaderExchangeHandler、DecodeHandler、HeaderExchangeServer能夠參考《dubbo源碼解析(十)遠程通訊——Exchange層》中的講解。

Transporters的bind()(6)
public static Server bind(URL url, ChannelHandler... handlers) throws RemotingException {
    if (url == null) {
        throw new IllegalArgumentException("url == null");
    }
    if (handlers == null || handlers.length == 0) {
        throw new IllegalArgumentException("handlers == null");
    }
    ChannelHandler handler;
    if (handlers.length == 1) {
        handler = handlers[0];
    } else {
        // 若是 handlers 元素數量大於1,則建立 ChannelHandler 分發器
        handler = new ChannelHandlerDispatcher(handlers);
    }
    // 獲取自適應 Transporter 實例,並調用實例方法
    return getTransporter().bind(url, handler);
}

getTransporter() 方法獲取的 Transporter 是在運行時動態建立的,類名爲 TransporterAdaptive,也就是自適應拓展類。TransporterAdaptive 會在運行時根據傳入的 URL 參數決定加載什麼類型的 Transporter,默認爲基於Netty4的實現。假設是 NettyTransporter 的 bind 方法。

NettyTransporter的bind()(6)

能夠參考《dubbo源碼解析(十七)遠程通訊——Netty4》的(六)NettyTransporter。

public Server bind(URL url, ChannelHandler listener) throws RemotingException {
    // 建立NettyServer
    return new NettyServer(url, listener);
}
NettyServer的構造方法(7)

能夠參考《dubbo源碼解析(十七)遠程通訊——Netty4》的(五)NettyServer

public NettyServer(URL url, ChannelHandler handler) throws RemotingException {
    super(url, ChannelHandlers.wrap(handler, ExecutorUtil.setThreadName(url, SERVER_THREAD_POOL_NAME)));
}

調用的是父類AbstractServer構造方法

AbstractServer的構造方法(7)

能夠參考《dubbo源碼解析(九)遠程通訊——Transport層》的(三)AbstractServer中的構造方法。

服務器實例建立完之後,就是開啓服務器了,AbstractServer中的doOpen是抽象方法,仍是拿netty4來說解,也就是看NettyServer的doOpen()的方法。

NettyServer的doOpen()

能夠參考《dubbo源碼解析(十七)遠程通訊——Netty4》中的(五)NettyServer中的源碼分析。這裏執行完成後,服務器被開啓,服務也暴露出來了。接下來就是講解服務註冊的內容。

服務註冊(9)

dubbo服務註冊並非必須的,由於dubbo支持直連的方式就能夠繞過註冊中心。直連的方式不少時候用來作服務測試。

回過頭去看一下RegistryProtocol的 export()方法的分割線下面部分。其中服務註冊先調用的是register()方法。

RegistryProtocol的register()
public void register(URL registryUrl, URL registeredProviderUrl) {
    // 獲取 Registry
    Registry registry = registryFactory.getRegistry(registryUrl);
    // 註冊服務
    registry.register(registeredProviderUrl);
}

因此服務註冊大體能夠分爲兩步:

  1. 得到註冊中心實例
  2. 註冊服務

得到註冊中心首先執行的是AbstractRegistryFactory的getRegistry()方法

AbstractRegistryFactory的getRegistry()

能夠參考《dubbo源碼解析(三)註冊中心——開篇》的(七)support包下的AbstractRegistryFactory中的源碼解析。大概的邏輯就是先從緩存中取,若是沒有命中,則建立註冊中心實例,這裏的createRegistry()是一個抽象方法,具體的實現邏輯由子類完成,假設這裏使用zookeeper做爲註冊中心,則調用的是ZookeeperRegistryFactory的createRegistry()。

ZookeeperRegistryFactory的createRegistry()
public Registry createRegistry(URL url) {
    return new ZookeeperRegistry(url, zookeeperTransporter);
}

就是建立了一個ZookeeperRegistry,執行了ZookeeperRegistry的構造方法。

ZookeeperRegistry的構造方法

能夠參考《dubbo源碼解析(七)註冊中心——zookeeper》的(一)ZookeeperRegistry中的源碼分析。大體的邏輯能夠分爲如下幾個步驟:

  1. 建立zookeeper客戶端
  2. 添加監聽器

主要看ZookeeperTransporter的connect方法,由於當connect方法執行完後,註冊中心建立過程就結束了。首先執行的是AbstractZookeeperTransporter的connect方法。

AbstractZookeeperTransporter的connect()
public ZookeeperClient connect(URL url) {
    ZookeeperClient zookeeperClient;
    // 得到全部url地址
    List<String> addressList = getURLBackupAddress(url);
    // The field define the zookeeper server , including protocol, host, port, username, password
    // 從緩存中查找可用的客戶端,若是有,則直接返回
    if ((zookeeperClient = fetchAndUpdateZookeeperClientCache(addressList)) != null && zookeeperClient.isConnected()) {
        logger.info("find valid zookeeper client from the cache for address: " + url);
        return zookeeperClient;
    }
    // avoid creating too many connections, so add lock
    synchronized (zookeeperClientMap) {
        if ((zookeeperClient = fetchAndUpdateZookeeperClientCache(addressList)) != null && zookeeperClient.isConnected()) {
            logger.info("find valid zookeeper client from the cache for address: " + url);
            return zookeeperClient;
        }

        // 建立客戶端
        zookeeperClient = createZookeeperClient(toClientURL(url));
        logger.info("No valid zookeeper client found from cache, therefore create a new client for url. " + url);
        // 加入緩存
        writeToClientMap(addressList, zookeeperClient);
    }
    return zookeeperClient;
}

看上面的源碼,主要是執行了createZookeeperClient()方法,而該方法是一個抽象方法,由子類實現,這裏是CuratorZookeeperTransporter的createZookeeperClient()

CuratorZookeeperTransporter的createZookeeperClient()
public ZookeeperClient createZookeeperClient(URL url) {
    return new CuratorZookeeperClient(url);
}

這裏就是執行了CuratorZookeeperClient的構造方法。

CuratorZookeeperClient的構造方法

能夠參考《dubbo源碼解析(十八)遠程通訊——Zookeeper》的(四)CuratorZookeeperClient中的源碼分析,其中邏輯主要用於建立和啓動 CuratorFramework 實例,基本都是調用Curator框架的API。

建立完註冊中心的實例後,咱們就要進行註冊服務了。也就是調用的是FailbackRegistry的register()方法。

FailbackRegistry的register()

能夠參考《dubbo源碼解析(三)註冊中心——開篇》的(六)support包下的FailbackRegistry中的源碼分析。能夠看到關鍵是執行了doRegister()方法,該方法是抽象方法,由子類完成。這裏由於假設是zookeeper,因此執行的是ZookeeperRegistry的doRegister()。

ZookeeperRegistry的doRegister()

能夠參考《dubbo源碼解析(七)註冊中心——zookeeper》的(一)ZookeeperRegistry中的源代碼,能夠看到邏輯就是調用Zookeeper 客戶端建立服務節點。節點路徑由 toUrlPath 方法生成。而這裏create方法執行的是AbstractZookeeperClient的create() 方法

AbstractZookeeperClient的create()

能夠參考dubbo源碼解析(十八)遠程通訊——Zookeeper》的(二)AbstractZookeeperClient中的源代碼分析。createEphemeral()和createPersistent()是抽象方法,具體實現由子類完成,也就是CuratorZookeeperClient類。代碼邏輯比較簡單。我就再也不贅述。到這裏爲止,服務也就註冊完成。

關於向註冊中心進行訂閱 override 數據的規則在最新版本有一些大變更,跟2.6.x及之前的都不同。因此這部份內容在新特性中去講解。

後記

參考官方文檔: https://dubbo.apache.org/zh-c...

該文章講解了dubbo的服務暴露過程,也是爲了以後講2.7新特性作鋪墊,下一篇講解服務的引用過程。

相關文章
相關標籤/搜索