背景
- 最近同事创建了一个kotlin新项目,新项目在调用dubbo服务失败,消费者异常信息:com.alibaba.com.caucho.hessian.io.HessianProtocolException: expected map/object at java.lang.String ()
- 提供者无异常日志
- 代码写法和其他项目一致
代码
@NoArgsConstructor
data class EewForwardDto (
var msgId: String
) : Serializable
interface IEndpointApiService {
fun doDelivery(eewForwardDto: EewForwardDto)
}
fun test() {
val dto = EewForwardDto( msgId = "" )
val res = iEndpointApiService.doDelivery(dto)
println(res)
}
异常栈
java.lang.reflect.InvocationTargetException
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
at com.xin.base.controller.TTTestController$$T.handleRequest(TTTestController$$T.java:128)
at com.xin.base.controller.HttpServer$$.serve(HttpServer$$.java:45)
at fi.iki.elonen.NanoHTTPD$HTTPSession.execute(NanoHTTPD.java:945)
at fi.iki.elonen.NanoHTTPD$ClientHandler.run(NanoHTTPD.java:192)
at java.lang.Thread.run(Thread.java:750)
Caused by: org.apache.dubbo.rpc.RpcException: Failed to invoke the method doDelivery in the service com.***.eew.ohs.expose.rpc.api.IEndpointApiService. Tried 3 times of the providers
com.alibaba.com.caucho.hessian.io.HessianProtocolException: expected map/object at java.lang.String ()
at com.alibaba.com.caucho.hessian.io.AbstractDeserializer.error(AbstractDeserializer.java:131)
at com.alibaba.com.caucho.hessian.io.AbstractMapDeserializer.readObject(AbstractMapDeserializer.java:70)
at com.alibaba.com.caucho.hessian.io.Hessian2Input.readObject(Hessian2Input.java:2297)
at com.alibaba.com.caucho.hessian.io.Hessian2Input.readObject(Hessian2Input.java:2104)
at org.apache.dubbo.common.serialize.hessian2.Hessian2ObjectInput.readObject(Hessian2ObjectInput.java:101)
at org.apache.dubbo.common.serialize.ObjectInput.readAttachments(ObjectInput.java:87)
at org.apache.dubbo.rpc.protocol.dubbo.DecodeableRpcInvocation.decode(DecodeableRpcInvocation.java:165)
at org.apache.dubbo.rpc.protocol.dubbo.DecodeableRpcInvocation.decode(DecodeableRpcInvocation.java:83)
at org.apache.dubbo.remoting.transport.DecodeHandler.decode(DecodeHandler.java:57)
at org.apache.dubbo.remoting.transport.DecodeHandler.received(DecodeHandler.java:44)
排查
- 首先确认下提供者和消费者的依赖、配置是否有差异,经确认都是dubbo2.7.9、协议dubbo、序列化使用的是hessian
- 虽然提供者没有异常信息,但是通过org.apache.dubbo.rpc.protocol.dubbo.DecodeableRpcInvocation.decode(DecodeableRpcInvocation.java:165)可以判断,消费者的请求已经到达提供者处
- 通过Hessian2Input.readObject异常日志可以确认是在反序列化时出了问题
- 本地搭建环境,在dubbo服务提供者debug代码
- 根据堆栈,debug到com.alibaba.com.caucho.hessian.io.AbstractMapDeserializer#readObject
图2
如图2所示,只要走到这个方法,不管序列化结果如何,最终都会报错
那为啥会走到AbstractMapDeserializer这个方法?
图3
图4
往前debug,从org.apache.dubbo.rpc.protocol.dubbo.DecodeableRpcInvocation#decode(org.apache.dubbo.remoting.Channel, java.io.InputStream)开始
如图3、4所示,getDeserializer方法会返回com.alibaba.com.caucho.hessian.io.MapDeserializer,该类没有覆盖AbstractMapDeserializer的readObject方法,因此一定会报错
图5
如图5所示,hessian获取有误反序列化器的逻辑已经是com.alibaba.com.caucho.hessian.io.Hessian2Input#readObject(java.lang.Class, java.lang.Class...)方法末尾了,猜测正常流程应该是走上面的case逻辑返回,打开其他项目验证也确实如此
那为啥新项目不会走到case逻辑?
图6
如图6所示,case是通过tag去匹配的,case_offset < _length 为true,_buffer是hessian的序列化数据,猜测是offset下标不对,导致tag读取有误,hessian应该是有序读取buffer,有可能是前面有步骤有误导致offset计算错误
图7
往前多打几个断点,如图7所示,发现in.readObject方法居然报错了,而这个方法刚好也会修改offset的值,猜测是这个方法失败,导致offset不符合预期,但由此也发现一个新问题,为啥dubbo异常日志没有输出,问题太多了,先解决readObject报错的问题
图8
如图8所示,com.alibaba.com.caucho.hessian.io.JavaDeserializer#instantiate使用有参构造函数实例化,但是构造函数传参却是null,EewForwardDto的msgId是不允许为null(kotlin特性),猜测是因为这个
导致实例化失败
验证猜想
图9
如图9所示,通过idea的debug工具给_constrctorArgs赋值,确实成功实例化了
debug下_constrctorArgs赋值处,看看为啥_constructorArgs参数值为啥为null
图10
图11
如图10、11所示,进入com.alibaba.com.caucho.hessian.io.JavaDeserializer#JavaDeserializer的getParamArg方法。
isPrimitive()方法用于判断类是否是基本类型,如果是基本类型则返回默认值,看这个逻辑也没问题,因为我们的
EewForwardDto的msgId参数就是string类型,那为啥一样的项目,其他项目的data class却可以正常实例化?
启动下其他项目,debug看下
@NoArgsConstructor
data class ReportDto(
var businessKey: String,
var datas: List<Any>
): Serializable
图12
如图12所示,排查发现其他项目会走无参构造示例化,因此不会报错,通过查询资料发现,kotlin data class默认是没有无参构造函数的,可是我们和其他项目一样,也加了lombok的@NoArgsConstructor注解,为啥效果不一样?
图13
如图13所示,通过arthus的jad命令反编译看下EewForwardDto(ps:idea的反编译对kotlin代码来说几乎不可用,不知道是不是我使用姿势不对), 发现确实没有无参构造,而其他成功调用的项目是有的
排查总结
那现在问题基本可以确定了,data class没有无参构造,所以导致dubbo反序列化失败
解决方法
通过查询资料发现,kotlin官方提供了一个maven插件,支持在编译时生成无参构造函数,排查其他用data class成功调用dubbo的项目,确实也引了这个插件,复用了lombok的NoArgsConstructor注解,作为插件生成无参构造的标识,因此实际上无参构造并不是lombok生成的
<plugins>
<plugin>
<groupId>org.jetbrains.kotlin</groupId>
<artifactId>kotlin-maven-plugin</artifactId>
<version>${kotlin.version}</version>
<configuration>
<jvmTarget>1.8</jvmTarget>
<compilerPlugins>
<plugin>no-arg</plugin>
</compilerPlugins>
<pluginOptions>
<option>no-arg:annotation=lombok.NoArgsConstructor</option>
</pluginOptions>
</configuration>
<dependencies>
<dependency>
<groupId>org.jetbrains.kotlin</groupId>
<artifactId>kotlin-maven-noarg</artifactId>
<version>${kotlin.version}</version>
</dependency>
</dependencies>
</plugin>
</plugins>
ps: dubbo提供者日志没有显示的问题,由于牵扯内容较多,留在其他篇章解决