004-protostuff踩坑-java bean新增字段反序列化失败问题

004-protostuff踩坑-java bean新增字段反序列化失败问题protostuff避免更改java对象字段,比如新增一个,导致redis等缓存的数据反序列化失败问题??问题重现:我们有个方法通过attrKey查询List,同时方法中有缓存,会优先查询缓存,没有读库,然后写缓存返回。方法伪代码如下:publicvoidsetId(StringattrKey){//从缓存查询List<ConfAttr>attrValues=cacheClient.get(attrKey);

大家好,又见面了,我是你们的朋友全栈君。

protostuff 避免 更改 java 对象字段 ,比如新增一个,导致 redis 等缓存 的数据反序列化失败问题??

问题重现:

我们有个方法 通过 attrKey 查询 List ,同时方法中有缓存,会优先查询缓存,没有读库,然后 写缓存 返回。

方法伪代码如下:

 public ConfAttr getConf(String attrKey) { 
   
    // 从 缓存查询
    List<ConfAttr> attrValues = cacheClient.get(attrKey);
    if(attrValues !=null && !attrValues.isEmpty(){ 
   
        return attrValues.get(0);
    }
    // 读库
     List<ConfAttr> attrValues = selectFromDb(attrKey);
     cacheClient.put(attrKey,attrValues, cacheSeconds);
     return attrValues.get(0);
}

然后某天业务迭代 ConfAttr类增加 source 属性。上线后 redis 反序列化出错。错误内容如下:

Exception in thread "main" java.lang.RuntimeException: Reading from a byte array threw an IOException (should never happen).
	at io.protostuff.IOUtil.mergeFrom(IOUtil.java:54)
	at io.protostuff.ProtostuffIOUtil.mergeFrom(ProtostuffIOUtil.java:104)
	at com.cm.cache.serialize.ProtostuffSerializer.deserialize(ProtostuffSerializer.java:34)
	at com.cm.cache.redis.ShardedRedisClient.get(ShardedRedisClient.java:88)
	at com.test.RedisTest.main(RedisTest.java:51)
Caused by: io.protostuff.ProtobufException: CodedInput encountered an embedded string or bytes that misreported its size.
	at io.protostuff.ProtobufException.misreportedSize(ProtobufException.java:86)
	at io.protostuff.ByteArrayInput.readString(ByteArrayInput.java:438)
	at io.protostuff.runtime.RuntimeUnsafeFieldFactory$9$1.mergeFrom(RuntimeUnsafeFieldFactory.java:753)
	at io.protostuff.runtime.RuntimeSchema.mergeFrom(RuntimeSchema.java:466)
	at io.protostuff.runtime.ObjectSchema.readObjectFrom(ObjectSchema.java:693)
	at io.protostuff.runtime.IdStrategy$8.mergeFrom(IdStrategy.java:503)
	at io.protostuff.ByteArrayInput.mergeObjectEncodedAsGroup(ByteArrayInput.java:518)
	at io.protostuff.ByteArrayInput.mergeObject(ByteArrayInput.java:490)
	at io.protostuff.runtime.IdStrategy$10.mergeFrom(IdStrategy.java:583)
	at io.protostuff.runtime.IdStrategy$10.mergeFrom(IdStrategy.java:528)
	at io.protostuff.runtime.ObjectSchema.readObjectFrom(ObjectSchema.java:590)
	at io.protostuff.runtime.ObjectSchema.mergeFrom(ObjectSchema.java:350)
	at io.protostuff.ByteArrayInput.mergeObjectEncodedAsGroup(ByteArrayInput.java:518)
	at io.protostuff.ByteArrayInput.mergeObject(ByteArrayInput.java:490)
	at io.protostuff.runtime.RuntimeUnsafeFieldFactory$15$1.mergeFrom(RuntimeUnsafeFieldFactory.java:1217)
	at io.protostuff.runtime.RuntimeSchema.mergeFrom(RuntimeSchema.java:466)
	at io.protostuff.IOUtil.mergeFrom(IOUtil.java:45)
	... 4 more

问题答案

正确答案1:
可以使用@Tag 注解 指定字段顺序。

错误答案1:
将新增的字段加载java bean 类 的末尾 就可以避免该问题了(但是实际上这种还是存在错误的可能 ,具体参考 下一节的原理分析),

PS: 这次出问题 是因为 把sourceid 加入到 属性定义的中间了。

基于错误答案1的尝试截图

改动前:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Putj03Ba-1627548803636)(../../images/049c9aada59c718ec5457109bf13e52743ddb00da5efb01a81dc7c3fd317f060.png)]

改动后:

在这里插入图片描述

知识点拓展 protostuff 按照什么顺序来给类的 字段 序列化呢?

说明

  1. protostuff 只序列话字段值,不序列化 key(map可能除外)
  2. 顺序默认按照 typeClass.getDeclaredFields() (但是 jdk的这个方法 返回顺序,不是按照源码 的字段申明顺序,可能会被jdk 重编译 而改变顺序,大部分时候是按照申明的顺序)
  3. 所以 有时候添加字段,如果加载类 字段申明的末尾,不会出问题,加在中间,反序列化就会出问题。
  4. 不能依赖于 typeClass.getDeclaredFields(), 强制要求 按照 @Tag 添加指定字段顺序。(参考 https://houbb.github.io/2018/07/01/reflection-12-fields)
  5. protostuff 根据 getDeclaredFields 获取字段列表:会忽略 static transient 以及用注解@Exclude 。

方法入口

入口
io.protostuff.runtime.RuntimeSchema#fill

static void fill(Map<String, java.lang.reflect.Field> fieldMap,
            Class<?> typeClass)
{ 
   
    if (Object.class != typeClass.getSuperclass())
        fill(fieldMap, typeClass.getSuperclass());

    for (java.lang.reflect.Field f : typeClass.getDeclaredFields())
    { 
   
        int mod = f.getModifiers();
        if (!Modifier.isStatic(mod) && !Modifier.isTransient(mod) && f.getAnnotation(Exclude.class) == null)
            fieldMap.put(f.getName(), f);
    }
}

调用图
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-zXSrKAzV-1627548803639)(../../images/a489204dd0545cf88c9bf8c4f2b996f4fd40a36276197174d4a00d1af0d9b201.png)]

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请联系我们举报,一经查实,本站将立刻删除。

发布者:全栈程序员-站长,转载请注明出处:https://javaforall.net/139827.html原文链接:https://javaforall.net

(0)
全栈程序员-站长的头像全栈程序员-站长


相关推荐

  • Django(14)模型中常用的属性(超详细)[通俗易懂]

    Django(14)模型中常用的属性(超详细)[通俗易懂]模型中常用字段字段说明AutoField一般不需要使用这个类型,自增长类型,数据表的字段类型为整数,长度为11位BigAutoField自增长类型,数据表的字段类型为bigint,长度为2

    2022年8月7日
    0
  • redis+springboot_redis部署

    redis+springboot_redis部署目录1、搭建环境2、测试Redis3、StringRedisTemplate3.1、介绍3.2、StringRedisTemplate常用操作3.3、StringRedisTemplate的使用4、RedisTemplate4.1、介绍4.2、RedisTemplate常用操作4.3、RedisTemplate的使用SpringBootData(数据)Redis中提供了RedisTemplate和StringRedisTemplate,其中StringRedisTemplate是RedisTem.

    2022年9月22日
    0
  • 观察float BIT

    观察float BIT

    2021年9月14日
    54
  • pip怎么卸载安装包_python pip升级

    pip怎么卸载安装包_python pip升级pip卸载安装的所有python包

    2022年10月19日
    0
  • 思维导图——快速掌握子网划分(实例详解)

    思维导图——快速掌握子网划分(实例详解)目录一、子网划分的作用二、IP地址的组成三、IPV4地址四、IP地址的分类五、如何计算网络号六、地址规划6.1子网数概念6.2求地址网络可分为几段6.3CIDR:把若干网络合并成一个网段6.4例题实战七、思维导图及总结一、子网划分的作用作用一:计算网络号,通过网络号选择正确的网络设备连接终端设备1.清楚IP地址四段点分十进制数和子网掩码,对应的网络号是什么2.交换机是用来连接相同网段的设备,路由器是用来连接不同网段的设备。网络号一…

    2022年6月27日
    26
  • WPF 使用TextBox做密码输入框

    WPF 使用TextBox做密码输入框密码输入框需要输入的密码不能显示明文,用其他的特殊字符代替显示。显示效果如下:Xaml部分代码如下:

    2022年7月25日
    113

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

关注全栈程序员社区公众号