1.背景
有一天同事找我看一個(gè)問(wèn)題,說(shuō)rpc調(diào)用出錯(cuò)了赏表,具體錯(cuò)誤:
java.lang.ClassCastException: java.lang.Integer cannot be cast to java.lang.Short
at XXXXXXXXXXXXXXXXXXXManageServiceInner.createXXXX(XXXXServiceInner.java:266)
at XXXXXXXXXXXXXXXXXXXManageServiceImpl.create(XXXXXServiceImpl.java:80)
at XXXXXXXXXXXXXXXXXXXManageServiceImpl$$FastClassBySpringCGLIB$$10c16c30.invoke(<generated>)
at org.springframework.cglib.proxy.MethodProxy.invoke(MethodProxy.java:204)
at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.invokeJoinpoint(CglibAopProxy.java:738)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:157)
at org.springframework.validation.beanvalidation.MethodValidationInterceptor.invoke(MethodValidationInterceptor.java:150)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179)
at org.springframework.validation.beanvalidation.MethodValidationInterceptor.invoke(MethodValidationInterceptor.java:150)
at com.XXXXXXXXXXXXXXXXXXX.ParamValidatorAspect.doRequestClass(ParamValidatorAspect.java:41)
at sun.reflect.GeneratedMethodAccessor402.invoke(Unknown Source)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
at org.springframework.aop.aspectj.AbstractAspectJAdvice.invokeAdviceMethodWithGivenArgs(AbstractAspectJAdvice.java:629)
at org.springframework.aop.aspectj.AbstractAspectJAdvice.invokeAdviceMethod(AbstractAspectJAdvice.java:618)
at org.springframework.aop.aspectj.AspectJAroundAdvice.invoke(AspectJAroundAdvice.java:70)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:168)
at org.springframework.aop.aspectj.MethodInvocationProceedingJoinPoint.proceed(MethodInvocationProceedingJoinPoint.java:85)
at com.XXXXXXXXXXXXXXXXXXX.ParamLogAspect.execWithLog(ParamLogAspect.java:128)
at com.XXXXXXXXXXXXXXXXXXX.ParamLogAspect.doRequestClass(ParamLogAspect.java:100)
at sun.reflect.GeneratedMethodAccessor401.invoke(Unknown Source)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
類型轉(zhuǎn)化錯(cuò)誤检诗?? 為啥我看了一下 接口定義類似如下:
public interface XXXXManageService {
PlainResult<Long> create(XXXRequest request);
}
public class XXXRequest implements Serializable {
private static final long serialVersionUID = 1L;
private String a;
private Boolean b;
private HashMap<String,Short> c;
}
感覺(jué)沒(méi)有毛病捌敖恕逢慌?!间狂!
現(xiàn)在出現(xiàn)的問(wèn)題:
A業(yè)務(wù)方調(diào)用通過(guò)普通rpc調(diào)用報(bào)類型轉(zhuǎn)換錯(cuò)誤
B公司對(duì)外網(wǎng)關(guān)也是通過(guò)rpc調(diào)用攻泼,為啥沒(méi)有報(bào)錯(cuò)?
第一反應(yīng)鉴象,sb業(yè)務(wù)方使用方式有問(wèn)題忙菠,會(huì)不會(huì)寫代碼!纺弊! 別人都可以就你不行
檢查了一下業(yè)務(wù)方的代碼沒(méi)毛病牛欢。。淆游。氢惋。
自己寫了一個(gè)demo,也是同樣的異常稽犁。焰望。。
公司的網(wǎng)關(guān)服務(wù)調(diào)用沒(méi)有問(wèn)題R押ァ熊赖!我們自己調(diào)用有問(wèn)題!B亲怠震鹉!
1.dubbo版本不一致導(dǎo)致的? (排除捆姜,版本是一致的)
2.開(kāi)始懷疑人生传趾。。泥技。
開(kāi)始debug 看了一下 dubbo的源碼解碼和編碼 在 dubboCodec類中來(lái)看一下decodeBody方法中的 這段代碼
DecodeableRpcInvocation inv;
if (channel.getUrl().getParameter(
Constants.DECODE_IN_IO_THREAD_KEY,
Constants.DEFAULT_DECODE_IN_IO_THREAD)) {
inv = new DecodeableRpcInvocation(channel, req, is, proto);
//從這里看到真正的解碼在 DecodeableRpcInvocation中 我們進(jìn)去看看他們的源碼
inv.decode();
} else {
inv = new DecodeableRpcInvocation(channel, req,
new UnsafeByteArrayInputStream(readMessageData(is)), proto);
}
主要的解碼邏輯在DecodeableRpcInvocation 中
public Object decode(Channel channel, InputStream input) throws IOException {
ObjectInput in = CodecSupport.getSerialization(channel.getUrl(), serializationType)
.deserialize(channel.getUrl(), input);
try {
//解析dubbo協(xié)議版本
setAttachment(Constants.DUBBO_VERSION_KEY, in.readUTF());
//解析接口類路徑 "com.XXXXXXXX.service.XXXXService"
setAttachment(Constants.PATH_KEY, in.readUTF());
/解析版本 協(xié)議版本0.0.0
setAttachment(Constants.VERSION_KEY, in.readUTF());
//解析方法名稱create 如果是泛化調(diào)用 方法名稱就是 $invoke
setMethodName(in.readUTF());
try {
Object[] args;
Class<?>[] pts;
// NOTICE modified by lishen
int argNum = -1;
if (CodecSupport.getSerialization(channel.getUrl(), serializationType) instanceof OptimizedSerialization) {
argNum = in.readInt();
}
if (argNum >= 0) {
浆兰。。。簸呈。榕订。忽略代碼
} else {
//重點(diǎn)在這里如果是普通的rpc調(diào)用 desc就是參數(shù)名稱 例如 XXXX.XX.XXXRequest
//如果是泛化調(diào)用 desc 就是 Ljava/lang/String;[Ljava/lang/String;[Ljava/lang/Object;
//說(shuō)明一下是這個(gè)desc 實(shí)際上就是入?yún)?來(lái)看一下泛化請(qǐng)求你就明白
//GenericService類中的 方法 Object $invoke(String method, String[] parameterTypes, Object[] args)
String desc = in.readUTF();
if (desc.length() == 0) {
pts = DubboCodec.EMPTY_CLASS_ARRAY;
args = DubboCodec.EMPTY_OBJECT_ARRAY;
} else {
//通過(guò)反射生成class XXXX.XX.XXXRequest
pts = ReflectUtils.desc2classArray(desc);
args = new Object[pts.length];
for (int i = 0; i < args.length; i++) {
try {
//把流中的數(shù)據(jù)寫入到 pts中 實(shí)際上就是 直接賦值 XXXX.XX.XXXRequest
//那為什么會(huì)出現(xiàn)類型轉(zhuǎn)換錯(cuò)誤?
//看了一下readObject方法的實(shí)現(xiàn) 我使用的dubbo序列化是 Hessian2
//然而Hessian2ObjectInput判斷是整數(shù)類型的化最終封裝成兩種類型一種是Integer蜕便,一種是Long
//readObject這個(gè)方法對(duì)把讀取到數(shù)據(jù)賦值給XXXX.XX.XXXRequest
//所以導(dǎo)致了在實(shí)際使用中會(huì)出現(xiàn)類型轉(zhuǎn)換問(wèn)題
//疑問(wèn)劫恒? 泛化調(diào)用為什么沒(méi)有問(wèn)題
//解答 泛化調(diào)用 desc 等于Ljava/lang/String;[Ljava/lang/String;[Ljava/lang/Object;
//args的數(shù)組長(zhǎng)度等于3 pts 等于 desc以逗號(hào)切割 arg[0] = create arg[1]=XXXX.XX.XXXRequest arg[2]=map 這個(gè)map實(shí)際上就是入?yún)⒌某蓡T變量和value
//也就是說(shuō)泛化調(diào)用的參數(shù)并沒(méi)有在下面這個(gè)方法被反序列,真正執(zhí)行把入?yún)⒎葱蛄械膭?dòng)作在后面
args[i] = in.readObject(pts[i]);
} catch (Exception e) {
if (log.isWarnEnabled()) {
log.warn("Decode argument failed: " + e.getMessage(), e);
}
}
}
}
}
轿腺。两嘴。。族壳。憔辫。。忽略
} catch (ClassNotFoundException e) {
throw new IOException(StringUtils.toString("Read invocation data failed.", e));
}
} finally {
// modified by lishen
if (in instanceof Cleanable) {
((Cleanable) in).cleanup();
}
}
return this;
}
來(lái)看一下 泛化調(diào)用的參數(shù)序列化實(shí)現(xiàn)在GenericFilter的invoke方法中
String name = ((String) inv.getArguments()[0]).trim();
String[] types = (String[]) inv.getArguments()[1];
Object[] args = (Object[]) inv.getArguments()[2];
try {
Method method = ReflectUtils.findMethodByMethodSignature(invoker.getInterface(), name, types);
Class<?>[] params = method.getParameterTypes();
if (args == null) {
args = new Object[params.length];
}
String generic = inv.getAttachment(Constants.GENERIC_KEY);
if (StringUtils.isEmpty(generic)
|| ProtocolUtils.isDefaultGenericSerialization(generic)) {
args = PojoUtils.realize(args, params, method.getGenericParameterTypes());
PojoUtils.realize這個(gè)作用就是就是把參數(shù)反序列到 XXXX.XX.XXXRequest中
這個(gè)里面會(huì)更具 XXXX.XX.XXXRequest的每一個(gè)參數(shù)類型去設(shè)置值决侈,如果dubbo解析的參數(shù)類型是integer 會(huì)被轉(zhuǎn)成 XXXX.XX.XXXRequest成員的真正類型螺垢。
這也就是為什么公司網(wǎng)關(guān)服務(wù)能夠成功調(diào)用,而為什么普通的rpc會(huì)出現(xiàn)異常的原因赖歌。枉圃。。庐冯。
總結(jié):dubbo在解析整數(shù)類型的時(shí)候只提供 Integer孽亲、和Long 如果在設(shè)計(jì)接口的時(shí)候使用short 類型的 就要注意了 。會(huì)導(dǎo)致類型轉(zhuǎn)化問(wèn)題