Dubbo泛化调用
网上对处理dubbo泛化调用入参的介绍并不多,有些也不是很准确,所以整理一下。
官方文档上有个小demo,但是仍有一些不清楚。本文结合dubbo源码记录一下dubbo的泛化调用,以及对入参的处理。
泛化调用介绍
Dubbo的泛化调用是dubbo提供的API,可以在调用侧无interface和请求出入参依赖的场景下,对服务端的dubbo接口进行调用。所以泛化调用通常会被我们用于测试联调。
当然想要仅仅想要测试dubbo接口的方式也比较多,例如通过反射将所有dubbo接口统一暴露成http接口,不过这种方式会破坏掉dubbo中大量filter,利用SPI的自定义filter也会失效。除此之外,dubbo还提供了Telnet的方式去调用服务端的接口,这种方式也适用测试。
泛化调用入参
dubbo泛化调用主要API
com.alibaba.dubbo.config.RegistryConfig; //注册中心配置 com.alibaba.dubbo.config.ApplicationConfig; //应用配置 com.alibaba.dubbo.config.ReferenceConfig; //引用的服务配置(类似reference注解或xml配置) com.alibaba.dubbo.rpc.service.GenericService;//实际调用的服务(泛化服务)
其中com.alibaba.dubbo.rpc.service.GenericService#$invoke
是泛化调用方法:
方法入参分别为方法名称、参数类型数组、入参对象数组;
dubbo对泛化调用入参处理
dubbo的泛化调用基于Filter实现(一定注意所有Filter都可以通过SPI机制重写)
基本调用过程如下:
调用 <-> GenericImplFilter <-> 网络 <-> GenericFilter <-> 服务实现
其中在GenericImplFilter中会在Consumer端,对泛化调用相关参数重新构建和处理。
ReferenceConfig配置中,设置generic(序列化方式,代表使用默认序列化方式)参数值为true。
测试生成GenericService 代码如下:
public GenericService getGenericService(Config config){
RegistryConfig registry = new RegistryConfig();
registry.setAddress(config.getZooKeeper());
ApplicationConfig application = new ApplicationConfig();
application.setRegistry(registry);
application.setName("application");
ReferenceConfig<GenericService> reference = new ReferenceConfig<>();
reference.setApplication(application);
reference.setInterface(config.getInterfaze());
reference.setTimeout(100000);
reference.setProtocol("dubbo");
reference.setGeneric(true);
reference.setValidation("false");
if(StringUtils.isNotEmpty(config.getVersion())){
reference.setVersion(config.getVersion());
}
if(StringUtils.isNotEmpty(config.getGroup())){
reference.setGroup(config.getGroup());
}
GenericService genericService = reference.get();
return genericService;
}
GenericImplFilter中会进入第一个if判断中:
然后会调用服务端的对应的接口和方法,会被GenericFilter拦截。
org.apache.dubbo.common.utils.PojoUtils
看一下这个工具类中realize方法的实现,
注意传入的参数,第一个是入参值数组,第二个是入参类型数组,第三个参数为反射获取的method对象,获取该method的入参类型Type实例,可以通过getTypeName()
方法获取对应的包含泛型类入参的类全限定名。
例如有一个方法的的入参是List<String>,那么其入参类型全限定名展示为java.util.List<java.lang.String>
realize0
方法代码过长,不贴出来了,方法中有根据第一个参数的类型判断,然后进行参数值的数据解析:
if (type != null && type.isEnum() && pojo.getClass() == String.class) {// 如果是String类型入参do something}
//....
if (pojo.getClass().isArray()) {//如果是数据类型入参 do something}
//....
if (pojo instanceof Collection<?>) {//如果是集合类型 do something}
//....
if (pojo instanceof Map<?, ?> && type != null) {//如果是Map类型 do something}
在泛化调用中,我们传入的入参类型是Object[],理论上我们可以传入任意类型的值。但是使用泛化调用不论是测试还是其他使用目的,都是在当前项目中不获取provider侧的interface作为依赖前提下进行。所以我们将入参值转换为某一固定类型即可,比如说Map类型就非常方便。如果是Map类型的话,那么就可以直接使用alibaba的JSONObject:
也就简单得出一结论,com.alibaba.dubbo.rpc.service.GenericService#$invoke
第3个参数可以丢进去一个JSONObject的数组作为入参。
多参数与泛型参数示例
简单的举一个例子,dubbo provider侧某接口有如下三个方法:
package service;
public interface TestGenericService {
//方法一 单参数
String singleParam(User user);
//方法二 多参数
String multiParam(String id, User user);
//方法三 带有泛型参数
String genericTypeParam(PageReq<User> userReq);
}
User类
package service;
import java.io.Serializable;
public class User implements Serializable {
private static final long serialVersionUID = 4741417427705290705L;
private String name;
private int gender;
//getter & setter & constructor
}
PageReq类
package service;
import java.io.Serializable;
public class PageReq<T> implements Serializable {
private static final long serialVersionUID = -6720015744986291779L;
private int pageSize;
private int pageNum;
private T queryParam;
//getter & setter & constructor
}
第一个方法处理
如果是第一个方法,那么泛化调用的入参可以处理为:
//构造config - 设置zk地址、interface全限定名、版本等
Config config = new Config();
//方法名
String methodName = "singleParam";
//参数类型
String[] parameterTypes = new String[1]; //1个参数类型
parameterTypes[0] = "service.User"; //参数类的全限定名
//参数对象
Object[] objectParams = new Object[1]; //1个参数值
JSONObject paramUser = new JSONObject();
paramUser.put("name", "hericjazz"); //看作是对象转换为JSONObject的过程
paramUser.put("gender", 1);
objectParams[0] = paramUser;
//执行泛化调用
this.getGenericService(config).$invoke(methodName, parameterTypes, objectParams);
第二个方法处理
//构造config - 设置zk地址、interface全限定名、版本等
Config config = new Config();
//方法名
String methodName = "multiParam";
//参数类型
String[] parameterTypes = new String[2]; //2个参数类型
parameterTypes[0] = "java.lang.String"; //第一个参数类全限定名
parameterTypes[1] = "service.User"; //第二个参数类全限定名
//参数对象
Object[] objectParams = new Object[2]; //1个参数值
String paramString = "i am another param";
objectParams[0] = paramString;
JSONObject paramUser = new JSONObject();
paramUser.put("name", "nicole"); //看作是对象转换为JSONObject的过程
paramUser.put("gender", 0);
objectParams[1] = paramUser;
//执行泛化调用
this.getGenericService(config).$invoke(methodName, parameterTypes, objectParams);
第三个方法处理
//构造config - 设置zk地址、interface全限定名、版本等
Config config = new Config();
//方法名
String methodName = "genericTypeParam";
//参数类型
String[] parameterTypes = new String[1]; //1个参数类型
parameterTypes[0] = "service.PageReq"; //参数类的全限定名
//参数对象
Object[] objectParams = new Object[1]; //1个参数值
JSONObject paramPage = new JSONObject(); //看作是对象转换为JSONObject的过程
paramPage.put("pageNum", 10);
paramPage.put("pageSize", 1);
JSONObject paramUser = new JSONObject();
paramUser.put("name", "nicolimine");
paramUser.put("gender", 1);
paramUser.put("class", "service.User");//注意这里,PojoUtils解析Map类型,如果存在key=class时,会直接指定该Object类型
paramPage.put("queryParam", paramUser);
objectParams[0] = paramPage;
//执行泛化调用
this.getGenericService(config).$invoke(methodName, parameterTypes, objectParams);
第三个方法中,增加一个key=class的入参,可以解决PojoUtils解析泛型参数中没有具体类型的问题。PojoUtils中解析Map的代码:
简单封装一下泛化调用方法
Parameters类
package service;
import javafx.beans.property.StringProperty;
import java.io.Serializable;
public class Parameters implements Serializable {
private static final long serialVersionUID = -7207836539579721342L;
/** 如上述示例中的泛型类 paramType格式为:
service.PageReq<service.User> */
private StringProperty paramType; //参数类型全限定类名
/** 如上述示例中的泛型类 paramValue格式为:
{"pageSize":10,"pageNum":1,"queryParam":{"name":"NNN","gender":0}} */
private StringProperty paramValue; //参数JSON字符串值
//getter & setter & constructor
}
泛化调用方法
public Object getResponse(Config config, List<Parameters> requestParams){
GenericService genericService = this.getGenericService(config);
String[] parameterTypes = new String[requestParams.size()];
Object[] objectParams = new Object[requestParams.size()];
for(int i = 0; i < requestParams.size(); i++){
String originParamType = requestParams.get(i).getParamType();
parameterTypes[i] = this.getParameterType(originParamType);
String paramValue = requestParams.get(i).getParamValue();
if(paramValue.indexOf("{") > -1){
JSONObject originParamValue = JSON.parseObject(paramValue);
if(StringUtils.isNotEmpty(getGenericType(originParamType))) {
String genericTypeKey = getGenericTypeKey(originParamValue);
JSONObject genericTypeValue = originParamValue.getJSONObject(genericTypeKey);
genericTypeValue.put("class", getGenericType(originParamType));
}
objectParams[i] = originParamValue;
} else if(paramValue.indexOf("[") > -1) {
objectParams[i] = JSON.parseArray(paramValue);
} else {
objectParams[i] = paramValue;
}
}
return genericService.$invoke(config.getMethodName(), parameterTypes, objectParams);
}
/**
* 可能存在的泛型类
*/
private String getGenericType(String paramType) {
if(paramType.indexOf("<") > -1) {
return paramType.substring(paramType.indexOf("<") + 1, paramType.indexOf(">"));
}
return null;
}
注意:上面的调用代码示例,不会对多层的泛型类进行处理,如需要对多层泛型类(例如PageReq<User<GirlType>>这种类似结构)处理,可以修改上面示例代码递归处理。
参考: