文章

API 配置(三)之服务消费者

本文基于 Dubbo 2.6.1 版本,望知悉。

友情提示,【配置】这块的内容,会相对比较枯燥。所以,如果看到一些很难懂的地方,建议先跳过。

对于 Dubbo ,重点是要去理解,多协议、RPC、容错等等模块,而不是【配置】。

估计好多胖友被【配置】这章劝退了把???

1. 概述

本文接 《API 配置(二)之服务提供者》 ,分享服务消费者相关的配置。

配置类关系

  • 红框 部分,consumer-side

还是老样子,我们先来看一段 《Dubbo 用户指南 —— API 配置》 ,服务消费者的初始化代码:

1
// 当前应用配置ApplicationConfig application = new ApplicationConfig();application.setName("yyy");// 连接注册中心配置RegistryConfig registry = new RegistryConfig();registry.setAddress("10.20.130.230:9090");registry.setUsername("aaa");registry.setPassword("bbb");// 注意:ReferenceConfig为重对象,内部封装了与注册中心的连接,以及与服务提供方的连接// 引用远程服务ReferenceConfig<XxxService> reference = new ReferenceConfig<XxxService>(); // 此实例很重,封装了与注册中心的连接以及与提供者的连接,请自行缓存,否则可能造成内存和连接泄漏reference.setApplication(application);reference.setRegistry(registry); // 多个注册中心可以用setRegistries()reference.setInterface(XxxService.class);reference.setVersion("1.0.0");// 和本地bean一样使用xxxServiceXxxService xxxService = reference.get(); // 注意:此代理对象内部封装了所有通讯细节,对象较重,请缓存复用

2. AbstractReferenceConfig

com.alibaba.dubbo.config.AbstractReferenceConfig ,实现 AbstractInterfaceConfig ,抽象引用配置类。

3. ConsumerConfig

com.alibaba.dubbo.config.ConsumerConfig ,实现 AbstractReferenceConfig ,服务消费者缺省值配置。

4. ReferenceConfig

com.alibaba.dubbo.config.ReferenceConfig ,服务消费者引用服务配置类

下面,我们进入正戏

在文初的 ReferenceConfig 的初始化示例代码中,最后调用的是 ServiceConfig#get() 方法。从方法的命名,我们可以看出,获取引用服务。该方法主要做了如下几件事情:

  1. 进一步初始化 ReferenceConfig 对象。
  2. 校验 ReferenceConfig 对象的配置项。
  3. 使用 ReferenceConfig 对象,生成数组 Dubbo URL 对象 。
  4. 使用 Dubbo URL 对象,应用服务

本文重点在服务提供者相关的配置,因此只解析 1+2+3 部分( 不包括 4 )。代码如下:

```plain text public synchronized T get() { // 已销毁,不可获得 if (destroyed) { throw new IllegalStateException(“Already destroyed!”); } // 初始化 if (ref == null) { init(); } return ref; }

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
- 第 2 至 5 行:若已经销毁(
destroyed = true
),抛出异常。
- 第 7 至 9 行:若未初始化,调用
#init()
方法,进行初始化。
- 第 10 行:返回引用服务。

---

[#init()](http://svip.iocoder.cn/Dubbo/configuration-api-3/) 方法,代码如下:

友情提示,该方法并未拆分更多的小方法,所以超级长,近 200+ 行。

```plain text
private void init() {
    // 已经初始化,直接返回
    if (initialized) {
        return;
    }
    initialized = true;
    // 校验接口名非空
    if (interfaceName == null || interfaceName.length() == 0) {
        throw new IllegalStateException("<dubbo:reference interface=\"\" /> interface not allow null!");
    }
    // 拼接属性配置(环境变量 + properties 属性)到 ConsumerConfig 对象
    // get consumer's global configuration
    checkDefault();
    // 拼接属性配置(环境变量 + properties 属性)到 ReferenceConfig 对象
    appendProperties(this);
    // 若未设置 `generic` 属性,使用 `ConsumerConfig.generic` 属性。
    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);
    }
    // 直连提供者,参见文档《直连提供者》http://dubbo.apache.org/zh-cn/docs/user/demos/explicit-target.html
    // 【直连提供者】第一优先级,通过 -D 参数指定 ,例如 java -Dcom.alibaba.xxx.XxxService=dubbo://localhost:20890
    String resolve = System.getProperty(interfaceName);
    String resolveFile = null;
    // 【直连提供者】第二优先级,通过文件映射,例如 com.alibaba.xxx.XxxService=dubbo://localhost:20890
    if (resolve == null || resolve.length() == 0) {
        // 默认先加载,`${user.home}/dubbo-resolve.properties` 文件 ,无需配置
        resolveFile = System.getProperty("dubbo.resolve.file");
        if (resolveFile == null || resolveFile.length() == 0) {
            File userResolveFile = new File(new File(System.getProperty("user.home")), "dubbo-resolve.properties");
            if (userResolveFile.exists()) {
                resolveFile = userResolveFile.getAbsolutePath();
            }
        }
        // 存在 resolveFile ,则进行文件读取加载。
        if (resolveFile != null && resolveFile.length() > 0) {
            Properties properties = new Properties();
            FileInputStream fis = null;
            try {
                fis = new FileInputStream(new File(resolveFile));
                properties.load(fis);
            } catch (IOException e) {
                throw new IllegalStateException("Unload " + resolveFile + ", cause: " + e.getMessage(), e);
            } finally {
                try {
                    if (null != fis) fis.close();
                } catch (IOException e) {
                    logger.warn(e.getMessage(), e);
                }
            }
            resolve = properties.getProperty(interfaceName);
        }
    }
    // 设置直连提供者的 url
    if (resolve != null && resolve.length() > 0) {
        url = resolve;
        if (logger.isWarnEnabled()) {
            if (resolveFile != null && resolveFile.length() > 0) {
                logger.warn("Using default dubbo resolve file " + resolveFile + " replace " + interfaceName + "" + resolve + " to p2p invoke remote service.");
            } else {
                logger.warn("Using -D" + interfaceName + "=" + resolve + " to p2p invoke remote service.");
            }
        }
    }
    // 从 ConsumerConfig 对象中,读取 application、module、registries、monitor 配置对象。
    if (consumer != null) {
        if (application == null) {
            application = consumer.getApplication();
        }
        if (module == null) {
            module = consumer.getModule();
        }
        if (registries == null) {
            registries = consumer.getRegistries();
        }
        if (monitor == null) {
            monitor = consumer.getMonitor();
        }
    }
    // 从 ModuleConfig 对象中,读取 registries、monitor 配置对象。
    if (module != null) {
        if (registries == null) {
            registries = module.getRegistries();
        }
        if (monitor == null) {
            monitor = module.getMonitor();
        }
    }
    // 从 ApplicationConfig 对象中,读取 registries、monitor 配置对象。
    if (application != null) {
        if (registries == null) {
            registries = application.getRegistries();
        }
        if (monitor == null) {
            monitor = application.getMonitor();
        }
    }
    // 校验 ApplicationConfig 配置。
    checkApplication();
    // 校验 Stub 和 Mock 相关的配置
    checkStubAndMock(interfaceClass);
    // 将 `side`,`dubbo`,`timestamp`,`pid` 参数,添加到 `map` 集合中。
    Map<String, String> map = new HashMap<String, String>();
    Map<Object, Object> attributes = new HashMap<Object, Object>();
    map.put(Constants.SIDE_KEY, Constants.CONSUMER_SIDE);
    map.put(Constants.DUBBO_VERSION_KEY, Version.getVersion());
    map.put(Constants.TIMESTAMP_KEY, String.valueOf(System.currentTimeMillis()));
    if (ConfigUtils.getPid() > 0) {
        map.put(Constants.PID_KEY, String.valueOf(ConfigUtils.getPid()));
    }
    // methods、revision、interface
    if (!isGeneric()) {
        String revision = Version.getVersion(interfaceClass, version);
        if (revision != null && revision.length() > 0) {
            map.put("revision", revision);
        }

        String[] methods = Wrapper.getWrapper(interfaceClass).getMethodNames(); // 获得方法数组
        if (methods.length == 0) {
            logger.warn("NO method found in service interface " + interfaceClass.getName());
            map.put("methods", Constants.ANY_VALUE);
        } else {
            map.put("methods", StringUtils.join(new HashSet<String>(Arrays.asList(methods)), ","));
        }
    }
    map.put(Constants.INTERFACE_KEY, interfaceName);
    // 将各种配置对象,添加到 `map` 集合中。
    appendParameters(map, application);
    appendParameters(map, module);
    appendParameters(map, consumer, Constants.DEFAULT_KEY);
    appendParameters(map, this);
    // 获得服务键,作为前缀
    String prefix = StringUtils.getServiceKey(map);
    // 将 MethodConfig 对象数组,添加到 `map` 集合中。
    if (methods != null && !methods.isEmpty()) {
        for (MethodConfig method : methods) {
            // 将 MethodConfig 对象,添加到 `map` 集合中。
            appendParameters(map, method, method.getName());
            // 当 配置了 `MethodConfig.retry = false` 时,强制禁用重试
            String retryKey = method.getName() + ".retry";
            if (map.containsKey(retryKey)) {
                String retryValue = map.remove(retryKey);
                if ("false".equals(retryValue)) {
                    map.put(method.getName() + ".retries", "0");
                }
            }
            // 将带有 @Parameter(attribute = true) 配置对象的属性,添加到参数集合。参见《事件通知》http://dubbo.apache.org/zh-cn/docs/user/demos/events-notify.html
            appendAttributes(attributes, method, prefix + "." + method.getName());
            // 检查属性集合中的事件通知方法是否正确。若正确,进行转换。
            checkAndConvertImplicitConfig(method, map, attributes);
        }
    }

    // 以系统环境变量( DUBBO_IP_TO_REGISTRY ) 作为服务注册地址,参见 https://github.com/dubbo/dubbo-docker-sample 项目。
    String hostToRegistry = ConfigUtils.getSystemProperty(Constants.DUBBO_IP_TO_REGISTRY);
    if (hostToRegistry == null || hostToRegistry.length() == 0) {
        hostToRegistry = NetUtils.getLocalHost();
    } else if (isInvalidLocalHost(hostToRegistry)) {
        throw new IllegalArgumentException("Specified invalid registry ip from property:" + Constants.DUBBO_IP_TO_REGISTRY + ", value:" + hostToRegistry);
    }
    map.put(Constants.REGISTER_IP_KEY, hostToRegistry);

    // 添加到 StaticContext 进行缓存
    //attributes are stored by system context.
    StaticContext.getSystemContext().putAll(attributes);

    // 省略【引用服务】
}

一本正经的水更和 《API 配置(二)之服务提供者》 大体类似。

本文由作者按照 CC BY 4.0 进行授权