版本:dubbo-2.6.5.jar(公司做了包装但是大同小异)
问题
老服务迁移至新服务。然后想用Telnet校验接口逻辑,调用老服务的telnet命令正常,但是新服务提示方法不存在。。。命令如下
--旧服务
invoke com.....provider.DispatchModeShopProvider.queryCache({
"orderId":"515323613902018048","platformId":"305","shopId":"33637","productType":"1",
"cityId":"1"})
--新服务
invoke com.....config.dispatch.mode.provider.DispatchMode4OrderProvider.queryCache({
"orderId":"515323613902018048","platformId":"305","shopId":"33637","productType":"1",
"cityId":"1"})
问题排查
- 首先执行ls命令核对命令中的类全限定名与方法名。均正确
- 核对参数类型与代码中的对象一毛一样
很懵圈,没办法,看源码吧。看哪块源码呢?那么大的项目,设计模式中的责任链模式就真得很便于我们这类小白了,没错直接查找TelnetHandler的实现类即可,可以看到其中一个为InvokeTelnetHandler,是他是他就是他-_-
InvokeTelnetHandler
遍历export导出的服务查找匹配的方法
Invoker<?> invoker = null;
Method invokeMethod = null;
for (Exporter<?> exporter : DubboProtocol.getDubboProtocol().getExporters()) {
if (service == null || service.length() == 0) {
invokeMethod = findMethod(exporter, method, list);
if (invokeMethod != null) {
invoker = exporter.getInvoker();
break;
}
} else {
if (service.equals(exporter.getInvoker().getInterface().getSimpleName())
|| service.equals(exporter.getInvoker().getInterface().getName())
|| service.equals(exporter.getInvoker().getUrl().getPath())) {
invokeMethod = findMethod(exporter, method, list);
invoker = exporter.getInvoker();
break;
}
}
}
findMethod
method为方法名称,list方法的入参,通过FASTJSon反序列化
list = JSON.parseArray("[" + args + "]", Object.class);
-- findMethod
private static Method findMethod(Exporter<?> exporter, String method, List<Object> args) {
Invoker<?> invoker = exporter.getInvoker();
Method[] methods = invoker.getInterface().getMethods();
for (Method m : methods) {
if (m.getName().equals(method) && isMatch(m.getParameterTypes(), args)) {
return m;
}
}
return null;
}
isMatch
- 参数类型长度与入参长度不一致,返回不匹配
- 如果参数类型是基础类型,invoke调用时传入的参数是null则抛出空指针,否则继续
- 如果参数类型是基础类型,invoke调用时传入的参数不是基础类型,返回不匹配
- 如果invoke调用时传入的参数是map类型(即对象);如果map中key=class的value存在,则判断invoke调用时传入的参数与指定的class是否同类型,返回是否匹配;如果不存在指定的class类型则直接判断是否与invoke传入时的arg的class是否同类型,返回是否匹配。
- 如果invoke调用时传入的参数是集合类型,而参数类型不是数组(不是动态参数类型)并且不是同类型,返回不匹配
- 如果参数类型与invoke调用时传入的参数类型不同,返回不匹配
private static boolean isMatch(Class<?>[] types, List<Object> args) {
if (types.length != args.size()) {
return false;
}
for (int i = 0; i < types.length; i++) {
Class<?> type = types[i];
Object arg = args.get(i);
if (arg == null) {
// if the type is primitive, the method to invoke will cause NullPointerException definitely
// so we can offer a specified error message to the invoker in advance and avoid unnecessary invoking
if (type.isPrimitive()) {
throw new NullPointerException(String.format(
"The type of No.%d parameter is primitive(%s), but the value passed is null.", i + 1, type.getName()));
}
// if the type is not primitive, we choose to believe what the invoker want is a null value
continue;
}
if (ReflectUtils.isPrimitive(arg.getClass())) {
if (!ReflectUtils.isPrimitive(type)) {
return false;
}
} else if (arg instanceof Map) {
String name = (String) ((Map<?, ?>) arg).get("class");
Class<?> cls = arg.getClass();
if (name != null && name.length() > 0) {
cls = ReflectUtils.forName(name);
}
if (!type.isAssignableFrom(cls)) {
return false;
}
} else if (arg instanceof Collection) {
if (!type.isArray() && !type.isAssignableFrom(arg.getClass())) {
return false;
}
} else {
if (!type.isAssignableFrom(arg.getClass())) {
return false;
}
}
}
return true;
}
类型问题
JSON反序列化的类型是什么?下面的代码输出结果为:class com.alibaba.fastjson.JSONObject,JSONObject实现了Map,也就是会走到Map分支处的代码,如果map中没有key=class的value指定类型,那么就会直接使用参数的getClass类型进行判断。我们的参数类型不是Map类型,所以匹配失败报错no such method
String jsonStr = "[{\n"
+ " \"orderId\":\"515323613902018048\",\"platformId\":\"305\",\"shopId\":\"33637\",\"productType\":\"1\",\n"
+ " \"cityId\":\"1\"}]";
List<Object> list = JSON.parseArray(jsonStr, Object.class);
System.out.println(list.get(0).getClass());
--
public class JSONObject extends JSON implements Map<String, Object>, Cloneable, Serializable, InvocationHandler {
为什么老接口不用指定class,新接口需要指定class?
版本问题?查看老服务使用的dubbo-2.5.3.jar版本,果然与新服务不是同版本
查看2.5.3版本代码。出入是有些大。该版本中只要方法同名,方法的参数数量相同便返回匹配。只有出现同名方法(重载)时才会触发isMatch逻辑(同2.6.5版本逻辑相同)
private static Method findMethod(Exporter<?> exporter, String method, List<Object> args) {
Invoker<?> invoker = exporter.getInvoker();
Method[] methods = invoker.getInterface().getMethods();
Method invokeMethod = null;
for (Method m : methods) {
if (m.getName().equals(method) && m.getParameterTypes().length == args.size()) {
if (invokeMethod != null) { // 重载
if (isMatch(invokeMethod.getParameterTypes(), args)) {
invokeMethod = m;
break;
}
} else {
invokeMethod = m;
}
invoker = exporter.getInvoker();
}
}
return invokeMethod;
}
问题总结
2.5.3中对于方法的匹配比较宽泛。只有出现重载时才会进行isMatch判断
2.6.5版本中比较严苛,必须方法同名,且参数类型也要匹配