泛化调用是指在调用方没有服务方提供的 API(SDK)的情况下,对服务方进行调用,并且可以正常拿到调用结果。
泛化调用主要用于实现一个通用的远程服务 Mock 框架,可通过实现 GenericService 接口处理所有服务请求。比如如下场景:
网关服务:如果要搭建一个网关服务,那么服务网关要作为所有 RPC 服务的调用端。但是网关本身不应该依赖于服务提供方的接口 API(这样会导致每有一个新的服务发布,就需要修改网关的代码以及重新部署),所以需要泛化调用的支持。
测试平台:如果要搭建一个可以测试 RPC 调用的平台,用户输入分组名、接口、方法名等信息,就可以测试对应的 RPC 服务。那么由于同样的原因(即会导致每有一个新的服务发布,就需要修改网关的代码以及重新部署),所以平台本身不应该依赖于服务提供方的接口 API。所以需要泛化调用的支持。
demo 可见 dubbo 项目中的示例代码
API 部分以此 demo 为例讲解使用方式。
- public interface HelloService {
-
- String sayHello(String name);
-
- CompletableFuture
sayHelloAsync(String name); -
- CompletableFuture
sayHelloAsyncComplex(String name); -
- CompletableFuture
> sayHelloAsyncGenericComplex(String name); - }
- public class HelloServiceImpl implements HelloService {
-
- @Override
- public String sayHello(String name) {
- return "sayHello: " + name;
- }
-
- @Override
- public CompletableFuture
sayHelloAsync(String name) { - CompletableFuture
future = new CompletableFuture<>(); - new Thread(() -> {
- try {
- Thread.sleep(5000);
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- future.complete("sayHelloAsync: " + name);
- }).start();
-
- return future;
- }
-
- @Override
- public CompletableFuture
sayHelloAsyncComplex(String name) { - Person person = new Person(1, "sayHelloAsyncComplex: " + name);
- CompletableFuture
future = new CompletableFuture<>(); - new Thread(() -> {
- try {
- Thread.sleep(5000);
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- future.complete(person);
- }).start();
-
- return future;
- }
-
- @Override
- public CompletableFuture
> sayHelloAsyncGenericComplex(String name) { - Person person = new Person(1, "sayHelloAsyncGenericComplex: " + name);
- GenericType
genericType = new GenericType<>(person); - CompletableFuture
> future = new CompletableFuture<>(); - new Thread(() -> {
- try {
- Thread.sleep(5000);
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- future.complete(genericType);
- }).start();
-
- return future;
- }
- }
在设置 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 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
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 服务启动一致即可
- 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
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
future = RpcContext.getContext().getCompletableFuture(); - future.whenComplete((value, t) -> {
- System.err.println("invokeSayHello(whenComplete): " + value);
- latch.countDown();
- });
- //打印结果
- System.err.println("invokeSayHello(return): " + result);
- latch.await();
- }
在Dubbo中,泛化调用可以实现对任何类型的服务方法的调用,包括复杂的参数类型。具体实现方式如下:
1. 首先需要使用泛化调用的客户端对象(GenericService)来调用服务方法,并传入方法名和参数列表。参数列表可以使用Object数组来表示,如下所示
- GenericService genericService = referenceConfig.get();
- Object[] params = new Object[]{arg1, arg2, ...};
- Object result = genericService.$invoke(methodName, parameterTypes, params);
2. 对于复杂参数类型,需要先生成对应的JavaBean类,并将参数封装到该类中。然后将该JavaBean对象作为参数列表中的一个元素传入泛化调用方法中,如下所示:
- @Data
- public class ComplexRequest implements Serializable {
-
- private Map
content; -
- private String key;
-
- private String value;
-
- private List
users; - }
-
-
- @Data
- public class User implements Serializable {
-
- private String name;
-
- private String age;
- }
3. 消费者调用代码完整如下
- @Test
- public void testDubbo() {
- ApplicationConfig applicationConfig = new ApplicationConfig();
- applicationConfig.setName("generic-call-consumer");
- //创建注册中心配置
- RegistryConfig registryConfig = new RegistryConfig();
- registryConfig.setAddress("zookeeper://127.0.0.1:2181");
- //创建服务引用配置
- ReferenceConfig
referenceConfig = new ReferenceConfig<>(); - //设置接口, 提供者的接口名
- referenceConfig.setInterface("com.test.api.provideFacade");
- applicationConfig.setRegistry(registryConfig);
- referenceConfig.setApplication(applicationConfig);
- //重点:设置为泛化调用
- //注:不再推荐使用参数为布尔值的setGeneric函数
- //应该使用referenceConfig.setGeneric("true")代替
- referenceConfig.setGeneric(true);
- //设置异步,不必须,根据业务而定。
- referenceConfig.setAsync(false);
- //设置超时时间
- referenceConfig.setTimeout(7000);
- //获取服务,由于是泛化调用,所以获取的一定是GenericService类型
- GenericService genericService = referenceConfig.get();
- //使用GenericService类对象的$invoke方法可以代替原方法使用
- //第一个参数是需要调用的方法名
- //第二个参数是需要调用的方法的参数类型数组,为String数组,里面存入参数的全类名。
- //第三个参数是需要调用的方法的参数数组,为Object数组,里面存入需要的参数。
- ComplexRequest entity = new ComplexRequest();
- Map
map = new HashMap<>(); - map.put("key", "key");
- map.put("value", "value");
- entity.setContent(map);
- entity.setKey("key");
- entity.setValue("value");
-
- List
users = new ArrayList<>(); - User user = new User();
- user.setName("name");
- user.setAge("18");
- users.add(user);
- entity.setUsers(user);
-
- // 构造泛化调用参数
- //消费者调用方的实体类
- String[] paramTypes = new String[]{"com.test.api.request.ComplexRequest"};
- Object[] params = new Object[]{entity};
-
- Object result = genericService.$invoke("method", paramTypes, params);
- System.out.println(result);
- }
4. 提供者代码如下
- public interface ProvideFacade {
-
- PlainResult
method(ComplexRequest req); - }
-
-
- public class testFacadeImpl implements testFacade {
-
- @Override
- public PlainResult
method(ComplexRequest req) {} -
- }
5. 执行单元测试
运行消费者单元测试代码,请求提供者的dubbo泛化接口,进入到提供者机器,发现请求体已经传递过来,当提供者有多台机器时,请求会负载均衡,轮询到不同的机器上。