Dubbo之AbstractConfig源码解析

功能概述

  • 配置对象用来承载来自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,以及后续的服务发布和服务引用
  • 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的命名空间无关。
  • ServiceBean是怎么与具体的服务关联起来的?

    • 解答:ServiceBean的继承关系为:ServiceBean extends ServiceConfig implements InitializingBean, DisposableBean,ApplicationContextAware, BeanNameAware, ApplicationEventPublisherAware
      ServiceBean 每个暴露出去的服务都会生成一个ServiceBean。2.7.8的ServiceBean简化了许多,主要由DubboBootStrap实现了。
  • 为什么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是指消费端调用服务,多久没响应,就认为服务不可用,报出超时异常。

归纳总结

  1. 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" />")
相关推荐
连连斯基14 小时前
Android Framework(八)WMS-窗口动效概述
android·dubbo
就叫飞六吧2 天前
html嵌入百度地图
百度·html·dubbo
天下蒂一厨3 天前
dubbo微服务
微服务·架构·dubbo·idea
高 朗5 天前
【从0开始搭建微服务并进行部署】SpringBoot+dubbo+zookeeper
spring boot·微服务·dubbo·java-zookeeper
TracyCoder1236 天前
Dubbo快速入门(一):分布式与微服务、Dubbo基本概念
分布式·dubbo
花千树-0107 天前
Dubbo 如何使用 Zookeeper 作为注册中心:原理、优势与实现详解
分布式·zookeeper·dubbo
m0_588383329 天前
进阶SpringBoot之 Dubbo-admin 安装测试
spring boot·后端·dubbo
hanlin.liu16889 天前
Dubbo入门案例
dubbo
HHoao10 天前
Flink Task 日志文件隔离
java·flink·dubbo
会洗碗的CV工程师12 天前
828华为云征文 | 使用Flexus X实例搭建Dubbo-Admin服务
java·linux·服务器·华为云·dubbo