dubbo telnet提示No such method

版本: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"})

问题排查

  1. 首先执行ls命令核对命令中的类全限定名与方法名。均正确
  2. 核对参数类型与代码中的对象一毛一样

很懵圈,没办法,看源码吧。看哪块源码呢?那么大的项目,设计模式中的责任链模式就真得很便于我们这类小白了,没错直接查找TelnetHandler的实现类即可,可以看到其中一个为InvokeTelnetHandler,是他是他就是他-_-
1

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

  1. 参数类型长度与入参长度不一致,返回不匹配
  2. 如果参数类型是基础类型,invoke调用时传入的参数是null则抛出空指针,否则继续
  3. 如果参数类型是基础类型,invoke调用时传入的参数不是基础类型,返回不匹配
  4. 如果invoke调用时传入的参数是map类型(即对象);如果map中key=class的value存在,则判断invoke调用时传入的参数与指定的class是否同类型,返回是否匹配;如果不存在指定的class类型则直接判断是否与invoke传入时的arg的class是否同类型,返回是否匹配。
  5. 如果invoke调用时传入的参数是集合类型,而参数类型不是数组(不是动态参数类型)并且不是同类型,返回不匹配
  6. 如果参数类型与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版本中比较严苛,必须方法同名,且参数类型也要匹配

发布了91 篇原创文章 · 获赞 103 · 访问量 3万+

猜你喜欢

转载自blog.csdn.net/u010597819/article/details/105306054