[2.7.16] dubbo协议反序列化mongo-java-driver 4.2.0以上版本ObjectId失败
- [x] I have searched the issues of this repository and believe that this is not a duplicate.
Environment
- Dubbo version: 2.7.16
- Operating System version: windows 11
- Java version: 1.8
Steps to reproduce this issue
在mongo-java-driver 4.2.0+版本中,ObjectId序列化方式发生变化 https://github.com/mongodb/mongo-java-driver/commit/57a20d675e0cdd1ae378b1b9ce969612953546a6 通过Hessian2Output写出到provider端时,Hessian2Input解析到的类型是org.bson.types.ObjectId.SerializationProxy 在调用com.alibaba.com.caucho.hessian.io.JavaDeserializer#readObject(com.alibaba.com.caucho.hessian.io.AbstractHessianInput, java.lang.String[])方法时,会试图利用反射构造其实例,调用构造方法org.bson.types.ObjectId.SerializationProxy#SerializationProxy时会报空指针
SerializationProxy(final ObjectId objectId) {
bytes = objectId.toByteArray();
}
类似的的可定义类
@ToString
public class TimeRecord implements Serializable {
private static final long serialVersionUID = 1L;
private final String timestamp;
public TimeRecord() {
this(System.currentTimeMillis());
}
public TimeRecord(final long timestamp) {
this.timestamp = String.valueOf(timestamp);
}
private long toLong() {
return Long.parseLong(timestamp);
}
private Object writeReplace() {
return new SerializationProxy(this);
}
private void readObject(final ObjectInputStream stream) throws InvalidObjectException {
throw new InvalidObjectException("Proxy required");
}
public static class SerializationProxy implements Serializable {
private static final long serialVersionUID = 1L;
private final long number;
SerializationProxy(final TimeRecord o) {
number = o.toLong();
}
private Object readResolve() {
return new TimeRecord(number);
}
}
}
在consumer端任意service方法调用前附加隐式参数RpcContext.getClientAttachment().setObjectAttachment("haha", new TimeRecord());即可复现问题
Expected Behavior
希望能正确反序列化ObjectId,以便兼容更高版本的mongodb server; 或者是否可提供扩展接口,方便用户自定义特殊类型的bean的序列化方式 另外 Exception的message组织是否有问题,好多重复信息
Actual Behavior
exception trace:
org.apache.dubbo.rpc.RpcException: Failed to invoke the method sayHello in the service com.example.api.DemoService. Tried 3 times of the providers [192.168.199.1:30880] (1/1) from the registry 127.0.0.1:2181 on the consumer 192.168.199.1 using the dubbo version 3.0.6. Last error is: Failed to invoke remote method: sayHello, provider: dubbo://192.168.199.1:30880/com.example.api.DemoService?application=provider&background=false&category=providers,configurators,routers&check=false&deprecated=false&dubbo=2.0.2&interface=com.example.api.DemoService&metadata-type=remote&pid=24752&qos.enable=false&release=3.0.6&side=consumer&sticky=false, cause: org.apache.dubbo.remoting.RemotingException: Fail to decode request due to: com.alibaba.com.caucho.hessian.io.HessianProtocolException: 'com.example.Application$TimeRecord$SerializationProxy' could not be instantiated
com.alibaba.com.caucho.hessian.io.HessianProtocolException: 'com.example.Application$TimeRecord$SerializationProxy' could not be instantiated
at com.alibaba.com.caucho.hessian.io.JavaDeserializer.instantiate(JavaDeserializer.java:317)
at com.alibaba.com.caucho.hessian.io.JavaDeserializer.readObject(JavaDeserializer.java:202)
at com.alibaba.com.caucho.hessian.io.SerializerFactory.readObject(SerializerFactory.java:548)
at com.alibaba.com.caucho.hessian.io.Hessian2Input.readObjectInstance(Hessian2Input.java:2850)
at com.alibaba.com.caucho.hessian.io.Hessian2Input.readObject(Hessian2Input.java:2773)
at com.alibaba.com.caucho.hessian.io.Hessian2Input.readObject(Hessian2Input.java:2308)
at com.alibaba.com.caucho.hessian.io.Hessian2Input.readObject(Hessian2Input.java:2747)
at com.alibaba.com.caucho.hessian.io.Hessian2Input.readObject(Hessian2Input.java:2308)
at com.alibaba.com.caucho.hessian.io.MapDeserializer.doReadMap(MapDeserializer.java:146)
at com.alibaba.com.caucho.hessian.io.MapDeserializer.readMap(MapDeserializer.java:126)
at com.alibaba.com.caucho.hessian.io.Hessian2Input.readObject(Hessian2Input.java:2123)
at com.alibaba.com.caucho.hessian.io.Hessian2Input.readObject(Hessian2Input.java:2104)
at org.apache.dubbo.common.serialize.hessian2.Hessian2ObjectInput.readObject(Hessian2ObjectInput.java:102)
at org.apache.dubbo.common.serialize.ObjectInput.readAttachments(ObjectInput.java:87)
at org.apache.dubbo.rpc.protocol.dubbo.DecodeableRpcInvocation.decode(DecodeableRpcInvocation.java:224)
at org.apache.dubbo.rpc.protocol.dubbo.DecodeableRpcInvocation.decode(DecodeableRpcInvocation.java:93)
at org.apache.dubbo.remoting.transport.DecodeHandler.decode(DecodeHandler.java:57)
at org.apache.dubbo.remoting.transport.DecodeHandler.received(DecodeHandler.java:44)
at org.apache.dubbo.remoting.transport.dispatcher.ChannelEventRunnable.run(ChannelEventRunnable.java:57)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
at org.apache.dubbo.common.threadlocal.InternalRunnable.run(InternalRunnable.java:41)
at java.lang.Thread.run(Thread.java:748)
Caused by: java.lang.reflect.InvocationTargetException
at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:62)
at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45)
at java.lang.reflect.Constructor.newInstance(Constructor.java:423)
at com.alibaba.com.caucho.hessian.io.JavaDeserializer.instantiate(JavaDeserializer.java:313)
... 22 more
Caused by: java.lang.NullPointerException
at com.example.Application$TimeRecord.access$000(Application.java:55)
at com.example.Application$TimeRecord$SerializationProxy.<init>(Application.java:87)
... 27 more
at org.apache.dubbo.rpc.cluster.support.FailoverClusterInvoker.doInvoke(FailoverClusterInvoker.java:114)
at org.apache.dubbo.rpc.cluster.support.AbstractClusterInvoker.invoke(AbstractClusterInvoker.java:340)
at org.apache.dubbo.rpc.cluster.router.RouterSnapshotFilter.invoke(RouterSnapshotFilter.java:46)
at org.apache.dubbo.rpc.cluster.filter.FilterChainBuilder$CopyOfFilterChainNode.invoke(FilterChainBuilder.java:321)
at org.apache.dubbo.monitor.support.MonitorFilter.invoke(MonitorFilter.java:99)
at org.apache.dubbo.rpc.cluster.filter.FilterChainBuilder$CopyOfFilterChainNode.invoke(FilterChainBuilder.java:321)
at org.apache.dubbo.rpc.protocol.dubbo.filter.FutureFilter.invoke(FutureFilter.java:51)
at org.apache.dubbo.rpc.cluster.filter.FilterChainBuilder$CopyOfFilterChainNode.invoke(FilterChainBuilder.java:321)
at org.apache.dubbo.rpc.cluster.filter.support.ConsumerContextFilter.invoke(ConsumerContextFilter.java:108)
at org.apache.dubbo.rpc.cluster.filter.FilterChainBuilder$CopyOfFilterChainNode.invoke(FilterChainBuilder.java:321)
at org.apache.dubbo.rpc.cluster.filter.FilterChainBuilder$CallbackRegistrationInvoker.invoke(FilterChainBuilder.java:193)
at org.apache.dubbo.rpc.cluster.support.wrapper.AbstractCluster$ClusterFilterInvoker.invoke(AbstractCluster.java:92)
at org.apache.dubbo.rpc.cluster.support.wrapper.MockClusterInvoker.invoke(MockClusterInvoker.java:97)
at org.apache.dubbo.registry.client.migration.MigrationInvoker.invoke(MigrationInvoker.java:280)
at org.apache.dubbo.rpc.proxy.InvokerInvocationHandler.invoke(InvokerInvocationHandler.java:97)
at com.example.api.DemoServiceDubboProxy0.sayHello(DemoServiceDubboProxy0.java)
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 org.springframework.aop.support.AopUtils.invokeJoinpointUsingReflection(AopUtils.java:344)
at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:208)
at com.sun.proxy.$Proxy56.sayHello(Unknown Source)
at com.example.Application.main(Application.java:47)
Caused by: java.util.concurrent.ExecutionException: org.apache.dubbo.remoting.RemotingException: Fail to decode request due to: com.alibaba.com.caucho.hessian.io.HessianProtocolException: 'com.example.Application$TimeRecord$SerializationProxy' could not be instantiated
com.alibaba.com.caucho.hessian.io.HessianProtocolException: 'com.example.Application$TimeRecord$SerializationProxy' could not be instantiated
at com.alibaba.com.caucho.hessian.io.JavaDeserializer.instantiate(JavaDeserializer.java:317)
at com.alibaba.com.caucho.hessian.io.JavaDeserializer.readObject(JavaDeserializer.java:202)
at com.alibaba.com.caucho.hessian.io.SerializerFactory.readObject(SerializerFactory.java:548)
at com.alibaba.com.caucho.hessian.io.Hessian2Input.readObjectInstance(Hessian2Input.java:2850)
at com.alibaba.com.caucho.hessian.io.Hessian2Input.readObject(Hessian2Input.java:2773)
at com.alibaba.com.caucho.hessian.io.Hessian2Input.readObject(Hessian2Input.java:2308)
at com.alibaba.com.caucho.hessian.io.Hessian2Input.readObject(Hessian2Input.java:2747)
at com.alibaba.com.caucho.hessian.io.Hessian2Input.readObject(Hessian2Input.java:2308)
at com.alibaba.com.caucho.hessian.io.MapDeserializer.doReadMap(MapDeserializer.java:146)
at com.alibaba.com.caucho.hessian.io.MapDeserializer.readMap(MapDeserializer.java:126)
at com.alibaba.com.caucho.hessian.io.Hessian2Input.readObject(Hessian2Input.java:2123)
at com.alibaba.com.caucho.hessian.io.Hessian2Input.readObject(Hessian2Input.java:2104)
at org.apache.dubbo.common.serialize.hessian2.Hessian2ObjectInput.readObject(Hessian2ObjectInput.java:102)
at org.apache.dubbo.common.serialize.ObjectInput.readAttachments(ObjectInput.java:87)
at org.apache.dubbo.rpc.protocol.dubbo.DecodeableRpcInvocation.decode(DecodeableRpcInvocation.java:224)
at org.apache.dubbo.rpc.protocol.dubbo.DecodeableRpcInvocation.decode(DecodeableRpcInvocation.java:93)
at org.apache.dubbo.remoting.transport.DecodeHandler.decode(DecodeHandler.java:57)
at org.apache.dubbo.remoting.transport.DecodeHandler.received(DecodeHandler.java:44)
at org.apache.dubbo.remoting.transport.dispatcher.ChannelEventRunnable.run(ChannelEventRunnable.java:57)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
at org.apache.dubbo.common.threadlocal.InternalRunnable.run(InternalRunnable.java:41)
at java.lang.Thread.run(Thread.java:748)
Caused by: java.lang.reflect.InvocationTargetException
at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:62)
at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45)
at java.lang.reflect.Constructor.newInstance(Constructor.java:423)
at com.alibaba.com.caucho.hessian.io.JavaDeserializer.instantiate(JavaDeserializer.java:313)
... 22 more
Caused by: java.lang.NullPointerException
at com.example.Application$TimeRecord.access$000(Application.java:55)
at com.example.Application$TimeRecord$SerializationProxy.<init>(Application.java:87)
... 27 more
at java.util.concurrent.CompletableFuture.reportGet(CompletableFuture.java:357)
at java.util.concurrent.CompletableFuture.get(CompletableFuture.java:1915)
at org.apache.dubbo.rpc.AsyncRpcResult.get(AsyncRpcResult.java:195)
at org.apache.dubbo.rpc.protocol.AbstractInvoker.waitForResultIfSync(AbstractInvoker.java:264)
at org.apache.dubbo.rpc.protocol.AbstractInvoker.invoke(AbstractInvoker.java:185)
at org.apache.dubbo.rpc.listener.ListenerInvokerWrapper.invoke(ListenerInvokerWrapper.java:78)
at org.apache.dubbo.rpc.cluster.support.AbstractClusterInvoker.invokeWithContext(AbstractClusterInvoker.java:378)
at org.apache.dubbo.rpc.cluster.support.FailoverClusterInvoker.doInvoke(FailoverClusterInvoker.java:80)
... 23 more
Caused by: org.apache.dubbo.remoting.RemotingException: Fail to decode request due to: com.alibaba.com.caucho.hessian.io.HessianProtocolException: 'com.example.Application$TimeRecord$SerializationProxy' could not be instantiated
com.alibaba.com.caucho.hessian.io.HessianProtocolException: 'com.example.Application$TimeRecord$SerializationProxy' could not be instantiated
at com.alibaba.com.caucho.hessian.io.JavaDeserializer.instantiate(JavaDeserializer.java:317)
at com.alibaba.com.caucho.hessian.io.JavaDeserializer.readObject(JavaDeserializer.java:202)
at com.alibaba.com.caucho.hessian.io.SerializerFactory.readObject(SerializerFactory.java:548)
at com.alibaba.com.caucho.hessian.io.Hessian2Input.readObjectInstance(Hessian2Input.java:2850)
at com.alibaba.com.caucho.hessian.io.Hessian2Input.readObject(Hessian2Input.java:2773)
at com.alibaba.com.caucho.hessian.io.Hessian2Input.readObject(Hessian2Input.java:2308)
at com.alibaba.com.caucho.hessian.io.Hessian2Input.readObject(Hessian2Input.java:2747)
at com.alibaba.com.caucho.hessian.io.Hessian2Input.readObject(Hessian2Input.java:2308)
at com.alibaba.com.caucho.hessian.io.MapDeserializer.doReadMap(MapDeserializer.java:146)
at com.alibaba.com.caucho.hessian.io.MapDeserializer.readMap(MapDeserializer.java:126)
at com.alibaba.com.caucho.hessian.io.Hessian2Input.readObject(Hessian2Input.java:2123)
at com.alibaba.com.caucho.hessian.io.Hessian2Input.readObject(Hessian2Input.java:2104)
at org.apache.dubbo.common.serialize.hessian2.Hessian2ObjectInput.readObject(Hessian2ObjectInput.java:102)
at org.apache.dubbo.common.serialize.ObjectInput.readAttachments(ObjectInput.java:87)
at org.apache.dubbo.rpc.protocol.dubbo.DecodeableRpcInvocation.decode(DecodeableRpcInvocation.java:224)
at org.apache.dubbo.rpc.protocol.dubbo.DecodeableRpcInvocation.decode(DecodeableRpcInvocation.java:93)
at org.apache.dubbo.remoting.transport.DecodeHandler.decode(DecodeHandler.java:57)
at org.apache.dubbo.remoting.transport.DecodeHandler.received(DecodeHandler.java:44)
at org.apache.dubbo.remoting.transport.dispatcher.ChannelEventRunnable.run(ChannelEventRunnable.java:57)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
at org.apache.dubbo.common.threadlocal.InternalRunnable.run(InternalRunnable.java:41)
at java.lang.Thread.run(Thread.java:748)
Caused by: java.lang.reflect.InvocationTargetException
at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:62)
at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45)
at java.lang.reflect.Constructor.newInstance(Constructor.java:423)
at com.alibaba.com.caucho.hessian.io.JavaDeserializer.instantiate(JavaDeserializer.java:313)
... 22 more
Caused by: java.lang.NullPointerException
at com.example.Application$TimeRecord.access$000(Application.java:55)
at com.example.Application$TimeRecord$SerializationProxy.<init>(Application.java:87)
... 27 more
at org.apache.dubbo.remoting.exchange.support.DefaultFuture.doReceived(DefaultFuture.java:214)
at org.apache.dubbo.remoting.exchange.support.DefaultFuture.received(DefaultFuture.java:176)
at org.apache.dubbo.remoting.exchange.support.DefaultFuture.received(DefaultFuture.java:164)
at org.apache.dubbo.remoting.exchange.support.header.HeaderExchangeHandler.handleResponse(HeaderExchangeHandler.java:60)
at org.apache.dubbo.remoting.exchange.support.header.HeaderExchangeHandler.received(HeaderExchangeHandler.java:181)
at org.apache.dubbo.remoting.transport.DecodeHandler.received(DecodeHandler.java:51)
at org.apache.dubbo.remoting.transport.dispatcher.ChannelEventRunnable.run(ChannelEventRunnable.java:57)
at org.apache.dubbo.common.threadpool.ThreadlessExecutor$RunnableWrapper.run(ThreadlessExecutor.java:184)
at org.apache.dubbo.common.threadpool.ThreadlessExecutor.waitAndDrain(ThreadlessExecutor.java:103)
at org.apache.dubbo.rpc.AsyncRpcResult.get(AsyncRpcResult.java:193)
... 28 more
SerializationProxy 这个类看起来就不太 Serializable 呀……
然后这个方法和 hessian 预留的方法名刚好一起对上了
是有点别扭,但官方文档https://docs.oracle.com/javase/8/docs/platform/serialization/spec/output.html#a5324 并未强调writeReplace的返回值需实现序列化接口,事实上SerializationProxy是否实现序列化接口也并不影响com.alibaba.com.caucho.hessian.io.JavaSerializer的逻辑 java原生的ObjectOutputStream与ObjectInputStream也可正常运行
@Test
public void testObjectSerialization() throws IOException, ClassNotFoundException {
// given
ByteArrayOutputStream baos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(baos);
ObjectId objectId = new ObjectId("5f8f4fcf27516f05e7eae5be");
// when
oos.writeObject(objectId);
// then
assertTrue(new String(baos.toByteArray()).contains("org.bson.types.ObjectId$SerializationProxy"));
assertArrayEquals(new byte[] {-84, -19, 0, 5, 115, 114, 0, 42, 111, 114, 103, 46, 98, 115, 111, 110, 46, 116, 121, 112, 101, 115,
46, 79, 98, 106, 101, 99, 116, 73, 100, 36, 83, 101, 114, 105, 97, 108, 105, 122, 97, 116, 105, 111, 110, 80, 114,
111, 120, 121, 0, 0, 0, 0, 0, 0, 0, 1, 2, 0, 1, 91, 0, 5, 98, 121, 116, 101, 115, 116, 0, 2, 91, 66, 120, 112, 117,
114, 0, 2, 91, 66, -84, -13, 23, -8, 6, 8, 84, -32, 2, 0, 0, 120, 112, 0, 0, 0, 12, 95, -113, 79, -49, 39, 81, 111,
5, -25, -22, -27, -66},
baos.toByteArray());
// when
ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
ObjectInputStream ois = new ObjectInputStream(bais);
ObjectId deserializedObjectId = (ObjectId) ois.readObject();
// then
assertEquals(objectId, deserializedObjectId);
}
从逻辑上 com.alibaba.com.caucho.hessian.io.JavaSerializer 确实不应该和 SerializationProxy 有什么关系,现在的问题是 ObjectId 里面刚好有个方法名和 hessian 里面保留的方法名一样,导致走了特殊逻辑……
也不能算“刚好”吧,writeReplace作为jdk提供给用户的自定义序列化方式之一,JavaSerializer试图提供支持(由#2031 可看出确实想支持此种序列化方式),虽然成功进行了序列化,但在反序列化过程中却没有恰当解析,强行调用有参构造方法而导致空指针。相对于对现有JavaDeserializer的完善,其实更想知道有没有关于com.alibaba.com.caucho.hessian.io.SerializerFactory的扩展,或者类似的接口,能对特定类型的序列化进行自定义
额,好久没回复了,如果自定义扩展实现较复杂的话,是否可以改下JavaDeserializer中的对象实例化逻辑,借助Objenesis.jar来实例化对象
看了下为啥 jdk 可以序列化,是因为 jdk 的直接建了个 constructor 出来,hessian 没有建的能力所以只能用 null 去填充

所以本质上 SerializationProxy 是无法按正常的方式序列化的,只是 jdk 底层给绕过了 constructor 的这个限制
Objenesis应该也是判断jvm提供商来调用底层逻辑,虽然没细看,但感觉应该可以,反正代码是能跑通的
public class ReflectTest {
public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, IllegalAccessException, NoSuchFieldException {
Objenesis objenesis = new ObjenesisStd();
Class<?> clazz = Class.forName("org.bson.types.ObjectId$SerializationProxy",false,ReflectTest.class.getClassLoader());
ObjectInstantiator<?> instantiator = objenesis.getInstantiatorOf(clazz);
Object a = instantiator.newInstance();
System.out.println(a);
Field field = clazz.getDeclaredField("bytes");
field.setAccessible(true);
field.set(a,new byte[12]);
Method method = clazz.getDeclaredMethod("readResolve");
method.setAccessible(true);
Object result = method.invoke(a);
System.out.println(result);
}
}