You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@dubbo.apache.org by al...@apache.org on 2022/06/30 12:46:25 UTC
[dubbo-website] branch master updated: 优化泛化调用文档 (#1168)
This is an automated email from the ASF dual-hosted git repository.
albumenj pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/dubbo-website.git
The following commit(s) were added to refs/heads/master by this push:
new b0afbcdfb1 优化泛化调用文档 (#1168)
b0afbcdfb1 is described below
commit b0afbcdfb1f5f1d571e888c305e3f2d8d3292601
Author: yswdqz <74...@users.noreply.github.com>
AuthorDate: Thu Jun 30 20:46:20 2022 +0800
优化泛化调用文档 (#1168)
---
.../rpc/generic-service.md | 399 ++++++++++++++++-----
1 file changed, 301 insertions(+), 98 deletions(-)
diff --git a/content/zh/docs3-building/java-sdk/advanced-features-and-usage/rpc/generic-service.md b/content/zh/docs3-building/java-sdk/advanced-features-and-usage/rpc/generic-service.md
index 5276c4429e..311388d9ae 100644
--- a/content/zh/docs3-building/java-sdk/advanced-features-and-usage/rpc/generic-service.md
+++ b/content/zh/docs3-building/java-sdk/advanced-features-and-usage/rpc/generic-service.md
@@ -3,116 +3,235 @@ type: docs
title: "泛化调用"
linkTitle: "泛化调用"
weight: 4
-description: "实现一个通用的远程服务 Mock 框架,可通过实现 GenericService 接口处理所有服务请求"
+description: "不需要服务端API的RPC调用"
---
-# 实现泛化服务暴露
+## 特性说明
-泛接口实现方式主要用于服务器端没有 API 接口及模型类元的情况,参数及返回值中的所有 POJO 均用 Map 表示,通常用于框架集成,比如:实现一个通用的远程服务 Mock 框架,可通过实现 GenericService 接口处理所有服务请求。
+泛化调用是指在调用方没有服务方提供的API(SDK)的情况下,对服务方进行调用,并且可以正常拿到调用结果。
+## 使用场景
+泛化调用主要用于实现一个通用的远程服务 Mock 框架,可通过实现 GenericService 接口处理所有服务请求。比如如下场景:
-在 Java 代码中实现 `GenericService` 接口:
+①网关服务:如果要搭建一个网关服务,那么服务网关要作为所有RPC服务的调用端。但是网关本身不应该依赖于服务提供方的接口API(这样会导致每有一个新的服务发布,就需要修改网关的代码以及重新部署),所以需要泛化调用的支持。
-```java
-package com.foo;
-public class MyGenericService implements GenericService {
-
- public Object $invoke(String methodName, String[] parameterTypes, Object[] args) throws GenericException {
- if ("sayHello".equals(methodName)) {
- return "Welcome " + args[0];
- }
- }
-}
-```
+②测试平台:如果要搭建一个可以测试RPC调用的平台,用户输入分组名、接口、方法名等信息,就可以测试对应的RPC服务。那么由于同样的原因(即会导致每有一个新的服务发布,就需要修改网关的代码以及重新部署),所以平台本身不应该依赖于服务提供方的接口API。所以需要泛化调用的支持。
-## 通过 Spring 暴露泛化实现
+## 使用方式
+demo可见[dubbo 项目中的示例代码](https://github.com/apache/dubbo-samples/tree/master/dubbo-samples-generic)
-在 Spring 配置申明服务的实现:
+API部分以此demo为例讲解使用方式。
+### 服务定义
+#### 服务接口
+``` java
+public interface HelloService {
-```xml
-<bean id="genericService" class="com.foo.MyGenericService" />
-<dubbo:service interface="com.foo.BarService" ref="genericService" />
-```
+ String sayHello(String name);
-## 通过 API 方式暴露泛化实现
+ CompletableFuture<String> sayHelloAsync(String name);
+
+ CompletableFuture<Person> sayHelloAsyncComplex(String name);
+
+ CompletableFuture<GenericType<Person>> sayHelloAsyncGenericComplex(String name);
+}
-```java
-...
-// 用org.apache.dubbo.rpc.service.GenericService可以替代所有接口实现
-GenericService xxxService = new XxxGenericService();
-
-// 该实例很重量,里面封装了所有与注册中心及服务提供方连接,请缓存
-ServiceConfig<GenericService> service = new ServiceConfig<GenericService>();
-// 弱类型接口名
-service.setInterface("com.xxx.XxxService");
-service.setVersion("1.0.0");
-// 指向一个通用服务实现
-service.setRef(xxxService);
-
-// 暴露及注册服务
-service.export();
```
+#### 服务实现类
+``` java
+public class HelloServiceImpl implements HelloService {
-# 实现泛化服务调用
+ @Override
+ public String sayHello(String name) {
+ return "sayHello: " + name;
+ }
-泛化接口调用方式主要用于客户端没有 API 接口及模型类元的情况,参数及返回值中的所有 POJO 均用 `Map` 表示,通常用于框架集成,比如:实现一个通用的服务测试框架,可通过 `GenericService` 调用所有服务实现。
+ @Override
+ public CompletableFuture<String> sayHelloAsync(String name) {
+ CompletableFuture<String> future = new CompletableFuture<>();
+ new Thread(() -> {
+ try {
+ Thread.sleep(5000);
+ } catch (InterruptedException e) {
+ e.printStackTrace();
+ }
+ future.complete("sayHelloAsync: " + name);
+ }).start();
+
+ return future;
+ }
-## 通过 Spring 使用泛化调用
+ @Override
+ public CompletableFuture<Person> sayHelloAsyncComplex(String name) {
+ Person person = new Person(1, "sayHelloAsyncComplex: " + name);
+ CompletableFuture<Person> future = new CompletableFuture<>();
+ new Thread(() -> {
+ try {
+ Thread.sleep(5000);
+ } catch (InterruptedException e) {
+ e.printStackTrace();
+ }
+ future.complete(person);
+ }).start();
+
+ return future;
+ }
-在 Spring 配置申明 `generic="true"`:
+ @Override
+ public CompletableFuture<GenericType<Person>> sayHelloAsyncGenericComplex(String name) {
+ Person person = new Person(1, "sayHelloAsyncGenericComplex: " + name);
+ GenericType<Person> genericType = new GenericType<>(person);
+ CompletableFuture<GenericType<Person>> future = new CompletableFuture<>();
+ new Thread(() -> {
+ try {
+ Thread.sleep(5000);
+ } catch (InterruptedException e) {
+ e.printStackTrace();
+ }
+ future.complete(genericType);
+ }).start();
+
+ return future;
+ }
+}
-```xml
-<dubbo:reference id="barService" interface="com.foo.BarService" generic="true" />
```
+### 通过API使用泛化调用
+#### 服务启动方
+①在设置ServiceConfig时,使用setGeneric("true")来开启泛化调用
+
+②在设置ServiceConfig时,使用setRef指定实现类时,要设置一个GenericService的对象。而不是真正的服务实现类对象
+
+
+③其他设置与正常Api服务启动一致即可
+``` java
+private static String zookeeperAddress = "zookeeper://" + System.getProperty("zookeeper.address", "127.0.0.1") + ":2181";
+
+ public static void main(String[] args) throws Exception {
+ new EmbeddedZooKeeper(2181, false).start();
+
+ //创建ApplicationConfig
+ ApplicationConfig applicationConfig = new ApplicationConfig();
+ applicationConfig.setName("generic-impl-provider");
+ //创建注册中心配置
+ RegistryConfig registryConfig = new RegistryConfig();
+ registryConfig.setAddress(zookeeperAddress);
+
+ //新建服务实现类,注意要使用GenericService接收
+ GenericService helloService = new GenericImplOfHelloService();
+
+ //创建服务相关配置
+ ServiceConfig<GenericService> service = new ServiceConfig<>();
+ service.setApplication(applicationConfig);
+ service.setRegistry(registryConfig);
+ service.setInterface("org.apache.dubbo.samples.generic.call.api.HelloService");
+ service.setRef(helloService);
+ //重点:设置为泛化调用
+ //注:不再推荐使用参数为布尔值的setGeneric函数
+ //应该使用referenceConfig.setGeneric("true")代替
+ service.setGeneric("true");
+ service.export();
+
+ System.out.println("dubbo service started");
+
+ new CountDownLatch(1).await();
+ }
+}
+```
+#### 泛化调用方
+步骤:
+
+①在设置ReferenceConfig时,使用setGeneric("true")来开启泛化调用
+
+②配置完ReferenceConfig后,使用referenceConfig.get()获取到GenericService类的实例
+
+
+③使用其$invoke方法获取结果
+
+④其他设置与正常Api服务启动一致即可
+
+``` java
+ //定义泛化调用服务类
+ private static GenericService genericService;
+ public static void main(String[] args) throws Exception {
+ //创建ApplicationConfig
+ ApplicationConfig applicationConfig = new ApplicationConfig();
+ applicationConfig.setName("generic-call-consumer");
+ //创建注册中心配置
+ RegistryConfig registryConfig = new RegistryConfig();
+ registryConfig.setAddress("zookeeper://127.0.0.1:2181");
+ //创建服务引用配置
+ ReferenceConfig<GenericService> referenceConfig = new ReferenceConfig<>();
+ //设置接口
+ referenceConfig.setInterface("org.apache.dubbo.samples.generic.call.api.HelloService");
+ applicationConfig.setRegistry(registryConfig);
+ referenceConfig.setApplication(applicationConfig);
+ //重点:设置为泛化调用
+ //注:不再推荐使用参数为布尔值的setGeneric函数
+ //应该使用referenceConfig.setGeneric("true")代替
+ referenceConfig.setGeneric(true);
+ //设置异步,不必须,根据业务而定。
+ referenceConfig.setAsync(true);
+ //设置超时时间
+ referenceConfig.setTimeout(7000);
+
+ //获取服务,由于是泛化调用,所以获取的一定是GenericService类型
+ genericService = referenceConfig.get();
+
+ //使用GenericService类对象的$invoke方法可以代替原方法使用
+ //第一个参数是需要调用的方法名
+ //第二个参数是需要调用的方法的参数类型数组,为String数组,里面存入参数的全类名。
+ //第三个参数是需要调用的方法的参数数组,为Object数组,里面存入需要的参数。
+ Object result = genericService.$invoke("sayHello", new String[]{"java.lang.String"}, new Object[]{"world"});
+ //使用CountDownLatch,如果使用同步调用则不需要这么做。
+ CountDownLatch latch = new CountDownLatch(1);
+ //获取结果
+ CompletableFuture<String> future = RpcContext.getContext().getCompletableFuture();
+ future.whenComplete((value, t) -> {
+ System.err.println("invokeSayHello(whenComplete): " + value);
+ latch.countDown();
+ });
+ //打印结果
+ System.err.println("invokeSayHello(return): " + result);
+ latch.await();
+ }
-在 Java 代码获取 barService 并开始泛化调用:
-
-```java
-GenericService barService = (GenericService) applicationContext.getBean("barService");
-Object result = barService.$invoke("sayHello", new String[] { "java.lang.String" }, new Object[] { "World" });
```
+### 通过Spring使用泛化调用
+Spring中服务暴露与服务发现有多种使用方式,如xml,注解。这里以xml为例。
+步骤:
-## 通过 API 方式使用泛化调用
+①生产者端无需改动
-```java
-import org.apache.dubbo.rpc.service.GenericService;
-...
-
-// 引用远程服务
-// 该实例很重量,里面封装了所有与注册中心及服务提供方连接,请缓存
-ReferenceConfig<GenericService> reference = new ReferenceConfig<GenericService>();
-// 弱类型接口名
-reference.setInterface("com.xxx.XxxService");
-reference.setVersion("1.0.0");
-// 声明为泛化接口
-reference.setGeneric(true);
-
-// 用org.apache.dubbo.rpc.service.GenericService可以替代所有接口引用
-GenericService genericService = reference.get();
-
-// 基本类型以及Date,List,Map等不需要转换,直接调用
-Object result = genericService.$invoke("sayHello", new String[] {"java.lang.String"}, new Object[] {"world"});
-
-// 用Map表示POJO参数,如果返回值为POJO也将自动转成Map
-Map<String, Object> person = new HashMap<String, Object>();
-person.put("name", "xxx");
-person.put("password", "yyy");
-// 如果返回POJO将自动转成Map
-Object result = genericService.$invoke("findPerson", new String[]
-{"com.xxx.Person"}, new Object[]{person});
-
-...
+②消费者端原有的reference标签加上generic=true的属性。
+``` xml
+ <dubbo:reference id="helloService" generic = "true" interface="org.apache.dubbo.samples.generic.call.api.HelloService"/>
```
+③获取到Bean容器,通过Bean容器拿到GenericService实例。
-## 有关泛化类型的进一步解释
+④调用$invoke方法获取结果
+``` java
-假设存在 POJO 如:
+ private static GenericService genericService;
-```java
-package com.xxx;
+ public static void main(String[] args) throws Exception {
+ ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("spring/generic-impl-consumer.xml");
+ context.start();
+ //服务对应bean的名字由xml标签的id决定
+ genericService = context.getBean("helloService");
+ //获得结果
+ Object result = genericService.$invoke("sayHello", new String[]{"java.lang.String"}, new Object[]{"world"});
+ }
+```
+## 注意事项
+
+①如果参数为基本类型或者Date,List,Map等,则不需要转换,直接调用。
+
+②如果参数为其他POJO,则使用Map代替。
-public class PersonImpl implements Person {
- private String name;
- private String password;
+如:
+``` java
+public class Student {
+ String name;
+ int age;
public String getName() {
return name;
@@ -122,30 +241,114 @@ public class PersonImpl implements Person {
this.name = name;
}
- public String getPassword() {
- return password;
+ public int getAge() {
+ return age;
}
- public void setPassword(String password) {
- this.password = password;
+ public void setAge(int age) {
+ this.age = age;
}
}
+
+```
+在调用时应该转换为:
+``` java
+Map<String, Object> student = new HashMap<String, Object>();
+student.put("name", "xxx");
+student.put("age", "xxx");
```
+③对于其他序列化格式,需要特殊配置
+### Protobuf对象泛化调用
+一般泛化调用只能用于生成的服务参数为POJO的情况,而 GoogleProtobuf 的对象是基于 Builder 生成的非正常POJO,可以通过 protobuf-json 泛化调用。
+
+GoogleProtobuf 序列化相关的Demo可以参考 [protobuf-demo](https://github.com/apache/dubbo-samples/tree/master/dubbo-samples-protobuf)
+
+#### 通过Spring对Google Protobuf对象泛化调用
-则 POJO 数据:
+在 Spring 中配置声明 generic = "protobuf-json"
+
+```xml
+<dubbo:reference id="barService" interface="com.foo.BarService" generic="protobuf-json" />
+```
+
+在 Java 代码获取 barService 并开始泛化调用:
```java
-Person person = new PersonImpl();
-person.setName("xxx");
-person.setPassword("yyy");
+GenericService barService = (GenericService) applicationContext.getBean("barService");
+Object result = barService.$invoke("sayHello",new String[]{"org.apache.dubbo.protobuf.GooglePbBasic$CDubboGooglePBRequestType"}, new Object[]{"{\"double\":0.0,\"float\":0.0,\"bytesType\":\"Base64String\",\"int32\":0}"});
+```
+
+#### 通过 API 方式对 Google Protobuf 对象泛化调用
+
+```java
+ReferenceConfig<GenericService> reference = new ReferenceConfig<GenericService>();
+// 弱类型接口名
+reference.setInterface(GenericService.class.getName());
+reference.setInterface("com.xxx.XxxService");
+// 声明为Protobuf-json
+reference.setGeneric(Constants.GENERIC_SERIALIZATION_PROTOBUF);
+
+GenericService genericService = reference.get();
+Map<String, Object> person = new HashMap<String, Object>();
+person.put("fixed64", "0");
+person.put("int64", "0");
+// 参考google官方的protobuf 3 的语法,服务的每个方法中只传输一个POJO对象
+// protobuf的泛化调用只允许传递一个类型为String的json对象来代表请求参数
+String requestString = new Gson().toJson(person);
+// 返回对象是GoolgeProtobuf响应对象的json字符串。
+Object result = genericService.$invoke("sayHello", new String[] {
+ "com.xxx.XxxService.GooglePbBasic$CDubboGooglePBRequestType"},
+ new Object[] {requestString});
```
-可用下面 Map 表示:
+#### GoogleProtobuf 对象的处理
+
+GoogleProtobuf 对象是由 Protocol 契约生成,相关知识请参考 [ProtocolBuffers 文档](https://developers.google.com/protocol-buffers/?hl=zh-CN)。假如有如下Protobuf 契约
+
+```proto
+syntax = "proto3";
+package com.xxx.XxxService.GooglePbBasic.basic;
+message CDubboGooglePBRequestType {
+ double double = 1;
+ float float = 2;
+ int32 int32 = 3;
+ bool bool = 13;
+ string string = 14;
+ bytes bytesType = 15;
+}
+
+message CDubboGooglePBResponseType {
+ string msg = 1;
+}
+
+service CDubboGooglePBService {
+ rpc sayHello (CDubboGooglePBRequestType) returns (CDubboGooglePBResponseType);
+}
+```
+
+则对应请求按照如下方法构造
```java
-Map<String, Object> map = new HashMap<String, Object>();
-// 注意:如果参数类型是接口,或者List等丢失泛型,可通过class属性指定类型。
-map.put("class", "com.xxx.PersonImpl");
-map.put("name", "xxx");
-map.put("password", "yyy");
+Map<String, Object> person = new HashMap<>();
+person.put("double", "1.000");
+person.put("float", "1.00");
+person.put("int32","1" );
+person.put("bool","false" );
+//String 的对象需要经过base64编码
+person.put("string","someBaseString");
+person.put("bytesType","150");
```
+
+#### GoogleProtobuf 服务元数据解析
+
+Google Protobuf 对象缺少标准的 JSON 格式,生成的服务元数据信息存在错误。请添加如下依赖元数据解析的依赖。
+
+```xml
+<dependency>
+ <groupId>org.apache.dubbo</groupId>
+ <artifactId>dubbo-metadata-definition-protobuf</artifactId>
+ <version>${dubbo.version}</version>
+</dependency>
+```
+
+从服务元数据中也可以比较容易构建泛化调用对象。