功能概述
- 配置对象用来承载来自XML配置或注解配置的信息,每个配置关联的XML元素或注解都对应着一个Config对象,而AbstractConfig是对相关Config通用功能的处理,比如附加参数Map与Config对象的转换等等。
功能分析
2.1 核心类AbstractConfig分析
2.1.1)Config类继承图示意
2.1.2)主要成员变量分析
java
private static final Map<String, String> LEGACY_PROPERTIES = new HashMap<String, String>();
private static final String[] SUFFIXES = new String[]{"Config", "Bean", "ConfigBase"}; //Config对象名的后缀
static { //已过时的属性(将一些旧版本使用的属性与当前的属性进行映射,起到适配转换的作用)
LEGACY_PROPERTIES.put("dubbo.protocol.name", "dubbo.service.protocol");
LEGACY_PROPERTIES.put("dubbo.protocol.host", "dubbo.service.server.host");
LEGACY_PROPERTIES.put("dubbo.protocol.port", "dubbo.service.server.port");
LEGACY_PROPERTIES.put("dubbo.protocol.threads", "dubbo.service.max.thread.pool.size");
LEGACY_PROPERTIES.put("dubbo.consumer.timeout", "dubbo.service.invoke.timeout");
LEGACY_PROPERTIES.put("dubbo.consumer.retries", "dubbo.service.max.retry.providers");
LEGACY_PROPERTIES.put("dubbo.consumer.check", "dubbo.service.allow.no.provider");
LEGACY_PROPERTIES.put("dubbo.service.url", "dubbo.service.address");
}
protected String id; //配置id(所有config子类都有)
protected String prefix; //配置对象名的前缀
2.1.3)主要成员方法分析
1)添加Config属性到参数Map中
java
public static void appendParameters(Map<String, String> parameters, Object config, String prefix) { //将Config对象的属性添加到参数Map中(将Config对象中的属性值进行筛选处理,并添加的参数Map中)
if (config == null) {
return;
}
Method[] methods = config.getClass().getMethods(); //利用反射,获取config对象中的所有public方法
for (Method method : methods) { //遍历Config对象的方法,找到getXxx()、isXxx()或getParameters()方法进行处理
try {
String name = method.getName();
if (MethodUtils.isGetter(method)) {
Parameter parameter = method.getAnnotation(Parameter.class); //取出方法上声明的注解@Parameter
if (method.getReturnType() == Object.class || parameter != null && parameter.excluded()) { //方法返回值为对象或在声明@Parameter且参数被排除时,跳过不处理
continue;
}
String key; //Config对象的属性名
if (parameter != null && parameter.key().length() > 0) { //若方法使用了@Parameter注解声明,且设置了key的值,则将该值作为参数key
key = parameter.key();
} else { //没有带@Parameter注解
key = calculatePropertyFromGetter(name); //提取属性名,如getProtocol()方法的属性名为protocol(若属性名是驼峰的,则按分隔符处理,如getProtocolName(),若分隔符为".",则最终的属性名为protocol.name)
}
Object value = method.invoke(config); //使用反射机制,获取config中get或is方法的返回值
String str = String.valueOf(value).trim();
if (value != null && str.length() > 0) {
if (parameter != null && parameter.escaped()) {
str = URL.encode(str); //若escaped指定是需要编码的,则编码字符串
}
if (parameter != null && parameter.append()) { //若相同的key对应多个值,且@Parameter注解中的append=true,则通过分隔符","将多个值拼接
String pre = parameters.get(key); //将自定义参数Map中的值与Config中的属性值,进行拼接
if (pre != null && pre.length() > 0) {
str = pre + "," + str; //带上分隔符,附加到原有的值value上
}
}
if (prefix != null && prefix.length() > 0) {
key = prefix + "." + key; //使用前缀与参数key进行拼接
}
parameters.put(key, str); //将参数key、value,写入到参数Map中
} else if (parameter != null && parameter.required()) { //在值value为空,且@parameter注解required声明为必须时,报出异常信息
throw new IllegalStateException(config.getClass().getSimpleName() + "." + key + " == null");
}
} else if (isParametersGetter(method)) { //若是getParameters()方法,则可以将该方法的返回值Map<String, String>直接设置到处理的参数map中
Map<String, String> map = (Map<String, String>) method.invoke(config, new Object[0]);
parameters.putAll(convert(map, prefix)); //map中的参数依次带上前缀,并添加到参数Map中
}
} catch (Exception e) {
throw new IllegalStateException(e.getMessage(), e);
}
}
}
- 代码解析:该方法的作用就是,将Config配置对象中的属性值进行筛选,按键值对写到参数map中,而参数map用于后续通讯的数据传输
2)将注解的内容附加到对应的Config对象中
java
protected void appendAnnotation(Class<?> annotationClass, Object annotation) {
Method[] methods = annotationClass.getMethods();
for (Method method : methods) { //遍历注解Class中的所有方法
if (method.getDeclaringClass() != Object.class
&& method.getReturnType() != void.class
&& method.getParameterTypes().length == 0
&& Modifier.isPublic(method.getModifiers())
&& !Modifier.isStatic(method.getModifiers())) { //找出注解中 公有的、非静态的,且有返回值的方法
try {
String property = method.getName(); //获取方法名(即为注解的属性名)
if ("interfaceClass".equals(property) || "interfaceName".equals(property)) {
property = "interface"; //归并属性名
}
String setter = "set" + property.substring(0, 1).toUpperCase() + property.substring(1); //将注解中的方法名,组装为config对象实例的set方法名,如setListener
Object value = method.invoke(annotation); //获取注解中方法对应的值
if (value != null && !value.equals(method.getDefaultValue())) { //注解方法中值不为空且不为默认值时,将注解方法中的返回值写到Config对象中
Class<?> parameterType = ReflectUtils.getBoxedClass(method.getReturnType()); //获取返回类型的封装类型
if ("filter".equals(property) || "listener".equals(property)) { //属性为filter(过滤器)、listener(监听器)时,是按数组配置的
parameterType = String.class;
value = StringUtils.join((String[]) value, ","); //将字符串数组按分隔符拼接为字符串
} else if ("parameters".equals(property)) { //属性为parameters,是按Map配置的(字符数组是按key、value形式依次存储的)
parameterType = Map.class;
value = CollectionUtils.toStringMap((String[]) value);
}
try {
Method setterMethod = getClass().getMethod(setter, parameterType); //获取Config对象对应的setter方法
setterMethod.invoke(this, value); //使用反射机制,调用setter方法,将注解方法中的返回值写到Config对象中
} catch (NoSuchMethodException e) {
// ignore
}
}
} catch (Throwable e) {
logger.error(e.getMessage(), e);
}
}
}
}
3)获取Config对象中的元数据
java
public Map<String, String> getMetaData() { //获取Config对象中的元数据(找到元数据方法或getParameters方法,进行方法调用并获取到返回值,最后设置到元数据Map中)
Map<String, String> metaData = new HashMap<>();
Method[] methods = this.getClass().getMethods(); //this.getClass() 指的是AbstractConfig的实例对象,比如ConfigCenterConfig
for (Method method : methods) {
try {
String name = method.getName();
if (MethodUtils.isMetaMethod(method)) { //判断是否是获取元数据方法(public的get/is方法,且返回值是基本类型)
String key;
Parameter parameter = method.getAnnotation(Parameter.class);
if (parameter != null && parameter.key().length() > 0 && parameter.useKeyAsProperty()) {
key = parameter.key(); //若方法上带有@Parameter注解,则直接取注解中key的值
} else {
key = calculateAttributeFromGetter(name); //若没有带有@Parameter注解或配置key,则从方法名中取出属性名
}
if (method.getReturnType() == Object.class) {
metaData.put(key, null);
continue;
}
if (MethodUtils.isDeprecated(method) && metaData.get(key) != null) { //若方法已经弃用了,则不再处理
continue;
}
Object value = method.invoke(this); //使用反射调用对应方法,并接收返回值
String str = String.valueOf(value).trim();
if (value != null && str.length() > 0) {
metaData.put(key, str); //将配置对象中的key、value设置到元数据中
} else {
metaData.put(key, null);
}
} else if (isParametersGetter(method)) { //判断是否是getParameters方法
Map<String, String> map = (Map<String, String>) method.invoke(this, new Object[0]); //调用getParameters()方法,new Object[0]表明没有参数
metaData.putAll(convert(map, ""));
}
} catch (Exception e) {
throw new IllegalStateException(e.getMessage(), e);
}
}
return metaData;
}
- 代码解析:此方法的作用为找到元数据信息,通过元数据方法或getParameters方法,进行方法调用并获取到返回值,最后设置到元数据Map中。元数据方法的判定:public的get/is方法,且返回值是基本类型的方法
4)刷新Config对象的属性值
java
public void refresh() { //刷新Config对象的属性值
Environment env = ApplicationModel.getEnvironment(); //获取环境信息
try {
CompositeConfiguration compositeConfiguration = env.getPrefixedConfiguration(this); //获取带有前缀的合成配置对象的实例(包含多种配置源对象的实例)
// loop methods, get override value and set the new value back to method
Method[] methods = getClass().getMethods();
for (Method method : methods) { //遍历当前配置对象的方法,从合成的配置实例中获取值,通过set()方法或setParameters()方法设置到XxxConfig对象中
if (MethodUtils.isSetter(method)) { //是否是setXXX()方法
try {
String value = StringUtils.trim(compositeConfiguration.getString(extractPropertyName(getClass(), method))); //从配置源获取属性对应的值
// isTypeMatch() is called to avoid duplicate and incorrect update, for example, we have two 'setGeneric' methods in ReferenceConfig.
if (StringUtils.isNotEmpty(value) && ClassUtils.isTypeMatch(method.getParameterTypes()[0], value)) { //若值不为空,且参数类型与参数值能够匹配,则执行invoke调用
method.invoke(this, ClassUtils.convertPrimitive(method.getParameterTypes()[0], value)); //调用set方法对Config对象的属性设置
}
} catch (NoSuchMethodException e) {
logger.info("Failed to override the property " + method.getName() + " in " +
this.getClass().getSimpleName() +
", please make sure every property has getter/setter method provided.");
}
} else if (isParametersSetter(method)) { //是否是setParameters()方法
String value = StringUtils.trim(compositeConfiguration.getString(extractPropertyName(getClass(), method))); //value的格式如:"[{key1:value1},{key2:value2}...]"
if (StringUtils.isNotEmpty(value)) {
Map<String, String> map = invokeGetParameters(getClass(), this); //获取Config对象中getParameters()方法的返回值
map = map == null ? new HashMap<>() : map;
map.putAll(convert(StringUtils.parseParameters(value), "")); //将属性值解析为Map形式,并设置到配置对象Config的Map
invokeSetParameters(getClass(), this, map); //调用setParameters()方法,对Config对象的参数设值
}
}
}
} catch (Exception e) {
logger.error("Failed to override ", e);
}
}
- 代码解析:刷新配置的值,是通过合成配置CompositeConfig获取最新的配置值,然后通过set方法或setParameters方法设置到Config对象中的
2.2 关联类Environment分析
2.2.1)主要成员变量分析
java
private final PropertiesConfiguration propertiesConfiguration; //装载"dubbo.properties"文件的配置信息
private final SystemConfiguration systemConfiguration; //装载System的properties配置系信息
private final EnvironmentConfiguration environmentConfiguration;//装载JVM环境变量的配置信息
private final InmemoryConfiguration externalConfiguration; //配置中心的额外配置(装载配置中心配置,存入到本地缓存中)
private final InmemoryConfiguration appExternalConfiguration; //配置中心按group隔离的配置(装载配置中心的配置信息)
private CompositeConfiguration globalConfiguration; //合成的配置信息
private Map<String, String> externalConfigurationMap = new HashMap<>(); //配置中心的额外配置对应的Map(从配置中心拉取的未按group隔离的配置内容)
private Map<String, String> appExternalConfigurationMap = new HashMap<>(); //配置中心按group隔离的配置对应的Map(按应用名做group隔离的配置内容)
private boolean configCenterFirst = true; //配置中心的配置是否优先
private DynamicConfiguration dynamicConfiguration; //动态配置实例
2.2.2)主要成员方法分析
1)配置中心初始化
java
public void initialize() throws IllegalStateException { //用配置中心的配置做初始化(对当前对象的属性进行初始化)
ConfigManager configManager = ApplicationModel.getConfigManager(); //获取配置管理对象实例(通过SPI机制获取)
Optional<Collection<ConfigCenterConfig>> defaultConfigs = configManager.getDefaultConfigCenter(); //获取默认配置中心列表
defaultConfigs.ifPresent(configs -> { //ifPresent:若值存在时,带着值执行对应的动作,否则什么都不做
for (ConfigCenterConfig config : configs) { //从默认配置中心获取配置值,设置到当前的缓存中(若有多个默认的配置中心,会出现值覆盖)
this.setExternalConfigMap(config.getExternalConfiguration());
this.setAppExternalConfigMap(config.getAppExternalConfiguration());
}
});
this.externalConfiguration.setProperties(externalConfigurationMap);
this.appExternalConfiguration.setProperties(appExternalConfigurationMap);
}
2)获取Config对应的合成配置
java
public synchronized CompositeConfiguration getPrefixedConfiguration(AbstractConfig config) { //获取Config对应的合成配置
CompositeConfiguration prefixedConfiguration = new CompositeConfiguration(config.getPrefix(), config.getId());
Configuration configuration = new ConfigConfigurationAdapter(config); //AbstractConfig对应的配置对象的实例
if (this.isConfigCenterFirst()) { //配置中心的配置优先(即配置中心的配置高于Config的配置)
// 在CompositeConfiguration#getInternalProperty进行取值时,会依次遍历列表中的配置对象的实例,越靠前的配置,越先获取到配置值。
prefixedConfiguration.addConfiguration(systemConfiguration); //systemConfiguration、environmentConfiguration等对象,在Environment构造函数中初始化的
prefixedConfiguration.addConfiguration(environmentConfiguration);
prefixedConfiguration.addConfiguration(appExternalConfiguration); //appExternalConfiguration、externalConfiguration的值来源于配置中心(在initialize方法中设置的值)
prefixedConfiguration.addConfiguration(externalConfiguration);
prefixedConfiguration.addConfiguration(configuration);
prefixedConfiguration.addConfiguration(propertiesConfiguration);
} else { //systemConfiguration、environmentConfiguration、propertiesConfiguration位置固定,主要根据isConfigCenterFirst()的值来调整 配置中心的位置
// Config center has the highest priority(配置中心有最高优先级)
prefixedConfiguration.addConfiguration(systemConfiguration);
prefixedConfiguration.addConfiguration(environmentConfiguration);
prefixedConfiguration.addConfiguration(configuration); //相比上面,配置信息加载的位置不一样
prefixedConfiguration.addConfiguration(appExternalConfiguration);
prefixedConfiguration.addConfiguration(externalConfiguration);
prefixedConfiguration.addConfiguration(propertiesConfiguration);
}
return prefixedConfiguration;
}
- 代码解析:在启动时,各种配置会被聚集到数据总线URL中,给后面的程序使用。而配置源有多种,如配置对象、JVM输入参数、配置中心等,该方法就是对各种配置源做排序
问题点答疑
-
MetricsConfig这个配置对象的功能用途是怎样的?
- 解答:对服务进行实时监控,了解服务当前的运行指标和健康状态,是微服务体系中不可或缺的环节。Metrics 作为微服务的重要组件,为服务的监控提供了全面的数据基础。
-
AbstractConfig#refresh该处的刷新操作都做哪些功能?
- 解答:从多种配置源获取配置信息,并刷新Config对象的属性值
-
Environment#getPrefixedConfiguration该方法中的配置中心加载位置会影响到哪些功能?
- 解答:加载的Config位置会影响配置参数值的获取顺序,排在前面的Config对象,会优先查询到配置。
配置中心在 Dubbo 中主要承担两个职责: 外部化配置 和 服务治理Dubbo 可以同时支持多种配置来源。在 Dubbo 初始化过程中,会从多个来源获取配置,并按照固定的优先级将这些配置整合起来,实现高优先级的配置覆盖低优先级配置的效果。这些配置的汇总结果将会参与形成 URL,以及后续的服务发布和服务引用
- 解答:加载的Config位置会影响配置参数值的获取顺序,排在前面的Config对象,会优先查询到配置。
-
org.apache.dubbo.config.support.Parameter这个注解是怎么使用的?
- 解答:@Parameter注解是作用在方法上,在处理方法时,可根据设置的参数做对应的逻辑处理。
-
Config相关对象是怎么注册为spring的bean的?
- 解答:在DubboBeanDefinitionParser#parse中做解析并注册Spring的bean的
-
AbstractMethodConfig中的parameters自定义参数是怎样判断以及设定的?一定要带上spring的p的命名空间吗?
- 解答:从设置的地方ServiceConfigBase#convertProtocolToProvider来看,provider.setParameters(protocol.getParameters());
与spring的p的命名空间无关。
- 解答:从设置的地方ServiceConfigBase#convertProtocolToProvider来看,provider.setParameters(protocol.getParameters());
-
ServiceBean是怎么与具体的服务关联起来的?
- 解答:ServiceBean的继承关系为:ServiceBean extends ServiceConfig implements InitializingBean, DisposableBean,ApplicationContextAware, BeanNameAware, ApplicationEventPublisherAware
ServiceBean 每个暴露出去的服务都会生成一个ServiceBean。2.7.8的ServiceBean简化了许多,主要由DubboBootStrap实现了。
- 解答:ServiceBean的继承关系为:ServiceBean extends ServiceConfig implements InitializingBean, DisposableBean,ApplicationContextAware, BeanNameAware, ApplicationEventPublisherAware
-
为什么System.setProperty设置的值,config对应能取到值?如:System.setProperty("dubbo.application.name", "demo");设置的值,applicationConfig.getName()能取到值?
- 解答:因为会调用AbstractConfig#refresh方法,该方法会通过合成配置,从各个配置源中获取参数值,包含System设置的值;
-
Config与Model两个数据模型的功能用途有何不同?
- 解答:Config对象承载着配置信息,用于与xml、url等转换,而Model存储提供者、消费者端的信息,用于缓存数据,提升性能
-
Config的前缀是怎样计算出来的,如ApplicationConfig对应为"dubbo.application." ?
- 解答:在AbstractConfig#getPrefix处理的。处理逻辑为:如ApplicationConfig对应的前缀为"dubbo:application."
java
public String getPrefix() { //获取Config的前缀名(若没有设置,则使用默认的前缀名,如ApplicationConfig对应的前缀为"dubbo.application")
return StringUtils.isNotEmpty(prefix) ? prefix : (CommonConstants.DUBBO + "." + getTagName(this.getClass()));
}
public static String getTagName(Class<?> cls) { //获取Config类对应的标签名,比如ConfigCenter为config-center
String tag = cls.getSimpleName(); //如ConfigCenterConfig,tag为ConfigCenterConfig
for (String suffix : SUFFIXES) { //若类名包含指定后缀名,则先去除掉后缀
if (tag.endsWith(suffix)) { //把包含的后缀去掉,比如ConfigCenterConfig改为ConfigCenter,又如ApplicationConfig对应为Application
tag = tag.substring(0, tag.length() - suffix.length());
break;
}
}
return StringUtils.camelToSplitName(tag, "-"); //将驼峰字符串转换为按分隔符处理
}
-
ApplicationConfig中的hostname是在哪里设置的?值从哪里获取的?
- 解答:若没有设置hostname,会取机器本地地址来处理,如ApplicationConfig#getHostname中的InetAddress.getLocalHost().getHostName();
-
ConfigConfigurationAdapter的功能用途是什么?
- 解答:这个类的用途是:适配Config中的元数据key,带上前缀prefix和id值。(这个类用于接收AbstractConfig,并且把它的属性值通过Configuration暴露出来),暴露的Map中的key带上了config对应的key,如dubbo.application.qos-host
-
AbstractMethodConfig#forks是怎样实现并行调用数的?与AbstractMethodConfig#actives并发调用数有什么区别?
- 解答:两者官方说明如下:
1)actives:消费者端的最大并发调用限制,即当Consumer对一个服务的并发调用到上限后,新调用会阻塞直到超时,在方法上配置 dubbo:method 则针对该方法进行并发限制,在接口上配置 dubbo:service,则针对该服务进行并发限制
(actives:活跃数,即连接数,也就是消费端调用服务的连接数)
2)forks:并行调用多个服务器,只要一个成功即返回。通常用于实时性要求较高的读操作,但需要浪费更多服务资源。可通过 forks="2" 来设置最大并行数。
forks的处理逻辑在ForkingClusterInvoker#doInvoke中
(形象记忆:forks:叉子,像叉子一样去叉东西,叉到那个就得到哪个)
- 解答:两者官方说明如下:
-
AbstractMethodConfig#sent的用途是什么?
- 解答:在异步调用时,可以设置是否等待消息发出:
1)sent="true" 等待消息发出,消息发送失败将抛出异常。
2)sent="false" 不等待消息发出,将消息放入 IO 队列,即刻返回
- 解答:在异步调用时,可以设置是否等待消息发出:
-
AbstractServiceConfig#delay延迟时间和超时时间timeOut有什么区别?
- 解答:若设置了延迟时间delay,表明需要在指定的时间后才暴露服务,执行的地方为ServiceConfig#export,会使用定时任务Schedule执行。而timeout是指消费端调用服务,多久没响应,就认为服务不可用,报出超时异常。
归纳总结
- AbstractConfig做的抽象:
- 主要的核心方法和功能有:
- appendParameters(...) //将Config对象的属性添加到参数Map中
- appendAttributes(...) //将Config对象方法上的注解@Parameter中的属性,设置到参数Map中
- convert(...) //将参数Map的key拼接上前缀
- appendAnnotation(...) //将注解的内容附加到对应的Config对象中
- getMetaData(...) //获取Config对象中的元数据(找到元数据方法或getParameters方法,进行方法调用并获取到返回值,最后设置到元数据Map中)
- refresh(...) //刷新Config对象的属性值(通过合成配置CompositeConfig获取最新的配置值,然后设置到Config对象中)
- toString() //将config对象按字符串输出(输出的内容如:"<dubbo:annotation listener="l1, l2" filter="f1, f2" />")