[toc]
什么是泛化调用
通常我们想调用别人的dubbo服务时,我们需要在项目中引入对应的jar包。而泛化调用的作用是,我们无需依赖相关jar包,也能调用到该服务。这个特性一般使用在网关类项目中,在业务开发中基本不会使用。
使用方式
假设我现在要调用下面的接口服务
1 2 3 4
| package com.scj.demo.dubbo.provider.service.impl; public interface ByeService{ String bye (String name); }
|
泛化调用实例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| ReferenceConfigCache referenceCache = ReferenceConfigCache.getCache();
ReferenceConfig<GenericService> referenceConfig = new ReferenceConfig<>(); referenceConfig.setApplication(new ApplicationConfig("test")); referenceConfig.setRegistry(new RegistryConfig("zookeeper://127.0.0.1:2181")); referenceConfig.setInterface("com.scj.demo.dubbo.provider.service.impl.ByeService"); referenceConfig.setGeneric(true); referenceConfig.setCheck(false);
GenericService genericService = referenceCache.get(referenceConfig); Object result = genericService.$invoke( "bye", new String[]{"java.lang.String"}, new Object[]{"1234"}); System.out.println(result);
|
需要使用被调用接口的字符串参数生成GenericService,通过GenericService的$invoke间接调用目标接口的接口。
1 2 3 4 5 6 7 8 9 10 11 12 13
| public interface GenericService {
Object $invoke(String method, String[] parameterTypes, Object[] args) throws GenericException; }
|
$invoke的三个参数分别为,方法名,方法参数类型数组,方法参数数组。
方法入参构造
可以看到泛化调用的一个复杂性在于$invoke的第三个参数的组装,下面介绍几种复杂入参的调用方式。首先丰富提供者接口:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| public interface ByeService { String bye(String name); String bye(String name, Long age, Date date); String bye(Person person); String bye(List<String> names); String bye(String[] names); String byePersons(List<Person> persons); String byePersons(Person[] persons); @Data public static class Person{ private String name; private Long age; private Date birth; } public static void main(String[] args) { System.out.println(Person.class.getName()); } }
|
多参数
1 2 3 4 5 6 7 8
| @Test public void testMultiParam(){ result = genericService.$invoke( "bye", new String[]{"java.lang.String","java.lang.Long","java.util.Date"}, new Object[]{"scj",12L,new Date()}); System.out.println(result); }
|
POJO
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| Map<String,Object> personMap = new HashMap<>(); { personMap.put("name","scj"); personMap.put("age","12"); personMap.put("birth",new Date()); } @Test public void testPOJO(){ result = genericService.$invoke( "bye", new String[]{"com.scj.demo.dubbo.provider.service.impl.ByeService$Person"}, new Object[]{personMap}); System.out.println(result); }
|
Map
1 2 3 4 5 6 7 8
| @Test public void testMap(){ result = genericService.$invoke( "bye", new String[]{"java.util.Map"}, new Object[]{personMap}); System.out.println(result); }
|
集合
1 2 3 4 5 6 7 8 9
| @Test public void testArray(){ String[] nameArray = new String[]{"scj1","scj3"}; result = genericService.$invoke( "bye", new String[]{"java.lang.String[]"}, new Object[]{nameArray}); System.out.println(result); }
|
数组
1 2 3 4 5 6 7 8 9
| @Test public void testArray(){ String[] nameArray = new String[]{"scj1","scj3"}; result = genericService.$invoke( "bye", new String[]{"java.lang.String[]"}, new Object[]{nameArray}); System.out.println(result); }
|
集合+POJO
1 2 3 4 5 6 7 8 9
| @Test public void testPOJOList(){ result = genericService.$invoke( "byePersons", new String[]{"java.util.List"}, new Object[]{Lists.newArrayList(personMap,personMap)});
System.out.println(result); }
|
数组+POJO
1 2 3 4 5 6 7 8 9
| @Test public void testPOJOArray(){ result = genericService.$invoke( "byePersons", new String[]{"com.scj.demo.dubbo.provider.service.impl.ByeService$Person[]"}, new Object[]{Lists.newArrayList(personMap,personMap)});
System.out.println(result); }
|
1 2 3 4 5
| Map<String,Object>map new HashMap<String,Object>();
map.put("class","com.test.PersonImpl"); map.put("name","jiaduo"); map.put("password","password");
|
结果返回
与入参相似,虽然$invoke的返回定义为Object,实际上针对不同类型有不同的返回。别想着转换为POJO,你都泛化调用了,搞不到接口,如何转换。当然自己定义一个完全一样的当然也行。
| 接口返回类型 |
$invoke返回类型 |
| 基础类型 |
基础类型 |
| POJO |
HashMap |
| Collection |
List返回ArrayList,Set返回HashSet |
| Array |
Array |
| 组合类型 |
根据上述映射组合返回 |
原理介绍
dubbo通过filter实现泛化:

GenericlmplFilter和GenericFilter分别在消费者端和提供者端进行序列化的检查和处理。
消费者端
泛化调用和直接调用在消费者者端,在使用上的区别是,我们调用服务时使用的接口为GenericService,方法为$invoker。在底层的区别是,消费者端发出的rpc报文发生了变化。
在使用上,不管哪种配置方式,我们都需要配置generic=true。设置generic=true后,RefereceConfig的interfaceClass会被强制设置为GenericService
1 2 3 4 5 6 7 8 9 10 11 12
| if (ProtocolUtils.isGeneric(getGeneric())) { interfaceClass = GenericService.class; } else { try { interfaceClass = Class.forName(interfaceName, true, Thread.currentThread() .getContextClassLoader()); } catch (ClassNotFoundException e) { throw new IllegalStateException(e.getMessage(), e); } checkInterfaceAndMethods(interfaceClass, methods); }
|
这也使得我们的RefereanceBean返回的是GenericService类型的代理。
1
| invoker = refprotocol.refer(interfaceClass, urls.get(``0``));
|
生成的代理是GenericService的代理只是我们使用方式上的变化,更为核心的是,底层发送的rpc报文发生了什么变化。
底层报文变化
Dubbo的rpc报文分为header和body两部分。我们这边只需要关注body部分。构造逻辑如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| @Override protected void encodeRequestData(Channel channel, ObjectOutput out, Object data, String version) throws IOException { RpcInvocation inv = (RpcInvocation) data; out.writeUTF(version); out.writeUTF(inv.getAttachment(Constants.PATH_KEY)); out.writeUTF(inv.getAttachment(Constants.VERSION_KEY)); out.writeUTF(inv.getMethodName()); out.writeUTF(ReflectUtils.getDesc(inv.getParameterTypes())); Object[] args = inv.getArguments(); if (args != null) { for (int i = 0; i < args.length; i++) { out.writeObject(encodeInvocationArgument(channel, inv, i)); } } out.writeObject(RpcUtils.getNecessaryAttachments(inv)); }
|
那么我们通过直接调用与泛化调用ByeService的bye方法在报文上有啥区别呢?
我一开始以为报文中的path是GenericeService,其实并没有,path就是我们调用的目标方法。
而报文中的方法名,方法参数类型以及具体参数,还是按照GenericeService的$invoke方法入参传递的。
这么个二合一的报文,发送到提供者那边,它估计也会很懵逼,我应该怎么执行?
所以针对泛化调用报文还会把generic=true放在attchment中传递过去

具体逻辑在GenericImplFilter中。
GenericImplFilter中有很多其他逻辑,比如泛化调用使用的序列化协议,正常接口走泛化调用的模式,我们只需要设置attachment的那部分。针对泛化调用,要进行2次序列化/反序列化。看下POJO的调用方式你就知道为啥了
1 2
| ((RpcInvocation) invocation).setAttachment( Constants.GENERIC_KEY, invoker.getUrl().getParameter(Constants.GENERIC_KEY));
|
知道消费者端报文发生了什么变化,那么接下来就去看提供者端如何处理这个改造后的报文。
提供者端
消费者泛化调用的rpc报文传递到提供者还不能直接使用,虽然path是对的,但是实际的方法名,参数类型,参数要从rpc报文的参数中提取出来。GenericFilter就是用来做这件事情。在提供者这边,针对泛化调用的逻辑全部封装到了GenericFilter,解耦的非常好。
GenericFilter逻辑分析
1 2 3 4 5 6
| if (inv.getMethodName().equals(Constants.$INVOKE) || inv.getArguments() != null || inv.getArguments().length == 3 || !GenericService.class.isAssignableFrom(invoker.getInterface())){ }
|
注意第4个条件,一开始很疑惑,后来发现rpc报文中的path是目标接口的,这边invoker.getInterface()返回的肯定就是实际接口了
1 2 3 4
| String name = ((String) inv.getArguments()[0]).trim(); String[] types = (String[]) inv.getArguments()[1]; Object[] args = (Object[]) inv.getArguments()[2];
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50
| Method method = ReflectUtils.findMethodByMethodSignature(invoker.getInterface(), name, types); Class<?>[] params = method.getParameterTypes(); if (args == null) { args = new Object[params.length]; } String generic = inv.getAttachment(Constants.GENERIC_KEY); if (StringUtils.isBlank(generic)) { generic = RpcContext.getContext().getAttachment(Constants.GENERIC_KEY); }
if (StringUtils.isEmpty(generic) || ProtocolUtils.isDefaultGenericSerialization(generic)) { args = PojoUtils.realize(args, params, method.getGenericParameterTypes()); } else if (ProtocolUtils.isJavaGenericSerialization(generic)) { for (int i = 0; i < args.length; i++) { if (byte[].class == args[i].getClass()) { try { UnsafeByteArrayInputStream is = new UnsafeByteArrayInputStream((byte[]) args[i]); args[i] = ExtensionLoader.getExtensionLoader(Serialization.class) .getExtension(Constants.GENERIC_SERIALIZATION_NATIVE_JAVA) .deserialize(null, is).readObject(); } catch (Exception e) { throw new RpcException("Deserialize argument [" + (i + 1) + "] failed.", e); } } else { throw new RpcException( "Generic serialization [" + Constants.GENERIC_SERIALIZATION_NATIVE_JAVA + "] only support message type " + byte[].class + " and your message type is " + args[i].getClass()); } } } else if (ProtocolUtils.isBeanGenericSerialization(generic)) { for (int i = 0; i < args.length; i++) { if (args[i] instanceof JavaBeanDescriptor) { args[i] = JavaBeanSerializeUtil.deserialize((JavaBeanDescriptor) args[i]); } else { throw new RpcException( "Generic serialization [" + Constants.GENERIC_SERIALIZATION_BEAN + "] only support message type " + JavaBeanDescriptor.class.getName() + " and your message type is " + args[i].getClass().getName()); } } }
|
这边有个疑问,为什么这边还要再次反序列化一次,netty不是有decoder么??
嗯,你别忘了,针对一个POJO你传过来是一个Map,从Map转换为POJO需要这边进一步处理。
1
| Result result = invoker.invoke(new RpcInvocation(method, args, inv.getAttachments()));
|
这边的invoker就是实际服务提供者的invoker,因为我们的path是正确的,invoker获取在DubboProtocl的requestHandler回调中
1 2 3 4
| if (result.hasException() && !(result.getException() instanceof GenericException)) { return new RpcResult(new GenericException(result.getException())); }
|
这边需要注意一下!!针对接口的泛化调用,抛出的异常都会经过GenericException包装一下。
转载自:
https://www.jb51.net/program/297913ee2.htm
扩展阅读:
我试图通过这篇文章告诉你,什么是神奇的泛化调用
dubbo泛化调用配置 dubbo泛化调用原理