1.背景
有一天同事找我看一个问题,说rpc调用出错了,具体错误:
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)
类型转化错误?? 为啥我看了一下 接口定义类似如下:
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;
}
感觉没有毛病啊?!!
现在出现的问题:
A业务方调用通过普通rpc调用报类型转换错误
B公司对外网关也是通过rpc调用,为啥没有报错?
第一反应,sb业务方使用方式有问题,会不会写代码!! 别人都可以就你不行
检查了一下业务方的代码没毛病。。。。
自己写了一个demo,也是同样的异常。。。
公司的网关服务调用没有问题!!我们自己调用有问题!!!
1.dubbo版本不一致导致的? (排除,版本是一致的)
2.开始怀疑人生。。。
开始debug 看了一下 dubbo的源码解码和编码 在 dubboCodec类中来看一下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中 我们进去看看他们的源码
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协议版本
setAttachment(Constants.DUBBO_VERSION_KEY, in.readUTF());
//解析接口类路径 "com.XXXXXXXX.service.XXXXService"
setAttachment(Constants.PATH_KEY, in.readUTF());
/解析版本 协议版本0.0.0
setAttachment(Constants.VERSION_KEY, in.readUTF());
//解析方法名称create 如果是泛化调用 方法名称就是 $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 {
//重点在这里如果是普通的rpc调用 desc就是参数名称 例如 XXXX.XX.XXXRequest
//如果是泛化调用 desc 就是 Ljava/lang/String;[Ljava/lang/String;[Ljava/lang/Object;
//说明一下是这个desc 实际上就是入参 来看一下泛化请求你就明白
//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 {
//通过反射生成class XXXX.XX.XXXRequest
pts = ReflectUtils.desc2classArray(desc);
args = new Object[pts.length];
for (int i = 0; i < args.length; i++) {
try {
//把流中的数据写入到 pts中 实际上就是 直接赋值 XXXX.XX.XXXRequest
//那为什么会出现类型转换错误?
//看了一下readObject方法的实现 我使用的dubbo序列化是 Hessian2
//然而Hessian2ObjectInput判断是整数类型的化最终封装成两种类型一种是Integer,一种是Long
//readObject这个方法对把读取到数据赋值给XXXX.XX.XXXRequest
//所以导致了在实际使用中会出现类型转换问题
//疑问? 泛化调用为什么没有问题
//解答 泛化调用 desc 等于Ljava/lang/String;[Ljava/lang/String;[Ljava/lang/Object;
//args的数组长度等于3 pts 等于 desc以逗号切割 arg[0] = create arg[1]=XXXX.XX.XXXRequest arg[2]=map 这个map实际上就是入参的成员变量和value
//也就是说泛化调用的参数并没有在下面这个方法被反序列,真正执行把入参反序列的动作在后面
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;
}
来看一下 泛化调用的参数序列化实现在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这个作用就是就是把参数反序列到 XXXX.XX.XXXRequest中
这个里面会更具 XXXX.XX.XXXRequest的每一个参数类型去设置值,如果dubbo解析的参数类型是integer 会被转成 XXXX.XX.XXXRequest成员的真正类型。
这也就是为什么公司网关服务能够成功调用,而为什么普通的rpc会出现异常的原因。。。。
总结:dubbo在解析整数类型的时候只提供 Integer、和Long 如果在设计接口的时候使用short 类型的 就要注意了 。会导致类型转化问题