Dubbo基础(五)- Consumer的初始化过程A:配置读取及获取代理

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

  1. 配置读取过程refresh过程
  2. 外部化配置初始化过程
  3. 服务暴露详解

Dubbo 是如何暴露一个服务呢?相信你读完上面三篇就懂了。

本文主要聚焦在配置读取以及代理对象获取,留下一个问题思考

  1. Consumer 在初始化过程中,会初始化哪些配置呢?

以上问题看完文章后相信大家就可以清楚,若有疑问,关注博主公众号:六点A君,回复标题获取最新答案><

示例

本篇主要讲解 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小吃街不迷路:
在这里插入图片描述

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值