Dubbo基礎(五)- Consumer的初始化過程A:配置讀取及獲取代理

前面花了三篇文章一起深入源碼,分析了 Dubbo 服務提供者是如何 暴露服務的:

  1. 配置讀取過程refresh過程
  2. 外部化配置初始化過程
  3. 服務暴露詳解

Dubbo 是如何暴露一個服務呢?相信你讀完上面三篇就懂了。

示例

本篇主要講解 Consumer,即消費者是如何運作的,還是以最開始的那個 例子代碼作爲 初始程序分析。
第一個例子 HelloDubbo

 ReferenceConfig<HelloService> reference = new ReferenceConfig<>();
        reference.setApplication(new ApplicationConfig("dubbo-consumer"));
        reference.setRegistry(new RegistryConfig("zookeeper://127.0.0.1:2181"));
        reference.setInterface(HelloService.class);
        HelloService service = reference.get();
        String message = service.hello("dubbo I am anla7856");
        System.out.println(message);

如上,開始使用一個 ReferenceConfig ,而後給它設置了 ApplicationConfigRegistryConfiginterfaceClass 等,設置完之後,就調用
reference.get() 獲取一個動態實例,而後直接像普通Java程序一樣調用它的方法即可。

所以最重要的就是 ReferenceConfig的 get 方法,接下來透過源碼,看看裏面幹了啥事。

ReferenceConfig 的 get

同樣的,這也是一個 synchronized 修飾的方法,

    public synchronized T get() {
    // 檢查配置
        checkAndUpdateSubConfigs();

        if (destroyed) {
            throw new IllegalStateException("The invoker of ReferenceConfig(" + url + ") has already destroyed!");
        }
        if (ref == null) {
            // 初始化
            init();
        }
        return ref;
    }

看看 checkAndUpdateSubConfigs 裏面代碼:

    public void checkAndUpdateSubConfigs() {
    // 檢查 interfaceName 是否爲空
        if (StringUtils.isEmpty(interfaceName)) {
            throw new IllegalStateException("<dubbo:reference interface=\"\" /> interface not allow null!");
        }
        // 在 completeCompoundConfigs 方法中設置 按照 配置優先級設置ReferenceConfig 的 application、module、registries、monitor 等參數
        completeCompoundConfigs();
        // 嘗試加載 配置中心(ConfigCenter) 中數據,然後執行 剩下所有配置的 refresh 方法
        startConfigCenter();
        // 如果ConsumerConfig 不爲空,那麼就會初始一個默認的,並且刷新
        checkDefault();
        // 嘗試刷新 ReferenceConfig
        this.refresh();
        // 判斷是否爲 泛化調用 generic ,如果是泛化調用,則會直接把 interfaceClass = GenericService.class,這樣纔不會發生類型轉化錯誤
        // 如果不是 泛化調用,則會加載 當前傳進來的 interfaceClass以及檢查傳入的方法(MethodConfig)是否在遠程服務提供的接口裏面。
        if (getGeneric() == null && getConsumer() != null) {
            setGeneric(getConsumer().getGeneric());
        }
        if (ProtocolUtils.isGeneric(getGeneric())) {
            interfaceClass = GenericService.class;
        } else {
            try {
                interfaceClass = Class.forName(interfaceName, true, Thread.currentThread()
                        .getContextClassLoader());
            } catch (ClassNotFoundException e) {
                throw new IllegalStateException(e.getMessage(), e);
            }
            checkInterfaceAndMethods(interfaceClass, methods);
        }
        // 檢查 外部默認未見 地址,如果有配置則將配置加載進來,默認文件默認地址是可配置爲 系統變量 dubbo.resolve.file 
        resolveFile();
        // 會去判斷 ApplicationConfig 是否存在,不存在則會創建一個默認的 ApplicationConfig,然後會嘗試設置 Dubbo 優雅關機的一些參數。
        checkApplication();
        // 檢查是否 有 MetadataReportConfig, 沒有則會默認創建一個。
        checkMetadataReport();
    }

checkAndUpdateSubConfigs 中是檢查和初始化配置,裏面包括 消費者涉及的包括ConfigCenter、ReferenceConfig等多個配置。

  1. 檢查 interfaceName 是否爲空
  2. completeCompoundConfigs 方法中設置 按照 配置優先級設置ReferenceConfigapplication、module、registries、monitor 等參數
  3. startConfigCenter 中,嘗試加載 配置中心(ConfigCenter) 中數據,然後執行 剩下所有配置的 refresh 方法
  4. checkDefault 中,如果ConsumerConfig 不爲空,那麼就會初始一個默認的,並且刷新
  5. 調用 this.refresh嘗試刷新 ReferenceConfig
  6. 判斷是否爲 泛化調用 generic ,如果是泛化調用,則會直接把 interfaceClass = GenericService.class,這樣纔不會發生類型轉化錯誤
  7. 如果不是 泛化調用,則會加載 當前傳進來的 interfaceClass以及檢查傳入的方法(MethodConfig)是否在遠程服務提供的接口裏面。
  8. resolveFile 檢查 外部默認未見 地址,如果有配置則將配置加載進來,默認文件默認地址是可配置爲 系統變量 dubbo.resolve.file
  9. checkApplication 會去判斷 ApplicationConfig 是否存在,不存在則會創建一個默認的 ApplicationConfig,然後會嘗試設置 Dubbo 優雅關機的一些參數。
  10. checkMetadataReport 則會檢查是否 有 MetadataReportConfig, 沒有則會默認創建一個。

其實具體這一段加載配置邏輯和 服務提供者 ServiceConfig 中基本一致,具體分析在服務提供者初始化博主已經給出了詳細的分析:
可以去文章頂部直接點擊查看即可。

init

過完了前期配置的初始化以及檢查之後,就會進入 init方法,並且會把 對應的 ref 變量初始化。當然,ref 只是一個接口,多次執行get 僅僅會初始化一次 ref變量。

    private void init() {
    	// 初始化
        if (initialized) {
            return;
        }
        // 檢查是否有 local 或者 stub方法,如果使得化,就去尋找並加載 interface+Local(Stub),而後驗證 Stub/Local 類與interface 類
        checkStubAndLocal(interfaceClass);
        // 檢查 interface是否配置了 Mock功能,如果配置了 Mock ,則會根據相應的Mock 配置去初始化 MockInvoker,這是一個過濾器,
        // Dubbo裏面第一個過濾器,在功能上直接決定了Dubbo 一些重要功能,Mock 就是一個例子。
        checkMock(interfaceClass);
        // 聲明一個Map存下所有配置
        Map<String, String> map = new HashMap<String, String>();
		// 說明是 consumer 這邊的
        map.put(SIDE_KEY, CONSUMER_SIDE);
		// 增加運行時信息,例如 dubbo 版本,以及 timestamp 等。
        appendRuntimeParameters(map);
        // 檢查 是否爲泛化調用
        if (!isGeneric()) {
            String revision = Version.getVersion(interfaceClass, version);
            if (revision != null && revision.length() > 0) {
                map.put(REVISION_KEY, revision);
            }
			
            String[] methods = Wrapper.getWrapper(interfaceClass).getMethodNames();
            if (methods.length == 0) {
                logger.warn("No method found in service interface " + interfaceClass.getName());
                map.put(METHODS_KEY, ANY_VALUE);
            } else {
                map.put(METHODS_KEY, StringUtils.join(new HashSet<String>(Arrays.asList(methods)), COMMA_SEPARATOR));
            }
        }
        // 設置 interface
        map.put(INTERFACE_KEY, interfaceName);
        // 設置metrics中參數
        appendParameters(map, metrics);
        // 設置application中參數
        appendParameters(map, application);
        // 設置module 中參數
        appendParameters(map, module);
        // 設置 consumer 中參數
        appendParameters(map, consumer);
        // 設置當前config, 即ReferenceConfig
        appendParameters(map, this);
        Map<String, Object> attributes = null;
        // 判斷有否 retry 參數
        if (CollectionUtils.isNotEmpty(methods)) {
            attributes = new HashMap<String, Object>();
            for (MethodConfig methodConfig : methods) {
                appendParameters(map, methodConfig, methodConfig.getName());
                String retryKey = methodConfig.getName() + ".retry";
                if (map.containsKey(retryKey)) {
                    String retryValue = map.remove(retryKey);
                    if ("false".equals(retryValue)) {
                        map.put(methodConfig.getName() + ".retries", "0");
                    }
                }
                attributes.put(methodConfig.getName(), convertMethodConfig2AyncInfo(methodConfig));
            }
        }
		// 判斷是否有DUBBO_IP_TO_REGISTRY參數,如果有,則判斷該配置是否符合規範
        String hostToRegistry = ConfigUtils.getSystemProperty(DUBBO_IP_TO_REGISTRY);
        if (StringUtils.isEmpty(hostToRegistry)) {
            hostToRegistry = NetUtils.getLocalHost();
        } else if (isInvalidLocalHost(hostToRegistry)) {
            throw new IllegalArgumentException("Specified invalid registry ip from property:" + DUBBO_IP_TO_REGISTRY + ", value:" + hostToRegistry);
        }
        map.put(REGISTER_IP_KEY, hostToRegistry);
		// 創建一個代理對象
        ref = createProxy(map);
		// 構造一個 serviceKey
        String serviceKey = URL.buildKey(interfaceName, group, version);
        // 初始化一個ApplicationModel 對象
        ApplicationModel.initConsumerModel(serviceKey, buildConsumerModel(serviceKey, attributes));
        initialized = true;
    }

以上init方法邏輯如下:

  1. 檢查是否已經初始化過了。
  2. 執行 checkStubAndLocal ,檢查是否有 local 或者 stub 方法,如果使得化,就去尋找並加載 interface+Local(Stub),而後驗證 Stub/Local 類與interface 類。
  3. 執行checkMock ,檢查 interface是否配置了 Mock功能,如果配置了 Mock ,則會根據相應的Mock 配置去初始化 MockInvoker,這是一個過濾器,Dubbo裏面第一個過濾器,在功能上直接決定了Dubbo 一些重要功能,Mock 就是一個例子。
  4. 判斷是否爲 generic 泛化調用,是泛化調用則不做操作,不是則需要填充 版本 version,如果是方法級別暴露,則需要將需要的方法記錄在map裏面
  5. 填充其他一些信息,諸如application、module、metrics 等信息。
  6. 解析並填充 retries 參數。
  7. 如果配置了 DUBBO_IP_TO_REGISTRY ,則需要檢查 這個所傳參數合法性。
  8. 執行 createProxy ,構造一個 反射對象ref

createProxy

前期檢查和設置完配置之後,就可以創建一個代理對象了。

    private T createProxy(Map<String, String> map) {
    	// 判斷是否爲 jvm應用 
        if (shouldJvmRefer(map)) {
        // 構造一個 127.0.0.1 的 url
            URL url = new URL(LOCAL_PROTOCOL, LOCALHOST_VALUE, 0, interfaceClass.getName()).addParameters(map);
            // 構造並初始乎一個 invoker
            invoker = REF_PROTOCOL.refer(interfaceClass, url);
            if (logger.isInfoEnabled()) {
                logger.info("Using injvm service " + interfaceClass.getName());
            }
        } else {
        // 清除當前urls內容,否則在 retry時候會再次加入到url中,導致OOM異常
            urls.clear(); // reference retry init will add url to urls, lead to OOM
            // 解析並分割 url中內容,如果有多個則需要將其拆分並放入到urls中
            if (url != null && url.length() > 0) { // user specified URL, could be peer-to-peer address, or register center's address.
                String[] us = SEMICOLON_SPLIT_PATTERN.split(url);
                if (us != null && us.length > 0) {
                    for (String u : us) {
                        URL url = URL.valueOf(u);
                        if (StringUtils.isEmpty(url.getPath())) {
                            url = url.setPath(interfaceName);
                        }
                        if (REGISTRY_PROTOCOL.equals(url.getProtocol())) {
                            urls.add(url.addParameterAndEncoded(REFER_KEY, StringUtils.toQueryString(map)));
                        } else {
                            urls.add(ClusterUtils.mergeUrl(url, map));
                        }
                    }
                }
            } else { 
            	// 嘗試去 register's center 加載配置
                if (!LOCAL_PROTOCOL.equalsIgnoreCase(getProtocol())){
                // 檢查註冊中心,沒有並新增,如果沒有配置中心,則將配置中心初始化爲配置中心
                    checkRegistry();
                    List<URL> us = loadRegistries(false);
                    if (CollectionUtils.isNotEmpty(us)) {
                        for (URL u : us) {
                            URL monitorUrl = loadMonitor(u);
                            if (monitorUrl != null) {
                                map.put(MONITOR_KEY, URL.encode(monitorUrl.toFullString()));
                            }
                            urls.add(u.addParameterAndEncoded(REFER_KEY, StringUtils.toQueryString(map)));
                        }
                    }
                    if (urls.isEmpty()) {
                        throw new IllegalStateException("No such any registry to reference " + interfaceName + " on the consumer " + NetUtils.getLocalHost() + " use dubbo version " + Version.getVersion() + ", please config <dubbo:registry address=\"...\" /> to your spring config.");
                    }
                }
            }

            if (urls.size() == 1) {
            // 如果url只有一個,則將 其初始化 invoker
                invoker = REF_PROTOCOL.refer(interfaceClass, urls.get(0));
            } else {
            // 如果有多個invoker,則需要將所有invokers給加入到 urls
                List<Invoker<?>> invokers = new ArrayList<Invoker<?>>();
                URL registryURL = null;
                for (URL url : urls) {
                    invokers.add(REF_PROTOCOL.refer(interfaceClass, url));
                    if (REGISTRY_PROTOCOL.equals(url.getProtocol())) {
                        registryURL = url; // use last registry url
                    }
                }
                if (registryURL != null) { // registry url is available
                    // 當 register's 的CLUSTER 可用,增加一個 RegistryAwareCluster
                    URL u = registryURL.addParameter(CLUSTER_KEY, RegistryAwareCluster.NAME);
                    // 此時包裝順序則爲: RegistryAwareClusterInvoker(StaticDirectory) -> FailoverClusterInvoker(RegistryDirectory, will execute route) -> Invoker
                    invoker = CLUSTER.join(new StaticDirectory(u, invokers));
                } else { 
                	// 直接的invoker
                    invoker = CLUSTER.join(new StaticDirectory(invokers));
                }
            }
        }
		// 如果需要檢查,則判斷invoker是否可用
        if (shouldCheck() && !invoker.isAvailable()) {
            throw new IllegalStateException("Failed to check the status of the service " + interfaceName + ". No provider available for the service " + (group == null ? "" : group + "/") + interfaceName + (version == null ? "" : ":" + version) + " from the url " + invoker.getUrl() + " to the consumer " + NetUtils.getLocalHost() + " use dubbo version " + Version.getVersion());
        }
        if (logger.isInfoEnabled()) {
            logger.info("Refer dubbo service " + interfaceClass.getName() + " from url " + invoker.getUrl());
        }
        /**
         * @since 2.7.0
         * ServiceData Store
         */
         // 設置metadata report信息
        MetadataReportService metadataReportService = null;
        if ((metadataReportService = getMetadataReportService()) != null) {
            URL consumerURL = new URL(CONSUMER_PROTOCOL, map.remove(REGISTER_IP_KEY), 0, map.get(INTERFACE_KEY), map);
            metadataReportService.publishConsumer(consumerURL);
        }
        // create service proxy
        // 使用代理工廠創建一個代理,封裝invoker 創建一個代理對象
        return (T) PROXY_FACTORY.getProxy(invoker);
    }

上面代碼主要包括以下幾個邏輯:

  1. 檢查是否是 injvm 應用
  2. 將配置中的url直連的配置,全部解析並加入到 List 中備用
  3. 初始 url,並構造Invoker對象。並加入集羣容錯相關配置。
  4. 初始化 MetadataReportService信息
  5. 調用代理創建一個代理 對象

總結

本文主要介紹了Consumer 初始化的一些邏輯,包括:

  1. 檢查並初始化 ReferenceConfig 中一些配置
  2. 使用代理工廠初始化 ref 對象

當然,Consumer 初始化過程和 Provider 初始化過程,在配置讀取方面,十分類似,可能本文對一些初始化配置相關方法沒有細究,因爲這些都是可以從 前幾篇 Provider初始化中讀取。

下一篇文章將主要圍繞調用來看,即當獲取到了一個代理對象後,執行方法,又是以一種怎樣的邏輯呢?
下一篇例子在本篇例子實例基礎上,將啓動多 Provider 實例進行研究。

覺得博主寫的有用,不妨關注博主公衆號: 六點A君。
哈哈哈,Dubbo小吃街不迷路:
在這裏插入圖片描述

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章