写在前面
在上一篇文章里,我们开始了新的篇章 —— Dubbo 的案例。

主要介绍了 Dubbo 服务需要怎么去注册到 soul-admin,简单的对比了和 http 服务的注册过程以及整个业务的调用过程。

从大体上讲,逻辑是一致的。

本篇文章主要关注 soul 网关在调用 Dubbo 服务时,用「泛化调用」是如何实现的。

不过在此之前,需要先了解 Dubbo 的「泛化调用」是什么东西。

1. Dubbo 的泛化调用

关于 Dubbo 的泛化调用,官方文档是这样解释的:

泛化接口调用方式主要用于客户端没有 API 接口及模型类元的情况,参数及返回值中的所有 POJO 均用 Map 表示,通常用于框架集成,比如:实现一个通用的服务测试框架,可通过 GenericService 调用所有服务实现。 详情见 http://dubbo.apache.org/zh/docs/v2.7/user/examples/generic-reference/

那么到底什么是 Dubbo 的泛化调用呢?

我们都知道 Dubbo 有 provider 和 consumer,注册中心不在我们这次的讨论范围内。

Dubbo provider 对外提供服务,Dubbo consumer 消费服务,但是它们之间的调用的方式不再是本地调用了,而是 RPC(远程过程调用)。

一般在 Dubbo 项目里面,provider 和 consumer 都会依赖一个相同的 api 模块,前者依赖它做服务的实现和具体的业务逻辑处理,后者用来指定需要消费的服务是什么、参数的定义等等。

这样的设计是没法集成到框架里面去的,因为假如需要添加一个 Dubbo 服务,就需要对 provider 和 consumer 引入对应的 api。

拿 soul 网关来举例,用这样的方式去实现就意味着 soul 网关需要引入和 provider 所需要的所有 api,因为网关就相当于是个 consumer 了,这显然是不合理的。

那么 Dubbo 的泛化调用是怎么一回事呢?

其实很简单,就是实现一个通用的 api,然后其他所有的 api 都可以用这个来代替。这样一来,网关层面(consumer)就可以只依赖这个通用的 api 就可以去调用所有 Dubbo 服务。

举个例子,Dubbo 的 api 一般都是一个类,然后这个类里面定义了几个方法,每个方法定义了参数类型,返回值类型,那么可以抽象成一个 Map 对象,key 分别是类的全名、方法名称、参数类型、返回值类型等等,然后就把这个 Map 对象传递到 provider,由 provider 去解析这个 Map 执行里面的方法。

2. soul 网关是如何使用 Dubbo 的泛化调用

大致了解了 Dubbo 的泛化调用的,我们接下来看看 soul 网关里是怎么去实现 Dubbo 的泛化调用的。

回顾之前的文章,我们知道 soul 网关会使用一个个的插件去处理不同种类的请求,那么针对 Dubbo 也只需要去关注 DubboPlugin 相关的类。

  1. public class ApacheDubboPlugin extends AbstractSoulPlugin {
  2. @Override
  3. protected Mono<Void> doExecute(final ServerWebExchange exchange, final SoulPluginChain chain, final SelectorData selector, final RuleData rule) {
  4. // 拿到 Dubbo 请求的参数和元数据信息
  5. String body = exchange.getAttribute(Constants.DUBBO_PARAMS);
  6. MetaData metaData = exchange.getAttribute(Constants.META_DATA);
  7. // 调用另一个方法拿到返回值
  8. final Mono<Object> result = dubboProxyService.genericInvoker(body, metaData, exchange);
  9. return result.then(chain.execute(exchange));
  10. }
  11. }

可以看到主要的代码就上面的几行,首先是从请求里面拿到 Dubbo 服务需要的数据,也就是上面提到的那些类名啊、参数类型、返回值类型等等,然后把这些参数交给一个代理方法去执行,拿到返回值之后继续处理下去。

  1. public class ApacheDubboProxyService {
  2. public Mono<Object> genericInvoker(final String body, final MetaData metaData, final ServerWebExchange exchange) throws SoulException {
  3. ReferenceConfig<GenericService> reference = ApplicationConfigCache.getInstance().get(metaData.getPath());
  4. GenericService genericService = reference.get();
  5. Pair<String[], Object[]> pair;
  6. // 省略 pair 赋值
  7. CompletableFuture<Object> future = genericService.$invokeAsync(metaData.getMethodName(), pair.getLeft(), pair.getRight());
  8. return Mono.fromFuture(future.thenApply(ret -> {
  9. // 省略判断...
  10. exchange.getAttributes().put(Constants.DUBBO_RPC_RESULT, ret);
  11. exchange.getAttributes().put(Constants.CLIENT_RESPONSE_RESULT_TYPE, ResultEnum.SUCCESS.getName());
  12. return ret;
  13. })).onErrorMap(exception -> exception instanceof GenericException ? new SoulException(((GenericException) exception).getExceptionMessage()) : new SoulException(exception));
  14. }
  15. }

简单解读下,这里使用了 Dubbo 自己的泛化调用方式,也就是上一节提到的内容。

从缓存里加载通用的 api —— GenericService,然后设置参数,最后通过 GenericService 的实例去完成 Dubbo 服务的调用。

可以看到末尾的代码跟之前 http 接口的写法很相似,最后会在 DubboResponsePlugin 里面去处理返回值。

  1. public class DubboResponsePlugin implements SoulPlugin {
  2. @Override
  3. public Mono<Void> execute(final ServerWebExchange exchange, final SoulPluginChain chain) {
  4. return chain.execute(exchange).then(Mono.defer(() -> {
  5. final Object result = exchange.getAttribute(Constants.DUBBO_RPC_RESULT);
  6. // 省略判断
  7. Object success = SoulResultWrap.success(SoulResultEnum.SUCCESS.getCode(), SoulResultEnum.SUCCESS.getMsg(), JsonUtils.removeClass(result));
  8. // WebFlux 返回数据
  9. return WebFluxResultUtils.result(exchange, success);
  10. }));
  11. }
  12. }

这里的代码也很简单,拿到返回值,判断结果,最后交给 WebFlux 返回就行了。

总结

这次简单的介绍了 Dubbo 的泛化调用原理,以及为什么需要用到 Dubbo 的泛化调用,然后结合 soul 网关简单的看了下它是怎么实现 Dubbo 的泛化调用的。

从分析结果来看,soul 是直接使用了 Dubbo 自己的泛化调用方式,下一篇文章里,我们尝试用 Dubbo 泛化调用自己的 Dubbo 服务,深入了解下 Dubbo 泛化调用的过程。