上一章,我们讲到了Dubbo的线程池策略,本章我们一起探讨,Dubbo如何实现泛化调用的。主要内容包括:
1、什么是泛化调用
2、泛化调用的三种方式
3、如何使用
4、源码分析
推荐阅读:
什么是泛化调用
我们基于Dubbo API搭建Dubbo服务时,服务消费端需要依赖于一个SDK二方包,其中存放这服务提供端提供的所有接口。
泛化调用主要用于,服务提供端没有API接口以及入参出参,参数和返回没有对应的POJO时,所有POJO参数均用Map表示,通常用于框架集成。使用泛化调用时,服务消费端不再需要依赖于二方的SDK。
泛化调用的三种方式
在Dubbo中,有三种泛化调用,分别是:generic=true,generic=bean和generic=nativejava,三种调用方式,使用的序列化和反序列化的方式不同。
如何使用
下面,用generic=true的方式,演示一下泛化调用怎么用。
Spring的XML配置:
<dubbo:reference id="userService" interface="com.ctx.userService" generic="true" />
在代码里,可以这样获取userService并泛化调用
// 引用远程服务
// 该实例很重量,里面封装了所有与注册中心及服务提供方连接,请缓存
ReferenceConfig<GenericService> reference = new ReferenceConfig<GenericService>();
// 弱类型接口名
reference.setInterface("com.ctx.XxxService");
reference.setVersion("1.0.0");
// 声明为泛化接口,方式为true
reference.setGeneric(true);
// 用org.apache.dubbo.rpc.service.GenericService可以替代所有接口引用
GenericService genericService = reference.get();
// 基本类型以及Date,List,Map等不需要转换,直接调用
Object result = genericService.$invoke("sayHello", new String[] {"java.lang.String"}, new Object[] {"world"});
// 用Map表示POJO参数,如果返回值为POJO也将自动转成Map
Map<String, Object> person = new HashMap<String, Object>();
person.put("name", "liuli");
person.put("password", "123");
// 如果返回POJO将自动转成Map
Object result = genericService.$invoke("findPerson", new String[]
{"com.ctx.Person"}, new Object[]{person});
注意:如果泛化调用的入参是POJO类型,比如:
public class Person {
private String name;
private String password;
// 省略getset方法
}
Person person = new Person();
person.setName("liuli");
此时,入参是需要将person转化为Map,map中有一个固定的key是class,value是POJO的包名+类名,这是为了在服务提供端进行反射使用。
Map<String, Object> map = new HashMap<String, Object>();
// 注意:如果参数类型是接口,或者List等丢失泛型,可通过class属性指定类型。
map.put("class", "com.ctx.Person");
map.put("name", "liuli");
map.put("password", "123");
可以通过实现GenericService接口处理所有泛化请求:
public class GenericServiceImpl implements GenericService {
@Override
public Object $invoke(String method, String[] parameterTypes, Object[] args) throws GenericException {
if (method.equals("hi")) {
return "hi, " + args[0];
} else if (method.equals("hello")) {
return "hello, " + args[0];
}
return "ni hao liuli";
}
}
源码分析
下面的代码,主要是:服务消费端如何使用GenericImplFilter拦截泛化调用,以及服务提供端如何使用GenericFilter拦截请求后并把泛化参数序列化然后请求给具体服务。
我们先看服务消费端org.apache.dubbo.rpc.filter.GenericImplFilter是如何拦截泛化调用的。
@Activate(group = Constants.CONSUMER, value = Constants.GENERIC_KEY, order = 20000)
public class GenericImplFilter implements Filter {
@Override
public Result invoke(Invoker<?> invoker, Invocation invocation) throws RpcException {
String generic = invoker.getUrl().getParameter(Constants.GENERIC_KEY);
// ... 省略非核心代码
// 判断是否为泛化调用
if (invocation.getMethodName().equals(Constants.$INVOKE)
&& invocation.getArguments() != null
&& invocation.getArguments().length == 3
&& ProtocolUtils.isGeneric(generic)) {
// 获取泛化参数
Object[] args = (Object[]) invocation.getArguments()[2];
// 如果泛化方为是nativejava
if (ProtocolUtils.isJavaGenericSerialization(generic)) {
for (Object arg : args) {
if (!(byte[].class == arg.getClass())) {
error(generic, byte[].class.getName(), arg.getClass().getName());
}
}
} else if (ProtocolUtils.isBeanGenericSerialization(generic)) {
// 如果泛化方式为bean
for (Object arg : args) {
if (!(arg instanceof JavaBeanDescriptor)) {
error(generic, JavaBeanDescriptor.class.getName(), arg.getClass().getName());
}
}
}
// 设置attachment,以便于服务端调用
((RpcInvocation) invocation).setAttachment(
Constants.GENERIC_KEY, invoker.getUrl().getParameter(Constants.GENERIC_KEY));//generic
}
// 发起远程调用
return invoker.invoke(invocation);
}
}
GenericImplFilter实现接口Filter(关于Dubbo中的Filter,不是本章节内容,此处不再赘述),然后执行invoke方法,invoke方法主要做了下面4件事情:
1、参数校验,检查这个调用是否是泛化调用。
2、获取泛化参数。
3、判断泛化调用方式:遍历每个参数,然后依次判断参数的泛化方式是nativejava方式还是bean方式。
4、发起远程调用。
下面,我们一起来看看,服务提供端是如何通过GenericFilter拦截泛化请求的。
org.apache.dubbo.rpc.filter.GenericFilter
@Activate(group = Constants.PROVIDER, order = -20000)
public class GenericFilter implements Filter {
@Override
public Result invoke(Invoker<?> invoker, Invocation inv) throws RpcException {
// 参数校验
if (inv.getMethodName().equals(Constants.$INVOKE)
&& inv.getArguments() != null
&& inv.getArguments().length == 3
&& !GenericService.class.isAssignableFrom(invoker.getInterface())) {
// 获取参数名称、参数类型、参数值
String name = ((String) inv.getArguments()[0]).trim();
String[] types = (String[]) inv.getArguments()[1];
Object[] args = (Object[]) inv.getArguments()[2];
try {
// 使用反射获取调用的方法
Method method = ReflectUtils.findMethodByMethodSignature(invoker.getInterface(), name, types);
Class<?>[] params = method.getParameterTypes();
if (args == null) {
args = new Object[params.length];
}
// 获取泛化引用使用的泛化类型,true or bean or nativejava
String generic = inv.getAttachment(Constants.GENERIC_KEY);
if (StringUtils.isBlank(generic)) {
generic = RpcContext.getContext().getAttachment(Constants.GENERIC_KEY);
}
// 如果generic=true,则使用true方式对入参进行反序列化
if (StringUtils.isEmpty(generic)
|| ProtocolUtils.isDefaultGenericSerialization(generic)) {
args = PojoUtils.realize(args, params, method.getGenericParameterTypes());
// 如果generic=nativajava,则使用nativejava方式对入参进行反序列化
} 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=bean,则使用bean方式对入参进行反序列化
} 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("..." );
}
}
}
// 将本次请求传递到FilterChain的下一个Filter中,并返回结果result
Result result = invoker.invoke(new RpcInvocation(method, args, inv.getAttachments()));
if (result.hasException()
&& !(result.getException() instanceof GenericException)) {
return new RpcResult(new GenericException(result.getException()));
}
// 如果generic=nativejava,则使用JAVA序列化方式对result序列化
if (ProtocolUtils.isJavaGenericSerialization(generic)) {
try {
UnsafeByteArrayOutputStream os = new UnsafeByteArrayOutputStream(512);
ExtensionLoader.getExtensionLoader(Serialization.class)
.getExtension(Constants.GENERIC_SERIALIZATION_NATIVE_JAVA)
.serialize(null, os).writeObject(result.getValue());
return new RpcResult(os.toByteArray());
} catch (IOException e) {
throw new RpcException("Serialize result failed.", e);
}
// 如果generic=bean,则使用bean序列化方式对result序列化
} else if (ProtocolUtils.isBeanGenericSerialization(generic)) {
return new RpcResult(JavaBeanSerializeUtil.serialize(result.getValue(), JavaBeanAccessor.METHOD));
} else {
// 如果generic=true,则使用bean序列化方式对result序列化
return new RpcResult(PojoUtils.generalize(result.getValue()));
}
} catch (NoSuchMethodException e) {
throw new RpcException(e.getMessage(), e);
} catch (ClassNotFoundException e) {
throw new RpcException(e.getMessage(), e);
}
}
// 如果不是泛化调用,直接把请求传给FilterChain的下一个Filter
return invoker.invoke(inv);
}
}
上述代码,就是Dubbo服务提供端如果拦截泛化请求,并进行处理的,大体流程如下:
1、参数校验,判断此次请求是不是泛化请求。
2、获取参数名称、参数类型、参数值。
3、使用反射获取调用的方法,和使用的泛化方式true or bean or nativejava。
4、根据泛化方式,反序列化泛化参数。
5、将本次请求,包括调用的方法、参数和上下文信息传递给FilterChain的下一个Filter中,并返回Result结果。
6、根据泛化方式,序列化Result结果返回给服务消费端。
上述,便是Dubbo实现泛化调用的全过程。
好了,今天的探讨就到这里,如果有不合理的地方请指出,谢谢大家。