dubbo小坑 ClassCastException

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业务方使用方式有问题,会不会写代码!! 别人都可以就你不行


image.png

检查了一下业务方的代码没毛病。。。。
自己写了一个demo,也是同样的异常。。。


image.png

公司的网关服务调用没有问题!!我们自己调用有问题!!!
1.dubbo版本不一致导致的? (排除,版本是一致的)
2.开始怀疑人生。。。


image.png

开始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 类型的 就要注意了 。会导致类型转化问题

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 215,794评论 6 498
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,050评论 3 391
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 161,587评论 0 351
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,861评论 1 290
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,901评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,898评论 1 295
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,832评论 3 416
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,617评论 0 271
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,077评论 1 308
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,349评论 2 331
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,483评论 1 345
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,199评论 5 341
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,824评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,442评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,632评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,474评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,393评论 2 352

推荐阅读更多精彩内容