fengjiachun / Jupiter

Jupiter是一款性能非常不错的, 轻量级的分布式服务框架

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

接口传参中,有null时,处理异常

chywwq opened this issue · comments

@ServiceProvider(group = "demo", name = "helloworld")
public interface HelloService {
    String m1(String arg1, String arg2, List<String> arg3);
}
helloService.m1("arg1", null, Arrays.asList("arg3.1", "arg3.2"))

调用后报错:
Exception in thread "main" org.jupiter.rpc.exception.JupiterBizException: java.lang.ClassCastException: java.util.ArrayList cannot be cast to java.lang.String
at org.jupiter.rpc.consumer.future.DefaultInvokeFuture.setException(DefaultInvokeFuture.java:215)
at org.jupiter.rpc.consumer.future.DefaultInvokeFuture.doReceived(DefaultInvokeFuture.java:189)
at org.jupiter.rpc.consumer.future.DefaultInvokeFuture.received(DefaultInvokeFuture.java:243)
at org.jupiter.rpc.consumer.processor.task.MessageTask.run(MessageTask.java:81)
at org.jupiter.rpc.executor.CallerRunsExecutorFactory$1.execute(CallerRunsExecutorFactory.java:36)
at org.jupiter.rpc.consumer.processor.DefaultConsumerProcessor.handleResponse(DefaultConsumerProcessor.java:52)
at org.jupiter.transport.netty.handler.connector.ConnectorHandler.channelRead(ConnectorHandler.java:52)
at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:362)
at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:348)
at io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:340)
at io.netty.handler.codec.ByteToMessageDecoder.fireChannelRead(ByteToMessageDecoder.java:310)
at io.netty.handler.codec.ByteToMessageDecoder.channelRead(ByteToMessageDecoder.java:284)
at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:362)
at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:348)
at io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:340)
at io.netty.channel.ChannelInboundHandlerAdapter.channelRead(ChannelInboundHandlerAdapter.java:86)
at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:362)
at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:348)
at io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:340)
at org.jupiter.transport.netty.handler.IdleStateChecker.channelRead(IdleStateChecker.java:186)
at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:362)
at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:348)
at io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:340)
at io.netty.channel.ChannelInboundHandlerAdapter.channelRead(ChannelInboundHandlerAdapter.java:86)
at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:362)
at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:348)
at io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:340)
at io.netty.channel.DefaultChannelPipeline$HeadContext.channelRead(DefaultChannelPipeline.java:1359)
at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:362)
at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:348)
at io.netty.channel.DefaultChannelPipeline.fireChannelRead(DefaultChannelPipeline.java:935)
at io.netty.channel.nio.AbstractNioByteChannel$NioByteUnsafe.read(AbstractNioByteChannel.java:141)
at io.netty.channel.nio.NioEventLoop.processSelectedKey(NioEventLoop.java:645)
at io.netty.channel.nio.NioEventLoop.processSelectedKeysOptimized(NioEventLoop.java:580)
at io.netty.channel.nio.NioEventLoop.processSelectedKeys(NioEventLoop.java:497)
at io.netty.channel.nio.NioEventLoop.run(NioEventLoop.java:451)
at io.netty.util.concurrent.SingleThreadEventExecutor$5.run(SingleThreadEventExecutor.java:886)
at io.netty.util.concurrent.FastThreadLocalRunnable.run(FastThreadLocalRunnable.java:30)
at java.lang.Thread.run(Thread.java:748)

序列化框架的问题 kryo/protostuff都有这个问题, protostuff的ArraySchemas.java类对pojos以及基本类型都有支持, 唯独Object[] 不支持包含null元素
目前思路是在序列化框架上层workaround, 这样就同时解决kryo/protostuff的问题, 我要先测下改动有没有性能损失, 晚点提交下代码

@chywwq 已经workaround, 请先下载最新代码, 需要设置一个参数(默认为false):
SystemPropertyUtil.setProperty("jupiter.message.args.allow_null_array_arg", "true")
或者 system property: -Djupiter.message.args.allow_null_array_arg=true
参考的protostuff作者提供的思路: protostuff/protostuff#165

1、需要在服务端和客户端同时加上该参数
2、处理有问题,参数都是null

arg1:null
arg2:null
arg3:null

@chywwq 1. 是的, 两端都要设置, 默认是不支持的 2. 请详细说下什么问题, 最好给下你的测试条件, 我没测出来

@chywwq 我补充了测试用例, 你可以帮忙看下什么条件没覆盖, 没出现你说的情况2

关于protostuff的bug, 我提交了一个PR: protostuff/protostuff#253
待protostuff新版本发布, jupiter会跟进更新版本并移除现有的workaround的相关代码

简单总结下, 这个issue其实包含两个问题:

  1. 对于rpc调用, 有几个重要参数是必须参与序列化/反序列化的(接口名, 方法名, 方法调用的参数值列表), 其中参数值列表肯定是一个Object[],
    对于Object[]中包含null元素的的场景, protostuff和kryo是不支持的, 并且我了解到protostuff作者也不认为这是一个bug, 不会修复, 但是他提过一个
    workaround的办法, 就是用一个标记来标记null元素, jupiter采用一个枚举(NullArg.NULL) 标记参数值列表中的null元素, 反序列化时再把枚举替换回null,
    这个解决方式是在序列化/反序列化层之上, 自然也就解决了kryo/protostuff共同的问题, 不过需要配置一个参数 -Djupiter.message.args.allow_null_array_arg=true

  2. 第二个问题是 在Object[]中还包含一个数组, 这个数组中包含null元素, 比如 Object[] { obj1, obj2, new String[] { "str1", null, "str3"} },
    这个情况protostuff同样无法序列化/反序列化, 这是个bug, 我已经提交PR, 现在已被merge到protostuff主干, 在protostuff发布新版本之前, 我临时的解决方案是在项目中覆盖
    java.io.protostuff.runtime.IdStrategy, 类加载器会先加载jupiter项目中的IdStrategy类