泛化调用

泛化调用,用于在调用方没有服务方提供的 API(SDK)的情况下,对服务方进行调用

泛化调用(客户端泛化调用)是指在调用方没有服务提供方 API(SDK)的情况下,对服务方进行调用,并且可以正常拿到调用结果。调用方没有接口及模型类元,知道服务的接口的全限定类名和方法名的情况下,可以通过泛化调用调用对应接口。

使用场景

泛化调用可通过一个通用的 GenericService 接口对所有服务发起请求。典型使用场景如下:

  1. 网关服务:如果要搭建一个网关服务,那么服务网关要作为所有 RPC 服务的调用端。但是网关本身不应该依赖于服务提供方的接口 API(这样会导致每有一个新的服务发布,就需要修改网关的代码以及重新部署),所以需要泛化调用的支持。

  2. 测试平台:如果要搭建一个可以测试 RPC 调用的平台,用户输入分组名、接口、方法名等信息,就可以测试对应的 RPC 服务。那么由于同样的原因(即会导致每有一个新的服务发布,就需要修改网关的代码以及重新部署),所以平台本身不应该依赖于服务提供方的接口 API。所以需要泛化调用的支持。

使用方式

本示例的完整源码请参考 dubbo-samples-generic-call

示例中有以下 Dubbo 服务定义和实现

服务接口定义:

public interface HelloService {
    String sayHello(String name);
    CompletableFuture<String> sayHelloAsync(String name);
    CompletableFuture<Person> sayHelloAsyncComplex(String name);
    CompletableFuture<GenericType<Person>> sayHelloAsyncGenericComplex(String name);
}

服务具体实现并发布:

@DubboService
public class HelloServiceImpl implements HelloService {

    @Override
    public String sayHello(String name) {
        return "sayHello: " + name;
    }

    @Override
    public CompletableFuture<String> sayHelloAsync(String name) {
        // ...
    }

    @Override
    public CompletableFuture<Person> sayHelloAsyncComplex(String name) {
         // ...
    }

    @Override
    public CompletableFuture<GenericType<Person>> sayHelloAsyncGenericComplex(String name) {
         // ...
    }
}

API 调用方式

针对以上 Dubbo 服务,我们可以通过泛化调用 API 直接发起调用。

private GenericService genericService;

public static void main(String[] args) throws Exception {
	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);
	referenceConfig.setGeneric("true");
	// do not wait for result, 'false' by default
	referenceConfig.setAsync(true);
	referenceConfig.setTimeout(7000);

	genericService = referenceConfig.get();
}

public static void invokeSayHello() throws InterruptedException {
	Object result = genericService.$invoke("sayHello", new String[]{"java.lang.String"}, new Object[]{"world"});
	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();
}
  1. 在设置 ReferenceConfig 时,使用 setGeneric("true") 来开启泛化调用
  2. 配置完 ReferenceConfig 后,使用 referenceConfig.get() 获取到 GenericService 类的实例
  3. 使用其 $invoke 方法获取结果
  4. 其他设置与正常服务调用配置一致即可

Spring 调用方式

Spring 中服务暴露与服务发现有多种使用方式,如 xml,注解。这里以 xml 为例。

  1. 生产者端无需改动

  2. 消费者端原有的 dubbo:reference 标签加上 generic=true 的属性。

   <dubbo:reference id="helloService" generic = "true" interface="org.apache.dubbo.samples.generic.call.api.HelloService"/>
  1. 获取到 Bean 容器,通过 Bean 容器拿到 GenericService 实例。

  2. 调用 $invoke 方法获取结果


    private static GenericService genericService;

    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"});
    }
最后修改 September 13, 2024: Refactor website structure (#2860) (1a4b998f54)