前言
我们通过 Dubbo URL统一模型 已经了解了Dubbo URL是Duboo的配置总线,贯穿整个Dubbo的生命周期。虽然Dubbo URL直接决定了Dubbo组件的角色并控制Dubbo的行为,但是Dubbo URL中的信息需要Dubbo的配置承对象来提供,而配置承载对象中的数据来源于多种配置和设置。
目前Dubbo框架同时支持4种配置方式:API硬编码配置、XML配置、注解配置、属性配置。而所有的配置项分为三大类:
- 服务注册和发现:表示该配置项用于服务的注册和发现。
- 服务治理:表示该配置项用于治理服务间的关系,或为开发测试提供便利条件。
- 性能调优:表示该配置项用于调优性能,不同的选项对性能会产生影响。
注意:所有配置最终都会转换为Dubbo URL
配置承载对象
不管是注解还是XML配置都需要配置对象来承载,XML配置、注解配置、属性配置都是和配置对象或其属性相映射的,为什么这里没有说API配置和配置对象的映射关系呢?其实API配置就是直接操作配置对象,而XML配置和注解配置都是由Spring来创建配置对象并设置属性的,而我们的属性配置是在配置对象已经存在的基础上,为其设置指定的属性值。下面是Dubbo的属性配置类的结构:
上图中我使用了黄色框和红色框分别对抽象配置类和配置实现类进行了标注,其中DubboShutdownHook先忽略。红色框中的配置类是直接的配置承载类,黄色框中的抽象配置类是配置承载类的父类。下面是配置承载类的UML图:
- 直观图
- 依赖关系图
通过上面的关系图我们可以很清楚地了解到每个配置之间的关系,我们接下来就顺着关系图分别介绍核心的配置类。
AbstractConfig 抽象配置类
除了ArgumentConfig配置类,几乎其他的所有配置类都直接或间接继承该类,该类主要提供配置解析与校验相关的工具方法。
格式校验
public abstract class AbstractConfig implements Serializable {
// 省略其它代码 ${}
//-------------------------- 格式检验 -----------------------------/
private static final int MAX_LENGTH = 200;
private static final int MAX_PATH_LENGTH = 200;
private static final Pattern PATTERN_NAME = Pattern.compile("[\\-._0-9a-zA-Z]+");
private static final Pattern PATTERN_MULTI_NAME = Pattern.compile("[,\\-._0-9a-zA-Z]+");
private static final Pattern PATTERN_METHOD_NAME = Pattern.compile("[a-zA-Z][0-9a-zA-Z]*");
private static final Pattern PATTERN_PATH = Pattern.compile("[/\\-$._0-9a-zA-Z]+");
private static final Pattern PATTERN_NAME_HAS_SYMBOL = Pattern.compile("[:*,/\\-._0-9a-zA-Z]+");
private static final Pattern PATTERN_KEY = Pattern.compile("[*,\\-._0-9a-zA-Z]+");
protected static void checkExtension(Class<?> type, String property, String value) {
checkName(property, value);
if (value != null && value.length() > 0
&& !ExtensionLoader.getExtensionLoader(type).hasExtension(value)) {
throw new IllegalStateException("No such extension " + value + " for " + property + "/" + type.getName());
}
}
protected static void checkMultiExtension(Class<?> type, String property, String value) {
checkMultiName(property, value);
if (value != null && value.length() > 0) {
String[] values = value.split("\\s*[,]+\\s*");
for (String v : values) {
if (v.startsWith(Constants.REMOVE_VALUE_PREFIX)) {
v = v.substring(1);
}
if (Constants.DEFAULT_KEY.equals(v)) {
continue;
}
if (!ExtensionLoader.getExtensionLoader(type).hasExtension(v)) {
throw new IllegalStateException("No such extension " + v + " for " + property + "/" + type.getName());
}
}
}
}
protected static void checkLength(String property, String value) {
checkProperty(property, value, MAX_LENGTH, null);
}
protected static void checkPathLength(String property, String value) {
checkProperty(property, value, MAX_PATH_LENGTH, null);
}
protected static void checkName(String property, String value) {
checkProperty(property, value, MAX_LENGTH, PATTERN_NAME);
}
protected static void checkNameHasSymbol(String property, String value) {
checkProperty(property, value, MAX_LENGTH, PATTERN_NAME_HAS_SYMBOL);
}
protected static void checkKey(String property, String value) {
checkProperty(property, value, MAX_LENGTH, PATTERN_KEY);
}
protected static void checkMultiName(String property, String value) {
checkProperty(property, value, MAX_LENGTH, PATTERN_MULTI_NAME);
}
protected static void checkPathName(String property, String value) {
checkProperty(property, value, MAX_PATH_LENGTH, PATTERN_PATH);
}
protected static void checkMethodName(String property, String value) {
checkProperty(property, value, MAX_LENGTH, PATTERN_METHOD_NAME);
}
protected static void checkParameterName(Map<String, String> parameters) {
if (parameters == null || parameters.size() == 0) {
return;
}
for (Map.Entry<String, String> entry : parameters.entrySet()) {
checkNameHasSymbol(entry.getKey(), entry.getValue());
}
}
protected static void checkProperty(String property, String value, int maxlength, Pattern pattern) {
if (value == null || value.length() == 0) {
return;
}
if (value.length() > maxlength) {
throw new IllegalStateException("Invalid " + property + "=\"" + value + "\" is longer than " + maxlength);
}
if (pattern != null) {
Matcher matcher = pattern.matcher(value);
if (!matcher.matches()) {
throw new IllegalStateException("Invalid " + property + "=\"" + value + "\" contains illegal " +
"character, only digit, letter, '-', '_' or '.' is legal.");
}
}
}
// 省略其它代码 ${}
}
AbstractConfig的子类会调用这里的方法进行相关的参数校验。
添加属性(属性和系统参数配置)
读取启动参数变量和Properties配置文件中属性到配置承载对象中,该方法其实就是属性配置
和 系统参数配置
的逻辑。这个逻辑非常重要,无论是API配置还是XML配置,以及注解配置,都会使用该逻辑为配置承载对象设置系统参数值以及配置属性值。
public abstract class AbstractConfig implements Serializable {
// 省略其它代码 ${}
/**
* 1 id 属性,Bean定义的名称,适用于除了API配置之外的三种配置(属性配置,xml配置,注解配置)方式,可用于对象之间的引用
* 2 不适用API配置,是因为API配置直接setter(xxx)对象即可
*/
protected String id;
/**
* 读取带有配置项名前缀的启动参数变量和properties配置到 配置承载对象中。
* 说明:在此之前配置承载对象中只可能有xml配置的属性值,或者注解配置的属性值
*
* @param config 配置对象
*/
protected static void appendProperties(AbstractConfig config) {
if (config == null) {
return;
}
// 获得配置项前缀(使用配置类的类名,获得对应的属性标签)-> dubbo.tag.
String prefix = "dubbo." + getTagName(config.getClass()) + ".";
// 获得配置类的所有方法,用于下面通过反射获得配置项的属性名,再用属性名去读取启动参数变量和.properties配置到配置对象
Method[] methods = config.getClass().getMethods();
for (Method method : methods) {
try {
// 拿到方法名
String name = method.getName();
// 选择方法是 【public && setter && 唯一参数为基本类型】 的方法
if (name.length() > 3 && name.startsWith("set") && Modifier.isPublic(method.getModifiers())
&& method.getParameterTypes().length == 1 && isPrimitive(method.getParameterTypes()[0])) {
// 获得属性名 如: ApplicationConfig#setName(...) 方法,对应的属性名为 name
String property = StringUtils.camelToSplitName(name.substring(3, 4).toLowerCase() + name.substring(4), ".");
//----------- 读取的覆盖策略: JVM -D > XML > .properties ----------/
String value = null;
//【启动参数变量】优先从带有 id属性的XxxConfig的配置中获取,例如 dubbo.application.demo-provider.name
if (config.getId() != null && config.getId().length() > 0) {
// id字段
String pn = prefix + config.getId() + "." + property;
value = System.getProperty(pn);
if (!StringUtils.isBlank(value)) {
logger.info("Use System Property " + pn + " to config dubbo");
}
}
//【启动参数变量】获取不到,再从不带 id属性 的XxxConfig的配置中获取,例如:dubbo.application.name
if (value == null || value.length() == 0) {
// 没有id字段
String pn = prefix + property;
value = System.getProperty(pn);
if (!StringUtils.isBlank(value)) {
logger.info("Use System Property " + pn + " to config dubbo");
}
}
// 配置优先级以及覆盖: 启动参数变量 > XML配置[注解/java配置] > properties配置 。因此需要使用getter判断XML是否已经配置
if (value == null || value.length() == 0) {
Method getter;
try {
getter = config.getClass().getMethod("get" + name.substring(3));
} catch (NoSuchMethodException e) {
try {
getter = config.getClass().getMethod("is" + name.substring(3));
} catch (NoSuchMethodException e2) {
getter = null;
}
}
if (getter != null) {
// 使用getter 判断XML是否已经设置过,如果没有设置的话就从.properties文件中读取
if (getter.invoke(config) == null) {
// [properties配置] 优先从带有 id 属性的配置中获取,例如:dubbo.application.demo-provider.name
if (config.getId() != null && config.getId().length() > 0) {
value = ConfigUtils.getProperty(prefix + config.getId() + "." + property);
}
// [properties配置]获取不到,再从不带 id 属性的配置中获取,例如:dubbo.application.name
if (value == null || value.length() == 0) {
value = ConfigUtils.getProperty(prefix + property);
}
// [properties配置]获取不到,这里进行老版本兼容,从不带id属性的配置中获取
if (value == null || value.length() == 0) {
String legacyKey = legacyProperties.get(prefix + property);
if (legacyKey != null && legacyKey.length() > 0) {
value = convertLegacyValue(legacyKey, ConfigUtils.getProperty(legacyKey));
}
}
}
}
}
// 获取到值(系统参数配置或者.properties文件中的,不包含xml配置,xml配置有单独的设置方法)
if (value != null && value.length() > 0) {
method.invoke(config, convertPrimitive(method.getParameterTypes()[0], value));
}
}
} catch (Exception e) {
logger.error(e.getMessage(), e);
}
}
}
// 省略其它代码 ${}
}
上面的代码主要就是为已经实例化好的配置承载对象设置属性值,主要逻辑如下:
- 根据配置对象获取属性配置的前缀,如 dubbo.application.
- 遍历配置承载对象中的所有方法找到符合条件的setter方法
- 根据配置覆盖策略的优先级,设置配置承载对象的属性值
配置对象的属性到参数集合
将配置承载对象的属性添加到参数集合中,用于构建Dubbo URL,如在服务暴露和引用是构建相关的URL。
public abstract class AbstractConfig implements Serializable {
// 省略其它代码 ${}
/**
* 将配置对象的属性添加到参数集合
*
* @param parameters
* @param config
*/
protected static void appendParameters(Map<String, String> parameters, Object config) {
appendParameters(parameters, config, null);
}
/**
* 将配置对象的属性添加到参数集合,主要逻辑:
* <p>
* 1 通过反射获取目标对象的getter方法,并调用该方法获取属性值,然后再通过getter方法名解析出属性名,如:从方法名getName中可解析出属性name,如果用户传入了属性名前缀,此时需要将属性名加入前缀内容。
* 2 将 属性名-属性值 键值对存入到map中就可以了
*
* @param parameters 参数集合,该集合会用于URL
* @param config 配置对象
* @param prefix 属性前缀。用于配置项添加到参数集合中时的前缀
*/
@SuppressWarnings("unchecked")
protected static void appendParameters(Map<String, String> parameters, Object config, String prefix) {
if (config == null) {
return;
}
// 获得所有方法的数组,为下面通过反射获得配置项的值做准备
Method[] methods = config.getClass().getMethods();
for (Method method : methods) {
try {
String name = method.getName();
// 选择方法为 返回值为基本类型 + public的getter/is方法 (和解析到配置类的过滤添加呼应)
if ((name.startsWith("get") || name.startsWith("is"))
&& !"getClass".equals(name)
&& Modifier.isPublic(method.getModifiers())
&& method.getParameterTypes().length == 0
&& isPrimitive(method.getReturnType())) {
// 尝试获取方法上的@Parameter注解
Parameter parameter = method.getAnnotation(Parameter.class);
// 方法返回类型是Object的或者方法的@Parameter(excluded = true)的, 不统计对应的值到参数集合
if (method.getReturnType() == Object.class || parameter != null && parameter.excluded()) {
continue;
}
// 获得属性名
int i = name.startsWith("get") ? 3 : 2;
String prop = StringUtils.camelToSplitName(name.substring(i, i + 1).toLowerCase() + name.substring(i + 1), ".");
String key;
// @Parameter注解有配置key属性就取出该值
if (parameter != null && parameter.key().length() > 0) {
key = parameter.key();
} else {
key = prop;
}
// 利用反射获得属性的值
Object value = method.invoke(config);
String str = String.valueOf(value).trim();
if (value != null && str.length() > 0) {
// 是否转移,默认不转译
if (parameter != null && parameter.escaped()) {
str = URL.encode(str);
}
// @Parameter注解有配置append属性,就进行拼接
if (parameter != null && parameter.append()) {
// 1. 看参数集合中是否有key为: default.key的值(默认属性值),有就拼接到属性值前面
String pre = parameters.get(Constants.DEFAULT_KEY + "." + key);
if (pre != null && pre.length() > 0) {
str = pre + "," + str;
}
// 2. 看参数集合中是否有key对应的值,有就拼接到属性值前面
pre = parameters.get(key);
if (pre != null && pre.length() > 0) {
str = pre + "," + str;
}
}
// 如果指定了属性前缀就拼接上去,就在属性名前面加上前缀
if (prefix != null && prefix.length() > 0) {
key = prefix + "." + key;
}
// 把最后处理的属性值加入参数集合中
parameters.put(key, str);
// 当配置对象的属性getter方法加了@Parameter(required=true)时,校验配置项非空
} else if (parameter != null && parameter.required()) {
throw new IllegalStateException(config.getClass().getSimpleName() + "." + key + " == null");
}
// 当方法为public Map getParameters(){...}时,就以此将Map中的key-value加入到参数集合
} else if ("getParameters".equals(name)
&& Modifier.isPublic(method.getModifiers())
&& method.getParameterTypes().length == 0
&& method.getReturnType() == Map.class) {
// 通过 getParameters()方法,获取动态设置的配置项
Map<String, String> map = (Map<String, String>) method.invoke(config, new Object[0]);
if (map != null && map.size() > 0) {
String pre = (prefix != null && prefix.length() > 0 ? prefix + "." : "");
for (Map.Entry<String, String> entry : map.entrySet()) {
parameters.put(pre + entry.getKey().replace('-', '.'), entry.getValue());
}
}
}
} catch (Exception e) {
throw new IllegalStateException(e.getMessage(), e);
}
}
}
// 省略其它代码 ${}
}
上面代码主要就是将配置承载对象中的属性设置到属性集合Map中,用于构建Dubbo URL。整个逻辑需要注意,配置承载对象的getter方法上标注的 @Parameter
注解,以及配置承载对象的getParameters方法。
添加事件通知属性
public abstract class AbstractConfig implements Serializable {
// 省略其它代码 ${}
protected static void appendAttributes(Map<Object, Object> parameters, Object config) {
appendAttributes(parameters, config, null);
}
/**
*
* @param parameters 参数集合
* @param config 配置对象
* @param prefix 属性前缀。用于配置项添加到参数集合中时的前缀
*/
protected static void appendAttributes(Map<Object, Object> parameters, Object config, String prefix) {
if (config == null) {
return;
}
Method[] methods = config.getClass().getMethods();
for (Method method : methods) {
try {
String name = method.getName();
// 选择方法为 返回值为基本类型 + public的getter/is方法 (和解析到配置类的过滤添加呼应)
if ((name.startsWith("get") || name.startsWith("is"))
&& !"getClass".equals(name)
&& Modifier.isPublic(method.getModifiers())
&& method.getParameterTypes().length == 0
&& isPrimitive(method.getReturnType())) {
// 选择带有@Parameter(attribute=true)的方法
Parameter parameter = method.getAnnotation(Parameter.class);
if (parameter == null || !parameter.attribute()) {
continue;
}
String key;
parameter.key();
if (parameter.key().length() > 0) {
key = parameter.key();
} else {
int i = name.startsWith("get") ? 3 : 2;
key = name.substring(i, i + 1).toLowerCase() + name.substring(i + 1);
}
// 获得属性值,存在则添加到参数集合中
Object value = method.invoke(config);
if (value != null) {
if (prefix != null && prefix.length() > 0) {
key = prefix + "." + key;
}
parameters.put(key, value);
}
}
} catch (Exception e) {
throw new IllegalStateException(e.getMessage(), e);
}
}
}
// 省略其它代码 ${}
}
上面代码主要用于Dubbo的事件通知的,具体是标注在MethodConfig配置承载对象的 getOnreturn(),getOnreturnMethod(),getOnthrow()…方法上。
AbstractInterfaceConfig 抽象配置类
继承关系如下:
AbstractConfig
- AbstractMethodConfig
- AbstractInterfaceConfig
AbstractConfig抽象类的核心逻辑已经分析过,AbstractMethodConfig抽象类中没有比较重要的逻辑,基本都是 配置属性的设置/获取方法
,就不再分析,接下来我们一起看下AbstractInterfaceConfig抽象类的逻辑。
校验注册中心配置
public abstract class AbstractInterfaceConfig extends AbstractMethodConfig {
// 省略其它代码 ${}
protected void checkRegistry() {
// for backward compatibility
if (registries == null || registries.isEmpty()) {
String address = ConfigUtils.getProperty("dubbo.registry.address");
if (address != null && address.length() > 0) {
registries = new ArrayList<RegistryConfig>();
String[] as = address.split("\\s*[|]+\\s*");
for (String a : as) {
RegistryConfig registryConfig = new RegistryConfig();
registryConfig.setAddress(a);
registries.add(registryConfig);
}
}
}
if ((registries == null || registries.isEmpty())) {
throw new IllegalStateException((getClass().getSimpleName().startsWith("Reference")
? "No such any registry to refer service in consumer "
: "No such any registry to export service in provider ")
+ NetUtils.getLocalHost()
+ " use dubbo version "
+ Version.getVersion()
+ ", Please add <dubbo:registry address=\"...\" /> to your spring config. If you want unregister, please set <dubbo:service registry=\"N/A\" />");
}
for (RegistryConfig registryConfig : registries) {
// 调用AbstractConfig中的方法
appendProperties(registryConfig);
}
}
// 省略其它代码 ${}
}
校验应用配置
public abstract class AbstractInterfaceConfig extends AbstractMethodConfig {
// 省略其它代码 ${}
protected void checkApplication() {
// for backward compatibility
if (application == null) {
String applicationName = ConfigUtils.getProperty("dubbo.application.name");
if (applicationName != null && applicationName.length() > 0) {
application = new ApplicationConfig();
}
}
if (application == null) {
throw new IllegalStateException(
"No such application config! Please add <dubbo:application name=\"...\" /> to your spring config.");
}
// 调用AbstractConfig中的方法
appendProperties(application);
String wait = ConfigUtils.getProperty(Constants.SHUTDOWN_WAIT_KEY);
if (wait != null && wait.trim().length() > 0) {
System.setProperty(Constants.SHUTDOWN_WAIT_KEY, wait.trim());
} else {
wait = ConfigUtils.getProperty(Constants.SHUTDOWN_WAIT_SECONDS_KEY);
if (wait != null && wait.trim().length() > 0) {
System.setProperty(Constants.SHUTDOWN_WAIT_SECONDS_KEY, wait.trim());
}
}
}
// 省略其它代码 ${}
}
加载注册中心URL数组
public abstract class AbstractInterfaceConfig extends AbstractMethodConfig {
// 省略其它代码 ${}
/**
* 加载注册中心URL数组
*
* @param provider 是否是服务提供者
* @return URL数组
*/
protected List<URL> loadRegistries(boolean provider) {
// 校验RegistryConfig 配置数组,不存在会抛出异常,并且该方法会初始化RegistryConfig的配置属性
checkRegistry();
// 创建注册中心URL数组
List<URL> registryList = new ArrayList<URL>();
if (registries != null && !registries.isEmpty()) {
// 遍历RegistryConfig 数组
for (RegistryConfig config : registries) {
// 获取注册中心的地址
String address = config.getAddress();
// 地址为空就使用 0.0.0.0 任意地址
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,用于Dubbo URL的构建
Map<String, String> map = new HashMap<String, String>();
// 将应用配置对象和注册中心配置对象的属性添加到参数集合map中
appendParameters(map, application);
/**
* 需要注意的是:RegistryConfig 的 getAddress方法上使用了 @Parameter(excluded = true)注解,因此它的address属性不会加入到参数集合map中
* @Parameter(excluded = true)
* public String getAddress() {return address;}
*/
appendParameters(map, config);
// 添加 path,dubbo,timestamp,pid 到参数集合map中
map.put("path", RegistryService.class.getName()); // 这里的path要和服务暴露逻辑中的path区分,注册中心的URL中的path为RegistryService的全路径名
map.put("dubbo", Version.getProtocolVersion());
map.put(Constants.TIMESTAMP_KEY, String.valueOf(System.currentTimeMillis()));
if (ConfigUtils.getPid() > 0) {
map.put(Constants.PID_KEY, String.valueOf(ConfigUtils.getPid()));
}
// 参数集合map中不存在 protocol 参数【以上配置对象的属性中没有有效的协议protocol参数】,就默认 使用 dubbo 作为 协议protocol的值
if (!map.containsKey("protocol")) {
// 不需考虑remote扩展实现的情况
if (ExtensionLoader.getExtensionLoader(RegistryFactory.class).hasExtension("remote")) {
map.put("protocol", "remote");
} else {
map.put("protocol", "dubbo");
}
}
// 解析地址,创建Dubbo URL数组,注意address可能包含多个注册中心ip, 【数组大小可以为一】
List<URL> urls = UrlUtils.parseURLs(address, map);
// 循环 dubbo Register url
for (URL url : urls) {
// 设置 registry=${protocol}参数,设置到注册中心的 URL的参数部分的位置上,并且是追加式的添加
url = url.addParameter(Constants.REGISTRY_KEY, url.getProtocol());
// 重置 URL中的 protocol属性为 'registry',即将URL的协议头设置为'registry'
url = url.setProtocol(Constants.REGISTRY_PROTOCOL);
/**
* 通过判断条件,决定是否添加url到registryList中,条件如下:
* 1 如果是服务提供者,是否只订阅不注册,如果是就不添加到注册中心URL数组中
* 2 如果是服务消费者,是否是只注册不订阅,如果是就不添加到注册中心URL数组中
*
*/
if ((provider && url.getParameter(Constants.REGISTER_KEY, true)) || (!provider && url.getParameter(Constants.SUBSCRIBE_KEY, true))) {
registryList.add(url);
}
}
}
}
}
return registryList;
}
// 省略其它代码 ${}
}
加载监控中心URL
public abstract class AbstractInterfaceConfig extends AbstractMethodConfig {
// 省略其它代码 ${}
/**
* 加载监控中心URL
*
* @param registryURL 注册中心URL
* @return 监控中心URL
*/
protected URL loadMonitor(URL registryURL) {
// 如果监控配置为空,就从属性配置中加载配置到MonitorConfig
if (monitor == null) {
// 获取监控地址
String monitorAddress = ConfigUtils.getProperty("dubbo.monitor.address");
// 获取监控协议
String monitorProtocol = ConfigUtils.getProperty("dubbo.monitor.protocol");
// 没有配置就直接返回
if ((monitorAddress == null || monitorAddress.length() == 0) && (monitorProtocol == null || monitorProtocol.length() == 0)) {
return null;
}
// 创建MonitorConfig
monitor = new MonitorConfig();
if (monitorAddress != null && monitorAddress.length() > 0) {
monitor.setAddress(monitorAddress);
}
if (monitorProtocol != null && monitorProtocol.length() > 0) {
monitor.setProtocol(monitorProtocol);
}
}
// 为MonitorConfig加载配置【启动参数变量和properties配置到配置对象】
appendProperties(monitor);
// 添加 interface,dubbo,timestamp,pid 到 map 集合中
Map<String, String> map = new HashMap<String, String>();
map.put(Constants.INTERFACE_KEY, MonitorService.class.getName());
map.put("dubbo", Version.getProtocolVersion());
map.put(Constants.TIMESTAMP_KEY, String.valueOf(System.currentTimeMillis()));
if (ConfigUtils.getPid() > 0) {
map.put(Constants.PID_KEY, String.valueOf(ConfigUtils.getPid()));
}
//set ip
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);
appendParameters(map, monitor);
appendParameters(map, application);
// 获得监控地址
String address = monitor.getAddress();
// 如果启动参数配置了监控中心地址,就进行覆盖,启动参数优先级最高
String sysaddress = System.getProperty("dubbo.monitor.address");
if (sysaddress != null && sysaddress.length() > 0) {
address = sysaddress;
}
// 直连监控中心服务器地址
if (ConfigUtils.isNotEmpty(address)) {
// 若监控地址不存在 protocol 参数,默认 dubbo 添加到 map 集合中
if (!map.containsKey(Constants.PROTOCOL_KEY)) {
// logstat这个拓展实现已经不存在了,可以忽略
if (ExtensionLoader.getExtensionLoader(MonitorFactory.class).hasExtension("logstat")) {
map.put(Constants.PROTOCOL_KEY, "logstat");
} else {
map.put(Constants.PROTOCOL_KEY, "dubbo");
}
}
// 解析地址,创建Dubbo URL 对象
return UrlUtils.parseURL(address, map);
/**
* 1 当 protocol=registry时,并且注册中心URL非空时,从注册中心发现监控中心地址,以注册中心URL为基础,创建监控中心URL
* 2 基于注册中心创建的监控中心URL: protocol = dubbo,parameters.protocol=registry,parameter.refer=map
*/
} else if (Constants.REGISTRY_PROTOCOL.equals(monitor.getProtocol()) && registryURL != null) {
return registryURL.setProtocol("dubbo").addParameter(Constants.PROTOCOL_KEY, "registry").addParameterAndEncoded(Constants.REFER_KEY, StringUtils.toQueryString(map));
}
return null;
}
// 省略其它代码 ${}
}
校验接口和方法列表
public abstract class AbstractInterfaceConfig extends AbstractMethodConfig {
// 省略其它代码 ${}
/**
* 校验接口和方法:
* 1 接口类必须非空并且必须是接口
* 2 方法在接口中已经定义
*
* @param interfaceClass
* @param methods
*/
protected void checkInterfaceAndMethods(Class<?> interfaceClass, List<MethodConfig> methods) {
// interface cannot be null
if (interfaceClass == null) {
throw new IllegalStateException("interface not allow null!");
}
// to verify interfaceClass is an interface
if (!interfaceClass.isInterface()) {
throw new IllegalStateException("The interface class " + interfaceClass + " is not a interface!");
}
// check if methods exist in the interface
if (methods != null && !methods.isEmpty()) {
for (MethodConfig methodBean : methods) {
String methodName = methodBean.getName();
if (methodName == null || methodName.length() == 0) {
throw new IllegalStateException("<dubbo:method> name attribute is required! Please check: <dubbo:service interface=\"" + interfaceClass.getName() + "\" ... ><dubbo:method name=\"\" ... /></<dubbo:reference>");
}
boolean hasMethod = false;
for (java.lang.reflect.Method method : interfaceClass.getMethods()) {
if (method.getName().equals(methodName)) {
hasMethod = true;
break;
}
}
if (!hasMethod) {
throw new IllegalStateException("The interface " + interfaceClass.getName()
+ " not found method " + methodName);
}
}
}
}
// 省略其它代码 ${}
}
校验Stub和Mock相关的配置
public abstract class AbstractInterfaceConfig extends AbstractMethodConfig {
// 省略其它代码 ${}
/**
* 校验Stub和Mock相关的配置
*
* @param interfaceClass
*/
protected void checkStubAndMock(Class<?> interfaceClass) {
if (ConfigUtils.isNotEmpty(local)) {
Class<?> localClass = ConfigUtils.isDefault(local) ? ReflectUtils.forName(interfaceClass.getName() + "Local") : ReflectUtils.forName(local);
if (!interfaceClass.isAssignableFrom(localClass)) {
throw new IllegalStateException("The local implementation class " + localClass.getName() + " not implement interface " + interfaceClass.getName());
}
try {
ReflectUtils.findConstructor(localClass, interfaceClass);
} catch (NoSuchMethodException e) {
throw new IllegalStateException("No such constructor \"public " + localClass.getSimpleName() + "(" + interfaceClass.getName() + ")\" in local implementation class " + localClass.getName());
}
}
if (ConfigUtils.isNotEmpty(stub)) {
Class<?> localClass = ConfigUtils.isDefault(stub) ? ReflectUtils.forName(interfaceClass.getName() + "Stub") : ReflectUtils.forName(stub);
if (!interfaceClass.isAssignableFrom(localClass)) {
throw new IllegalStateException("The local implementation class " + localClass.getName() + " not implement interface " + interfaceClass.getName());
}
try {
ReflectUtils.findConstructor(localClass, interfaceClass);
} catch (NoSuchMethodException e) {
throw new IllegalStateException("No such constructor \"public " + localClass.getSimpleName() + "(" + interfaceClass.getName() + ")\" in local implementation class " + localClass.getName());
}
}
// mock 配置校验
if (ConfigUtils.isNotEmpty(mock)) {
// 如果mock以 'return' 开头,则去掉该前缀
if (mock.startsWith(Constants.RETURN_PREFIX)) {
// 获取return 指定的内容
String value = mock.substring(Constants.RETURN_PREFIX.length());
try {
// 解析return指定的内容,并转换成对应的返回类型
MockInvoker.parseMockValue(value);
} catch (Exception e) {
throw new IllegalStateException("Illegal mock json value in <dubbo:service ... mock=\"" + mock + "\" />");
}
// 不是以 'return' 开头
} else {
// 获得Mock类
Class<?> mockClass = ConfigUtils.isDefault(mock) ? ReflectUtils.forName(interfaceClass.getName() + "Mock") : ReflectUtils.forName(mock);
// 校验是否实现接口
if (!interfaceClass.isAssignableFrom(mockClass)) {
throw new IllegalStateException("The mock implementation class " + mockClass.getName() + " not implement interface " + interfaceClass.getName());
}
// 校验是否有默认的构造方法
try {
mockClass.getConstructor(new Class<?>[0]);
} catch (NoSuchMethodException e) {
throw new IllegalStateException("No such empty constructor \"public " + mockClass.getSimpleName() + "()\" in mock implementation class " + mockClass.getName());
}
}
}
}
// 省略其它代码 ${}
}
ServiceConfig 配置类
该类是 服务暴露
的核心类,我们在 Dubbo示例 - API配置 中已经使用API的方式创建一个Dubbo应用,最后通过调用 ServiceConfig#export()方法
进行服务的导出,ServiceConfig 继承关系如下:
AbstractConfig
- AbstractMethodConfig
- AbstractInterfaceConfig
- AbstractServiceConfig
- ServiceConfig
AbstractServiceConfig 抽象类中也没有核心的逻辑,主要就是配置属性的设置和获取方法,因此也不再分析。
ServiceConfig#export()
方法主要做以下几件事:
- 进一步初始化Dubbo的配置承载对象,因为有的配置对象我们可能并没有显示创建或配置。
- 对配置对象们进行校验是否为空,为空则新建,或者抛出异常。
- ServiceConfig聚集了Dubbo服务的的所有配置属性,使用它的属性构建Dubbo URL对象
- 进行服务暴露
ServiceConfig 属性
public class ServiceConfig<T> extends AbstractServiceConfig {
/**
* 自适应 Protocol实现对象
*/
private static final Protocol protocol = ExtensionLoader.getExtensionLoader(Protocol.class).getAdaptiveExtension();
/**
* 自适应 ProxyFactory 实现对象
*/
private static final ProxyFactory proxyFactory = ExtensionLoader.getExtensionLoader(ProxyFactory.class).getAdaptiveExtension();
/**
* 随机端口集合
*/
private static final Map<String, Integer> RANDOM_PORT_MAP = new HashMap<String, Integer>();
/**
* 延迟暴露线程池
*/
private static final ScheduledExecutorService delayExportExecutor = Executors.newSingleThreadScheduledExecutor(new NamedThreadFactory("DubboServiceDelayExporter", true));
private final List<URL> urls = new ArrayList<URL>();
/**
* 服务配置暴露的Exporter 集合
*/
private final List<Exporter<?>> exporters = new ArrayList<Exporter<?>>();
/**
* 服务接口全路径名
*/
private String interfaceName;
/**
* 非配置,通过interfaceName 通过反射获得
*/
private Class<?> interfaceClass;
/**
* 服务接口的实现对象
*/
private T ref;
/**
* 服务名
*/
private String path;
/**
* 服务方法配置对象集合
*/
private List<MethodConfig> methods;
/**
* 服务提供者默认配置的配置对象
*/
private ProviderConfig provider;
private transient volatile boolean exported;
private transient volatile boolean unexported;
/**
* 泛化
*/
private volatile String generic;
// 省略其它代码 ${}
}
进一步初始化配置承载对象
public class ServiceConfig<T> extends AbstractServiceConfig {
// 省略其它代码 ${}
/**
* 暴露服务入口,加jvm锁
*/
public synchronized void export() {
// 当export 或者 delay 未配置时,从ProviderConfig对象读取
if (provider != null) {
if (export == null) {
export = provider.getExport();
}
if (delay == null) {
delay = provider.getDelay();
}
}
// 不暴露服务(export = false),则不进行暴露服务逻辑
if (export != null && !export) {
return;
}
// 延迟暴露的话,就是使用任务线程池ScheduledExecutorService处理
if (delay != null && delay > 0) {
delayExportExecutor.schedule(new Runnable() {
@Override
public void run() {
doExport();
}
}, delay, TimeUnit.MILLISECONDS);
} else {
doExport();
}
}
/**
* 服务暴露,jvm锁
*/
protected synchronized void doExport() {
// 检查是否可以暴露,若可以,标记已经暴露然后执行服务暴露逻辑
if (unexported) {
throw new IllegalStateException("Already unexported!");
}
// 如果已经暴露了直接返回
if (exported) {
return;
}
// 标记已经暴露过了
exported = true;
// 校验interfaceName 是否合法,即接口名非空
if (interfaceName == null || interfaceName.length() == 0) {
throw new IllegalStateException("<dubbo:service interface=\"\" /> interface not allow null!");
}
// 校验provider是否为空,为空则新建一个,并拼接属性配置(环境变量 + .properties文件中的 属性)到ProviderConfig对象
checkDefault();
// 检测application,module等核心配置类对象是否为空,若为空则尝试从其他配置类对象中获取对应的实例。即: 从ProviderConfig 对象中,读取application,module,registries,monitor,protocols配置对象
if (provider != null) {
if (application == null) {
application = provider.getApplication();
}
if (module == null) {
module = provider.getModule();
}
if (registries == null) {
registries = provider.getRegistries();
}
if (monitor == null) {
monitor = provider.getMonitor();
}
if (protocols == null) {
protocols = provider.getProtocols();
}
}
// 从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();
}
}
// 检测ref是否泛化接口的实现
if (ref instanceof GenericService) {
// 设置 interfaceClass 为 GenericService.class
interfaceClass = GenericService.class;
if (StringUtils.isEmpty(generic)) {
// 设置 generic = "true"
generic = Boolean.TRUE.toString();
}
// 普通接口的实现
} else {
try {
// 通过反射获取对应的接口的Class
interfaceClass = Class.forName(interfaceName, true, Thread.currentThread()
.getContextClassLoader());
} catch (ClassNotFoundException e) {
throw new IllegalStateException(e.getMessage(), e);
}
// 检验接口和方法 (接口非空,方法都在接口中定义)
checkInterfaceAndMethods(interfaceClass, methods);
// 校验引用ref是否实现了当前接口
checkRef();
// 标记为非泛化实现
generic = Boolean.FALSE.toString();
}
/** 处理服务接口客户端本地代理,即本地存根(local 属性 -> AbstractInterfaceConfig#setLocal)。目前已经废弃,此处主要用于兼容,使用stub属性. todo 服务端没有意义 {@link StubProxyFactoryWrapper#getInvoker(java.lang.Object, java.lang.Class, com.alibaba.dubbo.common.URL)} */
if (local != null) {
// 如果local属性设置为ture,表示使用缺省代理类名,即:接口名 + Local 后缀
if ("true".equals(local)) {
local = interfaceName + "Local";
}
Class<?> localClass;
try {
// 获取本地存根类
localClass = ClassHelper.forNameWithThreadContextClassLoader(local);
} catch (ClassNotFoundException e) {
throw new IllegalStateException(e.getMessage(), e);
}
// 检测本地存根类是否可赋值给接口类,若不可赋值则会抛出异常,提醒使用者本地存根类类型不合法
if (!interfaceClass.isAssignableFrom(localClass)) {
throw new IllegalStateException("The local implementation class " + localClass.getName() + " not implement interface " + interfaceName);
}
}
/** 处理服务接口客户端本地代理(stub 属性)相关,即本地存根。目的:想在客户端【服务消费方】执行需要的逻辑,不局限服务提供的逻辑。本地存根类编写方式是固定。todo 服务端没有意义 {@link StubProxyFactoryWrapper#getInvoker(java.lang.Object, java.lang.Class, com.alibaba.dubbo.common.URL)}*/
if (stub != null) {
// 如果stub属性设置为ture,表示使用缺省代理类名,即:接口名 + Stub 后缀
if ("true".equals(stub)) {
stub = interfaceName + "Stub";
}
Class<?> stubClass;
try {
// 获取本地存根类
stubClass = ClassHelper.forNameWithThreadContextClassLoader(stub);
} catch (ClassNotFoundException e) {
throw new IllegalStateException(e.getMessage(), e);
}
// 判断interfaceClass 是否是 stubClass 的接口,即 检测本地存根类是否可赋值给接口类,若不可赋值则会抛出异常,提醒使用者本地存根类类型不合法
if (!interfaceClass.isAssignableFrom(stubClass)) {
throw new IllegalStateException("The stub implementation class " + stubClass.getName() + " not implement interface " + interfaceName);
}
}
/* 检测各种对象是否为空,为空则新建,或者抛出异常*/
// 校验ApplicationConfig配置
checkApplication();
// 校验RegistryConfig配置
checkRegistry();
// 校验ProtocolConfig配置数组
checkProtocol();
// 读取环境变量和properties配置到ServiceConfig对象(自己)
appendProperties(this);
// 校验Stub和Mock相关的配置
checkStubAndMock(interfaceClass);
// 服务路径,缺省是接口名
if (path == null || path.length() == 0) {
path = interfaceName;
}
// 暴露服务
doExportUrls();
ProviderModel providerModel = new ProviderModel(getUniqueServiceName(), this, ref);
ApplicationModel.initProviderModel(getUniqueServiceName(), providerModel);
}
// 省略其它代码 ${}
}
多协议多注册中心
public class ServiceConfig<T> extends AbstractServiceConfig {
// 省略其它代码 ${}
/**
* Dubbo 允许我们使用不同的协议导出服务,也允许我们向多个注册中心注册服务。Dubbo 在 doExportUrls 方法中对多协议,多注册中心进行了支持
*/
@SuppressWarnings({"unchecked", "rawtypes"})
private void doExportUrls() {
// 加载注册中心URL 数组 【协议已经处理过,不再是配置的注册中心协议 如:zookeeper ,而是统一替换成了registry】
List<URL> registryURLs = loadRegistries(true);
// 遍历协议集合,支持多协议暴露。
for (ProtocolConfig protocolConfig : protocols) {
doExportUrlsFor1Protocol(protocolConfig, registryURLs);
}
}
/**
* 使用不同的协议,逐个向注册中心分组暴露服务。该方法中包含了本地和远程两种暴露方式
*
* @param protocolConfig 协议配置对象
* @param registryURLs 处理过的注册中心分组集合【已经添加了ApplicationConfig和RegistryConfig的参数】
*/
private void doExportUrlsFor1Protocol(ProtocolConfig protocolConfig, List<URL> registryURLs) {
// 协议名
String name = protocolConfig.getName();
// 协议名为空时,缺省设置为 dubbo
if (name == null || name.length() == 0) {
name = "dubbo";
}
// 创建参数集合map,用于Dubbo URL 的构建(服务提供者URL)
Map<String, String> map = new HashMap<String, String>();
// 将side,dubbo,timestamp,pid参数,添加到map集合中
map.put(Constants.SIDE_KEY, Constants.PROVIDER_SIDE);
map.put(Constants.DUBBO_VERSION_KEY, Version.getProtocolVersion());
map.put(Constants.TIMESTAMP_KEY, String.valueOf(System.currentTimeMillis()));
if (ConfigUtils.getPid() > 0) {
map.put(Constants.PID_KEY, String.valueOf(ConfigUtils.getPid()));
}
// 通过反射将各种配置对象中的属性添加到map集合中,map用于URL的构建【注意属性覆盖问题】
appendParameters(map, application);
appendParameters(map, module);
appendParameters(map, provider, Constants.DEFAULT_KEY);
appendParameters(map, protocolConfig);
appendParameters(map, this);
// 将MethodConfig 对象数组添加到 map 集合中。就是将每个MethodConfig和其对应的ArgumentConfig对象数组添加到map中【处理方法相关的属性到map】
if (methods != null && !methods.isEmpty()) {
// methods 为 MethodConfig 集合,MethodConfig 中存储了 <dubbo:method> 标签的配置信息
for (MethodConfig method : methods) {
/**
* 将MethodConfig对象的属性添加到map集合中,其中属性键 = 方法名.属性名。如:
* <dubbo:method name="sleep" retries="2"></dubbo:method>对应的MethodConfig,属性到map的格式 map={"sleep.retries":2,...}
*/
appendParameters(map, method, method.getName());
// 当配置了 MehodConfig.retry = false 时,强制禁用重试
String retryKey = method.getName() + ".retry";
if (map.containsKey(retryKey)) {
String retryValue = map.remove(retryKey);
// 检测 MethodConfig retry 是否为 false,若是,则设置重试次数为0
if ("false".equals(retryValue)) {
map.put(method.getName() + ".retries", "0");
}
}
// 将MethodConfig下的ArgumentConfig 对象数组,添加到 map 集合中
List<ArgumentConfig> arguments = method.getArguments();
if (arguments != null && !arguments.isEmpty()) {
for (ArgumentConfig argument : arguments) {
// 检测type 属性是否为空,
if (argument.getType() != null && argument.getType().length() > 0) {
// 通过反射取出接口的方法列表
Method[] methods = interfaceClass.getMethods();
// 遍历接口中的方法列表
if (methods != null && methods.length > 0) {
for (int i = 0; i < methods.length; i++) {
String methodName = methods[i].getName();
// 比对方法名,查找目标方法
if (methodName.equals(method.getName())) {
// 通过反射取出目标方法的参数类型列表
Class<?>[] argtypes = methods[i].getParameterTypes();
// 若果配置index配置项,且值不为-1
if (argument.getIndex() != -1) {
// 从argtypes数组中获取下标index处的元素argType,并检测ArgumentConfig中的type属性与argType名称是否一致,不一致则抛出异常
if (argtypes[argument.getIndex()].getName().equals(argument.getType())) {
// 将ArgumentConfig对象的属性添加到map集合中,键前缀=方法名.index,如:map = {"sleep.2":true}
appendParameters(map, argument, method.getName() + "." + argument.getIndex());
} else {
throw new IllegalArgumentException("argument config error : the index attribute and type attribute not match :index :" + argument.getIndex() + ", type:" + argument.getType());
}
} else {
// 遍历参数类型数组argtypes,查找argument.type类型的参数
for (int j = 0; j < argtypes.length; j++) {
Class<?> argclazz = argtypes[j];
// 从参数类型列表中查找类型名称为argument.type的参数
if (argclazz.getName().equals(argument.getType())) {
// 将ArgumentConfig对象的属性添加到map集合中
appendParameters(map, argument, method.getName() + "." + j);
if (argument.getIndex() != -1 && argument.getIndex() != j) {
throw new IllegalArgumentException("argument config error : the index attribute and type attribute not match :index :" + argument.getIndex() + ", type:" + argument.getType());
}
}
}
}
}
}
}
// 用户未配置 type 属性,但配置了index属性,且index != -1
} else if (argument.getIndex() != -1) { // 指定单个参数的位置
// 将ArgumentConfig对象的属性添加到map集合中
appendParameters(map, argument, method.getName() + "." + argument.getIndex());
} else {
throw new IllegalArgumentException("argument config must set index or type attribute.eg: <dubbo:argument index='0' .../> or <dubbo:argument type=xxx .../>");
}
}
}
} // end of methods for
}
//------------------- 检测 generic 是否 为 true ,并根据检测结果向map中添加不同的信息 ---/
// 将 generic,methods,revision 加入到数组
if (ProtocolUtils.isGeneric(generic)) {
map.put(Constants.GENERIC_KEY, generic);
map.put(Constants.METHODS_KEY, Constants.ANY_VALUE);
} else {
// 先从MAINFEST.MF 中获取版本号,若获取不到,再从jar包命名中可能带的版本号作为结果,如 2.6.5.RELEASE。若都不存在,返回默认版本号【源码运行可能会没有】
String revision = Version.getVersion(interfaceClass, version);
if (revision != null && revision.length() > 0) {
map.put("revision", revision); // 修订号
}
// 为接口生成包裹类 Wrapper,Wrapper 中包含了接口的详细信息,比如接口方法名数组,字段信息等【Dubbo 自定义功能类】
String[] methods = Wrapper.getWrapper(interfaceClass).getMethodNames();
// 添加方法名到 map 中,如果包含多个方法名,则用逗号隔开,比如:method=a,b
if (methods.length == 0) {
logger.warn("NO method found in service interface " + interfaceClass.getName());
// 没有方法名就添加 method=*
map.put(Constants.METHODS_KEY, Constants.ANY_VALUE);
} else {
// 将逗号作为分隔符连接方法名,并将连接后的字符串放入 map 中
map.put(Constants.METHODS_KEY, StringUtils.join(new HashSet<String>(Arrays.asList(methods)), ","));
}
}
// token 【使暴露出去的服务更安全,使用token做安全校验】
if (!ConfigUtils.isEmpty(token)) {
if (ConfigUtils.isDefault(token)) {
map.put(Constants.TOKEN_KEY, UUID.randomUUID().toString());
} else {
map.put(Constants.TOKEN_KEY, token);
}
}
// 协议为injvm时,不注册,不通知
if (Constants.LOCAL_PROTOCOL.equals(protocolConfig.getName())) {
protocolConfig.setRegister(false);
map.put("notify", "false");
}
// 获得基础路径
String contextPath = protocolConfig.getContextpath();
if ((contextPath == null || contextPath.length() == 0) && provider != null) {
contextPath = provider.getContextpath();
}
// --------------------------- 主机绑定----------------------------/
// 获得注册到注册中心的服务提供者host,并为map设置bind.ip , anyhost 两个key
String host = this.findConfigedHosts(protocolConfig, registryURLs, map);
// 获取端口,并为map设置bing.port key
Integer port = this.findConfigedPorts(protocolConfig, name, map);
/**
* 创建Dubbo URL对象 【注意这里的 path 的值】
* 1 name: 协议名
* 2 host: 主机名
* 3 port: 端口
* 4 path: 【基础路径】/path
* 5 parameters: 属性集合map
*/
URL url = new URL(name, host, port, (contextPath == null || contextPath.length() == 0 ? "" : contextPath + "/") + path, map);
// 省略服务暴露代码
}
// 省略其它代码 ${}
}
ReferenceConfig 配置类
该类是 服务引用
的核心类,我们在 Dubbo示例 - API配置 中已经使用API的方式创建一个Dubbo应用,最后通过调用 ReferenceConfig#get()方法
引用服务,ReferenceConfig 继承关系如下:
AbstractConfig
- AbstractMethodConfig
- AbstractInterfaceConfig
- AbstractReferenceConfig
- ReferenceConfig
AbstractReferenceConfig 抽象类中也没有核心的逻辑,主要就是配置属性的设置和获取方法,因此也不再分析。
ReferenceConfig#get()
方法主要做以下几件事:
- 进一步初始化Dubbo的配置承载对象,因为有的配置对象我们可能并没有显示创建或配置。
- 对配置对象们进行校验是否为空,为空则新建,或者抛出异常。
- ReferenceConfig聚集了Dubbo服务消费者的的所有配置属性,使用它的属性构建Dubbo URL对象
- 进行服务引用
ReferenceConfig 属性
public class ReferenceConfig<T> extends AbstractReferenceConfig {
/**
* 自适应 Protocol 拓展实现
*/
private static final Protocol refprotocol = ExtensionLoader.getExtensionLoader(Protocol.class).getAdaptiveExtension();
/**
* 自适应 Cluster 拓展实现
*/
private static final Cluster cluster = ExtensionLoader.getExtensionLoader(Cluster.class).getAdaptiveExtension();
/**
* 自适应 ProxyFactory 拓展实现
*/
private static final ProxyFactory proxyFactory = ExtensionLoader.getExtensionLoader(ProxyFactory.class).getAdaptiveExtension();
/**
* 服务引用URL数组
*/
private final List<URL> urls = new ArrayList<URL>();
/**
* 服务接口名
*/
private String interfaceName;
/**
* 服务接口
*/
private Class<?> interfaceClass;
/**
* 连接类型
*/
private String client;
/**
* 直连服务提供者地址
* 1 可以是注册中心,也可以是服务提供者
* 2 可以配置多个,使用 ";" 分割
*/
private String url;
/**
* 方法配置对象集合
*/
private List<MethodConfig> methods;
/**
* 消费者默认配置的配置对象
*/
private ConsumerConfig consumer;
/**
* 协议
*/
private String protocol;
/**
* 服务接口代理对象
*/
private transient volatile T ref;
/**
* Invoker
*/
private transient volatile Invoker<?> invoker;
private transient volatile boolean initialized;
private transient volatile boolean destroyed;
// 省略其它代码 ${}
}
进一步初始化配置承载对象
public class ReferenceConfig<T> extends AbstractReferenceConfig {
// 省略其它代码 ${}
public synchronized T get() {
// 已销毁,不可获得
if (destroyed) {
throw new IllegalStateException("Already destroyed!");
}
// 若未初始化,调用init()方法进行初始化
if (ref == null) {
init();
}
// 返回引用服务
return ref;
}
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对象
checkDefault();
// 拼接属性配置(环境变量 + .properties 中的属性)到ReferenceConfig(自己)
appendProperties(this);
// 若未设置 generic 属性,就使用ConsumerConfig的generic属性
if (getGeneric() == null && getConsumer() != null) {
setGeneric(getConsumer().getGeneric());
}
// 是否是泛化接口的实现,如果是泛化接口实现的话,就直接设置当前接口为 GenericService.class
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);
}
// 直连提供者,第一优先级,通过 -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) {
// 从系统属性中获取解析文件路径
resolveFile = System.getProperty("dubbo.resolve.file");
if (resolveFile == null || resolveFile.length() == 0) {
// 默认先加载 ${user.home}/dubbo-resolve.properties 文件,无需配置,自动加载
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);
}
}
// 根据服务全路径名获取对应的 直连提供者的url
resolve = properties.getProperty(interfaceName);
}
}
// 设置直连提供者的 url
if (resolve != null && resolve.length() > 0) {
url = resolve;
if (logger.isWarnEnabled()) {
if (resolveFile != null) {
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.");
}
}
}
// 不通过系统属性指定,就使用配置的直连(在配置的前提下),如:<dubbo:reference id="xxxService" interface="com.alibaba.xxx.XxxService" url="dubbo://localhost:20890" />
// 尝试从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);
// 创建参数集合map,用于下面创建Dubbo URL
Map<String, String> map = new HashMap<String, String>();
// 符合条件的方法对象的属性,主要用来Dubbo事件通知
Map<Object, Object> attributes = new HashMap<Object, Object>();
// 将 side,dubbo,timestamp,pid参数,添加到map集合中
map.put(Constants.SIDE_KEY, Constants.CONSUMER_SIDE);
map.put(Constants.DUBBO_VERSION_KEY, Version.getProtocolVersion());
map.put(Constants.TIMESTAMP_KEY, String.valueOf(System.currentTimeMillis()));
if (ConfigUtils.getPid() > 0) {
map.put(Constants.PID_KEY, String.valueOf(ConfigUtils.getPid()));
}
// 非泛化服务,设置revision,methods,interface加入到map集合中
if (!isGeneric()) {
String revision = Version.getVersion(interfaceClass, version);
if (revision != null && revision.length() > 0) {
map.put("revision", revision);
}
// 获取接口方法列表,并添加到map中
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);
// 获得服务键,作为前缀 格式:group/interface:version
String prefix = StringUtils.getServiceKey(map);
// 将MethodConfig 对象数组中每个MethodConfig中的属性添加到map中
if (methods != null && !methods.isEmpty()) {
// 遍历 MethodConfig 列表
for (MethodConfig method : methods) {
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)) {
// 添加重试次数配置 methodName.retries
map.put(method.getName() + ".retries", "0");
}
}
// 将带有@Parameter(attribute=true)配置对象的属性,添加到参数集合中
appendAttributes(attributes, method, prefix + "." + method.getName());
// 检查属性集合中的事件通知方法是否正确,若正确,进行转换
checkAndConvertImplicitConfig(method, map, attributes);
}
}
// 以系统环境变量(DUBBO_IP_TO_REGISTRY)的值作为服务消费者ip地址,没有设置再取主机地址
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);
// 把attributes集合添加到StaticContext进行缓存,为了以后的事件通知
StaticContext.getSystemContext().putAll(attributes);
try{
System.out.println(" ref = createProxy(map); is begin.....");
Thread.sleep(5000);
}catch (Exception ex){
}
// 省略服务引用代码
}
// 省略其它代码 ${}
}
其它配置类
前面只是针对Dubbo的核心配置类进行了分析,还很多其它的配置类并没有分析到(ServiceBean和ReferenceBean属于整合Spring的配置类,我们在XML配置中分析),不过没有分析到的配置类中几乎都没有复杂的逻辑,大多是封装了配置属性的设置和获取操作。每个配置类中封装的配置属性都有所不同,那些抽象的配置类封装的都是可供不同子类复用的属性和方法,每个配置类可以设置那些属性我们可以参考官方文档,需要说明的是,官网给出的是XML配置形式,不过按照对应的规则转换就可以相通了。
总结
Dubbo的配置相对比较枯燥,刚开始看的时候可能有点蒙圈,笔者也是硬着头皮看了好久,看完后也不是很理解,但是把整个流程看完后再回来看体会就更深了。XML配置和注解配置也是基于API配置和属性配置的,区别是XML配置和注解配置要解决和Spring融合问题,我们在接下来的文章中再详细分析。嘿咻,整篇文章都在贴代码!