fastjson2
fastjson2 copied to clipboard
[BUG] 空List反序列化后向其中添加元素时报java.lang.UnsupportedOperationException异常
问题描述
ObjectReaderImplList中239行使用了Collections.emptyList(),导致空List反序列化后向其中添加元素时报java.lang.UnsupportedOperationException异常
环境信息
- 版本信息:[Fastjson2 2.0.40]
重现步骤
参见以下bug复现代码
public class App {
public static void main(String[] args) {
List<Person> sourceList = new ArrayList<>();
// sourceList.add(new Person("zhangsan"));
Map<String, Object> source = new HashMap<>();
source.put("data", sourceList);
String json = JSON.toJSONString(source);
System.out.println(json);
JSONObject target = JSON.parseObject(json);
Type type = getType();
List<Person> targetList = target.getObject("data", type);
System.out.println(targetList.getClass().getName());
targetList.add(new Person("lisi"));
System.out.println(JSON.toJSONString(targetList));
}
public static Type getType() {
try {
App app = new App();
Method method = app.getClass().getMethod("test");
return method.getGenericReturnType();
} catch (Throwable e) {
e.printStackTrace();
throw new RuntimeException(e);
}
}
public List<Person> test() {
return new ArrayList<>();
}
public static class Person {
private String name;
public Person() {
}
public Person(String name) {
this.name = name;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
}
期待的正确结果
空List反序列化后可以正常向其中添加元素。
全面排查并仔细评估所有使用了Collections.emptyList()和Collections.emptySet()的地方,有类似问题的地方建议一并修改。
相关日志输出
以上bug复现代码日志输出如下
{"data":[]}
java.util.Collections$EmptyList
Exception in thread "main" java.lang.UnsupportedOperationException
at java.base/java.util.AbstractList.add(AbstractList.java:153)
at java.base/java.util.AbstractList.add(AbstractList.java:111)
at com.dcr.demo.App.main(App.java:34)
附加信息
我使用fastjson2做Openfeign接口的序列化及反序列化,当Openfeign接口方法返回值不为空时,向list中添加元素不报异常。当返回空List后,再向list中添加元素时会报UnsupportedOperationException异常。
https://oss.sonatype.org/content/repositories/snapshots/com/alibaba/fastjson2/fastjson2/2.0.41-SNAPSHOT/ 问题已修复,请用2.0.41-SNAPSHOT帮忙验证
https://github.com/alibaba/fastjson2/releases/tag/2.0.41 问题已修复,请用新版本
@wenshao 此问题在 2.0.42以及以后版本又出现了
从42-45版本都有这个问题
@cycle2zhou 新版本的异常信息能提供下么?
@cycle2zhou 新版本的异常信息能提供下么?
温少,这是我们升级dubbo-3.2.10
后遇到的异常调用栈。 @wenshao
{"timestamp":"2024-01-26T14:02:04.917",
"message":" [DUBBO] Decode rpc result failed: org.apache.dubbo.common.serialize.SerializationException: java.lang.UnsupportedOperationException, dubbo version: 3.2.10, current host: 192.168.108.128, error code: 4-20. This may be caused by , go to https://dubbo.apache.org/faq/4/20 to find instructions. ","thread":"DubboServerHandler-192.168.108.128:8504-thread-392","logger":"org.apache.dubbo.rpc.protocol.dubbo.DecodeableRpcResult","level":"WARN","trace_id":"21c6f0fc84474d55a2e32f6cd7ddbc13.70.17062485467755809","exception":""}
java.io.IOException: org.apache.dubbo.common.serialize.SerializationException: java.lang.UnsupportedOperationException
at org.apache.dubbo.common.serialize.DefaultSerializationExceptionWrapper.handleToIOException(DefaultSerializationExceptionWrapper.java:353)
at org.apache.dubbo.common.serialize.DefaultSerializationExceptionWrapper.access$000(DefaultSerializationExceptionWrapper.java:27)
at org.apache.dubbo.common.serialize.DefaultSerializationExceptionWrapper$ProxyObjectInput.readObject(DefaultSerializationExceptionWrapper.java:172)
at org.apache.dubbo.rpc.protocol.dubbo.DecodeableRpcResult.handleValue(DecodeableRpcResult.java:176)
at org.apache.dubbo.rpc.protocol.dubbo.DecodeableRpcResult.decode(DecodeableRpcResult.java:110)
at org.apache.dubbo.rpc.protocol.dubbo.DecodeableRpcResult.decode(DecodeableRpcResult.java:153)
at org.apache.dubbo.remoting.transport.DecodeHandler.decode(DecodeHandler.java:61)
at org.apache.dubbo.remoting.transport.DecodeHandler.received(DecodeHandler.java:49)
at org.apache.dubbo.remoting.transport.dispatcher.ChannelEventRunnable.run(ChannelEventRunnable.java:64)
at org.apache.dubbo.common.threadpool.ThreadlessExecutor$RunnableWrapper.run(ThreadlessExecutor.java:151)
at org.apache.dubbo.common.threadpool.ThreadlessExecutor.waitAndDrain(ThreadlessExecutor.java:77)
at org.apache.dubbo.rpc.AsyncRpcResult.get(AsyncRpcResult.java:219)
at org.apache.dubbo.rpc.protocol.AbstractInvoker.waitForResultIfSync(AbstractInvoker.java:292)
at org.apache.dubbo.rpc.protocol.AbstractInvoker.invoke(AbstractInvoker.java:194)
at org.apache.dubbo.rpc.listener.ListenerInvokerWrapper.invoke(ListenerInvokerWrapper.java:71)
at io.seata.integration.dubbo.ApacheDubboTransactionPropagationFilter.invoke(ApacheDubboTransactionPropagationFilter.java:69)
at com.leoao.starter.dubbo.generic.GenericFilterWrapper.invoke(GenericFilterWrapper.java:67)
at org.apache.dubbo.rpc.cluster.filter.FilterChainBuilder$CopyOfFilterChainNode.invoke(FilterChainBuilder.java:349)
at com.alibaba.dubbo.rpc.Invoker$CompatibleInvoker.invoke(Invoker.java:77)
at io.seata.integration.dubbo.alibaba.AlibabaDubboTransactionPropagationFilter.invoke(AlibabaDubboTransactionPropagationFilter.java:45)
at com.alibaba.dubbo.rpc.Filter.invoke(Filter.java:34)
at com.leoao.starter.dubbo.generic.GenericFilterWrapper.invoke(GenericFilterWrapper.java:67)
at org.apache.dubbo.rpc.cluster.filter.FilterChainBuilder$CopyOfFilterChainNode.invoke(FilterChainBuilder.java:349)
at org.apache.dubbo.rpc.filter.RpcExceptionFilter.invoke(RpcExceptionFilter.java:40)
at com.leoao.starter.dubbo.generic.GenericFilterWrapper.invoke(GenericFilterWrapper.java:67)
at org.apache.dubbo.rpc.cluster.filter.FilterChainBuilder$CopyOfFilterChainNode.invoke(FilterChainBuilder.java:349)
at org.apache.dubbo.rpc.filter.ActiveLimitFilter.invoke(ActiveLimitFilter.java:85)
at com.leoao.starter.dubbo.generic.GenericFilterWrapper.invoke(GenericFilterWrapper.java:67)
at org.apache.dubbo.rpc.cluster.filter.FilterChainBuilder$CopyOfFilterChainNode.invoke(FilterChainBuilder.java:349)
at org.apache.dubbo.rpc.cluster.filter.FilterChainBuilder$CallbackRegistrationInvoker.invoke(FilterChainBuilder.java:197)
at org.apache.dubbo.rpc.protocol.ReferenceCountInvokerWrapper.invoke(ReferenceCountInvokerWrapper.java:106)
at org.apache.dubbo.rpc.cluster.support.AbstractClusterInvoker.invokeWithContext(AbstractClusterInvoker.java:412)
at org.apache.dubbo.rpc.cluster.support.FailoverClusterInvoker.doInvoke(FailoverClusterInvoker.java:82)
at org.apache.dubbo.rpc.cluster.support.AbstractClusterInvoker.invoke(AbstractClusterInvoker.java:366)
at org.apache.dubbo.rpc.cluster.router.RouterSnapshotFilter.invoke(RouterSnapshotFilter.java:46)
at org.apache.dubbo.rpc.cluster.filter.FilterChainBuilder$CopyOfFilterChainNode.invoke(FilterChainBuilder.java:349)
at ...
Caused by: org.apache.dubbo.common.serialize.SerializationException: java.lang.UnsupportedOperationException
... 150 common frames omitted
Caused by: java.lang.UnsupportedOperationException: null
at java.util.AbstractList.add(AbstractList.java:148)
at java.util.AbstractList.add(AbstractList.java:108)
at com.alibaba.fastjson2.reader.ORG_20_5_Page.readJSONBObject(Unknown Source)
at com.alibaba.fastjson2.JSONB.parseObject(JSONB.java:540)
at org.apache.dubbo.common.serialize.fastjson2.FastJson2ObjectInput.readObject(FastJson2ObjectInput.java:171)
at org.apache.dubbo.common.serialize.DefaultSerializationExceptionWrapper$ProxyObjectInput.readObject(DefaultSerializationExceptionWrapper.java:170)
... 147 common frames omitted
Page的类结构可以提供下么?
Page的类结构可以提供下么?
温少,这个问题调整后已修复 @wenshao
先前使用private List<T> data = Collections.emptyList();
,这个问题调整后已修复。
package basic.model;
import lombok.Data;
import org.springframework.util.CollectionUtils;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;
/**
* 分页模型
*/
@Data
public class Page<T> implements Serializable {
private static final long serialVersionUID = 201612008194101999L;
/**
* 页面元素
*/
// 有问题
private List<T> data = Collections.emptyList();
// 修复正常
// private List<T> data = new ArrayList<>();
/**
* 当前页码
*/
private Integer pageIndex = 0;
/**
* 每页展示数
*/
private Integer pageSize = 0;
/**
* 总页码
*/
private Integer totalPage = 0;
/**
* 总数
*/
private Long total = 0L;
/**
* 构建
*/
public static <T> Page<T> of (List<T> data, Integer pageIndex, Integer pageSize, Long total) {
Page<T> page = new Page<>();
page.setData(data);
page.setPageIndex(pageIndex);
page.setPageSize(pageSize);
page.setTotal(total);
page.setTotalPage(total, pageSize);
return page;
}
@Deprecated
public void setData(List<T> items) {
if (CollectionUtils.isEmpty(items)) {
return;
}
this.data = items.stream().filter(Objects::nonNull).collect(Collectors.toList());
}
/**
* 设置总页数
*
* @param total 总数
* @param pageSize 分页大小
*/
@Deprecated
public void setTotalPage (Long total, Integer pageSize) {
this.total = total;
this.pageSize = pageSize;
if (total == null || total == 0) {
return;
}
if (pageSize == null || pageSize == 0) {
return;
}
int totalPage;
if (0 == total % pageSize)
totalPage = (int) (total / pageSize);
else
totalPage = (int) (total / pageSize + 1);
if(0==totalPage)
totalPage = 1;
this.totalPage = totalPage;
}
}
问题无法重现,能够调试看下序列化之前这个Page里面的data是什么类型么?或者调试反序列化报错UnsupportedOperationException时当前List的类型
问题无法重现,能够调试看下序列化之前这个Page里面的data是什么类型么?或者调试反序列化报错UnsupportedOperationException时当前List的类型
温少,data是java.util.Collections.EmptyList
类型 @wenshao
package basic.model;
import lombok.Data;
import org.springframework.util.CollectionUtils;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;
/**
* 分页模型
*/
@Data
public class Page<T> implements Serializable {
private static final long serialVersionUID = 201612008194101999L;
/**
* 页面元素
*/
// 有问题
private List<T> data = Collections.emptyList();
// 修复正常
// private List<T> data = new ArrayList<>();
/**
* 当前页码
*/
private Integer pageIndex = 0;
/**
* 每页展示数
*/
private Integer pageSize = 0;
/**
* 总页码
*/
private Integer totalPage = 0;
/**
* 总数
*/
private Long total = 0L;
/**
* 构建
*/
public static <T> Page<T> of (List<T> data, Integer pageIndex, Integer pageSize, Long total) {
Page<T> page = new Page<>();
page.setData(data);
page.setPageIndex(pageIndex);
page.setPageSize(pageSize);
page.setTotal(total);
page.setTotalPage(total, pageSize);
return page;
}
@Deprecated
public void setData(List<T> items) {
if (CollectionUtils.isEmpty(items)) {
return;
}
this.data = items.stream().filter(Objects::nonNull).collect(Collectors.toList());
}
/**
* 设置总页数
*
* @param total 总数
* @param pageSize 分页大小
*/
@Deprecated
public void setTotalPage (Long total, Integer pageSize) {
this.total = total;
this.pageSize = pageSize;
if (total == null || total == 0) {
return;
}
if (pageSize == null || pageSize == 0) {
return;
}
int totalPage;
if (0 == total % pageSize)
totalPage = (int) (total / pageSize);
else
totalPage = (int) (total / pageSize + 1);
if(0==totalPage)
totalPage = 1;
this.totalPage = totalPage;
}
}
Page类只有JDK生成的默认构造函数,其data
字段值会初始化为Collections.emptyList()
。
提供者端是通过静态方法Page.of(...)
构造Page对象,会返回ArrayList<>()的多条数据;
消费者端,FastJson2通过默认构造函数创建Page对象,data
字段只能使用默认值Collections.emptyList()
,其实现类是jdk-8的EmptyList<E> extends AbstractList<E>
,EmptyList未覆盖add方法,AbstractList定义了add(int index, E element)
这个不好解决呢
这个不好解决呢
温少,这个我们通过初始化其他类型,升级解决了。是我们使用的问题,不是框架层面的。不用解决哈
https://github.com/alibaba/fastjson2/releases/tag/2.0.48 请用新版本
@wenshao 使用了2.0.49,此问题还是会出现,会考虑优化这个问题吗?
https://github.com/alibaba/fastjson2/releases/tag/2.0.52 问题已修复,请用新版本