泛化呼叫(客戶端泛化)
功能描述
泛化呼叫是指在沒有伺服器提供的 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 為例。 步驟
生產端無需修改
消費端在原有
dubbo:reference
標籤上加上generic=true
的屬性即可
<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 物件泛化呼叫
一般的泛化呼叫只能在生成的服務參數為 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 格式化,導致服務元數據信息有誤,請添加如下依賴 metadata 解析的依賴。
<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");
- 對於其他序列化格式,需要特殊配置