泛化调用(客户端泛化)
功能描述
泛化调用是指在没有服务端提供的 API(SDK)的情况下调用服务端,并且可以正常获取调用结果。
使用场景
泛化调用主要用于实现通用的远程服务 Mock 框架,通过实现 GenericService 接口来处理所有服务请求。例如以下场景
网关服务:如果你想构建一个网关服务,那么网关服务应该是所有 RPC 服务的调用端。但是,网关本身不应该依赖于服务提供者的接口 API(这会导致每次发布新服务时都需要修改和重新部署网关的代码),因此需要支持泛化调用。
测试平台:如果你想构建一个可以测试 RPC 调用的平台,用户可以通过输入组名、接口、方法名等信息来测试相应的 RPC 服务。然后,出于同样的原因(即每次发布新服务时都需要修改和重新部署网关的代码),平台本身不应该依赖于服务提供者的接口 API。因此需要支持泛化调用。
如何使用
演示可以在 dubbo 项目中的示例代码 中看到
API 部分以该演示为例,说明如何使用它。
服务定义
服务接口
public interface HelloService {
String sayHello(String name);
CompletableFuture<String> sayHelloAsync(String name);
CompletableFuture<Person> sayHelloAsyncComplex(String name);
CompletableFuture<GenericType<Person>> sayHelloAsyncGenericComplex(String name);
}
服务实现类
public class HelloServiceImpl implements HelloService {
@Override
public String sayHello(String name) {
return "sayHello: " + name;
}
@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;
}
@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;
}
@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;
}
}
通过 API 使用泛化调用
服务发起者
设置
ServiceConfig
时,使用setGeneric("true")
来启用泛化调用设置
ServiceConfig
时,使用 setRef 指定实现类时,必须设置一个GenericService
对象,而不是真正的服务实现类对象其他设置与正常 Api 服务启动一致
private static String zookeeperAddress = "zookeeper://" + System.getProperty("zookeeper.address", "127.0.0.1") + ":2181";
public static void main(String[] args) throws Exception {
new Embedded ZooKeeper(2181, false).start();
//Create ApplicationConfig
ApplicationConfig applicationConfig = new ApplicationConfig();
applicationConfig.setName("generic-impl-provider");
//Create registry configuration
RegistryConfig registryConfig = new RegistryConfig();
registryConfig.setAddress(zookeeperAddress);
//Create a new service implementation class, pay attention to use GenericService to receive
GenericService helloService = new GenericImplOfHelloService();
//Create service related configuration
ServiceConfig<GenericService> service = new ServiceConfig<>();
service.setApplication(applicationConfig);
service.setRegistry(registryConfig);
service.setInterface("org.apache.dubbo.samples.generic.call.api.HelloService");
service.setRef(helloService);
// Key point: set to generalization call
// Note: it is no longer recommended to use the setGeneric function whose parameter is a Boolean value
//should use referenceConfig.setGeneric("true") instead
service.setGeneric("true");
service. export();
System.out.println("dubbo service started");
new CountDownLatch(1). await();
}
}
泛化调用者
步骤
设置
ReferenceConfig
时,使用setGeneric("true")
来启用泛化调用配置完
ReferenceConfig
后,使用referenceConfig.get()
获取GenericService
类的实例使用它的
$invoke
方法获取结果其他设置与正常 Api 服务启动一致
//Define generalized call service class
private static GenericService genericService;
public static void main(String[] args) throws Exception {
//Create ApplicationConfig
ApplicationConfig applicationConfig = new ApplicationConfig();
applicationConfig.setName("generic-call-consumer");
//Create registry configuration
RegistryConfig registryConfig = new RegistryConfig();
registryConfig.setAddress("zookeeper://127.0.0.1:2181");
//Create service reference configuration
ReferenceConfig<GenericService> referenceConfig = new ReferenceConfig<>();
//Set the interface
referenceConfig.setInterface("org.apache.dubbo.samples.generic.call.api.HelloService");
applicationConfig.setRegistry(registryConfig);
referenceConfig.setApplication(applicationConfig);
// Key point: set to generalization call
// Note: it is no longer recommended to use the setGeneric function whose parameter is a Boolean value
//should use referenceConfig.setGeneric("true") instead
referenceConfig.setGeneric(true);
//Set asynchronous, not necessary, it depends on the business.
referenceConfig.setAsync(true);
//Set the timeout
referenceConfig.setTimeout(7000);
//Get the service, because it is a generalized call, so it must be of the GenericService type
genericService = referenceConfig. get();
//Using the $invoke method of the GenericService class object can be used instead of the original method
//The first parameter is the name of the method to call
//The second parameter is the parameter type array of the method to be called, which is a String array, and the full class name of the parameter is stored in it.
//The third parameter is the parameter array of the method to be called, which is an Object array, and the required parameters are stored in it.
Object result = genericService. $invoke("sayHello", new String[]{"java. lang. String"}, new Object[]{"world"});
//Use CountDownLatch, if you use synchronous calls, you don't need to do this.
CountDownLatch latch = new CountDownLatch(1);
//Get the result
CompletableFuture<String> future = RpcContext.getContext().getCompletableFuture();
future. whenComplete((value, t) -> {
System.err.println("invokeSayHello(whenComplete): " + value);
latch. countDown();
});
// print the result
System.err.println("invokeSayHello(return): " + result);
latch. await();
}
使用 Spring 进行泛化调用
在 Spring 中使用服务暴露和服务发现有很多方法,例如 xml 和注解。这里以 xml 为例。步骤
生产者端无需更改
在消费者端,将
generic=true
的属性添加到原始的dubbo:reference
标签中。
<dubbo:reference id="helloService" generic = "true" interface="org.apache.dubbo.samples.generic.call.api.HelloService"/>
获取 Bean 容器,并通过 Bean 容器获取
GenericService
实例。调用
$invoke
方法获取结果
private static GenericService genericService;
public static void main(String[] args) throws Exception {
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("spring/generic-impl-consumer.xml");
context. start();
//The name of the bean corresponding to the service is determined by the id of the xml tag
genericService = context. getBean("helloService");
//Get the result
Object result = genericService. $invoke("sayHello", new String[]{"java. lang. String"}, new Object[]{"world"});
}
Protobuf 对象泛化调用
普通的泛化调用只能在生成的 service 参数是 POJO 时使用,而 GoogleProtobuf 对象是基于 Builder 生成的非正常 POJO,可以通过 protobuf-json 进行泛化调用。
GoogleProtobuf 序列化相关的 Demo 可以参考 protobuf-demo
通过 Spring 对 Google Protobuf 对象进行泛化调用
在 Spring 中配置声明 generic = “protobuf-json”
<dubbo:reference id="barService" interface="com.foo.BarService" generic="protobuf-json" />
在 Java 代码中获取 barService 并开始泛化调用
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 对象进行泛化调用
ReferenceConfig<GenericService> reference = new ReferenceConfig<GenericService>();
// Weakly typed interface name
reference.setInterface(GenericService.class.getName());
reference.setInterface("com.xxx.XxxService");
// Declare as 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");
// Referring to Google's official protobuf 3 syntax, only one POJO object is transmitted in each method of the service
// The generalized call of protobuf only allows passing a json object of type String to represent the request parameter
String requestString = new Gson().toJson(person);
// The return object is the json string of the GoolgeProtobuf response object.
Object result = genericService. $invoke("sayHello", new String[] {
"com.xxx.XxxService.GooglePbBasic$CDubboGooglePBRequestType"},
new Object[] {requestString});
GoogleProtobuf 对象的处理
GoogleProtobuf 对象是通过 Protocol 契约生成的。有关相关知识,请参考 ProtocolBuffers 文档。如果存在以下 Protobuf 契约
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);
}
那么相应的请求将根据以下方法构建
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 objects need to be base64 encoded
person. put("string","someBaseString");
person. put("bytesType","150");
GoogleProtobuf 服务元数据分析
Google Protobuf 对象缺乏标准的 JSON 格式,导致生成的 service 元数据信息不正确。请添加以下依赖项,这些依赖项依赖于元数据解析。
<dependency>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo-metadata-definition-protobuf</artifactId>
<version>${dubbo.version}</version>
</dependency>
从服务元数据构建泛化调用对象也比较容易。
注意事项
如果参数是基本类型或 Date、List、Map 等,则无需转换,直接调用即可。
如果参数是另一个 POJO,则使用 Map 代替。
例如
public class Student {
String name;
int age;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this. age = age;
}
}
应该转换为
Map<String, Object> student = new HashMap<String, Object>();
student. put("name", "xxx");
student. put("age", "xxx");
- 对于其他序列化格式,需要特殊配置