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

資訊專欄INFORMATION COLUMN

Dubbo 源碼分析 - 服務(wù)導(dǎo)出

劉玉平 / 3180人閱讀

摘要:支持兩種服務(wù)導(dǎo)出方式,分別延遲導(dǎo)出和立即導(dǎo)出。本文打算分析服務(wù)延遲導(dǎo)出過(guò)程,因此不會(huì)分析方法。服務(wù)導(dǎo)出之前,要進(jìn)行對(duì)一系列的配置進(jìn)行檢查,以及生成。返回時(shí),表示需要延遲導(dǎo)出。賽程預(yù)告,下一站是服務(wù)導(dǎo)出的前置工作。

1.服務(wù)導(dǎo)出過(guò)程

本篇文章,我們來(lái)研究一下 Dubbo 導(dǎo)出服務(wù)的過(guò)程。Dubbo 服務(wù)導(dǎo)出過(guò)程始于 Spring 容器發(fā)布刷新事件,Dubbo 在接收到事件后,會(huì)立即執(zhí)行服務(wù)導(dǎo)出邏輯。整個(gè)邏輯大致可分為三個(gè)部分,第一是前置工作,主要用于檢查參數(shù),組裝 URL。第二是導(dǎo)出服務(wù),包含導(dǎo)出服務(wù)到本地 (JVM),和導(dǎo)出服務(wù)到遠(yuǎn)程兩個(gè)過(guò)程。第三是向注冊(cè)中心注冊(cè)服務(wù),用于服務(wù)發(fā)現(xiàn)。本篇文章將會(huì)對(duì)這三個(gè)部分代碼進(jìn)行詳細(xì)的分析,在分析之前,我們先來(lái)了解一下服務(wù)的導(dǎo)出過(guò)程。

Dubbo 支持兩種服務(wù)導(dǎo)出方式,分別延遲導(dǎo)出和立即導(dǎo)出。延遲導(dǎo)出的入口是 ServiceBean 的 afterPropertiesSet 方法,立即導(dǎo)出的入口是 ServiceBean 的 onApplicationEvent 方法。本文打算分析服務(wù)延遲導(dǎo)出過(guò)程,因此不會(huì)分析 afterPropertiesSet 方法。下面從 onApplicationEvent 方法說(shuō)起,該方法收到 Spring 容器的刷新事件后,會(huì)調(diào)用 export 方法執(zhí)行服務(wù)導(dǎo)出操作。服務(wù)導(dǎo)出之前,要進(jìn)行對(duì)一系列的配置進(jìn)行檢查,以及生成 URL。準(zhǔn)備工作做完,隨后開始導(dǎo)出服務(wù)。首先導(dǎo)出到本地,然后再導(dǎo)出到遠(yuǎn)程。導(dǎo)出到本地就是將服務(wù)導(dǎo)出到 JVM 中,此過(guò)程比較簡(jiǎn)單。導(dǎo)出到遠(yuǎn)程的過(guò)程則要復(fù)雜的多,以 dubbo 協(xié)議為例,DubboProtocol 類的 export 方法將會(huì)被調(diào)用。該方法主要用于創(chuàng)建 Exporter 和 ExchangeServer。ExchangeServer 本身并不具備通信能力,需要借助更底層的 Server 實(shí)現(xiàn)通信功能。因此,在創(chuàng)建 ExchangeServer 實(shí)例時(shí),需要先創(chuàng)建 NettyServer 或者 MinaServer 實(shí)例,并將實(shí)例作為參數(shù)傳給 ExchangeServer 實(shí)現(xiàn)類的構(gòu)造方法。ExchangeServer 實(shí)例創(chuàng)建完成后,導(dǎo)出服務(wù)到遠(yuǎn)程的過(guò)程也就接近尾聲了。服務(wù)導(dǎo)出結(jié)束后,服務(wù)消費(fèi)者即可通過(guò)直聯(lián)的方式消費(fèi)服務(wù)。當(dāng)然,一般我們不會(huì)使用直聯(lián)的方式消費(fèi)服務(wù)。所以,在服務(wù)導(dǎo)出結(jié)束后,緊接著要做的事情是向注冊(cè)中心注冊(cè)服務(wù)。此時(shí),客戶端即可從注冊(cè)中心發(fā)現(xiàn)服務(wù)。

以上就是 Dubbo 服務(wù)導(dǎo)出的過(guò)程,比較復(fù)雜。下面開始分析源碼,從源碼的角度展現(xiàn)整個(gè)過(guò)程。

2.源碼分析

一場(chǎng) Dubbo 源碼分析的馬拉松比賽即將開始,現(xiàn)在我們站在賽道的起點(diǎn)進(jìn)行熱身準(zhǔn)備。本次比賽的起點(diǎn)位置位于 ServiceBean 的 onApplicationEvent 方法處。好了,發(fā)令槍響了,我將和一些朋友從 onApplicationEvent 方法處出發(fā),探索 Dubbo 服務(wù)導(dǎo)出的全過(guò)程。下面我們來(lái)看一下 onApplicationEvent 方法的源碼。

public void onApplicationEvent(ContextRefreshedEvent event) {
    // 是否有延遲導(dǎo)出 && 是否已導(dǎo)出 && 是不是已被取消導(dǎo)出
    if (isDelay() && !isExported() && !isUnexported()) {
        // 導(dǎo)出服務(wù)
        export();
    }
}

onApplicationEvent 是一個(gè)事件響應(yīng)方法,該方法會(huì)在收到 Spring 上下文刷新事件后執(zhí)行。這個(gè)方法首先會(huì)根據(jù)條件決定是否導(dǎo)出服務(wù),比如有些服務(wù)設(shè)置了延時(shí)導(dǎo)出,那么此時(shí)就不應(yīng)該在此處導(dǎo)出。還有一些服務(wù)已經(jīng)被導(dǎo)出了,或者當(dāng)前服務(wù)被取消導(dǎo)出了,此時(shí)也不能再次導(dǎo)出相關(guān)服務(wù)。注意這里的 isDelay 方法,這個(gè)方法字面意思是“是否延遲導(dǎo)出服務(wù)”,返回 true 表示延遲導(dǎo)出,false 表示不延遲導(dǎo)出。但是該方法真實(shí)意思卻并非如此,當(dāng)方法返回 true 時(shí),表示無(wú)需延遲導(dǎo)出。返回 false 時(shí),表示需要延遲導(dǎo)出。與字面意思恰恰相反,讓人覺(jué)得很奇怪。下面我們來(lái)看一下這個(gè)方法的邏輯。

// -☆- ServiceBean
private boolean isDelay() {
    // 獲取 delay
    Integer delay = getDelay();
    ProviderConfig provider = getProvider();
    if (delay == null && provider != null) {
        // 如果前面獲取的 delay 為空,這里繼續(xù)獲取
        delay = provider.getDelay();
    }
    // 判斷 delay 是否為空,或者等于 -1
    return supportedApplicationListener && (delay == null || delay == -1);
}

暫時(shí)忽略 supportedApplicationListener 這個(gè)條件,當(dāng) delay 為空,或者等于-1時(shí),該方法返回 true,而不是 false。這個(gè)方法的返回值讓人有點(diǎn)困惑,因此我重構(gòu)了該方法的代碼,并給 Dubbo 提了一個(gè) Pull Request,最終這個(gè) PR 被合到了 Dubbo 主分支中。詳細(xì)請(qǐng)參見 Dubbo #2686。

現(xiàn)在解釋一下 supportedApplicationListener 變量含義,該變量用于表示當(dāng)前的 Spring 容器是否支持 ApplicationListener,這個(gè)值初始為 false。在 Spring 容器將自己設(shè)置到 ServiceBean 中時(shí),ServiceBean 的 setApplicationContext 方法會(huì)檢測(cè) Spring 容器是否支持 ApplicationListener。若支持,則將 supportedApplicationListener 置為 true。代碼就不分析了,大家自行查閱了解。

ServiceBean 是 Dubbo 與 Spring 框架進(jìn)行整合的關(guān)鍵,可以看做是兩個(gè)框架之間的橋梁。具有同樣作用的類還有 ReferenceBean。ServiceBean 實(shí)現(xiàn)了 Spring 的一些拓展接口,有 FactoryBean、ApplicationContextAware、ApplicationListener、DisposableBean 和 BeanNameAware。這些接口我在 Spring 源碼分析系列文章中介紹過(guò),大家可以參考一下,這里就不贅述了。

現(xiàn)在我們知道了 Dubbo 服務(wù)導(dǎo)出過(guò)程的起點(diǎn)。那么接下來(lái),我們快馬加鞭,繼續(xù)進(jìn)行比賽。賽程預(yù)告,下一站是“服務(wù)導(dǎo)出的前置工作”。

2.1 前置工作

前置工作主要包含兩個(gè)部分,分別是配置檢查,以及 URL 裝配。在導(dǎo)出服務(wù)之前,Dubbo 需要檢查用戶的配置是否合理,或者為用戶補(bǔ)充缺省配置。配置檢查完成后,接下來(lái)需要根據(jù)這些配置組裝 URL。在 Dubbo 中,URL 的作用十分重要。Dubbo 使用 URL 作為配置載體,所有的拓展點(diǎn)都是通過(guò) URL 獲取配置。這一點(diǎn),官方文檔中有所說(shuō)明。

采用 URL 作為配置信息的統(tǒng)一格式,所有擴(kuò)展點(diǎn)都通過(guò)傳遞 URL 攜帶配置信息。

接下來(lái),我們先來(lái)分析配置檢查部分的源碼,隨后再來(lái)分析 URL 組裝部分的源碼。

2.1.1 檢查配置

本節(jié)我們接著前面的源碼向下分析,前面說(shuō)過(guò) onApplicationEvent 方法在經(jīng)過(guò)一些判斷后,會(huì)決定是否調(diào)用 export 方法導(dǎo)出服務(wù)。那么下面我們從 export 方法開始進(jìn)行分析,如下:

public synchronized void export() {
    if (provider != null) {
        // 獲取 export 和 delay 配置
        if (export == null) {
            export = provider.getExport();
        }
        if (delay == null) {
            delay = provider.getDelay();
        }
    }
    // 如果 export 為 false,則不導(dǎo)出服務(wù)
    if (export != null && !export) {
        return;
    }

    if (delay != null && delay > 0) {    // delay > 0,延時(shí)導(dǎo)出服務(wù)
        delayExportExecutor.schedule(new Runnable() {
            @Override
            public void run() {
                doExport();
            }
        }, delay, TimeUnit.MILLISECONDS);
    } else {    // 立即導(dǎo)出服務(wù)
        doExport();
    }
}

export 對(duì)兩個(gè)配置進(jìn)行了檢查,并配置執(zhí)行相應(yīng)的動(dòng)作。首先是 export,這個(gè)配置決定了是否導(dǎo)出服務(wù)。有時(shí)候我們只是想本地啟動(dòng)服務(wù)進(jìn)行一些調(diào)試工作,這個(gè)時(shí)候我們并不希望把本地啟動(dòng)的服務(wù)暴露出去給別人調(diào)用。此時(shí),我們就可以通過(guò)配置 export 禁止服務(wù)導(dǎo)出,比如:

delay 見名知意了,用于延遲導(dǎo)出服務(wù)。下面,我們繼續(xù)分析源碼,這次要分析的是 doExport 方法。

protected synchronized void doExport() {
    if (unexported) {
        throw new IllegalStateException("Already unexported!");
    }
    if (exported) {
        return;
    }
    exported = true;
    // 檢測(cè) interfaceName 是否合法
    if (interfaceName == null || interfaceName.length() == 0) {
        throw new IllegalStateException("interface not allow null!");
    }
    // 檢測(cè) provider 是否為空,為空則新建一個(gè),并通過(guò)系統(tǒng)變量為其初始化
    checkDefault();

    // 下面幾個(gè) if 語(yǔ)句用于檢測(cè) provider、application 等核心配置類對(duì)象是否為空,
    // 若為空,則嘗試從其他配置類對(duì)象中獲取相應(yīng)的實(shí)例。
    if (provider != null) {
        if (application == null) {
            application = provider.getApplication();
        }
        if (module == null) {
            module = provider.getModule();
        }
        if (registries == null) {...}
        if (monitor == null) {...}
        if (protocols == null) {...}
    }
    if (module != null) {
        if (registries == null) {
            registries = module.getRegistries();
        }
        if (monitor == null) {...}
    }
    if (application != null) {
        if (registries == null) {
            registries = application.getRegistries();
        }
        if (monitor == null) {...}
    }

    // 檢測(cè) ref 是否泛化服務(wù)類型
    if (ref instanceof GenericService) {
        // 設(shè)置 interfaceClass 為 GenericService.class
        interfaceClass = GenericService.class;
        if (StringUtils.isEmpty(generic)) {
            // 設(shè)置 generic = "true"
            generic = Boolean.TRUE.toString();
        }
    } else {    // ref 非 GenericService 類型
        try {
            interfaceClass = Class.forName(interfaceName, true, Thread.currentThread()
                    .getContextClassLoader());
        } catch (ClassNotFoundException e) {
            throw new IllegalStateException(e.getMessage(), e);
        }
        // 對(duì) interfaceClass,以及  必要字段進(jìn)行檢查
        checkInterfaceAndMethods(interfaceClass, methods);
        // 對(duì) ref 合法性進(jìn)行檢測(cè)
        checkRef();
        // 設(shè)置 generic = "false"
        generic = Boolean.FALSE.toString();
    }

    // local 屬性 Dubbo 官方文檔中沒(méi)有說(shuō)明,不過(guò) local 和 stub 在功能應(yīng)該是一致的,用于配置本地存根
    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);
        }
        // 檢測(cè)本地存根類是否可賦值給接口類,若不可賦值則會(huì)拋出異常,提醒使用者本地存根類類型不合法
        if (!interfaceClass.isAssignableFrom(localClass)) {
            throw new IllegalStateException("The local implementation class " + localClass.getName() + " not implement interface " + interfaceName);
        }
    }

    // stub 和 local 均用于配置本地存根
    if (stub != null) {
        // 此處的代碼和上一個(gè) if 分支的代碼基本一致,這里省略了
    }

    // 檢測(cè)各種對(duì)象是否為空,為空則新建,或者拋出異常
    checkApplication();
    checkRegistry();
    checkProtocol();
    appendProperties(this);
    checkStubAndMock(interfaceClass);
    if (path == null || path.length() == 0) {
        path = interfaceName;
    }

    // 導(dǎo)出服務(wù)
    doExportUrls();

    // ProviderModel 表示服務(wù)提供者模型,此對(duì)象中存儲(chǔ)了和服務(wù)提供者相關(guān)的信息。
    // 比如服務(wù)的配置信息,服務(wù)實(shí)例等。每個(gè)被導(dǎo)出的服務(wù)對(duì)應(yīng)一個(gè) ProviderModel。
    // ApplicationModel 持有所有的 ProviderModel。
    ProviderModel providerModel = new ProviderModel(getUniqueServiceName(), this, ref);
    ApplicationModel.initProviderModel(getUniqueServiceName(), providerModel);
}

以上就是配置檢查的相關(guān)分析,代碼比較多,需要大家耐心看一下。下面對(duì)配置檢查的邏輯進(jìn)行簡(jiǎn)單的總結(jié),如下:

檢測(cè) 標(biāo)簽的 interface 屬性合法性,不合法則拋出異常

檢測(cè) ProviderConfig、ApplicationConfig 等核心配置類對(duì)象是否為空,若為空,則嘗試從其他配置類對(duì)象中獲取相應(yīng)的實(shí)例。

檢測(cè)并處理泛化服務(wù)和普通服務(wù)類

檢測(cè)本地存根配置,并進(jìn)行相應(yīng)的處理

對(duì) ApplicationConfig、RegistryConfig 等配置類進(jìn)行檢測(cè),為空則嘗試創(chuàng)建,若無(wú)法創(chuàng)建則拋出異常

配置檢查并非本文重點(diǎn),因此我不打算對(duì) doExport 方法所調(diào)用的方法進(jìn)行分析(doExportUrls 方法除外)。在這些方法中,除了 appendProperties 方法稍微復(fù)雜一些,其他方法都還好。因此,大家可自行進(jìn)行分析。好了,其他的就不多說(shuō)了,繼續(xù)向下分析。

2.1.2 多協(xié)議多注冊(cè)中心導(dǎo)出服務(wù)

Dubbo 允許我們使用不同的協(xié)議導(dǎo)出服務(wù),也允許我們向多個(gè)注冊(cè)中心注冊(cè)服務(wù)。Dubbo 在 doExportUrls 方法中對(duì)多協(xié)議,多注冊(cè)中心進(jìn)行了支持。相關(guān)代碼如下:

private void doExportUrls() {
    // 加載注冊(cè)中心鏈接
    List registryURLs = loadRegistries(true);
    // 遍歷 protocols,導(dǎo)出每個(gè)服務(wù)
    for (ProtocolConfig protocolConfig : protocols) {
        doExportUrlsFor1Protocol(protocolConfig, registryURLs);
    }
}

上面代碼比較簡(jiǎn)單,首先是通過(guò) loadRegistries 加載注冊(cè)中心鏈接,然后再遍歷 ProtocolConfig 集合導(dǎo)出每個(gè)服務(wù)。并在導(dǎo)出服務(wù)的過(guò)程中,將服務(wù)注冊(cè)到注冊(cè)中心處。下面,我們先來(lái)看一下 loadRegistries 方法的邏輯。

protected List loadRegistries(boolean provider) {
    // 檢測(cè)是否存在注冊(cè)中心配置類,不存在則拋出異常
    checkRegistry();
    List registryList = new ArrayList();
    if (registries != null && !registries.isEmpty()) {
        for (RegistryConfig config : registries) {
            String address = config.getAddress();
            if (address == null || address.length() == 0) {
                // 若 address 為空,則將其設(shè)為 0.0.0.0
                address = Constants.ANYHOST_VALUE;
            }

            // 從系統(tǒng)屬性中加載注冊(cè)中心地址
            String sysaddress = System.getProperty("dubbo.registry.address");
            if (sysaddress != null && sysaddress.length() > 0) {
                address = sysaddress;
            }
            // 判斷 address 是否合法
            if (address.length() > 0 && !RegistryConfig.NO_AVAILABLE.equalsIgnoreCase(address)) {
                Map map = new HashMap();
                // 添加 ApplicationConfig 中的字段信息到 map 中
                appendParameters(map, application);
                // 添加 RegistryConfig 字段信息到 map 中
                appendParameters(map, config);
                map.put("path", RegistryService.class.getName());
                map.put("dubbo", Version.getProtocolVersion());
                map.put(Constants.TIMESTAMP_KEY, String.valueOf(System.currentTimeMillis()));
                if (ConfigUtils.getPid() > 0) {
                    map.put(Constants.PID_KEY, String.valueOf(ConfigUtils.getPid()));
                }
                if (!map.containsKey("protocol")) {
                    if (ExtensionLoader.getExtensionLoader(RegistryFactory.class).hasExtension("remote")) {
                        map.put("protocol", "remote");
                    } else {
                        map.put("protocol", "dubbo");
                    }
                }

                // 解析得到 URL 列表,address 可能包含多個(gè)注冊(cè)中心 ip,
                // 因此解析得到的是一個(gè) URL 列表
                List urls = UrlUtils.parseURLs(address, map);
                for (URL url : urls) {
                    url = url.addParameter(Constants.REGISTRY_KEY, url.getProtocol());
                    // 將 URL 協(xié)議頭設(shè)置為 registry
                    url = url.setProtocol(Constants.REGISTRY_PROTOCOL);
                    // 通過(guò)判斷條件,決定是否添加 url 到 registryList 中,條件如下:
                    // (服務(wù)提供者 && register = true 或 null) 
                    //    || (非服務(wù)提供者 && subscribe = true 或 null)
                    if ((provider && url.getParameter(Constants.REGISTER_KEY, true))
                            || (!provider && url.getParameter(Constants.SUBSCRIBE_KEY, true))) {
                        registryList.add(url);
                    }
                }
            }
        }
    }
    return registryList;
}

上面代碼不是很復(fù)雜,包含如下邏輯:

檢測(cè)是否存在注冊(cè)中心配置類,不存在則拋出異常

構(gòu)建參數(shù)映射集合,也就是 map

構(gòu)建注冊(cè)中心鏈接列表

遍歷鏈接列表,并根據(jù)條件決定是否將其添加到 registryList 中

關(guān)于多協(xié)議多注冊(cè)中心導(dǎo)出服務(wù)就先分析到這,代碼不是很多,就不過(guò)多敘述了。接下來(lái)分析 URL 組裝過(guò)程。

2.1.3 組裝 URL

配置檢查完畢后,緊接著要做的事情是根據(jù)配置,以及其他一些信息組裝 URL。前面說(shuō)過(guò),URL 是 Dubbo 配置的載體,通過(guò) URL 可讓 Dubbo 的各種配置在各個(gè)模塊之間傳遞。URL 之于 Dubbo,猶如水之于魚,非常重要。大家在閱讀 Dubbo 服務(wù)導(dǎo)出相關(guān)源碼的過(guò)程中,要注意 URL 內(nèi)容的變化。既然 URL 如此重要,那么下面我們來(lái)了解一下 URL 組裝的過(guò)程。

private void doExportUrlsFor1Protocol(ProtocolConfig protocolConfig, List registryURLs) {
    String name = protocolConfig.getName();
    // 如果協(xié)議名為空,或空串,則將協(xié)議名變量設(shè)置為 dubbo
    if (name == null || name.length() == 0) {
        name = "dubbo";
    }

    Map map = new HashMap();
    // 添加 side、版本、時(shí)間戳以及進(jìn)程號(hào)等信息到 map 中
    map.put(Constants.SIDE_KEY, Constants.PROVIDER_SIDE);
    map.put(Constants.DUBBO_VERSION_KEY, Version.getProtocolVersion());
    map.put(Constants.TIMESTAMP_KEY, String.valueOf(System.currentTimeMillis()));
    if (ConfigUtils.getPid() > 0) {
        map.put(Constants.PID_KEY, String.valueOf(ConfigUtils.getPid()));
    }

    // 通過(guò)反射將對(duì)象的字段信息到 map 中
    appendParameters(map, application);
    appendParameters(map, module);
    appendParameters(map, provider, Constants.DEFAULT_KEY);
    appendParameters(map, protocolConfig);
    appendParameters(map, this);

    // methods 為 MethodConfig 集合,MethodConfig 中存儲(chǔ)了  標(biāo)簽的配置信息
    if (methods != null && !methods.isEmpty()) {
        // 這段代碼用于添加 Callback 配置到 map 中,代碼太長(zhǎng),待會(huì)多帶帶分析
    }

    // 檢測(cè) generic 是否為 "true",并根據(jù)檢測(cè)結(jié)果向 map 中添加不同的信息
    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);
        if (revision != null && revision.length() > 0) {
            map.put("revision", revision);
        }

        // 為接口生成包裹類 Wrapper,Wrapper 中包含了接口的詳細(xì)信息,比如接口方法名數(shù)組,字段信息等
        String[] methods = Wrapper.getWrapper(interfaceClass).getMethodNames();
        // 添加方法名到 map 中,如果包含多個(gè)方法名,則用逗號(hào)隔開,比如 method = init,destroy
        if (methods.length == 0) {
            logger.warn("NO method found in service interface ...");
            map.put(Constants.METHODS_KEY, Constants.ANY_VALUE);
        } else {
            // 將逗號(hào)作為分隔符連接方法名,并將連接后的字符串放入 map 中
            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);
        }
    }
    // 判斷協(xié)議名是否為 injvm
    if (Constants.LOCAL_PROTOCOL.equals(protocolConfig.getName())) {
        protocolConfig.setRegister(false);
        map.put("notify", "false");
    }

    // 獲取上下文路徑
    String contextPath = protocolConfig.getContextpath();
    if ((contextPath == null || contextPath.length() == 0) && provider != null) {
        contextPath = provider.getContextpath();
    }

    // 獲取 host 和 port
    String host = this.findConfigedHosts(protocolConfig, registryURLs, map);
    Integer port = this.findConfigedPorts(protocolConfig, name, map);
    // 組裝 URL
    URL url = new URL(name, host, port, (contextPath == null || contextPath.length() == 0 ? "" : contextPath + "/") + path, map);
    
    // 省略無(wú)關(guān)代碼
}

上面的代碼首先是將一些信息,比如版本、時(shí)間戳、方法名以及各種配置對(duì)象的字段信息放入到 map 中,map 中的內(nèi)容將作為 URL 的查詢字符串。構(gòu)建好 map 后,緊接著是獲取上下文路徑、主機(jī)名以及端口號(hào)等信息。最后將 map 和主機(jī)名等數(shù)據(jù)傳給 URL 構(gòu)造方法創(chuàng)建 URL 對(duì)象。需要注意的是,這里出現(xiàn)的 URL 并非 java.net.URL,而是 com.alibaba.dubbo.common.URL。

上面省略了一段代碼,這里簡(jiǎn)單分析一下。這段代碼用于檢測(cè) 標(biāo)簽中的配置信息,并將相關(guān)配置添加到 map 中。代碼如下:

private void doExportUrlsFor1Protocol(ProtocolConfig protocolConfig, List registryURLs) {
    // ...

    // methods 為 MethodConfig 集合,MethodConfig 中存儲(chǔ)了  標(biāo)簽的配置信息
    if (methods != null && !methods.isEmpty()) {
        for (MethodConfig method : methods) {
            // 添加 MethodConfig 對(duì)象的字段信息到 map 中,鍵 = 方法名.屬性名。
            // 比如存儲(chǔ)  對(duì)應(yīng)的 MethodConfig,
            // 鍵 = sayHello.retries,map = {"sayHello.retries": 2, "xxx": "yyy"}
            appendParameters(map, method, method.getName());

            String retryKey = method.getName() + ".retry";
            if (map.containsKey(retryKey)) {
                String retryValue = map.remove(retryKey);
                // 檢測(cè) MethodConfig retry 是否為 false,若是,則設(shè)置重試次數(shù)為0
                if ("false".equals(retryValue)) {
                    map.put(method.getName() + ".retries", "0");
                }
            }
            
            // 獲取 ArgumentConfig 列表
            List arguments = method.getArguments();
            if (arguments != null && !arguments.isEmpty()) {
                for (ArgumentConfig argument : arguments) {
                    // 檢測(cè) type 屬性是否為空,或者空串(分支1 ??)
                    if (argument.getType() != null && argument.getType().length() > 0) {
                        Method[] methods = interfaceClass.getMethods();
                        if (methods != null && methods.length > 0) {
                            for (int i = 0; i < methods.length; i++) {
                                String methodName = methods[i].getName();
                                // 比對(duì)方法名,查找目標(biāo)方法
                                if (methodName.equals(method.getName())) {
                                    Class[] argtypes = methods[i].getParameterTypes();
                                    if (argument.getIndex() != -1) {
                                        // 檢測(cè) ArgumentConfig 中的 type 屬性與方法參數(shù)列表
                                        // 中的參數(shù)名稱是否一致,不一致則拋出異常(分支2 ??)
                                        if (argtypes[argument.getIndex()].getName().equals(argument.getType())) {
                                            // 添加 ArgumentConfig 字段信息到 map 中,
                                            // 鍵前綴 = 方法名.index,比如:
                                            // map = {"sayHello.3": true}
                                            appendParameters(map, argument, method.getName() + "." + argument.getIndex());
                                        } else {
                                            throw new IllegalArgumentException("argument config error: ...");
                                        }
                                    } else {    // 分支3 ??
                                        for (int j = 0; j < argtypes.length; j++) {
                                            Class argclazz = argtypes[j];
                                            // 從參數(shù)類型列表中查找類型名稱為 argument.type 的參數(shù)
                                            if (argclazz.getName().equals(argument.getType())) {
                                                appendParameters(map, argument, method.getName() + "." + j);
                                                if (argument.getIndex() != -1 && argument.getIndex() != j) {
                                                    throw new IllegalArgumentException("argument config error: ...");
                                                }
                                            }
                                        }
                                    }
                                }
                            }
                        }

                    // 用戶未配置 type 屬性,但配置了 index 屬性,且 index != -1
                    } else if (argument.getIndex() != -1) {    // 分支4 ??
                        // 添加 ArgumentConfig 字段信息到 map 中
                        appendParameters(map, argument, method.getName() + "." + argument.getIndex());
                    } else {
                        throw new IllegalArgumentException("argument config must set index or type");
                    }
                }
            }
        }
    }

    // ...
}

上面這段代碼 for 循環(huán)和 if else 分支嵌套太多,導(dǎo)致層次太深,不利于閱讀,需要耐心看一下。大家在看這段代碼時(shí),注意把幾個(gè)重要的條件分支找出來(lái)。只要理解了這幾個(gè)分支的意圖,就可以弄懂這段代碼。我在上面代碼中用??符號(hào)標(biāo)識(shí)出了4個(gè)重要的分支,下面用偽代碼解釋一下這幾個(gè)分支的含義。

// 獲取 ArgumentConfig 列表
for (遍歷 ArgumentConfig 列表) {
    if (type 不為 null,也不為空串) {    // 分支1
        1. 通過(guò)反射獲取 interfaceClass 的方法列表
        for (遍歷方法列表) {
            1. 比對(duì)方法名,查找目標(biāo)方法
            2. 通過(guò)反射獲取目標(biāo)方法的參數(shù)類型數(shù)組 argtypes
            if (index != -1) {    // 分支2
                1. 從 argtypes 數(shù)組中獲取下標(biāo) index 處的元素 argType
                2. 檢測(cè) argType 的名稱與 ArgumentConfig 中的 type 屬性是否一致
                3. 添加 ArgumentConfig 字段信息到 map 中,或拋出異常
            } else {    // 分支3
                1. 遍歷參數(shù)類型數(shù)組 argtypes,查找 argument.type 類型的參數(shù)
                2. 添加 ArgumentConfig 字段信息到 map 中
            }
        }
    } else if (index != -1) {    // 分支4
        1. 添加 ArgumentConfig 字段信息到 map 中
    }
}

在本節(jié)分析的源碼中,appendParameters 這個(gè)方法出現(xiàn)的次數(shù)比較多,該方法用于將對(duì)象字段信息添加到 map 中。實(shí)現(xiàn)上則是通過(guò)反射獲取目標(biāo)對(duì)象的 getter 方法,并調(diào)用該方法獲取屬性值。然后再通過(guò) getter 方法名解析出屬性名,比如從方法名 getName 中可解析出屬性 name。如果用戶傳入了屬性名前綴,此時(shí)需要將屬性名加入前綴內(nèi)容。最后將 <屬性名,屬性值> 鍵值對(duì)存入到 map 中就行了。限于篇幅原因,這里就不分析 appendParameters 方法的源碼了,大家請(qǐng)自行分析。

2.2 導(dǎo)出 Dubbo 服務(wù)

前置工作做完,接下來(lái)就可以進(jìn)行服務(wù)導(dǎo)出工作。服務(wù)導(dǎo)出,分為導(dǎo)出到本地 (JVM),和導(dǎo)出到遠(yuǎn)程。在深入分析服務(wù)導(dǎo)出源碼前,我們先來(lái)從宏觀層面上看一下服務(wù)導(dǎo)出邏輯。如下:

private void doExportUrlsFor1Protocol(ProtocolConfig protocolConfig, List registryURLs) {
    
    // 省略無(wú)關(guān)代碼
    
    if (ExtensionLoader.getExtensionLoader(ConfiguratorFactory.class)
            .hasExtension(url.getProtocol())) {
        // 加載 ConfiguratorFactory,并生成 Configurator 配置 url
        url = ExtensionLoader.getExtensionLoader(ConfiguratorFactory.class)
                .getExtension(url.getProtocol()).getConfigurator(url).configure(url);
    }

    String scope = url.getParameter(Constants.SCOPE_KEY);
    // 如果 scope = none,則什么都不做
    if (!Constants.SCOPE_NONE.toString().equalsIgnoreCase(scope)) {
        // scope != remote,導(dǎo)出到本地
        if (!Constants.SCOPE_REMOTE.toString().equalsIgnoreCase(scope)) {
            exportLocal(url);
        }

        // scope != local,導(dǎo)出到遠(yuǎn)程
        if (!Constants.SCOPE_LOCAL.toString().equalsIgnoreCase(scope)) {
            if (registryURLs != null && !registryURLs.isEmpty()) {
                for (URL registryURL : registryURLs) {
                    url = url.addParameterIfAbsent(Constants.DYNAMIC_KEY, registryURL.getParameter(Constants.DYNAMIC_KEY));
                    // 加載監(jiān)視器鏈接
                    URL monitorUrl = loadMonitor(registryURL);
                    if (monitorUrl != null) {
                        // 將監(jiān)視器鏈接作為參數(shù)添加到 url 中
                        url = url.addParameterAndEncoded(Constants.MONITOR_KEY, monitorUrl.toFullString());
                    }

                    String proxy = url.getParameter(Constants.PROXY_KEY);
                    if (StringUtils.isNotEmpty(proxy)) {
                        registryURL = registryURL.addParameter(Constants.PROXY_KEY, proxy);
                    }

                    // 為服務(wù)提供類(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);

                    // 導(dǎo)出服務(wù),并生成 Exporter
                    Exporter exporter = protocol.export(wrapperInvoker);
                    exporters.add(exporter);
                }
            } else {    // 不存在注冊(cè)中心,僅導(dǎo)出服務(wù)
                Invoker invoker = proxyFactory.getInvoker(ref, (Class) interfaceClass, url);
                DelegateProviderMetaDataInvoker wrapperInvoker = new DelegateProviderMetaDataInvoker(invoker, this);

                Exporter exporter = protocol.export(wrapperInvoker);
                exporters.add(exporter);
            }
        }
    }
    this.urls.add(url);
}

上面代碼根據(jù) url 中的 scope 參數(shù)決定服務(wù)導(dǎo)出方式,分別如下:

scope = none,不導(dǎo)出服務(wù)

scope != remote,導(dǎo)出到本地

scope != local,導(dǎo)出到遠(yuǎn)程

不管是導(dǎo)出到本地,還是遠(yuǎn)程。進(jìn)行服務(wù)導(dǎo)出之前,均需要先創(chuàng)建 Invoker。這是一個(gè)很重要的步驟,因此接下來(lái)我會(huì)先分析 Invoker 的創(chuàng)建過(guò)程。

2.2.1 Invoker 創(chuàng)建過(guò)程

在 Dubbo 中,Invoker 是一個(gè)非常重要的模型。在服務(wù)提供端,以及服務(wù)引用端均會(huì)出現(xiàn) Invoker。Dubbo 官方文檔中對(duì) Invoker 進(jìn)行了說(shuō)明,這里引用一下。

Invoker 是實(shí)體域,它是 Dubbo 的核心模型,其它模型都向它靠擾,或轉(zhuǎn)換成它,它代表一個(gè)可執(zhí)行體,可向它發(fā)起 invoke 調(diào)用,它有可能是一個(gè)本地的實(shí)現(xiàn),也可能是一個(gè)遠(yuǎn)程的實(shí)現(xiàn),也可能一個(gè)集群實(shí)現(xiàn)。

既然 Invoker 如此重要,那么我們很有必要搞清楚 Invoker 的用途。Invoker 是由 ProxyFactory 創(chuàng)建而來(lái),Dubbo 默認(rèn)的 ProxyFactory 實(shí)現(xiàn)類是 JavassistProxyFactory。下面我們到 JavassistProxyFactory 代碼中,探索 Invoker 的創(chuàng)建過(guò)程。如下:

-- JavassistProxyFactory
public  Invoker getInvoker(T proxy, Class type, URL url) {
    // 為目標(biāo)類創(chuàng)建 Wrapper
    final Wrapper wrapper = Wrapper.getWrapper(proxy.getClass().getName().indexOf("$") < 0 ? proxy.getClass() : type);
    // 創(chuàng)建匿名 Invoker 類對(duì)象,并實(shí)現(xiàn) doInvoke 方法。
    return new AbstractProxyInvoker(proxy, type, url) {
        @Override
        protected Object doInvoke(T proxy, String methodName,
                                  Class[] parameterTypes,
                                  Object[] arguments) throws Throwable {
            // 調(diào)用 Wrapper 的 invokeMethod 方法,invokeMethod 最終會(huì)調(diào)用目標(biāo)方法
            return wrapper.invokeMethod(proxy, methodName, parameterTypes, arguments);
        }
    };
}

如上,JavassistProxyFactory 創(chuàng)建了一個(gè)繼承自 AbstractProxyInvoker 類的匿名對(duì)象,并覆寫了抽象方法 doInvoke。覆寫后的 doInvoke 邏輯比較簡(jiǎn)單,僅是將調(diào)用請(qǐng)求轉(zhuǎn)發(fā)給了 Wrapper 類的 invokeMethod 方法。Wrapper 用于“包裹”目標(biāo)類,Wrapper 是一個(gè)抽象類,僅可通過(guò) getWrapper(Class) 方法創(chuàng)建子類。在創(chuàng)建 Wrapper 子類的過(guò)程中,子類代碼生成邏輯會(huì)對(duì) getWrapper 方法傳入的 Class 對(duì)象進(jìn)行解析,拿到諸如類方法,類成員變量等信息。以及生成 invokeMethod 方法代碼,和其他一些方法代碼。代碼生成完畢后,通過(guò) Javassist 生成 Class 對(duì)象,最后再通過(guò)反射創(chuàng)建 Wrapper 實(shí)例。相關(guān)的代碼如下:

 public static Wrapper getWrapper(Class c) {
    while (ClassGenerator.isDynamicClass(c))
        c = c.getSuperclass();

    if (c == Object.class)
        return OBJECT_WRAPPER;

    // 訪存
    Wrapper ret = WRAPPER_MAP.get(c);
    if (ret == null) {
        // 緩存未命中,創(chuàng)建 Wrapper
        ret = makeWrapper(c);
        // 寫入緩存
        WRAPPER_MAP.put(c, ret);
    }
    return ret;
}

getWrapper 方法只是包含了一些緩存操作邏輯,非重點(diǎn)。下面我們重點(diǎn)關(guān)注 makeWrapper 方法。

private static Wrapper makeWrapper(Class c) {
    // 檢測(cè) 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 用于存儲(chǔ) setPropertyValue 方法代碼
    StringBuilder c1 = new StringBuilder("public void setPropertyValue(Object o, String n, Object v){ ");
    // c2 用于存儲(chǔ) getPropertyValue 方法代碼
    StringBuilder c2 = new StringBuilder("public Object getPropertyValue(Object o, String n){ ");
    // c3 用于存儲(chǔ) invokeMethod 方法代碼
    StringBuilder c3 = new StringBuilder("public Object invokeMethod(Object o, String n, Class[] p, Object[] v) throws " + InvocationTargetException.class.getName() + "{ ");

    // 生成類型轉(zhuǎn)換代碼及異常捕捉代碼,比如:
    //   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 用于存儲(chǔ)成員變量名和類型
    Map> pts = new HashMap>();
    // ms 用于存儲(chǔ)方法描述信息(可理解為方法簽名)及 Method 實(shí)例
    Map ms = new LinkedHashMap();
    // mns 為方法名列表
    List mns = new ArrayList();
    // dmns 用于存儲(chǔ)定義在當(dāng)前類中的方法的名稱
    List dmns = new ArrayList();

    // --------------------------------? 分割線1 ?-------------------------------------

    // 獲取 public 訪問(wèn)級(jí)別的字段,并為所有字段生成條件判斷語(yǔ)句
    for (Field f : c.getFields()) {
        String fn = f.getName();
        Class ft = f.getType();
        if (Modifier.isStatic(f.getModifiers()) || Modifier.isTransient(f.getModifiers()))
            // 忽略關(guān)鍵字 static 或 transient 修飾的變量
            continue;

        // 生成條件判斷及賦值語(yǔ)句,比如:
        // 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; }");

        // 生成條件判斷及返回語(yǔ)句,比如:
        // if( $2.equals("name") ) { return ($w)w.name; }
        c2.append(" if( $2.equals("").append(fn).append("") ){ return ($w)w.").append(fn).append("; }");

        // 存儲(chǔ) <字段名, 字段類型> 鍵值對(duì)到 pts 中
        pts.put(fn, ft);
    }

    // --------------------------------? 分割線2 ?-------------------------------------

    Method[] methods = c.getMethods();
    // 檢測(cè) c 中是否包含在當(dāng)前類中聲明的方法
    boolean hasMethod = hasMethods(methods);
    if (hasMethod) {
        c3.append(" try{");
    }
    for (Method m : methods) {
        if (m.getDeclaringClass() == Object.class)
            // 忽略 Object 中定義的方法
            continue;

        String mn = m.getName();
        // 生成方法名判斷語(yǔ)句,示例如下:
        // if ( "sayHello".equals( $2 )
        c3.append(" if( "").append(mn).append("".equals( $2 ) ");
        int len = m.getParameterTypes().length;
        // 生成運(yùn)行時(shí)傳入?yún)?shù)的數(shù)量與方法的參數(shù)列表長(zhǎng)度判斷語(yǔ)句,示例如下:
        // && $3.length == 2
        c3.append(" && ").append(" $3.length == ").append(len);

        boolean override = false;
        for (Method m2 : methods) {
            // 檢測(cè)方法是否存在重載情況,條件為:方法對(duì)象不同 && 方法名相同
            if (m != m2 && m.getName().equals(m2.getName())) {
                override = true;
                break;
            }
        }
        // 對(duì)重載方法進(jìn)行處理,考慮下面的方法:
        //    1. void sayHello(Integer, String)
        //    2. void sayHello(Integer, Integer)
        // 方法名相同,參數(shù)列表長(zhǎng)度也相同,因此不能僅通過(guò)這兩項(xiàng)判斷兩個(gè)方法是否相等。
        // 需要進(jìn)一步判斷方法的參數(shù)類型
        if (override) {
            if (len > 0) {
                for (int l = 0; l < len; l++) {
                    // && $3[0].getName().equals("java.lang.Integer") 
                    //    && $3[1].getName().equals("java.lang.String")
                    c3.append(" && ").append(" $3[").append(l).append("].getName().equals("")
                            .append(m.getParameterTypes()[l].getName()).append("")");
                }
            }
        }

        // 添加 ) {,完成方法判斷語(yǔ)句,此時(shí)生成的方法可能如下(已格式化):
        // if ("sayHello".equals($2) 
        //     && $3.length == 2
        //     && $3[0].getName().equals("java.lang.Integer") 
        //     && $3[1].getName().equals("java.lang.String")) {
        c3.append(" ) { ");

        // 根據(jù)返回值類型生成目標(biāo)方法調(diào)用語(yǔ)句
        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(");");

        // 添加 }, 當(dāng)前”方法判斷條件“代碼生成完畢,示例代碼如下(已格式化):
        // if ("sayHello".equals($2) 
        //     && $3.length == 2
        //     && $3[0].getName().equals("java.lang.Integer") 
        //     && $3[1].getName().equals("java.lang.String")) {
        //
        //     w.sayHello((java.lang.Integer)$4[0], (java.lang.String)$4[1]); 
        //     return null;
        // }
        c3.append(" }");

        // 添加方法名到 mns 集合中
        mns.add(mn);
        // 檢測(cè)當(dāng)前方法是否在 c 中被聲明的
        if (m.getDeclaringClass() == c)
            // 若是,則將當(dāng)前方法名添加到 dmns 中
            dmns.add(mn);
        ms.put(ReflectUtils.getDesc(m), m);
    }
    if (hasMethod) {
        // 添加異常捕捉語(yǔ)句
        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() + "."); }");

    // --------------------------------? 分割線3 ?-------------------------------------

    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));
            // 生成屬性判斷以及返回語(yǔ)句,示例如下:
            // 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());

        // 匹配以 is/has/can 開頭的方法
        } else if ((matcher = ReflectUtils.IS_HAS_CAN_METHOD_DESC_PATTERN.matcher(md)).matches()) {
            String pn = propertyName(matcher.group(1));
            // 生成屬性判斷以及返回語(yǔ)句,示例如下:
            // if( $2.equals("dream") ) { return ($w).w.hasDream(); }
            c2.append(" if( $2.equals("").append(pn).append("") ){ return ($w)w.").append(method.getName()).append("(); }");
            pts.put(pn, method.getReturnType());

        // 匹配以 set 開頭的方法
        } else if ((matcher = ReflectUtils.SETTER_METHOD_DESC_PATTERN.matcher(md)).matches()) {
            Class pt = method.getParameterTypes()[0];
            String pn = propertyName(matcher.group(1));
            // 生成屬性判斷以及 setter 調(diào)用語(yǔ)句,示例如下:
            // 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+"" filed or setter method in class " + c.getName() + "."); }");
    c2.append(" throw new " + NoSuchPropertyException.class.getName() + "("Not found property ""+$2+"" filed or setter method in class " + c.getName() + "."); }");

    // --------------------------------? 分割線4 ?-------------------------------------

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

    // 添加默認(rèn)構(gòu)造方法
    cc.addDefaultConstructor();

    // 添加字段
    cc.addField("public static String[] pns;");
    cc.addField("public static " + Map.class.getName() + " pts;");
    cc.addField("public static String[] mns;");
    cc.addField("public static String[] dmns;");
    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();
        
        // 設(shè)置字段值
        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());

        // 創(chuàng)建 Wrapper 實(shí)例
        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();
    }
}

上面代碼很長(zhǎng),大家耐心看一下。我在上面代碼中做了大量的注釋,并按功能對(duì)代碼進(jìn)行了分塊,以幫助大家理解代碼邏輯。下面對(duì)這段代碼進(jìn)行講解。首先我們把目光移到分割線1之上的代碼,這段代碼主要用于進(jìn)行一些初始化操作。比如創(chuàng)建 c1、c2、c3 以及 pts、ms、mns 等變量,以及向 c1、c2、c3 中添加方法定義和類型類型轉(zhuǎn)換代碼。接下來(lái)是分割線1到分割線2之間的代碼,這段代碼用于為 public 級(jí)別的字段生成條件判斷取值與賦值代碼。這段代碼不是很難看懂,就不多說(shuō)了。繼續(xù)向下看,分割線2和分隔線3之間的代碼用于為定義在當(dāng)前類中的方法生成判斷語(yǔ)句,和方法調(diào)用語(yǔ)句。因?yàn)樾枰獙?duì)方法重載進(jìn)行校驗(yàn),因此到這這段代碼看起來(lái)有點(diǎn)復(fù)雜。不過(guò)耐心開一下,也不是很難理解。接下來(lái)是分割線3和分隔線4之間的代碼,這段代碼用于處理 getter、setter 以及以 is/has/can 開頭的方法。處理方式是通過(guò)正則表達(dá)式獲取方法類型(get/set/is/...),以及屬性名。之后為屬性名生成判斷語(yǔ)句,然后為方法生成調(diào)用語(yǔ)句。最后我們?cè)賮?lái)看一下分隔線4以下的代碼,這段代碼通過(guò) ClassGenerator 為剛剛生成的代碼構(gòu)建 Class 類,并通過(guò)反射創(chuàng)建對(duì)象。ClassGenerator 是 Dubbo 自己封裝的,該類的核心是 toClass() 的重載方法 toClass(ClassLoader, ProtectionDomain),該方法通過(guò) javassist 構(gòu)建 Class。這里就不分析 toClass 方法了,大家請(qǐng)自行分析。

閱讀 Wrapper 類代碼需要對(duì) javassist 框架有所了解。關(guān)于 javassist,大家如果不熟悉,請(qǐng)自行查閱資料,本節(jié)不打算介紹 javassist 相關(guān)內(nèi)容。

好了,關(guān)于 Wrapper 類生成過(guò)程就分析到這。如果大家看的不是很明白,可以多帶帶為 Wrapper 創(chuàng)建單元測(cè)試,然后單步調(diào)試。并將生成的代碼拷貝出來(lái),格式化后再進(jìn)行觀察和理解。好了,本節(jié)先到這。

2.2.2 導(dǎo)出服務(wù)到本地

本節(jié)我們來(lái)看一下服務(wù)導(dǎo)出相關(guān)的代碼,按照代碼執(zhí)行順序,本節(jié)先來(lái)分析導(dǎo)出服務(wù)到本地的過(guò)程。相關(guān)代碼如下:

private void exportLocal(URL url) {
    // 如果 URL 的協(xié)議頭等于 injvm,說(shuō)明已經(jīng)導(dǎo)出到本地了,無(wú)需再次導(dǎo)出
    if (!Constants.LOCAL_PROTOCOL.equalsIgnoreCase(url.getProtocol())) {
        URL local = URL.valueOf(url.toFullString())
            .setProtocol(Constants.LOCAL_PROTOCOL)    // 設(shè)置協(xié)議頭為 injvm
            .setHost(LOCALHOST)
            .setPort(0);
        ServiceClassHolder.getInstance().pushServiceClass(getServiceClass(ref));
        // 創(chuàng)建 Invoker,并導(dǎo)出服務(wù),這里的 protocol 會(huì)在運(yùn)行時(shí)調(diào)用 InjvmProtocol 的 export 方法
        Exporter exporter = protocol.export(
            proxyFactory.getInvoker(ref, (Class) interfaceClass, local));
        exporters.add(exporter);
    }
}

exportLocal 方法比較簡(jiǎn)單,首先根據(jù) URL 協(xié)議頭決定是否導(dǎo)出服務(wù)。若需導(dǎo)出,則創(chuàng)建一個(gè)新的 URL 并將協(xié)議頭、主機(jī)名以及端口設(shè)置成新的值。然后創(chuàng)建 Invoker,并調(diào)用 InjvmProtocol 的 export 方法導(dǎo)出服務(wù)。下面我們來(lái)看一下 InjvmProtocol 的 export 方法都做了哪些事情。

public  Exporter export(Invoker invoker) throws RpcException {
    // 創(chuàng)建 InjvmExporter
    return new InjvmExporter(invoker, invoker.getUrl().getServiceKey(), exporterMap);
}

如上,InjvmProtocol 的 export 方法僅創(chuàng)建了一個(gè) InjvmExporter,無(wú)其他邏輯。到此導(dǎo)出服務(wù)到本地就分析完了,接下來(lái),我們繼續(xù)分析導(dǎo)出服務(wù)到遠(yuǎn)程的過(guò)程。

2.2.3 導(dǎo)出服務(wù)到遠(yuǎn)程

與導(dǎo)出服務(wù)到本地相比,導(dǎo)出服務(wù)到遠(yuǎn)程的過(guò)程要復(fù)雜不少,其包含了服務(wù)導(dǎo)出與服務(wù)注冊(cè)兩個(gè)過(guò)程。這兩個(gè)過(guò)程涉及到了大量的調(diào)用,因此比較復(fù)雜。不過(guò)不管再難,我們都要看一下,萬(wàn)一看懂了呢。按照代碼執(zhí)行順序,本節(jié)先來(lái)分析服務(wù)導(dǎo)出邏輯,服務(wù)注冊(cè)邏輯將在下一節(jié)進(jìn)行分析。下面開始分析,我們把目光移動(dòng)到 RegistryProtocol 的 export 方法上。

public  Exporter export(final Invoker originInvoker) throws RpcException {
    // 導(dǎo)出服務(wù)
    final ExporterChangeableWrapper exporter = doLocalExport(originInvoker);

    // 獲取注冊(cè)中心 URL,以 zookeeper 注冊(cè)中心為例,得到的示例 URL 如下:
    // zookeeper://127.0.0.1:2181/com.alibaba.dubbo.registry.RegistryService?application=demo-provider&dubbo=2.0.2&export=dubbo%3A%2F%2F172.17.48.52%3A20880%2Fcom.alibaba.dubbo.demo.DemoService%3Fanyhost%3Dtrue%26application%3Ddemo-provider
    URL registryUrl = getRegistryUrl(originInvoker);

    // 根據(jù) URL 加載 Registry 實(shí)現(xiàn)類,比如 ZookeeperRegistry
    final Registry registry = getRegistry(originInvoker);
    
    // 獲取已注冊(cè)的服務(wù)提供者 URL,比如:
    // dubbo://172.17.48.52:20880/com.alibaba.dubbo.demo.DemoService?anyhost=true&application=demo-provider&dubbo=2.0.2&generic=false&interface=com.alibaba.dubbo.demo.DemoService&methods=sayHello
    final URL registeredProviderUrl = getRegisteredProviderUrl(originInvoker);

    // 獲取 register 參數(shù)
    boolean register = registeredProviderUrl.getParameter("register", true);

    // 向服務(wù)提供者與消費(fèi)者注冊(cè)表中注冊(cè)服務(wù)提供者
    ProviderConsumerRegTable.registerProvider(originInvoker, registryUrl, registeredProviderUrl);

    // 根據(jù) register 的值決定是否注冊(cè)服務(wù)
    if (register) {
        // 向注冊(cè)中心注冊(cè)服務(wù)
        register(registryUrl, registeredProviderUrl);
        ProviderConsumerRegTable.getProviderWrapper(originInvoker).setReg(true);
    }

    // 獲取訂閱 URL,比如:
    // provider://172.17.48.52:20880/com.alibaba.dubbo.demo.DemoService?category=configurators&check=false&anyhost=true&application=demo-provider&dubbo=2.0.2&generic=false&interface=com.alibaba.dubbo.demo.DemoService&methods=sayHello
    final URL overrideSubscribeUrl = getSubscribedOverrideUrl(registeredProviderUrl);
    // 創(chuàng)建監(jiān)聽器
    final OverrideListener overrideSubscribeListener = new OverrideListener(overrideSubscribeUrl, originInvoker);
    overrideListeners.put(overrideSubscribeUrl, overrideSubscribeListener);
    // 向注冊(cè)中心進(jìn)行訂閱 override 數(shù)據(jù)
    registry.subscribe(overrideSubscribeUrl, overrideSubscribeListener);
    // 創(chuàng)建并返回 DestroyableExporter
    return new DestroyableExporter(exporter, originInvoker, overrideSubscribeUrl, registeredProviderUrl);
}

上面代碼看起來(lái)比較復(fù)雜,主要做如下一些操作:

調(diào)用 doLocalExport 導(dǎo)出服務(wù)

向注冊(cè)中心注冊(cè)服務(wù)

向注冊(cè)中心進(jìn)行訂閱 override 數(shù)據(jù)

創(chuàng)建并返回 DestroyableExporter

在以上操作中,除了創(chuàng)建并返回 DestroyableExporter 沒(méi)啥難度外,其他幾步操作都不是很簡(jiǎn)單。這其中,導(dǎo)出服務(wù)和注冊(cè)服務(wù)是本章要重點(diǎn)分析的邏輯。 訂閱 override 數(shù)據(jù)這個(gè)是非重點(diǎn)內(nèi)容,后面會(huì)簡(jiǎn)單介紹一下。下面開始本節(jié)的分析,先來(lái)分析 doLocalExport 方法的邏輯,如下:

private  ExporterChangeableWrapper doLocalExport(final Invoker originInvoker) {
    String key = getCacheKey(originInvoker);
    // 訪問(wèn)緩存
    ExporterChangeableWrapper exporter = (ExporterChangeableWrapper) bounds.get(key);
    if (exporter == null) {
        synchronized (bounds) {
            exporter = (ExporterChangeableWrapper) bounds.get(key);
            if (exporter == null) {
                // 創(chuàng)建 Invoker 為委托類對(duì)象
                final Invoker invokerDelegete = new InvokerDelegete(originInvoker, getProviderUrl(originInvoker));
                // 調(diào)用 protocol 的 export 方法導(dǎo)出服務(wù)
                exporter = new ExporterChangeableWrapper((Exporter) protocol.export(invokerDelegete), originInvoker);
                
                // 寫緩存
                bounds.put(key, exporter);
            }
        }
    }
    return exporter;
}

上面的代碼是典型的雙重檢查,這個(gè)大家應(yīng)該都知道。接下來(lái),我們把重點(diǎn)放在 Protocol 的 export 方法上。假設(shè)運(yùn)行時(shí)協(xié)議為 dubbo,此處的 protocol 會(huì)在運(yùn)行時(shí)加載 DubboProtocol,并調(diào)用 DubboProtocol 的 export 方法。我們目光轉(zhuǎn)移到 DubboProtocol 的 export 方法上,相關(guān)分析如下:

public  Exporter export(Invoker invoker) throws RpcException {
    URL url = invoker.getUrl();

    // 獲取服務(wù)標(biāo)識(shí),理解成服務(wù)坐標(biāo)也行。由服務(wù)組名,服務(wù)名,服務(wù)版本號(hào)以及端口組成。比如:
    // demoGroup/com.alibaba.dubbo.demo.DemoService:1.0.1:20880
    String key = serviceKey(url);
    // 創(chuàng)建 DubboExporter
    DubboExporter exporter = new DubboExporter(invoker, key, exporterMap);
    // 將  鍵值對(duì)放入緩存中
    exporterMap.put(key, exporter);

    // 以下代碼應(yīng)該和本地存根有關(guān),代碼不難看懂,但具體用途暫時(shí)不清楚,先忽略
    Boolean isStubSupportEvent = url.getParameter(Constants.STUB_EVENT_KEY, Constants.DEFAULT_STUB_EVENT);
    Boolean isCallbackservice = url.getParameter(Constants.IS_CALLBACK_SERVICE, false);
    if (isStubSupportEvent && !isCallbackservice) {
        String stubServiceMethods = url.getParameter(Constants.STUB_EVENT_METHODS_KEY);
        if (stubServiceMethods == null || stubServiceMethods.length() == 0) {
            // 省略日志打印代碼
        } else {
            stubServiceMethodsMap.put(url.getServiceKey(), stubServiceMethods);
        }
    }

    // 啟動(dòng)服務(wù)器
    openServer(url);
    // 優(yōu)化序列化
    optimizeSerialization(url);
    return exporter;
}

如上,我們重點(diǎn)關(guān)注 DubboExporter 的創(chuàng)建以及 openServer 方法,其他邏輯看不懂也沒(méi)關(guān)系,不影響理解服務(wù)導(dǎo)出過(guò)程。另外,DubboExporter 的代碼比較簡(jiǎn)單,就不分析了。下面分析 openServer 方法。

private void openServer(URL url) {
    // 獲取 host:port,并將其作為服務(wù)器實(shí)例的 key,用于標(biāo)識(shí)當(dāng)前的服務(wù)器實(shí)例
    String key = url.getAddress();
    boolean isServer = url.getParameter(Constants.IS_SERVER_KEY, true);
    if (isServer) {
        // 訪問(wèn)緩存
        ExchangeServer server = serverMap.get(key);
        if (server == null) {
            // 創(chuàng)建服務(wù)器實(shí)例
            serverMap.put(key, createServer(url));
        } else {
            // 服務(wù)器已創(chuàng)建,則根據(jù) url 中的配置重置服務(wù)器
            server.reset(url);
        }
    }
}

如上,在同一臺(tái)機(jī)器上(單網(wǎng)卡),同一個(gè)端口上僅允許啟動(dòng)一個(gè)服務(wù)器實(shí)例。若某個(gè)端口上已有服務(wù)器實(shí)例,此時(shí)則調(diào)用 reset 方法重置服務(wù)器的一些配置。考慮到篇幅問(wèn)題,關(guān)于服務(wù)器實(shí)例重置的代碼就不分析了。接下來(lái)分析服務(wù)器實(shí)例的創(chuàng)建過(guò)程。如下:

private ExchangeServer createServer(URL url) {
    url = url.addParameterIfAbsent(Constants.CHANNEL_READONLYEVENT_SENT_KEY,
    // 添加心跳檢測(cè)配置到 url 中
    url = url.addParameterIfAbsent(Constants.HEARTBEAT_KEY, String.valueOf(Constants.DEFAULT_HEARTBEAT));
    // 獲取 server 參數(shù),默認(rèn)為 netty
    String str = url.getParameter(Constants.SERVER_KEY, Constants.DEFAULT_REMOTING_SERVER);

    // 通過(guò) SPI 檢測(cè)是否存在 server 參數(shù)所代表的 Transporter 拓展,不存在則拋出異常
    if (str != null && str.length() > 0 && !ExtensionLoader.getExtensionLoader(Transporter.class).hasExtension(str))
        throw new RpcException("Unsupported server type: " + str + ", url: " + url);

    // 添加編碼解碼器參數(shù)
    url = url.addParameter(Constants.CODEC_KEY, DubboCodec.NAME);
    ExchangeServer server;
    try {
        // 創(chuàng)建 ExchangeServer
        server = Exchangers.bind(url, requestHandler);
    } catch (RemotingException e) {
        throw new RpcException("Fail to start server...");
    }
                                   
    // 獲取 client 參數(shù),可指定 netty,mina
    str = url.getParameter(Constants.CLIENT_KEY);
    if (str != null && str.length() > 0) {
        // 獲取所有的 Transporter 實(shí)現(xiàn)類名稱集合,比如 supportedTypes = [netty, mina]
        Set supportedTypes = ExtensionLoader.getExtensionLoader(Transporter.class).getSupportedExtensions();
        // 檢測(cè)當(dāng)前 Dubbo 所支持的 Transporter 實(shí)現(xiàn)類名稱列表中,
        // 是否包含 client 所表示的 Transporter,若不包含,則拋出異常
        if (!supportedTypes.contains(str)) {
            throw new RpcException("Unsupported client type...");
        }
    }
    return server;
}

如上,createServer 包含三個(gè)核心的操作。第一是檢測(cè)是否存在 server 參數(shù)所代表的 Transporter 拓展,不存在則拋出異常。第二是創(chuàng)建服務(wù)器實(shí)例。第三是檢測(cè)是否支持 client 參數(shù)所表示的 Transporter 拓展,不存在也是拋出異常。兩次檢測(cè)操作所對(duì)應(yīng)的代碼比較直白了,無(wú)需多說(shuō)。但創(chuàng)建服務(wù)器的操作目前還不是很清晰,我們繼續(xù)往下看。

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,默認(rèn)為 HeaderExchanger。
    // 緊接著調(diào)用 HeaderExchanger 的 bind 方法創(chuàng)建 ExchangeServer 實(shí)例
    return getExchanger(url).bind(url, handler);
}

上面代碼比較簡(jiǎn)單,就不多說(shuō)了。下面看一下 HeaderExchanger 的 bind 方法。

public ExchangeServer bind(URL url, ExchangeHandler handler) throws RemotingException {
    // 創(chuàng)建 HeaderExchangeServer 實(shí)例,該方法包含了多步操作,本別如下:
    //   1. new HeaderExchangeHandler(handler)
    //     2. new DecodeHandler(new HeaderExchangeHandler(handler))
    //   3. Transporters.bind(url, new DecodeHandler(new HeaderExchangeHandler(handler)))
    return new HeaderExchangeServer(Transporters.bind(url, new DecodeHandler(new HeaderExchangeHandler(handler))));
}

HeaderExchanger 的 bind 方法包含的邏輯比較多,但目前我們僅需關(guān)心 Transporters 的 bind 方法邏輯即可。該方法的代碼如下:

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 元素?cái)?shù)量大于1,則創(chuàng)建 ChannelHandler 分發(fā)器
        handler = new ChannelHandlerDispatcher(handlers);
    }
    // 獲取自適應(yīng) Transporter 實(shí)例,并調(diào)用實(shí)例方法
    return getTransporter().bind(url, handler);
}

如上,getTransporter() 方法獲取的 Transporter 是在運(yùn)行時(shí)動(dòng)態(tài)創(chuàng)建的,類名為 Transporter$Adaptive,也就是自適應(yīng)拓展類。我在[上一篇文章](http://www.tianxiaobo.com/2018/10/13/Dubbo-%E6%BA%90%E7%A0%81%E5%88%86%E6%9E%90-%E8%87%AA%E9%80%82%E5%BA%94%E6%8B%93%E5%B1%95%E5%8E%9F%E7%90%86/)中詳細(xì)分析了自適應(yīng)拓展類的生成過(guò)程,對(duì)自適應(yīng)拓展類不了解的同學(xué)可以參考我之前的文章,這里不再贅述。Transporter$Adaptive 會(huì)在運(yùn)行時(shí)根據(jù)傳入的 URL 參數(shù)決定加載什么類型的 Transporter,默認(rèn)為 NettyTransporter。下面我們繼續(xù)跟下去,這次分析的是 NettyTransporter 的 bind 方法。

public Server bind(URL url, ChannelHandler listener) throws RemotingException {
    // 創(chuàng)建 NettyServer
    return new NettyServer(url, listener);
}

這里僅有一句創(chuàng)建 NettyServer 的代碼,沒(méi)啥好講的,我們繼續(xù)向下看。

public class NettyServer extends AbstractServer implements Server {
    public NettyServer(URL url, ChannelHandler handler) throws RemotingException {
        // 調(diào)用父類構(gòu)造方法
        super(url, ChannelHandlers.wrap(handler, ExecutorUtil.setThreadName(url, SERVER_THREAD_POOL_NAME)));
    }
}


public abstract class AbstractServer extends AbstractEndpoint implements Server {
    public AbstractServer(URL url, ChannelHandler handler) throws RemotingException {
        // 調(diào)用父類構(gòu)造方法,這里就不用跟進(jìn)去了,沒(méi)什么復(fù)雜邏輯
        super(url, handler);
        localAddress = getUrl().toInetSocketAddress();

        // 獲取 ip 和端口
        String bindIp = getUrl().getParameter(Constants.BIND_IP_KEY, getUrl().getHost());
        int bindPort = getUrl().getParameter(Constants.BIND_PORT_KEY, getUrl().getPort());
        if (ur           
               
                                           
                       
                 

文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請(qǐng)注明本文地址:http://m.specialneedsforspecialkids.com/yun/71977.html

相關(guān)文章

  • Dubbo 2.7.1 踩坑記

    摘要:面試題服務(wù)提供者能實(shí)現(xiàn)失效踢出是什么原理高頻題服務(wù)宕機(jī)的時(shí)候,該節(jié)點(diǎn)由于是持久節(jié)點(diǎn)會(huì)永遠(yuǎn)存在,而且當(dāng)服務(wù)再次重啟的時(shí)候會(huì)將重新注冊(cè)一個(gè)新節(jié)點(diǎn)。 Dubbo 2.7 版本增加新特性,新系統(tǒng)開始使用 Dubbo 2.7.1 嘗鮮新功能。使用過(guò)程中不慎踩到這個(gè)版本的 Bug。 系統(tǒng)架構(gòu) Spring Boot 2.14-Release + Dubbo 2.7.1 現(xiàn)象 Dubbo 服務(wù)者啟動(dòng)...

    wudengzan 評(píng)論0 收藏0
  • dubbo源碼解析(四十四)服務(wù)暴露過(guò)程

    摘要:服務(wù)暴露過(guò)程目標(biāo)從源碼的角度分析服務(wù)暴露過(guò)程。導(dǎo)出服務(wù),包含暴露服務(wù)到本地,和暴露服務(wù)到遠(yuǎn)程兩個(gè)過(guò)程。其中服務(wù)暴露的第八步已經(jīng)沒(méi)有了。將泛化調(diào)用版本號(hào)或者等信息加入獲得服務(wù)暴露地址和端口號(hào),利用內(nèi)數(shù)據(jù)組裝成。 dubbo服務(wù)暴露過(guò)程 目標(biāo):從源碼的角度分析服務(wù)暴露過(guò)程。 前言 本來(lái)這一篇一個(gè)寫異步化改造的內(nèi)容,但是最近我一直在想,某一部分的優(yōu)化改造該怎么去撰寫才能更加的讓讀者理解。我覺(jué)...

    light 評(píng)論0 收藏0
  • Dubbo 源碼分析 - 集群容錯(cuò)之 Cluster

    摘要:集群用途是將多個(gè)服務(wù)提供者合并為一個(gè),并將這個(gè)暴露給服務(wù)消費(fèi)者。比如發(fā)請(qǐng)求,接受服務(wù)提供者返回的數(shù)據(jù)等。如果包含,表明對(duì)應(yīng)的服務(wù)提供者可能因網(wǎng)絡(luò)原因未能成功提供服務(wù)。如果不包含,此時(shí)還需要進(jìn)行可用性檢測(cè),比如檢測(cè)服務(wù)提供者網(wǎng)絡(luò)連通性等。 1.簡(jiǎn)介 為了避免單點(diǎn)故障,現(xiàn)在的應(yīng)用至少會(huì)部署在兩臺(tái)服務(wù)器上。對(duì)于一些負(fù)載比較高的服務(wù),會(huì)部署更多臺(tái)服務(wù)器。這樣,同一環(huán)境下的服務(wù)提供者數(shù)量會(huì)大于1...

    denson 評(píng)論0 收藏0
  • Dubbo 源碼分析 - 集群容錯(cuò)之 Router

    摘要:源碼分析條件路由規(guī)則有兩個(gè)條件組成,分別用于對(duì)服務(wù)消費(fèi)者和提供者進(jìn)行匹配。如果服務(wù)提供者匹配條件為空,表示對(duì)某些服務(wù)消費(fèi)者禁用服務(wù)。此時(shí)第六次循環(huán)分隔符,,。第二個(gè)和第三個(gè)參數(shù)來(lái)自方法的參數(shù)列表,這兩個(gè)參數(shù)分別為服務(wù)提供者和服務(wù)消費(fèi)者。 1. 簡(jiǎn)介 上一篇文章分析了集群容錯(cuò)的第一部分 -- 服務(wù)目錄 Directory。服務(wù)目錄在刷新 Invoker 列表的過(guò)程中,會(huì)通過(guò) Router...

    jcc 評(píng)論0 收藏0

發(fā)表評(píng)論

0條評(píng)論

最新活動(dòng)
閱讀需要支付1元查看
<