java 如果对象被移动到另一个包或重命名,我该如何反序列化它?
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/2358886/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
How can I deserialize the object, if it was moved to another package or renamed?
提问by dma_k
Consider the following situation:
考虑以下情况:
There is a serialization file, created by the older version of the application. Unfortunately, the package has changed for the class, that has been serialized. And now I need to load the information from this file into the same class, but located in different package. This class has serialVersionUIDdefined and has not changed (i.e. is compatible).
有一个序列化文件,由旧版本的应用程序创建。不幸的是,该包已更改为已序列化的类。现在我需要将这个文件中的信息加载到同一个类中,但位于不同的包中。此类已serialVersionUID定义且未更改(即兼容)。
Question: Is it possible to load the new class instances from this file using any tricks (except trivial copying the class into old package and then using the deserialization wrapper logic)? It is possible to use readResolve()to recover from moving/renaming the class? If not, please, explain why.
问题:是否可以使用任何技巧(除了简单地将类复制到旧包然后使用反序列化包装器逻辑)从该文件加载新类实例?可以用来readResolve()从移动/重命名类中恢复吗?如果不是,请解释原因。
采纳答案by Michael Borgwardt
Question: Is it possible to load the new class instances from this file using any tricks (except trivial copying the class into old package and then using the deserialization wrapper logic)?
问题:是否可以使用任何技巧(除了简单地将类复制到旧包然后使用反序列化包装器逻辑)从该文件加载新类实例?
I don't think there are any other "tricks" you could use that don't involve at least a partial reimplementation of the serialization protocol.
我认为没有任何其他“技巧”可以使用,至少不涉及序列化协议的部分重新实现。
It is possible to use readResolve() to recover from moving/renaming the class? If not, please, explain why.
可以使用 readResolve() 从移动/重命名类中恢复吗?如果不是,请解释原因。
No, because the deserialization mechanism will fail much earlier, at the stage where it tries to locate the class that's being deserialized - it has no way of knowing that a class in a different package has a readResolve()method it's supposed to use.
不,因为反序列化机制会更早地失败,在它尝试定位正在反序列化的类的阶段 - 它无法知道不同包中的类具有readResolve()它应该使用的方法。
回答by Igor Nardin
It is possible:
有可能的:
class HackedObjectInputStream extends ObjectInputStream {
public HackedObjectInputStream(InputStream in) throws IOException {
super(in);
}
@Override
protected ObjectStreamClass readClassDescriptor() throws IOException, ClassNotFoundException {
ObjectStreamClass resultClassDescriptor = super.readClassDescriptor();
if (resultClassDescriptor.getName().equals("oldpackage.Clazz"))
resultClassDescriptor = ObjectStreamClass.lookup(newpackage.Clazz.class);
return resultClassDescriptor;
}
}
This also allows one to ignore serialVersionUIDs mismatch or even deserialize a class if its field structure was changed.
这也允许忽略serialVersionUIDs不匹配,甚至在其字段结构发生变化时反序列化一个类。
回答by José Roberto Araújo Júnior
If you use Cygnus Hex Editor you can manually change the name of the package/class.
如果您使用 Cygnus Hex Editor,您可以手动更改包/类的名称。
If the new name (always including the package) has the same size you can just replace the old name by the new name, but if the size has changed you need to update the first 2 chars before the name with new new length.
如果新名称(始终包括包)具有相同的大小,您可以用新名称替换旧名称,但如果大小已更改,则需要使用新的长度更新名称前的前 2 个字符。
Right click the Standard Data Types and change to Big Endian.
右键单击标准数据类型并更改为 Big Endian。
The length is a Signed Word.
长度是一个签名字。
For example:
例如:
00 0E 70 61 63 6B 61 67 65 2E 53 61 6D 70 6C 65
. . p a c k a g e . S a m p l e
is how package.Sample is writen. 00 0E means 14, the number of chars "package.Sample" has.
是 package.Sample 的编写方式。00 0E 表示 14,即“package.Sample”的字符数。
If we want to change to newpackage.Sample we replace that string to:
如果我们想更改为 newpackage.Sample 我们将该字符串替换为:
00 12 6E 65 77 70 61 63 6B 61 67 65 2E 53 61 6D 70 6C 65
. . n e w p a c k a g e . S a m p l e
00 12 means 18, the number of chars "newpackage.Sample" has.
00 12 表示 18,即“newpackage.Sample”的字符数。
And of course you can make a patcher to update this automatically.
当然,您可以制作一个修补程序来自动更新它。
回答by oxbow_lakes
Probably your best bet is to recreate the old class (name, package and serial ID), read in the serialized form, then copy the data to an instance of the new object and reserialize that.
最好的办法可能是重新创建旧类(名称、包和序列 ID),以序列化形式读取,然后将数据复制到新对象的实例并重新序列化。
If you have a lot of these serialized objects, perhaps you could write a small script to do this so the "schema change" gets done in one go.
如果您有很多这样的序列化对象,也许您可以编写一个小脚本来执行此操作,以便一次性完成“架构更改”。
Another option is to resurrect the old class and implement its readResolvemethod to return an instance of the new class (perhaps by declaring a copy constructor). Personally I think I'd go for the schema change script and then delete the old class for good.
另一种选择是复活旧类并实现其readResolve方法以返回新类的实例(可能通过声明复制构造函数)。我个人认为我会使用模式更改脚本,然后永久删除旧类。
回答by Ivan Nikitin
Use this class instead of ObjectInputStream if your classes moved to another namespace.
如果您的类移动到另一个命名空间,请使用此类而不是 ObjectInputStream。
class SafeObjectInputStream extends ObjectInputStream {
private final String oldNameSpace;
private final String newNameSpace;
public SafeObjectInputStream(InputStream in, String oldNameSpace, String newNameSpace) throws IOException {
super(in);
this.oldNameSpace = oldNameSpace;
this.newNameSpace = newNameSpace;
}
@Override
protected ObjectStreamClass readClassDescriptor() throws IOException, ClassNotFoundException {
ObjectStreamClass result = super.readClassDescriptor();
try {
if (result.getName().contains(oldNameSpace)) {
String newClassName = result.getName().replace(oldNameSpace, newNameSpace);
// Test the class exists
Class localClass = Class.forName(newClassName);
Field nameField = ObjectStreamClass.class.getDeclaredField("name");
nameField.setAccessible(true);
nameField.set(result, newClassName);
ObjectStreamClass localClassDescriptor = ObjectStreamClass.lookup(localClass)
Field suidField = ObjectStreamClass.class.getDeclaredField("suid");
suidField.setAccessible(true);
suidField.set(result, localClassDescriptor.getSerialVersionUID());
}
} catch(Exception e) {
throw new IOException("Exception when trying to replace namespace", e);
}
return result;
}
@Override
protected Class<?> resolveClass(ObjectStreamClass desc) throws IOException, ClassNotFoundException {
if (desc.getName().contains(oldNameSpace)) {
String newClassName = desc.getName().replace(oldNameSpace, newNameSpace);
return Class.forName(newClassName);
}
return super.resolveClass(desc);
}
}
You may use it as follows:
您可以按如下方式使用它:
ObjectInputStream objectStream = new SafeObjectInputStream(inputStream, "org.oldnamespace", "org.newnamespace");
objectStream.readObject();
It won't fail with StreamCorruptedException if some of your classes change. Instead, it will try to load as many fields as possible. You may perform data validation/upgrade by implementing readObjectmethod in your classes.
如果您的某些类发生更改,它不会因 StreamCorruptedException 而失败。相反,它会尝试加载尽可能多的字段。您可以通过readObject在您的类中实现方法来执行数据验证/升级。
private void readObject(java.io.ObjectInputStream in) throws IOException, ClassNotFoundException {
in.defaultReadObject();
// Validate read data here
}
回答by Roman
I don't think it's possible to do what you want.
我认为不可能做你想做的事。
Format of serialization file keeps class names. In detail it has next structure:
序列化文件的格式保留类名。详细地说,它具有下一个结构:
AC ED
protocol version number
object data
object's class description
交流电
协议版本号
对象数据
对象的类描述
Class description has next format:
类描述具有下一个格式:
full class name
serial version unique ID (SHA1 from fields and methods signatures)
serialization options
field descriptors
全班名
序列版本唯一 ID(来自字段和方法签名的 SHA1)
序列化选项
字段描述符
When you try to deserialize object serialization mechanism compares class names first (and you don't pass this step), then it compares serialVersionUID's and only after passing these 2 steps deserializes object.
当您尝试反序列化对象序列化机制时,首先比较类名(并且您没有通过此步骤),然后它比较serialVersionUID,并且只有在通过这两个步骤后才会反序列化对象。
回答by Ivan Kovtun
Addition to the hex editing way.
除了十六进制编辑方式。
It worked for me and it was easier to replace old package name with the new ones instead of implementing class replacements overriding ObjectInputStream. Especially because there were anonymous classes as well.
它对我有用,用新的包名替换旧的包名更容易,而不是实现覆盖 ObjectInputStream 的类替换。特别是因为还有匿名类。
Here is a script which replaces old class path with the new class path in a binary format.
这是一个用二进制格式的新类路径替换旧类路径的脚本。
Here is a content o my hexreplace.shscript:
这是我的hexreplace.sh脚本的内容:
#!/bin/bash
set -xue
OLD_STR=$(echo -n | hexdump -ve '1/1 "%.2X"')
NEW_STR=$(echo -n | hexdump -ve '1/1 "%.2X"')
SRC_FILE=
DST_FILE=
TMP_FILE=$(mktemp /tmp/bin.patched.XXXXXXXXXX)
[ -f $SRC_FILE ]
hexdump -ve '1/1 "%.2X"' "$SRC_FILE" | sed "s/$OLD_STR/$NEW_STR/g" | xxd -r -p > "$TMP_FILE"
mv "$TMP_FILE" "$DST_FILE"
Run
跑
hexreplace.sh old.class.path new.class.path source_file destination_file
Script works correctly when source and destination files are the same.
当源文件和目标文件相同时,脚本可以正常工作。

