两年前,笔者读Dubbo源码的时候就看到了泛化调用(GenericService)的逻辑,当时不知道为啥会有这个逻辑,想不明白,于是就跳过了。
两年后再次接触Dubbo才看懂了泛化调用的逻辑,是因为公司内部业务项目用了这个功能。而笔者负责这块功能的易用性改造,也涉及到泛化调用的功能改造。
从同事那了解到,泛化调用主要用于跨业务的调用,例如其它业务项目调支付接口,或者支付业务调其它业务接口。
为什么要用泛化调用,以及泛化调用是啥?
借助泛型理解,例如go语言目前还不支持泛型,实现一个List集合类型,需要为int、string等多种类型都实现一个类(只是举例,实际上虽然go还不支持泛型,但也不需要这样做)。而java支持泛型,一个List类通用。
Dubbo的泛化调用功能就类似于Java语言提供的泛型功能,目的都是通用。
那为什么需要泛化调用功能呢?
在我司主要用于跨业务调用场景。对于跨业务调用场景,两个业务之间的接口交互,不应该依赖对方的代码,也就是不应该要求对方提供一个jar包。
使用Dubbo需要创建一个接口项目,所有业务接口都在这个项目中声明,消费者和提供者都需要依赖这个项目打包后的jar。但跨业务的情况下,我们是否需要暴露所有接口给其它业务呢?显然是不需要的。
在不依赖对方提供jar包情况下,就无法直接通过接口调用,这时候就可以用泛化调用功能实现RPC调用。
因为没引入jar包,没有接口类,也没有请求/响应的DTO类,因此泛化调用需要消费者在调用之前,指定调用的目标接口类名、方法名、方法参数类型,并将pojo参数转为map,提供者响应结果如果是pojo,也需要转为map。
当然,泛化调用还有其它用途。例如,通过泛化调用实现接口测试工具。
这是官方的使用介绍文档:https://dubbo.apache.org/zh/docsv2.7/user/examples/generic-reference/
原生泛化接口:
泛化调用功能改造
为什么要改造?
因为我们修改了Dubbo的使用方式,新的使用方式与原生有些区别,约定返回值必须是一个Result,使用code和message代替原生的抛异常。并且优化了异步调用,封装同步与异步调用的差异逻辑,使其对使用者无感知。
为能继续提供泛化调用功能支持,我们弃用了原生泛化调用接口,自己去实现泛化调用。但依然不需要修改Dubbo源码,得益于Dubbo的扩展性,只需要通过扩展点去实现。
改造后的GenericService接口声明如下。
class GenericInvokeParams {
private String serviceName;
private String methodName;
private String[] paramTypes;
private Object[] request;
}
public interface GenericService {
Result<Object> invoke(InvokeParams params);
}
我们还给Invoker增加了serviceName参数,这样一个GenericService代理实例就能调用任何接口。
改造后的泛化调用使用demo如下:
以使用http协议为例,简单概括下实现原理:
GenericService提供者:
普通调用,以接口类名作为uri暴露接口,泛化调用需要加上“/generic”后缀。因为需要区分处理,泛化调用的处理逻辑与普通调用的处理逻辑不同。
服务提供者在接收到泛化调用请求时,需要将泛化调用转化为普通调用,再走完普通调用的逻辑,然后将返回结果转为泛化调用的返回结果。
GenericService消费者:
在发起请求时,将serviceName参数作为普通调用的uri,并拼接“/generic”后缀作为泛化调用的uri。
根据serviceName获取服务提供者,然后发起远程调用。
实现上涉及到的技术点:动态代理、ProxyFactory扩展点、Protocol扩展点。