定义泛化。举个例子_网易考拉应用的dubbo泛化调用,是如何实现的?
发布日期:2021-06-24 10:13:31 浏览次数:3 分类:技术文章

本文共 4636 字,大约阅读时间需要 15 分钟。

在网易考拉,泛化调用的使用场景比较广泛,比如GOApi 接口测试平台,无线网关等都是通过泛化调用直接调用后端的dubbo服务,那么什么是泛化调用呢?

本文来自一名网易考拉工程师的分享~

0d16aab4e39688980cf45baff43031f0.png

最近整理的Java架构学习视频和大厂项目底层知识点,需要的同学欢迎私信我【资料】发给你~一起学习进步!

简介

在dubbo 官方文档中,泛化调用的定义如下

泛化接口调用方式主要用于客户端没有 API 接口及模型类元的情况,参数及返回值中的所有 POJO 均用 Map 表示,通常用于框架集成,比如:实现一个通用的服务测试框架,可通过 GenericService 调用所有服务实现。

消费端没有API 接口模型类元,那就意味着在消费端不需要像传统的dubbo消费者那样,依赖服务提供方给予Api 包,从而可以自适应的应对服务变更。 比如在网关的实现中,只需要在前端配置dubbo服务方法的参数类型,返回值等信息,就可以进行dubbo路由调用,签名变更也只需要在页面修改,而不需要变更接口依赖或者代码。

dubbo是如何实现泛化调用的?

没有Api接口,dubbo提供了统一的泛化接口来替代具体的服务接口

public interface GenericService {    /**     * Generic invocation     *     * @param method         Method name, e.g. findPerson. If there are overridden methods, parameter info is     *                       required, e.g. findPerson(java.lang.String)     * @param parameterTypes Parameter types     * @param args           Arguments     * @return invocation return value     * @throws GenericException potential exception thrown from the invocation     */    Object $invoke(String method, String[] parameterTypes, Object[] args) throws GenericException;}

在泛化调用中,每一个dubbo service 的引用对应一个GenericService

只有一个 $invoke 方法,是对方法调用的抽象,参数列表分别为

  • 方法名
  • 参数类型列表
  • 对应的参数列表

**没有模型类元, **意味着在消费端,如果有POJO 对象,也需要使用统一的模型抽象去表示,比如Map。

举个例子,对于Java Bean

public class Person {    private String name;    private int password;    public String getName() {        return name;    }    public void setName(String name) {        this.name = name;    }    public String getPassword() {        return password;    }    public void setPassword(String password) {        this.password = password;    }}

可以用Map表示

Map person = new HashMap<>();     person.put("name", "xxxx");     person.put("age", 18);

在服务提供端,可以根据 GenericService 对应的具体的service 名,以及 $invoke 方法中的method ,参数类型确定一个唯一的方法, 并将该Map转换为POJO对象,执行方法调用。这些都是在提供端的GenericFilter 中完成的。

一个例子

对于服务

public interface GreetingsService {    String sayHello(Person person);}

服务提供者和正常的使用方式相同,这里不再赘述。消费这使用Api 方式泛化调用的例子

// 引用远程服务 // 该实例很重量,里面封装了所有与注册中心及服务提供方连接,请缓存ReferenceConfig referenceConfig = new ReferenceConfig<>();referenceConfig.setApplication(new ApplicationConfig("demo-app"));RegistryConfig registry = new RegistryConfig();registry.setAddress(factory.dubboBaseConfig.getRegistryAddress());registry.setClient("curator");referenceConfig.setRegistry("zookeeper://127.0.0.1:2181");referenceConfig.setInterface("GreetingsService");referenceConfig.setGroup(group);referenceConfig.setVersion(version);// 声明为泛化接口 referenceConfig.setGeneric("true");referenceConfig.setCluster("failfast");referenceConfig.setCheck(false);// 用com.alibaba.dubbo.rpc.service.GenericService可以替代所有接口引用  GenericService genericService = reference.get();// 用Map表示POJO参数Map person = new HashMap<>();person.put("name", "xxxx");person.put("age", 18);Object result = genericService.$invoke("sayHello", new String[]{"com.test.Person"}, new Object[] {person});

通过 referenceConfig.setGeneric("true") 将接口声明为泛化接口,generic 配置不同的类型,主要是针对不通对象的转换方式, 比如 true 表示使用默认的PojoUtils 进行 Map和POJO的转换。

源码分析

在提供端,比起常规的dubbo调用,泛化调用会多经过一个 GenericFilter

if (inv.getMethodName().equals(Constants.$INVOKE)                 && inv.getArguments() != null                && inv.getArguments().length == 3                && ! invoker.getUrl().getParameter(Constants.GENERIC_KEY, false)) {    //... 省略}

即判断是泛化方法才会经过该Filter进行处理。主要以下几个处理步骤:

  1. 首先根据方法签名,通过Java反射获取Method对象
Method method = ReflectUtils.findMethodByMethodSignature(invoker.getInterface(), name, types);
  1. 根据不同的generic配置,对参数列表进行反序列化,比如之前介绍的将Map转换为POJO
if (StringUtils.isEmpty(generic)       || ProtocolUtils.isJsonGenericSerialization(generic)       || ProtocolUtils.isDefaultGenericSerialization(generic)       || ProtocolUtils.isGenericReturnRawResult(generic)) {      args = PojoUtils.realize(args, params, method.getGenericParameterTypes());} else if (ProtocolUtils.isJavaGenericSerialization(generic)) {       //... 省略}
  1. 进行方法调用
Result result = invoker.invoke(new RpcInvocation(method, args, inv.getAttachments()));
  1. 根据generic 的配置,对返回结果进行转换处理

在dubbok中,generic有以下几种配置

  • true: 默认值,使用PojoUtils进行入参反序列化,和返回值序列化
  • json: 使用PojoUtils进行入参反序列化,和Json序列化方式对返回值序列化
  • nativejava: 使用NativeJavaSerialization进行处理入参数,即byte[] => 方法参数,以及返回值序列化,结果 => byte[]
  • raw.return: 使用PojoUtils进行入参反序列化,性能考虑,返回值不做处理直接返回

其他

调用监控

不管是哨兵还是dubbo自身的监控,由于泛化调用的服务是 GenericService , 方法 $invoke ,如果不做特殊处理,比如从入参中获取真实的方法名,则监控上获取不到真正调用的服务方法。

PojoUtils入参转换

目前大部分场景下是使用PojoUtils 进行参数转换,但是dubbo对于PojoUtils 的实现对于不同类型的兼容还有待补全,比如曾经遇到的Pojo 构造方法中有原生类型比如boolean.class, 实例化会异常,对象中是boolean类型的属性,map 中传了int 值等,导致反序列化失败等。

来源:网易工程师-陶杨

有任何问题欢迎留言交流~


整理总结不易,如果觉得这篇文章有意思的话,欢迎转发、收藏,给我一些鼓励~

有想看的内容或者建议,敬请留言!

最近利用空余时间整理了一些精选Java架构学习视频和大厂项目底层知识点,需要的同学欢迎私信我发给你~一起学习进步!有任何问题也欢迎交流~

Java日记本,每日存档超实用的技术干货学习笔记,每天陪你前进一点点~

转载地址:https://blog.csdn.net/weixin_32048757/article/details/113329831 如侵犯您的版权,请留言回复原文章的地址,我们会给您删除此文章,给您带来不便请您谅解!

上一篇:mysql里可以用cube吗_sql server的cube操作符使用详解_mysql
下一篇:python中两个时间相减结果转为小时_Python起步(二)基础数据类型1

发表评论

最新留言

路过按个爪印,很不错,赞一个!
[***.219.124.196]2024年04月13日 06时15分22秒