Dubbo泛化调用,暨http版dubbo测试框架
引言
Dubbo通常的调用方式是需要Consumer引入Provider的api包。
期望做一个通用Dubbo测试平台,即前端输入相关信息(zk、service,method、params)即可实现dubbo调用,无需引入对应的api包。
此处使用Dubbo泛化方案。
参考资料:
https://www.jianshu.com/p/3a22a53c7068
https://qsli.github.io/2018/05/02/dubbo-generic-invoke/
具体代码
@Test
public void test_baseType(){
ApplicationConfig applicationConfig = new ApplicationConfig();
applicationConfig.setName("dubbo-consumer-test-baseType");
String zk="zktestserver1.xx.info:22181,zktestserver2.xx.info:22181,zktestserver3.xx.info:22181";
String service = "com.xx.entry.service.OrgNewService";
String method = "queryOrgDetailById";
RegistryConfig registry = new RegistryConfig();
registry.setProtocol("zookeeper");
registry.setAddress(zk);
registry.setGroup("dubbo_test");
ReferenceConfig<GenericService> reference = new ReferenceConfig<GenericService>();
reference.setProtocol("dubbo");
reference.setApplication(applicationConfig);
reference.setRegistry(registry);
reference.setInterface(service);
// 此参数表明为泛化接口
reference.setGeneric(true);
ReferenceConfigCache cache = ReferenceConfigCache.getCache();
GenericService genericService = cache.get(reference);
Object res = genericService.$invoke(method,
new String[] { "java.lang.String" },
new Object[] { "1050001" });
System.out.println(res);
System.out.println(JSON.toJSONString(res));
}
上述例子为基本数据类型,但也可能会出现方法入参数负责Java对象,需要将入参类型字符串,及入参的Map格式传递。如下:
Map<String, String> data = new HashMap<>();
// 如果参数类型是接口,或者如List等丢失泛型,可以通过class属性指定类型
data.put("class", "com.xx.entry.param.RouteEntryParam");
data.put("tid", "00000");
data.put("vorgId", "1030101");
data.put("entryId", "03220103");
data.put("nickName", "JL-10086");
Object res2 = genericService.$invoke(method,
new String[] {"com.xx.entry.param.RouteEntryParam"},
new Object[] { data });
System.out.println(res2);
FAQ
1. 如果POJO对象没有无参构造函数(如使用 lombok 的 @Builder 构建器),则调用时会报错:java.lang.RuntimeException: Illegal constructor:
?
问题排查过程:
Step1: com.alibaba.dubbo.common.utils.PojoUtils
中搜Illegal constructor
:
private static Object newInstance(Class<?> cls) {
try {
return cls.newInstance();
} catch (Throwable t) {
try {
Constructor<?>[] constructors = cls.getConstructors();
if (constructors != null && constructors.length == 0) {
throw new RuntimeException("Illegal constructor: " + cls.getName());
}
......
其中catch异常部分的Constructor<?>[] constructors = cls.getConstructors()
读取的是public 构造函数。
Step2:进一步查看cls.newInstance()
源码:
@CallerSensitive
public T newInstance()
throws InstantiationException, IllegalAccessException
{
if (System.getSecurityManager() != null) {
checkMemberAccess(Member.PUBLIC, Reflection.getCallerClass(), false);
}
// NOTE: the following code may not be strictly correct under
// the current Java memory model.
// Constructor lookup
if (cachedConstructor == null) {
if (this == Class.class) {
throw new IllegalAccessException(
"Can not call newInstance() on the Class for java.lang.Class"
);
}
try {
Class<?>[] empty = {};
final Constructor<T> c = getConstructor0(empty, Member.DECLARED);
......
由此可以看出:因为empty
为空,所以Class.newInstance()
只能调用无参的构造函数。
猜测:如AOP 也只支持 public ,不然存在安全风险一样,处于安全考虑!
何解?TODO!!
方案一:修改服务端的Java Bean声明,确保有无参构造器。如修改为 @Data
, 如果要保留链式调用,可以加@Accessors(chain = true)
- 单独使用
@Data
会产生一个无参构造器- 单独使用
@Builder
会产生一个全参构造器- 混合使用,会产生一个全参构造器
详见:https://blog.csdn.net/w605283073/article/details/89221853
2. 如何获取参数对象的各变量类型元数据?最好是对应一个Map/JSON 格式
何解?TODO!!
暂时想到的一个方案是:定义一套元数据存储系统,首次成功调用时存储,后续根据key自动查找并前端展示。
或者升级到2.7版本以上(包含元数据中心)。