国产xxxx99真实实拍_久久不雅视频_高清韩国a级特黄毛片_嗯老师别我我受不了了小说

資訊專欄INFORMATION COLUMN

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

light / 842人閱讀

摘要:服務暴露過程目標從源碼的角度分析服務暴露過程。導出服務,包含暴露服務到本地,和暴露服務到遠程兩個過程。其中服務暴露的第八步已經沒有了。將泛化調用版本號或者等信息加入獲得服務暴露地址和端口號,利用內數據組裝成。

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

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

我在前面的文章都很少提及各個調用鏈的關系,各模塊之間也沒有串起來,并且要講解異步化改造我認為先弄懂服務的暴露和引用過程是非常有必要的,所以我將用兩片文章來講解服務暴露和服務引用的過程。

服務暴露過程

服務暴露過程大致可分為三個部分:

前置工作,主要用于檢查參數,組裝 URL。

導出服務,包含暴露服務到本地 (JVM),和暴露服務到遠程兩個過程。

向注冊中心注冊服務,用于服務發現。

暴露起點

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

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

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

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

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

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

前置工作(1)

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

配置檢查

在調用export方法之后,執行的是ServiceConfig中的export方法。

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(" 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,以及  標簽中的必要字段進行檢查
        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 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 loadRegistries(boolean provider) {
    // check && override if necessary
    List registryList = new ArrayList();
    // 如果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 map = new HashMap();
                // 添加 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 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 registryURLs) {
    // 獲取協議名
    String name = protocolConfig.getName();
    // 如果為空,則是默認的dubbo
    if (StringUtils.isEmpty(name)) {
        name = Constants.DUBBO;
    }

    Map map = new HashMap();
    // 設置服務提供者冊
    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 中,鍵 = 方法名.屬性名。
            // 比如存儲  對應的 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 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:  or ");
                    }

                }
            }
        } // 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(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的全過程,我覺得大致可以分為一下步驟:

它把metrics、application、module、provider、protocol等所有配置都放入map中,

針對method都配置,先做簽名校驗,先找到該服務是否有配置的方法存在,然后該方法簽名是否有這個參數存在,都核對成功才將method的配置加入map。

將泛化調用、版本號、method或者methods、token等信息加入map

獲得服務暴露地址和端口號,利用map內數據組裝成URL。

創建invoker(2,3)

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

獲取invoker方法
getInvoker()
public  Invoker getInvoker(T proxy, Class 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(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> pts = new HashMap<>(); // 
    // ms 用于存儲方法描述信息(可理解為方法簽名)及 Method 實例
    Map ms = new LinkedHashMap<>(); // 
    // mns 為方法名列表
    List mns = new ArrayList<>(); // method names.
    // dmns 用于存儲“定義在當前類中的方法”的名稱
    List 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 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();
    }
}

該方法有點長,大致可以分為幾個步驟:

初始化了c1、c2、c3、pts、ms、mns、dmns變量,向 c1、c2、c3 中添加方法定義和類型轉換代碼。

為 public 級別的字段生成條件判斷取值與賦值代碼

為定義在當前類中的方法生成判斷語句,和方法調用語句。

處理 getter、setter 以及以 is/has/can 開頭的方法。處理方式是通過正則表達式獲取方法類型(get/set/is/...),以及屬性名。之后為屬性名生成判斷語句,然后為方法生成調用語句。

通過 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  Exporter export(Invoker invoker) throws RpcException {
    return new InjvmExporter(invoker, invoker.getUrl().getServiceKey(), exporterMap);
}

該方法只是創建了一個,因為暴露到本地,所以在同一個jvm中。所以不需要其他操作。

暴露到遠程

暴露到遠程的邏輯要比本地復雜的多,它大致可以分為服務暴露和服務注冊兩個過程。先來看看服務暴露。我們知道dubbo有很多協議實現,在doExportUrlsFor1Protocol()方法分割線下半部分中,生成了Invoker后,就需要調用protocol 的 export()方法,很多人會認為這里的export()就是配置中指定的協議實現中的方法,但這里是不對的。因為暴露到遠程后需要進行服務注冊,而RegistryProtocol的 export()方法就是實現了服務暴露和服務注冊兩個過程。所以這里的export()調用的是RegistryProtocol的 export()。

export()
public  Exporter export(final Invoker 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 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 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);
}

從代碼上看,我用分割線分成兩部分,分別是服務暴露和服務注冊。該方法的邏輯大致分為以下幾個步驟:

獲得服務提供者的url,再通過override數據重新配置url,然后執行doLocalExport()進行服務暴露。

加載注冊中心實現類,向注冊中心注冊服務。

向注冊中心進行訂閱 override 數據。

創建并返回 DestroyableExporter

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

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

    // 加入緩存
    return (ExporterChangeableWrapper) bounds.computeIfAbsent(key, s -> {
        // 創建 Invoker 為委托類對象
        Invoker invokerDelegate = new InvokerDelegate<>(originInvoker, providerUrl);
        // 調用 protocol 的 export 方法暴露服務
        return new ExporterChangeableWrapper<>((Exporter) 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。該方法大致可以分為以下幾個步驟:

對服務端遠程通訊服務端實現方式配置是否支持的檢測

創建服務器實例,也就是調用bind()方法

對服務端遠程通訊客戶端實現方式配置是否支持的檢測

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()方法做了大致以下步驟:

創建HeaderExchangeHandler

創建DecodeHandler

Transporters.bind(),創建服務器實例。

創建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);
}

所以服務注冊大致可以分為兩步:

獲得注冊中心實例

注冊服務

獲得注冊中心首先執行的是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中的源碼分析。大致的邏輯可以分為以下幾個步驟:

創建zookeeper客戶端

添加監聽器

主要看ZookeeperTransporter的connect方法,因為當connect方法執行完后,注冊中心創建過程就結束了。首先執行的是AbstractZookeeperTransporter的connect方法。

AbstractZookeeperTransporter的connect()
public ZookeeperClient connect(URL url) {
    ZookeeperClient zookeeperClient;
    // 獲得所有url地址
    List 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新特性做鋪墊,下一篇講解服務的引用過程。

文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。

轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/74258.html

相關文章

  • dubbo源碼解析四十八)異步化改造

    摘要:大揭秘異步化改造目標從源碼的角度分析的新特性中對于異步化的改造原理。看源碼解析四十六消費端發送請求過程講到的十四的,在以前的邏輯會直接在方法中根據配置區分同步異步單向調用。改為關于可以參考源碼解析十遠程通信層的六。 2.7大揭秘——異步化改造 目標:從源碼的角度分析2.7的新特性中對于異步化的改造原理。 前言 dubbo中提供了很多類型的協議,關于協議的系列可以查看下面的文章: du...

    lijinke666 評論0 收藏0
  • dubbo源碼解析四十六)消費端發送請求過程

    摘要:可以參考源碼解析二十四遠程調用協議的八。十六的該類也是用了適配器模式,該類主要的作用就是增加了心跳功能,可以參考源碼解析十遠程通信層的四。二十的可以參考源碼解析十七遠程通信的一。 2.7大揭秘——消費端發送請求過程 目標:從源碼的角度分析一個服務方法調用經歷怎么樣的磨難以后到達服務端。 前言 前一篇文章講到的是引用服務的過程,引用服務無非就是創建出一個代理。供消費者調用服務的相關方法。...

    fish 評論0 收藏0
  • dubbo源碼解析四十七)服務端處理請求過程

    摘要:而存在的意義就是保證請求或響應對象可在線程池中被解碼,解碼完成后,就會分發到的。 2.7大揭秘——服務端處理請求過程 目標:從源碼的角度分析服務端接收到請求后的一系列操作,最終把客戶端需要的值返回。 前言 上一篇講到了消費端發送請求的過程,該篇就要將服務端處理請求的過程。也就是當服務端收到請求數據包后的一系列處理以及如何返回最終結果。我們也知道消費端在發送請求的時候已經做了編碼,所以我...

    yzzz 評論0 收藏0
  • dubbo源碼解析四十五)服務引用過程

    摘要:服務引用過程目標從源碼的角度分析服務引用過程。并保留服務提供者的部分配置,比如版本,,時間戳等最后將合并后的配置設置為查詢字符串中。的可以參考源碼解析二十三遠程調用的一的源碼分析。 dubbo服務引用過程 目標:從源碼的角度分析服務引用過程。 前言 前面服務暴露過程的文章講解到,服務引用有兩種方式,一種就是直連,也就是直接指定服務的地址來進行引用,這種方式更多的時候被用來做服務測試,不...

    xiaowugui666 評論0 收藏0
  • dubbo源碼解析四十三)2.7新特性

    摘要:大揭秘目標了解的新特性,以及版本升級的引導。四元數據改造我們知道以前的版本只有注冊中心,注冊中心的有數十個的鍵值對,包含了一個服務所有的元數據。 DUBBO——2.7大揭秘 目標:了解2.7的新特性,以及版本升級的引導。 前言 我們知道Dubbo在2011年開源,停止更新了一段時間。在2017 年 9 月 7 日,Dubbo 悄悄的在 GitHub 發布了 2.5.4 版本。隨后,版本...

    qqlcbb 評論0 收藏0

發表評論

0條評論

light

|高級講師

TA的文章

閱讀更多
最新活動
閱讀需要支付1元查看
<