Dubbo泛化调用以及多入参、泛型入参等处理

Dubbo泛化调用

网上对处理dubbo泛化调用入参的介绍并不多,有些也不是很准确,所以整理一下。

官方文档:https://dubbo.apache.org/zh/docs/v2.7/user/examples/generic-reference/#m-zhdocsv27userexamplesgeneric-reference

官方文档上有个小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>>这种类似结构)处理,可以修改上面示例代码递归处理。

参考:

  • 3
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值