在网易考拉,泛化调用的使用场景比较广泛,比如GOApi 接口测试平台,无线网关等都是通过泛化调用直接调用后端的dubbo服务,那么什么是泛化调用呢?
本文来自一名网易考拉工程师的分享~
![68d7e2b858501a03d263c843fb8025e5.png](https://i-blog.csdnimg.cn/blog_migrate/b7f9e01a64b6d6aa12a790d1b718daf9.jpeg)
最近整理的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进行处理。主要以下几个处理步骤:
- 首先根据方法签名,通过Java反射获取Method对象
Method method = ReflectUtils.findMethodByMethodSignature(invoker.getInterface(), name, types);
- 根据不同的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)) { //... 省略}
- 进行方法调用
Result result = invoker.invoke(new RpcInvocation(method, args, inv.getAttachments()));
- 根据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日记本,每日存档超实用的技术干货学习笔记,每天陪你前进一点点~