大家好,又见面了,我是你们的朋友全栈君。
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的尝试截图
改动前:
改动后:
知识点拓展 protostuff 按照什么顺序来给类的 字段 序列化呢?
说明
- protostuff 只序列话字段值,不序列化 key(map可能除外)
- 顺序默认按照 typeClass.getDeclaredFields() (但是 jdk的这个方法 返回顺序,不是按照源码 的字段申明顺序,可能会被jdk 重编译 而改变顺序,大部分时候是按照申明的顺序)
- 所以 有时候添加字段,如果加载类 字段申明的末尾,不会出问题,加在中间,反序列化就会出问题。
- 不能依赖于 typeClass.getDeclaredFields(), 强制要求 按照 @Tag 添加指定字段顺序。(参考 https://houbb.github.io/2018/07/01/reflection-12-fields)
- 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);
}
}
调用图
发布者:全栈程序员-用户IM,转载请注明出处:https://javaforall.cn/139827.html原文链接:https://javaforall.cn
【正版授权,激活自己账号】: Jetbrains全家桶Ide使用,1年售后保障,每天仅需1毛
【官方授权 正版激活】: 官方授权 正版激活 支持Jetbrains家族下所有IDE 使用个人JB账号...