Dubbo源码解析(十五) Dubbo URL 创建逻辑

引入

在dubbo中,URL是整个服务发布和调用流程的串联信息,它包含了服务的基本信息(服务名、服务方法、版本、分组),注册中心配置,应用配置等信息,并且通过URL可以实现扩展点自适应等。

它是在服务发布和注册的时候,进行生成的,即在 ServiceConfig.export() 或 ReferenceConfig.get() 初始化时,将 Bean 对象转换 URL 格式,所有 Bean 属性转成 URL 的参数。然后将 URL 传给 协议扩展点,基于扩展点的 扩展点自适应机制,根据 URL 的协议头,进行不同协议的服务暴露或引用。

演示

下面将以这个例子,来演示服务发布后生成的URL

public static void main(String[] args) throws IOException {
        ServiceConfig<GreetingService> serviceConfig = new ServiceConfig<GreetingService>();
        // 设置应用
        serviceConfig.setApplication(new ApplicationConfig("first-dubbo-provider"));
        // 设置注册中心
        serviceConfig.setRegistry(new RegistryConfig("zookeeper://127.0.0.1:2181"));
        // 设置服务接口
        serviceConfig.setInterface(GreetingService.class);
        serviceConfig.setRef(new GreetingServiceImpl());
        // 设置服务具体方法,如这里设置服务超时时间为1000
        MethodConfig methodConfig = new MethodConfig();
        methodConfig.setName("sayHello");
        methodConfig.setTimeout(1000);
        serviceConfig.setMethods(Collections.singletonList(methodConfig));
        // 发布服务
        serviceConfig.export();
        System.in.read();
    }

可以看到生成的信息如下

dubbo://10.0.75.1:20880/com.wuhulala.dubbo.gateway.GreetingService?anyhost=true&application=first-dubbo-provider&default.deprecated=false&default.dynamic=false&default.register=true&deprecated=false&dubbo=2.0.2&dynamic=false&generic=false&interface=com.wuhulala.dubbo.gateway.GreetingService&methods=sayHello&pid=3272®ister=true&release=2.7.2-SNAPSHOT&sayHello.timeout=1000&side=provider×tamp=1554523631703

分析一下可以看到如
application=first-dubbo-provider
sayHello.timeout=1000
interface=com.wuhulala.dubbo.gateway.GreetingService
generic=false

那么具体是如何实现的呢

实现

ServiceConfig#doExportUrlsFor1Protocol

private void doExportUrlsFor1Protocol(ProtocolConfig protocolConfig, List<URL> registryURLs) {
    String name = protocolConfig.getName();
    if (StringUtils.isEmpty(name)) {
        name = Constants.DUBBO;
    }

    Map<String, String> map = new HashMap<String, String>();
    // 设置为服务端
    map.put(Constants.SIDE_KEY, Constants.PROVIDER_SIDE);
     // 设置运行时参数,dubbo-version、pid、release、timestamp
    appendRuntimeParameters(map);
    // 设置dubbo:application 信息,如应用名称、owner、organization等
    appendParameters(map, application);
    // 设置模块的信息
    appendParameters(map, module);
    // 设置默认的provider配置,具体指的就是dubbo:provider 的配置
    appendParameters(map, provider, Constants.DEFAULT_KEY);
    // 设置协议的信息 ,dubbo:protocol
    appendParameters(map, protocolConfig);
    // 设置dubbo:service 即服务自身的配置
    appendParameters(map, this);
    // 设置dubbo:method 的配置,  
    // 代码可以优化吧  ,这里应该抽出来一个方法吧,从而方便阅读
    if (CollectionUtils.isNotEmpty(methods)) {
        for (MethodConfig method : methods) {
            appendParameters(map, method, method.getName());
            String retryKey = method.getName() + ".retry";
            if (map.containsKey(retryKey)) {
                String retryValue = map.remove(retryKey);
                if ("false".equals(retryValue)) {
                    map.put(method.getName() + ".retries", "0");
                }
            }
            List<ArgumentConfig> arguments = method.getArguments();
            if (CollectionUtils.isNotEmpty(arguments)) {
                for (ArgumentConfig argument : arguments) {
                    // convert argument type
                    if (argument.getType() != null && argument.getType().length() > 0) {
                        Method[] methods = interfaceClass.getMethods();
                        // visit all methods
                        if (methods != null && methods.length > 0) {
                            for (int i = 0; i < methods.length; i++) {
                                String methodName = methods[i].getName();
                                // target the method, and get its signature
                                if (methodName.equals(method.getName())) {
                                    Class<?>[] argtypes = methods[i].getParameterTypes();
                                    // one callback in the method
                                    if (argument.getIndex() != -1) {
                                        if (argtypes[argument.getIndex()].getName().equals(argument.getType())) {
                                            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 {
                                        // multiple callbacks in the method
                                        for (int j = 0; j < argtypes.length; j++) {
                                            Class<?> argclazz = argtypes[j];
                                            if (argclazz.getName().equals(argument.getType())) {
                                                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());
                                                }
                                            }
                                        }
                                    }
                                }
                            }
                        }
                    } else if (argument.getIndex() != -1) {
                        appendParameters(map, argument, method.getName() + "." + argument.getIndex());
                    } else {
                        throw new IllegalArgumentException("Argument config must set index or type attribute.eg:  or ");
                    }

                }
            }
        } // end of methods for
    }


    // 如果是泛化实现
    if (ProtocolUtils.isGeneric(generic)) {
        map.put(Constants.GENERIC_KEY, generic);
        map.put(Constants.METHODS_KEY, Constants.ANY_VALUE);
    } else {
        // 非繁华实现
        String revision = Version.getVersion(interfaceClass, version);
        if (revision != null && revision.length() > 0) {
            map.put(Constants.REVISION_KEY, revision);
        }

        // 设置服务接口的所有方法
        String[] methods = Wrapper.getWrapper(interfaceClass).getMethodNames();
        if (methods.length == 0) {
            logger.warn("No method found in service interface " + interfaceClass.getName());
            map.put(Constants.METHODS_KEY, Constants.ANY_VALUE);
        } else {
            map.put(Constants.METHODS_KEY, StringUtils.join(new HashSet<String>(Arrays.asList(methods)), ","));
        }
    }
    if (!ConfigUtils.isEmpty(token)) {
        if (ConfigUtils.isDefault(token)) {
            map.put(Constants.TOKEN_KEY, UUID.randomUUID().toString());
        } else {
            map.put(Constants.TOKEN_KEY, token);
        }
    }
    // .....省略其它信息
    }

可以在上面看到,对dubbo的一系列配置(dubbo:application/dubbo:provider等),会被加到服务发布的URL上,这样在后面的负载均衡、扩展点加载等可以使用

实际的添加参数方法

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();
            // 判断getter函数上的Parameter注解
            // 如 ApplicationConfig  
            // @Parameter(key = Constants.APPLICATION_KEY, required = true, useKeyAsProperty = false)
               //public String getName() {
              //    return name;
               //}
               // 那么就会在url上面加上&application=xxx
            if (ClassHelper.isGetter(method)) {
                Parameter parameter = method.getAnnotation(Parameter.class);
                if (method.getReturnType() == Object.class || parameter != null && parameter.excluded()) {
                    continue;
                }
                String key;
                if (parameter != null && parameter.key().length() > 0) {
                    key = parameter.key();
                } else {
                    key = calculatePropertyFromGetter(name);
                }
                // 通过反射读取实例对应的属性
                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);
                    }
                    if (parameter != null && parameter.append()) {
                        String pre = parameters.get(Constants.DEFAULT_KEY + "." + key);
                        if (pre != null && pre.length() > 0) {
                            str = pre + "," + str;
                        }
                        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);
                } else if (parameter != null && parameter.required()) {
                    throw new IllegalStateException(config.getClass().getSimpleName() + "." + key + " == null");
                }
                // 如果是getParameters,那么把所有的自定义的parameters添加到URL上面
            } else if ("getParameters".equals(name)
                    && Modifier.isPublic(method.getModifiers())
                    && method.getParameterTypes().length == 0
                    && method.getReturnType() == Map.class) {
                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);
        }
    }
}

自定义参数

如果我们想在URL后面加上自定义参数,比如 加上两个参数 &a=1&b=2

  1. 硬编码
    如果是java配置的serviceConfig的话
  Map<String, String> customParams = new HashMap<>(16);
        customParams.put("a", "1");
        customParams.put("b", "2");
        serviceConfig.setParameters(customParams);

  1. xml
    目前未找到实现方式

  2. 注解

   /**
     * Customized parameter key-value pair, for example: {key1, value1, key2, value2}
     */
    String[] parameters() default {};


@Service(parameters={"a", "1","b","1"})

你可能感兴趣的:(dubbo,Dubbo,源码解析)