项目
博客
文档
归档
资源链接
关于我
项目
博客
文档
归档
资源链接
关于我
Dubbo 源码分析 —— API 配置(三)之服务消费者
2020-06-20
·
芋道源码
·
转载
·
Dubbo
·
本文共 2,094个字,预计阅读需要 7分钟。
> `转载 `于【[芋道源码](http://svip.iocoder.cn/)】 > 本文基于 Dubbo 2.6.1 版本,望知悉。 ## 1. 概述 本文接 《API 配置(二)之服务提供者》,分享**服务消费者**相关的配置。 [![配置类关系](http://static2.iocoder.cn/images/Dubbo/2018_01_07/03.png)](http://static2.iocoder.cn/images/Dubbo/2018_01_07/03.png)配置类关系 - **红框**部分,consumer-side ------ 还是老样子,我们先来看一段 [《Dubbo 用户指南 —— API 配置》](http://dubbo.apache.org/zh-cn/docs/user/configuration/api.html) ,服务消费者的初始化代码: ``` // 当前应用配置 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
reference = new ReferenceConfig
(); // 此实例很重,封装了与注册中心的连接以及与提供者的连接,请自行缓存,否则可能造成内存和连接泄漏 reference.setApplication(application); reference.setRegistry(registry); // 多个注册中心可以用setRegistries() reference.setInterface(XxxService.class); reference.setVersion("1.0.0"); // 和本地bean一样使用xxxService XxxService xxxService = reference.get(); // 注意:此代理对象内部封装了所有通讯细节,对象较重,请缓存复用 ``` ## 2. AbstractReferenceConfig [`com.alibaba.dubbo.config.AbstractReferenceConfig`](https://github.com/YunaiV/dubbo/blob/9d38b6f9f95798755141d6140e311e8fd51fecc1/dubbo-config/dubbo-config-api/src/main/java/com/alibaba/dubbo/config/AbstractReferenceConfig.java) ,实现 AbstractInterfaceConfig ,抽象引用配置类。 - 具体属性的解释,**需要寻找**在 [《Dubbo 用户指南 —— dubbo:reference》](http://dubbo.apache.org/zh-cn/docs/user/references/xml/dubbo-reference.html) 或 [《Dubbo 用户指南 —— dubbo:consumer》](http://dubbo.apache.org/zh-cn/docs/user/references/xml/dubbo-consumer.html) 文档。 ## 3. ConsumerConfig [`com.alibaba.dubbo.config.ConsumerConfig`](https://github.com/YunaiV/dubbo/blob/9d38b6f9f95798755141d6140e311e8fd51fecc1/dubbo-config/dubbo-config-api/src/main/java/com/alibaba/dubbo/config/ConsumerConfig.java) ,实现 AbstractReferenceConfig ,服务消费者缺省值配置。 - 具体属性的解释,参见 [《Dubbo 用户指南 —— dubbo:consumer》](http://dubbo.apache.org/zh-cn/docs/user/references/xml/dubbo-consumer.html) 文档。 ## 4. ReferenceConfig [`com.alibaba.dubbo.config.ReferenceConfig`](https://github.com/YunaiV/dubbo/blob/9d38b6f9f95798755141d6140e311e8fd51fecc1/dubbo-config/dubbo-config-api/src/main/java/com/alibaba/dubbo/config/ReferenceConfig.java) ,服务消费者引用**服务配置类**。 - 具体属性的解释,参见 [《Dubbo 用户指南 —— dubbo:consumer》](http://dubbo.apache.org/zh-cn/docs/user/references/xml/dubbo-consumer.html) 文档。 下面,我们进入**正戏**。 在文初的 ReferenceConfig 的初始化示例代码中,最后调用的是 [`ServiceConfig#get()`](https://github.com/YunaiV/dubbo/blob/d3c3975f320c78452f96098b04441fed4c00ab70/dubbo-config/dubbo-config-api/src/main/java/com/alibaba/dubbo/config/ReferenceConfig.java#L185-L195) 方法。从方法的命名,我们可以看出,获取**引用服务**。该方法主要做了如下几件事情: 1. **进一步初始化** ReferenceConfig 对象。 2. **校验** ReferenceConfig 对象的配置项。 3. 使用 ReferenceConfig 对象,**生成** Dubbo URL 对象**数组**。 4. 使用 Dubbo URL 对象,**应用服务**。 😈 本文重点在服务提供者相关的配置,因此只解析 **1+2+3** 部分( 不包括 4 )。代码如下: ``` 1: public synchronized T get() { 2: // 已销毁,不可获得 3: if (destroyed) { 4: throw new IllegalStateException("Already destroyed!"); 5: } 6: // 初始化 7: if (ref == null) { 8: init(); 9: } 10: return ref; 11: } ``` - 第 2 至 5 行:若已经销毁( `destroyed = true` ),抛出异常。 - 第 7 至 9 行:若未初始化,调用 `#init()` 方法,进行初始化。 - 第 10 行:返回引用服务。 ------ [`#init()`](http://svip.iocoder.cn/Dubbo/configuration-api-3/) 方法,代码如下: > 友情提示,该方法并未拆分更多的小方法,所以超级长,近 200+ 行。 ``` 1: private void init() { 2: // 已经初始化,直接返回 3: if (initialized) { 4: return; 5: } 6: initialized = true; 7: // 校验接口名非空 8: if (interfaceName == null || interfaceName.length() == 0) { 9: throw new IllegalStateException("
interface not allow null!"); 10: } 11: // 拼接属性配置(环境变量 + properties 属性)到 ConsumerConfig 对象 12: // get consumer's global configuration 13: checkDefault(); 14: // 拼接属性配置(环境变量 + properties 属性)到 ReferenceConfig 对象 15: appendProperties(this); 16: // 若未设置 `generic` 属性,使用 `ConsumerConfig.generic` 属性。 17: if (getGeneric() == null && getConsumer() != null) { 18: setGeneric(getConsumer().getGeneric()); 19: } 20: // 泛化接口的实现 21: if (ProtocolUtils.isGeneric(getGeneric())) { 22: interfaceClass = GenericService.class; 23: // 普通接口的实现 24: } else { 25: try { 26: interfaceClass = Class.forName(interfaceName, true, Thread. currentThread().getContextClassLoader()); 27: } catch (ClassNotFoundException e) { 28: throw new IllegalStateException(e.getMessage(), e); 29: } 30: // 校验接口和方法 31: checkInterfaceAndMethods(interfaceClass, methods); 32: } 33: // 直连提供者,参见文档《直连提供者》http://dubbo.apache.org/zh-cn/docs/user/demos/explicit-target.html 34: // 【直连提供者】第一优先级,通过 -D 参数指定 ,例如 java -Dcom.alibaba.xxx.XxxService=dubbo://localhost:20890 35: String resolve = System.getProperty(interfaceName); 36: String resolveFile = null; 37: // 【直连提供者】第二优先级,通过文件映射,例如 com.alibaba.xxx.XxxService=dubbo://localhost:20890 38: if (resolve == null || resolve.length() == 0) { 39: // 默认先加载,`${user.home}/dubbo-resolve.properties` 文件 ,无需配置 40: resolveFile = System.getProperty("dubbo.resolve.file"); 41: if (resolveFile == null || resolveFile.length() == 0) { 42: File userResolveFile = new File(new File(System.getProperty("user.home")), "dubbo-resolve.properties"); 43: if (userResolveFile.exists()) { 44: resolveFile = userResolveFile.getAbsolutePath(); 45: } 46: } 47: // 存在 resolveFile ,则进行文件读取加载。 48: if (resolveFile != null && resolveFile.length() > 0) { 49: Properties properties = new Properties(); 50: FileInputStream fis = null; 51: try { 52: fis = new FileInputStream(new File(resolveFile)); 53: properties.load(fis); 54: } catch (IOException e) { 55: throw new IllegalStateException("Unload " + resolveFile + ", cause: " + e.getMessage(), e); 56: } finally { 57: try { 58: if (null != fis) fis.close(); 59: } catch (IOException e) { 60: logger.warn(e.getMessage(), e); 61: } 62: } 63: resolve = properties.getProperty(interfaceName); 64: } 65: } 66: // 设置直连提供者的 url 67: if (resolve != null && resolve.length() > 0) { 68: url = resolve; 69: if (logger.isWarnEnabled()) { 70: if (resolveFile != null && resolveFile.length() > 0) { 71: logger.warn("Using default dubbo resolve file " + resolveFile + " replace " + interfaceName + "" + resolve + " to p2p invoke remote service."); 72: } else { 73: logger.warn("Using -D" + interfaceName + "=" + resolve + " to p2p invoke remote service."); 74: } 75: } 76: } 77: // 从 ConsumerConfig 对象中,读取 application、module、registries、monitor 配置对象。 78: if (consumer != null) { 79: if (application == null) { 80: application = consumer.getApplication(); 81: } 82: if (module == null) { 83: module = consumer.getModule(); 84: } 85: if (registries == null) { 86: registries = consumer.getRegistries(); 87: } 88: if (monitor == null) { 89: monitor = consumer.getMonitor(); 90: } 91: } 92: // 从 ModuleConfig 对象中,读取 registries、monitor 配置对象。 93: if (module != null) { 94: if (registries == null) { 95: registries = module.getRegistries(); 96: } 97: if (monitor == null) { 98: monitor = module.getMonitor(); 99: } 100: } 101: // 从 ApplicationConfig 对象中,读取 registries、monitor 配置对象。 102: if (application != null) { 103: if (registries == null) { 104: registries = application.getRegistries(); 105: } 106: if (monitor == null) { 107: monitor = application.getMonitor(); 108: } 109: } 110: // 校验 ApplicationConfig 配置。 111: checkApplication(); 112: // 校验 Stub 和 Mock 相关的配置 113: checkStubAndMock(interfaceClass); 114: // 将 `side`,`dubbo`,`timestamp`,`pid` 参数,添加到 `map` 集合中。 115: Map
map = new HashMap
(); 116: Map
attributes = new HashMap
(); 117: map.put(Constants.SIDE_KEY, Constants.CONSUMER_SIDE); 118: map.put(Constants.DUBBO_VERSION_KEY, Version.getVersion()); 119: map.put(Constants.TIMESTAMP_KEY, String.valueOf(System.currentTimeMillis())); 120: if (ConfigUtils.getPid() > 0) { 121: map.put(Constants.PID_KEY, String.valueOf(ConfigUtils.getPid())); 122: } 123: // methods、revision、interface 124: if (!isGeneric()) { 125: String revision = Version.getVersion(interfaceClass, version); 126: if (revision != null && revision.length() > 0) { 127: map.put("revision", revision); 128: } 129: 130: String[] methods = Wrapper.getWrapper(interfaceClass).getMethodNames(); // 获得方法数组 131: if (methods.length == 0) { 132: logger.warn("NO method found in service interface " + interfaceClass.getName()); 133: map.put("methods", Constants.ANY_VALUE); 134: } else { 135: map.put("methods", StringUtils.join(new HashSet
(Arrays.asList(methods)), ",")); 136: } 137: } 138: map.put(Constants.INTERFACE_KEY, interfaceName); 139: // 将各种配置对象,添加到 `map` 集合中。 140: appendParameters(map, application); 141: appendParameters(map, module); 142: appendParameters(map, consumer, Constants.DEFAULT_KEY); 143: appendParameters(map, this); 144: // 获得服务键,作为前缀 145: String prefix = StringUtils.getServiceKey(map); 146: // 将 MethodConfig 对象数组,添加到 `map` 集合中。 147: if (methods != null && !methods.isEmpty()) { 148: for (MethodConfig method : methods) { 149: // 将 MethodConfig 对象,添加到 `map` 集合中。 150: appendParameters(map, method, method.getName()); 151: // 当 配置了 `MethodConfig.retry = false` 时,强制禁用重试 152: String retryKey = method.getName() + ".retry"; 153: if (map.containsKey(retryKey)) { 154: String retryValue = map.remove(retryKey); 155: if ("false".equals(retryValue)) { 156: map.put(method.getName() + ".retries", "0"); 157: } 158: } 159: // 将带有 @Parameter(attribute = true) 配置对象的属性,添加到参数集合。参见《事件通知》http://dubbo.apache.org/zh-cn/docs/user/demos/events-notify.html 160: appendAttributes(attributes, method, prefix + "." + method.getName()); 161: // 检查属性集合中的事件通知方法是否正确。若正确,进行转换。 162: checkAndConvertImplicitConfig(method, map, attributes); 163: } 164: } 165: 166: // 以系统环境变量( DUBBO_IP_TO_REGISTRY ) 作为服务注册地址,参见 https://github.com/dubbo/dubbo-docker-sample 项目。 167: String hostToRegistry = ConfigUtils.getSystemProperty(Constants.DUBBO_IP_TO_REGISTRY); 168: if (hostToRegistry == null || hostToRegistry.length() == 0) { 169: hostToRegistry = NetUtils.getLocalHost(); 170: } else if (isInvalidLocalHost(hostToRegistry)) { 171: throw new IllegalArgumentException("Specified invalid registry ip from property:" + Constants.DUBBO_IP_TO_REGISTRY + ", value:" + hostToRegistry); 172: } 173: map.put(Constants.REGISTER_IP_KEY, hostToRegistry); 174: 175: // 添加到 StaticContext 进行缓存 176: //attributes are stored by system context. 177: StaticContext.getSystemContext().putAll(attributes); 178: 179: // 省略【引用服务】 185: } ``` - 第 2 至 6 行:若已经初始化( `initialized = true` ) 时,直接返回。否则,标记已经初始化。 - 第 7 至 10 行:校验接口名 [`interfaceName`](https://github.com/YunaiV/dubbo/blob/d3c3975f320c78452f96098b04441fed4c00ab70/dubbo-config/dubbo-config-api/src/main/java/com/alibaba/dubbo/config/ReferenceConfig.java#L78) 非空。 - 第 13 行:调用 `#checkDefault()` 方法,读取属性配置( 环境变量 + properties 属性 )到 ConsumerConfig 对象。 - 关于“**属性配置**” ,在 [《精尽 Dubbo 源码解析 —— 属性配置》](http://svip.iocoder.cn/Dubbo/configuration-properties/?self?self) 详细解析。 - 🙂 直接点击方法查看,较为简单,已经添加详细注释。 - 第 15 行:调用 [`#appendProperties(config)`](https://github.com/YunaiV/dubbo/blob/9d38b6f9f95798755141d6140e311e8fd51fecc1/dubbo-config/dubbo-config-api/src/main/java/com/alibaba/dubbo/config/AbstractConfig.java#L132-L212) 方法,读取**属性配置**( 环境变量 + properties 属性 )到 ReferenceConfig 对象(**自己**) - 第 16 至 19 行:若未设置 [`generic`](https://github.com/YunaiV/dubbo/blob/d3c3975f320c78452f96098b04441fed4c00ab70/dubbo-config/dubbo-config-api/src/main/java/com/alibaba/dubbo/config/AbstractReferenceConfig.java#L43) 属性,使用 [`ConsumerConfig.generic`](https://github.com/YunaiV/dubbo/blob/d3c3975f320c78452f96098b04441fed4c00ab70/dubbo-config/dubbo-config-api/src/main/java/com/alibaba/dubbo/config/AbstractReferenceConfig.java#L43) 属性。 - 第 20 至 22 行:泛化接口的实现。 - [《Dubbo 用户指南 —— 泛化引用》](http://dubbo.apache.org/zh-cn/docs/user/demos/generic-reference.html) - 第 23 至 32 行:普通接口的实现。 - 第 60 至 64 行:根据 [`interfaceName`](https://github.com/YunaiV/dubbo/blob/d3c3975f320c78452f96098b04441fed4c00ab70/dubbo-config/dubbo-config-api/src/main/java/com/alibaba/dubbo/config/ReferenceConfig.java#L78) ,获得对应的**接口类**,并赋值给 [`interfaceClass`](https://github.com/YunaiV/dubbo/blob/d3c3975f320c78452f96098b04441fed4c00ab70/dubbo-config/dubbo-config-api/src/main/java/com/alibaba/dubbo/config/ReferenceConfig.java#L79)。 - 第 31 行:调用`#checkInterfaceAndMethods(interfaceClass, methods)`方法,检查接口和方法。 - 🙂 直接点击方法查看,较为简单,已经添加详细注释。 - 第 33 至 76 行:直连提供者。 - [《Dubbo 用户指南 —— 直连提供者》](http://dubbo.apache.org/zh-cn/docs/user/demos/explicit-target.html) - 🙂 中间有一些逻辑处理,胖友看下代码的注释。结合文档。 - 第 77 至 109 行:从 ConsumerConfig、ModuleConfig、ApplicationConfig 配置对象,复制 `application` `module` `registries` `monitor` 给 ReferenceConfig ( **自己** )。 - 第 111 行:调用`#checkApplication()`方法,校验 ApplicationConfig 配置。 - 🙂 直接点击方法查看,较为简单,已经添加详细注释。 - 第 113 行:调用 [`#checkStubAndMock(interfaceClass)`](https://github.com/YunaiV/dubbo/blob/9d38b6f9f95798755141d6140e311e8fd51fecc1/dubbo-config/dubbo-config-api/src/main/java/com/alibaba/dubbo/config/AbstractInterfaceConfig.java#L319-L368) 方法,校验 Stub 和 Mock 相关的配置。 - **第 115 行:创建参数集合 map ,用于下面创建 Dubbo URL 的 parameters 属性**。 - 第 116 至 122 行:将 `side` `dubbo` `timestamp` `timestamp` `pid` 添加到 `map` 中。 - 第 123 至 137 行:将 `interface` `methods` `revision` 到 `map` 中。 - 第 139 至 143 行:调用`#appendParameters(map, config)`方法,将各种配置对象添加到`map`中。 - 🙂 `#appendParameters(map, config)` 方法,在 [《API 配置(一)之应用》](http://svip.iocoder.cn/Dubbo/configuration-api-1/?self) 有详细解析。 - 第 146 至 164 行:调用 MethodConfig 对象数组,添加到`map`中。 - 目的是将**每个** MethodConfig 和其对应的 ArgumentConfig 对象数组,添加到 `map` 中。 - 第 160 行:调用`#appendAttributes(parameters, config, prefix)`方法,将`@Parameter(attribute = true)`配置对象的属性,添加到参数集合。在《API 配置(一)之应用》有详细解析。 - [《Dubbo 用户指南 —— 事件通知》](http://dubbo.apache.org/zh-cn/docs/user/demos/events-notify.html) - 第 162 行:调用`#checkAndConvertImplicitConfig(method, map, attributes)`方法,检查属性集合中的事件通知方法是否正确。若正确,进行转换。 - 🙂 直接点击方法查看,较为简单,已经添加详细注释。 - 第 166 至 173 行:以系统换将变量 ( DUBBO_IP_TO_REGISTRY ) 作为服务注册地址,参见 [dubbo-docker-sample](https://github.com/dubbo/dubbo-docker-sample) 项目。 - 第 177 行:添加到`com.alibaba.dubbo.rpc.StaticContext`进行缓存。 - 目的是 [《Dubbo 用户指南 —— 事件通知》](http://dubbo.apache.org/zh-cn/docs/user/demos/events-notify.html) 。 - 第 179 行:**省略**【服务引用】逻辑