我们从ReferenceConfig的init( ) 方法开始讲,ReferenceConfig代表着Dubbo的消费者的工厂对象,类似Spring的FactoryBean作用,可以通过它来获得Dubbo对业务对象封装的RPC对象。
public class ReferenceConfig<T> extends AbstractReferenceConfig {
/**
* Service 对象
*/
private transient volatile T ref;
public synchronized T get() {
// 已销毁,不可获得
if (destroyed) {
throw new IllegalStateException("Already destroyed!");
}
// 初始化
if (ref == null) {
init();
}
return ref;
}
private void init() {
// 已经初始化,直接返回
if (initialized) {
return;
}
initialized = true;
// 校验接口名非空
if (interfaceName == null || interfaceName.length() == 0) {
throw new IllegalStateException(" 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);
}
// 直连提供者,参见文档《直连提供者》https://dubbo.gitbooks.io/dubbo-user-book/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 map = new HashMap();
Map
在createProxy(Map< String, String > map) 方法里完成消费者的创建过程,返回给应用容器。
这里面有两步最关键:
假设我们的消费者配置形式如下:
dubbo.application.name=consumer
dubbo.registry.address=zookeeper://lanboal:123456@127.0.0.1:2181?login=false
我们先看第一步(上文代码上标识了:###########1),如何生成URL对象:
public class ReferenceConfig<T> extends AbstractReferenceConfig {
/**
* 加载注册中心 URL 数组
*
* @param provider 是否是服务提供者
* @return URL 数组
*/
protected List loadRegistries(boolean provider) {
// 校验 RegistryConfig 配置数组。
checkRegistry();
// 创建 注册中心 URL 数组
List registryList = new ArrayList();
if (registries != null && !registries.isEmpty()) {
for (RegistryConfig config : registries) {
// 获得注册中心的地址,格式为:zookeeper://127.0.0.1:2181
String address = config.getAddress();
if (address == null || address.length() == 0) {
address = Constants.ANYHOST_VALUE;
}
String sysaddress = System.getProperty("dubbo.registry.address"); // 从启动参数读取
if (sysaddress != null && sysaddress.length() > 0) {
address = sysaddress;
}
// 有效的地址
if (address.length() > 0 && !RegistryConfig.NO_AVAILABLE.equalsIgnoreCase(address)) {
Map map = new HashMap();
// 将各种配置对象,添加到 `map` 集合中。
appendParameters(map, application);
appendParameters(map, config);
// 添加 `path` `dubbo` `timestamp` `pid` 到 `map` 集合中。
map.put("path", RegistryService.class.getName());
map.put("dubbo", Version.getVersion());
map.put(Constants.TIMESTAMP_KEY, String.valueOf(System.currentTimeMillis()));
if (ConfigUtils.getPid() > 0) {
map.put(Constants.PID_KEY, String.valueOf(ConfigUtils.getPid()));
}
// 若不存在 `protocol` 参数,默认 "dubbo" 添加到 `map` 集合中。
if (!map.containsKey("protocol")) {
if (ExtensionLoader.getExtensionLoader(RegistryFactory.class).hasExtension("remote")) { // "remote"
map.put("protocol", "remote");
} else {
map.put("protocol", "dubbo");
}
}
// ##################解析地址,创建 Dubbo URL 数组。(数组大小可以为一),同时将map内的参数填充入每个url
List urls = UrlUtils.parseURLs(address, map);
// 循环 `url` ,设置 "registry" 和 "protocol" 属性。
for (URL url : urls) {
// 设置 `registry=${protocol}` 和 `protocol=registry` 到 URL
url = url.addParameter(Constants.REGISTRY_KEY, url.getProtocol());
// 首先需要使用url向zookeeper注册自身
url = url.setProtocol(Constants.REGISTRY_PROTOCOL);
// 添加到结果
if ((provider && url.getParameter(Constants.REGISTER_KEY, true)) // 服务提供者 && 注册
|| (!provider && url.getParameter(Constants.SUBSCRIBE_KEY, true))) { // 服务消费者 && 订阅
registryList.add(url);
}
}
}
}
}
return registryList;
}
}
上段代码中,有一行 : UrlUtils.parseURLs(address, map) ,这一行的作用就是将字符串形式的url,参数,解析封装成一个URL对象:
public class UrlUtils {
/**
* 解析多个 URL ,将 `defaults` 里的参数,合并到 `address` 中。
*
* 合并的逻辑如下:
*
* 我们可以把 `address` 认为是 url ;`defaults` 认为是 defaultURL 。
* 若 url 有不存在的属性时,从 defaultURL 获得对应的属性,设置到 url 中。
*
* @param address 地址
* @param defaults 默认参数集合
* @return URL
*/
public static List parseURLs(String address, Map defaults) {
if (address == null || address.length() == 0) {
return null;
}
// 拆分注册中心地址,按照逗号或者分号。
String[] addresses = Constants.REGISTRY_SPLIT_PATTERN.split(address);
if (addresses == null || addresses.length == 0) {
return null; //here won't be empty
}
List registries = new ArrayList();
for (String addr : addresses) {
// 解析 URL ,将 `defaults` 里的参数,合并到 `addr` 中。
registries.add(parseURL(addr, defaults));
}
return registries;
}
/**
* 解析单个 URL ,将 `defaults` 里的参数,合并到 `address` 中。
*
* 合并的逻辑如下:
*
* 我们可以把 `address` 认为是 url ;`defaults` 认为是 defaultURL 。
* 若 url 有不存在的属性时,从 defaultURL 获得对应的属性,设置到 url 中。
*
* @param address 地址
* @param defaults 默认参数集合
* @return URL
*/
public static URL parseURL(String address, Map defaults) {
if (address == null || address.length() == 0) {
return null;
}
// 以 Zookeeper 注册中心,配置集群的例子如下:
// java config : dubbo.registry.address = zookeeper://lanboal:[email protected]:2181?login=false
// 第一种,
// 第二种,
String url;
if (address.contains("://")) { // 第一种
url = address;
} else { // 第二种
String[] addresses = Constants.COMMA_SPLIT_PATTERN.split(address); // 按照 逗号 拆分
url = addresses[0];
if (addresses.length > 1) {
StringBuilder backup = new StringBuilder();
for (int i = 1; i < addresses.length; i++) {
if (i > 1) {
backup.append(",");
}
backup.append(addresses[i]);
}
url += "?" + Constants.BACKUP_KEY + "=" + backup.toString();
}
}
// 从 `defaults` 中,获得 "protocol" "username" "password" "host" "port" "path" 到 `defaultXXX` 属性种。
// 因为,在 Dubbo URL 中,这几个是独立的属性,不在 `Dubbo.parameters` 属性中。
String defaultProtocol = defaults == null ? null : defaults.get("protocol");
if (defaultProtocol == null || defaultProtocol.length() == 0) { // 如果地址没有协议缺省为 dubbo
defaultProtocol = "dubbo";
}
String defaultUsername = defaults == null ? null : defaults.get("username");
String defaultPassword = defaults == null ? null : defaults.get("password");
int defaultPort = StringUtils.parseInteger(defaults == null ? null : defaults.get("port"));
String defaultPath = defaults == null ? null : defaults.get("path");
Map defaultParameters = defaults == null ? null : new HashMap(defaults);
if (defaultParameters != null) { // 需要移除,因为这几个是独立属性。
defaultParameters.remove("protocol");
defaultParameters.remove("username");
defaultParameters.remove("password");
defaultParameters.remove("host");
defaultParameters.remove("port");
defaultParameters.remove("path");
}
// #################### 创建 Dubbo URL 。
URL u = URL.valueOf(url);
// 若 `u` 的属性存在非空的情况下,从 `defaultXXX` 属性,赋值到 `u` 的属性中。
boolean changed = false; // 是否改变,即从 `defaultXXX` 属性,赋值到 `u` 的属性中。
String protocol = u.getProtocol();
String username = u.getUsername();
String password = u.getPassword();
String host = u.getHost();
int port = u.getPort();
String path = u.getPath();
Map parameters = new HashMap(u.getParameters());
// 如果原始url上未指定protocol,username,password, 但有default值,则追加到url上
if ((protocol == null || protocol.length() == 0) && defaultProtocol.length() > 0) {
changed = true;
protocol = defaultProtocol;
}
if ((username == null || username.length() == 0) && defaultUsername != null && defaultUsername.length() > 0) {
changed = true;
username = defaultUsername;
}
if ((password == null || password.length() == 0) && defaultPassword != null && defaultPassword.length() > 0) {
changed = true;
password = defaultPassword;
}
/*if (u.isAnyHost() || u.isLocalHost()) {
changed = true;
host = NetUtils.getLocalHost();
}*/
if (port <= 0) {
if (defaultPort > 0) {
changed = true;
port = defaultPort;
} else { // 如果地址没有端口缺省为9090。FROM http://dubbo.io/books/dubbo-user-book/references/xml/dubbo-registry.html 文档。
changed = true;
port = 9090;
}
}
if (path == null || path.length() == 0) {
if (defaultPath != null && defaultPath.length() > 0) {
changed = true;
path = defaultPath;
}
}
if (defaultParameters != null && defaultParameters.size() > 0) {
for (Map.Entry entry : defaultParameters.entrySet()) {
String key = entry.getKey();
String defaultValue = entry.getValue();
if (defaultValue != null && defaultValue.length() > 0) {
String value = parameters.get(key);
if (value == null || value.length() == 0) {
changed = true;
parameters.put(key, defaultValue);
}
}
}
}
// 若改变,创建新的 Dubbo URL 。
if (changed) {
u = new URL(protocol, username, password, host, port, path, parameters);
}
return u;
}
}
上段代码中有一行 : URL u = URL.valueOf(url); 这个函数的作用就是才是通过String字符串生成URL对象,其他的仅仅是完善URL对象内的参数。
public final class URL implements Serializable {
/**
* Parse url string
* 解析 dubbo.registry.address=zookeeper://lanboal:[email protected]:2181?sercicecheck=false 指定的值
*
* @param url URL string
* @return URL instance
* @see URL
*/
public static URL valueOf(String url) {
if (url == null || (url = url.trim()).length() == 0) {
throw new IllegalArgumentException("url == null");
}
String protocol = null;
String username = null;
String password = null;
String host = null;
int port = 0;
String path = null;
Map parameters = null;
int i = url.indexOf("?"); // seperator between body and parameters
if (i >= 0) { // 如果存在参数,那么提取参数 sercicecheck=false
String[] parts = url.substring(i + 1).split("\\&");
parameters = new HashMap();
for (String part : parts) {
part = part.trim();
if (part.length() > 0) {
int j = part.indexOf('=');
if (j >= 0) {
parameters.put(part.substring(0, j), part.substring(j + 1));
} else {
parameters.put(part, part);
}
}
}
url = url.substring(0, i); // 截取url,抹去参数,也就是 ? 之后的数据
}
i = url.indexOf("://");
if (i >= 0) {
if (i == 0) { throw new IllegalStateException("url missing protocol: \"" + url + "\""); }
protocol = url.substring(0, i); // 提取协议 : zookeeper
url = url.substring(i + 3); // 截取协议之后的剩余数据,也就是 :// 之后,?之前的数据 zookeeper://lanboal:[email protected]:2181 >> lanboal:[email protected]:2181
} else {
// case: file:/path/to/file.txt
i = url.indexOf(":/");
if (i >= 0) {
if (i == 0) { throw new IllegalStateException("url missing protocol: \"" + url + "\""); }
protocol = url.substring(0, i);
url = url.substring(i + 1);
}
}
i = url.indexOf("/");
if (i >= 0) {
path = url.substring(i + 1);
url = url.substring(0, i);
}
// 提取用户名和密码
i = url.indexOf("@");
if (i >= 0) {
username = url.substring(0, i);
int j = username.indexOf(":");
if (j >= 0) {
username = username.substring(0, j); // 用户名: lanboal
password = username.substring(j + 1); // 密码 : 123456
}
url = url.substring(i + 1); // 截取url : 127.0.0.1:2181
}
i = url.indexOf(":");
if (i >= 0 && i < url.length() - 1) {
port = Integer.parseInt(url.substring(i + 1)); // 端口 : 2181
url = url.substring(0, i); // url :127.0.0.1
}
if (url.length() > 0) {
host = url;
}
// new URL("zookeeper", "lanboal", "123456", "127.0.0.1", "2181", null, sercicecheck=false)
return new URL(protocol, username, password, host, port, path, parameters);
}
}