目录
引言
在日常工作中,我们在编写完一个dubbo接口后,经常会遇到dubbo接口的调试问题,之前我们一般的处理方法是再写一个http接口去透明化调用dubbo接口,这样就可以在网页或者postman等接口调试工具去调试这个dubbo接口。但是这种方法耗时耗力,这时,Dubbo提供的泛化调用(Generic Invocation)机制就能派上用场。本文将详细解析Dubbo泛化调用的使用及原理。
什么是泛化调用
Dubbo 泛化调用是一种在分布式服务框架 Dubbo 中实现的服务调用机制,它允许服务消费者在没有服务提供者接口定义的情况下进行服务调用。这种机制特别适用于以下场景:
-
透传式调用,发起方只是想调用提供者拿到结果,没有过多的业务逻辑诉求,即使有,也是拿到结果后再继续做分发处理。
-
代理服务,所有的请求都会经过代理服务器,而代理服务器不会感知任何业务逻辑,只是一个通道,接收数据 -> 发起调用 -> 返回结果,调用流程非常简单纯粹。
-
前端网关,有些内网环境的运营页面,对 URL 的格式没有那么严格的讲究,页面的功能都是和后端服务一对一的操作,非常简单直接。
通俗地讲,泛化可以理解为采用一种统一的方式来发起对任何服务方法的调用,至少我们知道是一种接口调用的方式,只是这种方式有一个比较独特的名字而已。
Dubbo泛化调用(客户端泛化)
泛化调用 :要在服务消费端没有API接口类及模型类元(比如入参和出参的POJO 类)的情况下使用。在进行服务调用时相关参数通过 Map 形式将数据传递,由服务提供者将 Map 转换为 实体类,再进行调用。
简单来说 ,泛化调用即服务消费者端启用了泛化调用,而服务提供者端则是正常服务。
-
通过 Spring XML 配置进行泛化调用
在 Spring 配置申明 generic="true"
,如:
<dubbo:reference id="userService" interface="com.alibaba.dubbo.samples.generic.api.IUserService" generic="true"/>
需要使用的地方,通过强制类型转化为 GenericService 进行调用:
GenericService userService = (GenericService) context.getBean("userService");
// primary param and return value
String name = (String) userService.$invoke("delete", new String[]{int.class.getName()}, new Object[]{1});
System.out.println(name);
其中:
-
GenericService 这个接口只有一个方法,名为
$invoke
,它接受三个参数,分别为方法名、方法参数类型数组和参数值数组; -
对于方法参数类型数组:
-
如果是基本类型,如 int 或 long,可以使用
int.class.getName()
获取其类型; -
如果是基本类型数组,如 int[],则可以使用
int[].class.getName()
; -
如果是 POJO,则直接使用全类名,如
com.alibaba.dubbo.samples.generic.api.Params
。
-
-
通过 API 编程进行泛化调用
ApplicationConfig application = new ApplicationConfig();
application.setName("api-generic-consumer");
//注册中心
RegistryConfig registry = new RegistryConfig();
registry.setAddress("zookeeper://127.0.0.1:2181");
application.setRegistry(registry);
ReferenceConfig<GenericService> reference = new ReferenceConfig<GenericService>();
// 弱类型接口名
reference.setInterface("com.alibaba.dubbo.samples.generic.api.IUserService");
// 声明为泛化接口
reference.setGeneric(true);
reference.setApplication(application);
// 用com.alibaba.dubbo.rpc.service.GenericService可以替代所有接口引用
GenericService genericService = reference.get();
// 泛化调用核心方法
String name = (String) genericService.$invoke("delete", new String[]{int.class.getName()}, new Object[]{1});
System.out.println(name);
通过 API 的方式,不需要像 XML 的方式需要提前将服务配置好,可以动态构建 ReferenceConfig;相对 XML 来说,API 的方式更常见。
-
参数或返回值是 POJO 的场景
比如方法签名是 User get(Params params);
其中 User 有 id 和 name 两个属性,Params 有 query 一个属性。
以下是消费端的调用代码:
String[] parameterTypes = new String[]{"com.alibaba.dubbo.samples.generic.api.Params"};
Map<String, Object params = new HashMap<String, Object();
param.put("class", "com.alibaba.dubbo.samples.generic.api.Params");
param.put("query", "a=b");
Object user = userService.$invoke("get", parameterTypes, new Object[]{param});
System.out.println("sample one result: " + user);
上述代码的输出结果为:
sample one result: {name=charles, id=1, class=com.alibaba.dubbo.samples.generic.api.User}
这里,Dubbo 框架会自动将 POJO 的返回值转换成 Map。可以看到,返回值 user
是一个 HashMap,里面分别存放了 name、id、class 三个 k/v。
-
总结一下泛化调用的三部曲
-
接口类名、接口方法名、接口方法参数类名、业务请求参数,四个维度的数据不能少。
-
根据接口类名创建 ReferenceConfig 对象,设置 generic = true 属性,调用 referenceConfig.get 拿到 genericService 泛化对象。
-
传入接口方法名、接口方法参数类名、业务请求参数,调用 genericService.$invoke 方法拿到响应对象,并判断响应成功或失败,然后完成数据最终返回。
Dubbo泛化实现(服务端泛化)
泛化接口实现主要用于服务提供端没有API接口类及模型类元(比如入参和出参的POJO 类)的情况下使用。消费者发起接口请求时需要将相关信息转换为 Map 传递给 提供者,由提供者根据信息找到对应的泛型实现来进行处理。
简单来说 ,泛化实现即服务提供者端启用了泛化实现,而服务消费者端则是正常调用。
-
服务端实现 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];
}
return "welcome";
}
}
-
服务端暴露服务
ApplicationConfig application = new ApplicationConfig();
application.setName("api-generic-provider");
// 注册中心
RegistryConfig registry = new RegistryConfig();
registry.setAddress("zookeeper://127.0.0.1:2181");
application.setRegistry(registry);
// 用org.apache.dubbo.rpc.service.GenericService可以替代所有接口实现
GenericService genericService = new GenericServiceImpl();
// 暴露服务
ServiceConfig<GenericService> service2 = new ServiceConfig<GenericService>();
service2.setApplication(application);
// 弱类型接口名
service2.setInterface("com.alibaba.dubbo.samples.generic.api.HiService");
// 指向一个通用服务实现
service2.setRef(genericService);
service2.export();
以上代码是使用API编程,也可以使用 XML 配置的方式暴露服务:
<dubbo:service interface="com.alibaba.dubbo.samples.generic.api.HiService" ref="genericService" />
源码实现
Dubbo 泛化调用和泛化实现依赖于下面两个过滤器来完成。如下图:
-
GenericImplFilter:完成了消费者端的泛化功能。
-
GenericFilter:完成了提供者端的泛化功能。
-
GenericImplFilter
当消费者进行调用的是泛化实现时,会将参数信息按照指定的序列化方式进行序列化后进行泛化调用。(这里会将调用方法指定为 $invoke,因为 GenericFilter 中判断是否是泛化调用的条件之一就是 方法名为 $invoke)
当消费者进行泛化调用时,会将参数信息进行序列化后进行泛化调用。
/**
* GenericImplInvokerFilter
*/
@Activate(group = Constants.CONSUMER, value = Constants.GENERIC_KEY, order = 20000)
public class GenericImplFilter implements Filter {
private static final Logger logger = LoggerFactory.getLogger(GenericImplFilter.class);
// 泛化调用的参数类型
private static final Class<?>[] GENERIC_PARAMETER_TYPES = new Class<?>[]{String.class, String[].class, Object[].class};
@Override
public Result invoke(Invoker<?> invoker, Invocation invocation) throws RpcException {
String generic = invoker.getUrl().getParameter(Constants.GENERIC_KEY);
// 1. 判断服务端是否是泛化实现
// generic 满足三种泛化情况之一 && 调用方法名不为 $invoke && 参数类型为 RpcInvocation
if (ProtocolUtils.isGeneric(generic)
&& !Constants.$INVOKE.equals(invocation.getMethodName())
&& invocation instanceof RpcInvocation) {
// 1.1 获取泛化调用的参数 :调用方法名、调用参数类型、调用参数值等
RpcInvocation invocation2 = (RpcInvocation) invocation;
String methodName = invocation2.getMethodName();
Class<?>[] parameterTypes = invocation2.getParameterTypes();
Object[] arguments = invocation2.getArguments();
String[] types = new String[parameterTypes.length];
for (int i = 0; i < parameterTypes.length; i++) {
types[i] = ReflectUtils.getName(parameterTypes[i]);
}
Object[] args;
// 1.2 判断序列化方式,进行序列化
// 如果是 Bean 序列化方式,则使用JavaBeanSerializeUtil 进行序列化
if (ProtocolUtils.isBeanGenericSerialization(generic)) {
args = new Object[arguments.length];
for (int i = 0; i < arguments.length; i++) {
args[i] = JavaBeanSerializeUtil.serialize(arguments[i], JavaBeanAccessor.METHOD);
}
} else {
// 否则(generic = true || nativejava) 使用PojoUtils 进行序列化
args = PojoUtils.generalize(arguments);
}
// 设置调用方法为 $invoke、参数类型为GENERIC_PARAMETER_TYPES,并设置参数具体值。
// 目的是为了让 GenericFilter 能识别出这次调用是泛化调用。
invocation2.setMethodName(Constants.$INVOKE);
invocation2.setParameterTypes(GENERIC_PARAMETER_TYPES);
invocation2.setArguments(new Object[]{methodName, types, args});
// 1.3 进行泛化调用
Result result = invoker.invoke(invocation2);
// 1.4 如果泛化调用没有异常, 则将结果集反序列化后返回。
if (!result.hasException()) {
Object value = result.getValue();
try {
Method method = invoker.getInterface().getMethod(methodName, parameterTypes);
// 对结果进行反序列化
if (ProtocolUtils.isBeanGenericSerialization(generic)) {
if (value == null) {
return new RpcResult(value);
} else if (value instanceof JavaBeanDescriptor) {
return new RpcResult(JavaBeanSerializeUtil.deserialize((JavaBeanDescriptor) value));
} else {
throw new RpcException(
"The type of result value is " +
value.getClass().getName() +
" other than " +
JavaBeanDescriptor.class.getName() +
", and the result is " +
value);
}
} else {
return new RpcResult(PojoUtils.realize(value, method.getReturnType(), method.getGenericReturnType()));
}
} catch (NoSuchMethodException e) {
throw new RpcException(e.getMessage(), e);
}
} else if (result.getException() instanceof GenericException) {
// 返回异常是 GenericException 类型,则说明是泛化异常而非调用过程中异常。进行处理
GenericException exception = (GenericException) result.getException();
try {
String className = exception.getExceptionClass();
Class<?> clazz = ReflectUtils.forName(className);
Throwable targetException = null;
Throwable lastException = null;
try {
targetException = (Throwable) clazz.newInstance();
} catch (Throwable e) {
lastException = e;
for (Constructor<?> constructor : clazz.getConstructors()) {
try {
targetException = (Throwable) constructor.newInstance(new Object[constructor.getParameterTypes().length]);
break;
} catch (Throwable e1) {
lastException = e1;
}
}
}
if (targetException != null) {
try {
Field field = Throwable.class.getDeclaredField("detailMessage");
if (!field.isAccessible()) {
field.setAccessible(true);
}
field.set(targetException, exception.getExceptionMessage());
} catch (Throwable e) {
logger.warn(e.getMessage(), e);
}
result = new RpcResult(targetException);
} else if (lastException != null) {
throw lastException;
}
} catch (Throwable e) {
throw new RpcException("Can not deserialize exception " + exception.getExceptionClass() + ", message: " + exception.getExceptionMessage(), e);
}
}
return result;
}
// 2. 判断消费者是否开启了泛化调用
// 调用方法名为 $invoke && invocation参数有三个 && generic 参数满足三种泛化方式之一
if (invocation.getMethodName().equals(Constants.$INVOKE)
&& invocation.getArguments() != null
&& invocation.getArguments().length == 3
&& ProtocolUtils.isGeneric(generic)) {
// 2.1 序列化参数
Object[] args = (Object[]) invocation.getArguments()[2];
if (ProtocolUtils.isJavaGenericSerialization(generic)) {
for (Object arg : args) {
if (!(byte[].class == arg.getClass())) {
// 抛出 RpcException 异常
error(generic, byte[].class.getName(), arg.getClass().getName());
}
}
} else if (ProtocolUtils.isBeanGenericSerialization(generic)) {
for (Object arg : args) {
if (!(arg instanceof JavaBeanDescriptor)) {
// 抛出 RpcException 异常
error(generic, JavaBeanDescriptor.class.getName(), arg.getClass().getName());
}
}
}
// 设置参数
((RpcInvocation) invocation).setAttachment(
Constants.GENERIC_KEY, invoker.getUrl().getParameter(Constants.GENERIC_KEY));
}
// 进行序列化调用并返回
return invoker.invoke(invocation);
}
}
代码点1:该分支是泛化实现,如果是泛化实现,则根据generic的值进行序列化,然后调用$invoke方法,因为服务端实现为泛化实现,所有的服务提供者实现GenericeServer#$invoker方法,其实现方式就是将Bean转换成Map。
代码点2:泛化引用,调用方是直接通过GenericService#$invoke方法进行调用,以此来区分是泛化调用还是泛化引用,那不经要问,为什么invoker.getUrl().getParameter(Constants.GENERIC_KEY)中获取的generic参数到底是< dubbo:service/>中配置的还是< dubbo:reference/>中配置的呢?其实不难理解:
dubbo:servcie未配置而dubbo:reference配置了,则代表的是消费端的,必然是泛化调用。
dubbo:servcie配置而dubbo:reference未配置了,则代表的是服务端的,必然是泛化实现。
如果两者都配置了,generic以消费端为主。消费端参数与服务端参数的合并在服务发现时,注册中心首先会将服务提供者的URL通知消费端,然后消费端会使用当前的配置与服务提供者URL中的配置进行合并,如遇到相同参数,则消费端覆盖服务端。
-
GenericFilter
GenericFilter 作用于提供者。在 GenericImplFilter 中我们知道,一旦Dubbo确定了是泛化调用或提供者时泛化实现时就会将参数序列化,所以 GenericFilter 判断如果是泛化操作第一步则是按照序列化方式进行反序列化,并进行服务调用。
@Activate(group = Constants.PROVIDER, order = -20000)
public class GenericFilter implements Filter {
@Override
public Result invoke(Invoker<?> invoker, Invocation inv) throws RpcException {
// 1. 消费者进行泛化调用
// 调用方法为 $invoke && 参数有三个 && 调用接口不是 GenericService
if (inv.getMethodName().equals(Constants.$INVOKE)
&& inv.getArguments() != null
&& inv.getArguments().length == 3
&& !GenericService.class.isAssignableFrom(invoker.getInterface())) {
// 1.1 参数解析
// 调用方法名
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];
}
// 获取泛化调用的泛化类型
String generic = inv.getAttachment(Constants.GENERIC_KEY);
// 泛化类型为空,则从上下文获取
if (StringUtils.isBlank(generic)) {
generic = RpcContext.getContext().getAttachment(Constants.GENERIC_KEY);
}
// generic 为空 || 默认情况,则使用 generic=true 的方式
if (StringUtils.isEmpty(generic)
|| ProtocolUtils.isDefaultGenericSerialization(generic)) {
args = PojoUtils.realize(args, params, method.getGenericParameterTypes());
} else if (ProtocolUtils.isJavaGenericSerialization(generic)) {
// generic = nativjava 的方式
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)) {
// generic = bean的方式
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());
}
}
}
// 进行服务调用。这里会先传递给下一个 filter,最后进行服务调用
Result result = invoker.invoke(new RpcInvocation(method, args, inv.getAttachments()));
// 对结果集进行反序列化并返回
if (result.hasException()
&& !(result.getException() instanceof GenericException)) {
return new RpcResult(new GenericException(result.getException()));
}
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);
}
} else if (ProtocolUtils.isBeanGenericSerialization(generic)) {
return new RpcResult(JavaBeanSerializeUtil.serialize(result.getValue(), JavaBeanAccessor.METHOD));
} else {
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);
}
}
// 2. 常规调用
return invoker.invoke(inv);
}
}
代码点1:如果方法名为$invoker,并且只有3个参数,并且服务端实现为非返回实现,则认为本次服务调用时客户端泛化引用服务端,客户端的泛化调用,需要将请求参数反序列化为该接口真实的pojo对象。
代码点2:根据接口名(API类)、方法名、方法参数类型列表,根据反射机制获取对应的方法。
代码点3:处理普通的泛化引用调用,即处理<dubbo:referecnce generic=“true” …/>,只需要将参数列表Object[]反序列化为pojo即可,具体的反序列化为PojoUtils#realize。
代码点4:处理< dubbo:reference generic=“nativejava” /> 启用泛化引用,并使用nativejava序列化参数,在服务端这边通过nativejava反序列化参数成pojo对象。
代码点5:处理< dubbo:reference generic=“bean” /> 启用泛化引用,并使用javabean序列化参数,在服务端这边通过javabean反序列化参数成pojo对象。
代码点6:序列化API方法中声明的类型,构建new RpcInvocation(method, args, inv.getAttachments())调用环境,继续调用后续过滤器。
代码点7:处理执行结果,如果是nativejava或bean,则需要对返回结果序列化,如果是generic=true,则使用PojoUtils.generalize序列化,也即将pojo序列化为Map。