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版本中比較嚴苛,必須方法同名,且參數類型也要匹配

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章