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