在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
Map<String, String> customParams = new HashMap<>(16);
customParams.put("a", "1");
customParams.put("b", "2");
serviceConfig.setParameters(customParams);
xml
目前未找到实现方式
注解
/**
* Customized parameter key-value pair, for example: {key1, value1, key2, value2}
*/
String[] parameters() default {};
如
@Service(parameters={"a", "1","b","1"})