【问题标题】:ObjectStream: is there a way to read serialized object as properties map?ObjectStream:有没有办法将序列化对象读取为属性映射?
【发布时间】:2017-01-09 13:18:53
【问题描述】:

是否有任何标准方式或任何实用程序库来读取/浏览序列化(通过 ObjectOutputStream)对象的属性?

我要解决的问题是升级使用 ObjectOutputStream(旧版)序列化并存储在数据库中的数据。在我的例子中,一些内部字段被彻底改变和重命名。我无法使用 ObjectInputStream 读回对象,因为更改字段的值会丢失(设置为 null)。

特别是将来可能需要再次升级它,所以如果我可以用 XML 序列化替换以这种方式存储的旧数据会更好。但是完成这项任务的一般方法需要遍历属性(它们的名称、类型和值)。我无法找到从序列化数据中读取此类元数据的标准方法(例如,jackson 库可以将 JSON 读取为对象或属性和映射的映射,您可以轻松操作它们)。

是否有任何低级库来处理数据,用 ObjectOutputStream 序列化?结果输出看起来像是包含有关序列化字段名称及其类型的信息。作为最后的手段,我可​​以整理格式,但我认为已经有人可以做到这一点,但我自己却找不到任何库。

例如,我有一堂课

public class TestCase implements Serializable
{
    int id;
    double doubleValue;
    String stringValue;

    public TestCase(int id, double doubleValue, String stringValue)
    {
        this.id = id;
        this.doubleValue = doubleValue;
        this.stringValue = stringValue;
    }
}

改成

public class TestCase implements Serializable
{
    ComplexId id;
    double doubleValue;
    String stringValue;

    public TestCase(ComplexId id, double doubleValue, String stringValue)
    {
        this.id = id;
        this.doubleValue = doubleValue;
        this.stringValue = stringValue;
    }
}

class ComplexId implements Serializable
{
    int staticId;
    String uuid;

    public ComplexId(int staticId, String uuid)
    {
        this.staticId = staticId;
        this.uuid = uuid;
    }
}

升级值本身不是问题,我只是不知道如何偷看它并放回一个新的(具有新类型)而无需自定义实现序列化/反序列化协议(这是最后的手段)我)。

【问题讨论】:

    标签: java serialization objectoutputstream


    【解决方案1】:

    如果您有带有原始 .java 文件的版本控制系统编译它们,请使用 ObjectInputStream 读取信息。

    另一种选择是根据Object Serialization Stream ProtocolUseful information about serialization手动读取字节数据。

    我编写了这个示例来证明没有类文件的反序列​​化是可能的。不支持继承。它仅适用于原始类型字段和 java.lang.String。

    class CustomDeserialization {
    
        public static class A implements Serializable {
            private static final long serialVersionUID = 123124345135L;
    
            int foo = 1;
            String bar = "baz";
        }
    
        private byte[] bytes;
        private int cursor;
    
        CustomDeserialization(byte[] bytes) {
            this.bytes = bytes;
        }
    
        private List<List<Object>> parse() {
            cursor = 2; //skip STREAM_MAGIC
            short classNameLength = getShort();
            String className = getString(classNameLength);
            cursor += 9; //skip serialVersionUID and flag tells object supports serialization
            short numberOfFields = getShort();
            List<List<Object>> result = new ArrayList<>();
            List<Character> types = new ArrayList<>();
            List<Object> values = new ArrayList<>();
            List<String> classNames = new ArrayList<>();
            for (int fieldIndex = 0; fieldIndex < numberOfFields; fieldIndex++) {
                char c = getCharType();
                types.add(c);
                short fieldNameLength = getShort();
                String fieldName = getString(fieldNameLength);
                List<Object> objects = new ArrayList<>();
                if (c == 'L') {
                    byte objectType = getByte();
                    if (objectType == ObjectStreamConstants.TC_REFERENCE) {
                        getShort();
                        objects.add(classNames.get(getShort() - 1));
                    } else {
                        short fieldClassNameLength = getShort();
                        String fieldClassName = getString(fieldClassNameLength);
                        classNames.add(fieldClassName);
                        objects.add(fieldClassName);
                    }
                } else {
                    Class clazz = getCorrectType(c);
                    objects.add(clazz);
                }
                objects.add(fieldName);
                result.add(objects);
            }
            cursor += 2; //skip TC_ENDBLOCKDATA & TC_NULL
            for (int fieldIndex = 0; fieldIndex < numberOfFields; fieldIndex++) {
                result.get(fieldIndex).add(getValue(types.get(fieldIndex), values));
            }
            return result;
        }
    
        private String getString(int lengthOfClassName) {
            String s = new String(Arrays.copyOfRange(bytes, cursor, cursor + lengthOfClassName));
            cursor += lengthOfClassName;
            return s;
        }
    
        private char getCharType() {
            char c = (char) (bytes[cursor] & 0xFF);
            cursor++;
            return c;
        }
    
        private char getChar() {
            ByteBuffer bb = ByteBuffer.allocate(2);
            bb.order(ByteOrder.LITTLE_ENDIAN);
            bb.put(bytes[cursor + 1]);
            bb.put(bytes[cursor]);
            cursor += 2;
            return bb.getChar(0);
        }
    
        private short getShort() {
            ByteBuffer bb = ByteBuffer.allocate(2);
            bb.order(ByteOrder.LITTLE_ENDIAN);
            bb.put(bytes[cursor + 1]);
            bb.put(bytes[cursor]);
            cursor += 2;
            return bb.getShort(0);
        }
    
        private double getDouble() {
            ByteBuffer bb = ByteBuffer.allocate(8);
            bb.order(ByteOrder.LITTLE_ENDIAN);
            bb.put(bytes[cursor + 7]);
            bb.put(bytes[cursor + 6]);
            bb.put(bytes[cursor + 5]);
            bb.put(bytes[cursor + 4]);
            bb.put(bytes[cursor + 3]);
            bb.put(bytes[cursor + 2]);
            bb.put(bytes[cursor + 1]);
            bb.put(bytes[cursor]);
            cursor += 8;
            return bb.getDouble(0);
        }
    
        private long getLong() {
            ByteBuffer bb = ByteBuffer.allocate(8);
            bb.order(ByteOrder.LITTLE_ENDIAN);
            bb.put(bytes[cursor + 7]);
            bb.put(bytes[cursor + 6]);
            bb.put(bytes[cursor + 5]);
            bb.put(bytes[cursor + 4]);
            bb.put(bytes[cursor + 3]);
            bb.put(bytes[cursor + 2]);
            bb.put(bytes[cursor + 1]);
            bb.put(bytes[cursor]);
            cursor += 8;
            return bb.getLong(0);
        }
    
        private byte getByte() {
            byte b = bytes[cursor];
            cursor++;
            return b;
        }
    
        private int getInt() {
            ByteBuffer bb = ByteBuffer.allocate(4);
            bb.order(ByteOrder.LITTLE_ENDIAN);
            bb.put(bytes[cursor + 3]);
            bb.put(bytes[cursor + 2]);
            bb.put(bytes[cursor + 1]);
            bb.put(bytes[cursor]);
            cursor += 4;
            return bb.getInt(0);
        }
    
        private float getFloat() {
            ByteBuffer bb = ByteBuffer.allocate(4);
            bb.order(ByteOrder.LITTLE_ENDIAN);
            bb.put(bytes[cursor + 3]);
            bb.put(bytes[cursor + 2]);
            bb.put(bytes[cursor + 1]);
            bb.put(bytes[cursor]);
            cursor += 4;
            return bb.getFloat(0);
        }
    
        private boolean getBoolean() {
            boolean b = bytes[cursor] == 1;
            cursor++;
            return b;
        }
    
        private Class getCorrectType(char type) {
            switch (type) {
                case 'B':
                    return byte.class;
                case 'C':
                    return char.class;    // char
                case 'D':
                    return double.class;    // double
                case 'F':
                    return float.class;    // float
                case 'I':
                    return int.class;    // integer
                case 'J':
                    return long.class;    // long
                case 'S':
                    return short.class;    // short
                case 'Z':
                    return boolean.class;    // boolean
                case 'L':
                    return Object.class;
            }
            throw new IllegalArgumentException();
        }
    
        private Object getValue(char type, List<Object> values) {
            switch (type) {
                case 'B':
                    byte b = getByte();
                    values.add(b);
                    return b;
                case 'C':
                    char c = getChar();
                    values.add(c);
                    return c;    // char
                case 'D':
                    double d = getDouble();
                    values.add(d);
                    return d;    // double
                case 'F':
                    float f = getFloat();
                    values.add(f);
                    return f;    // float
                case 'I':
                    int i = getInt();
                    values.add(i);
                    return i;    // integer
                case 'J':
                    long l = getLong();
                    values.add(l);
                    return l;    // long
                case 'S':
                    short s = getShort();
                    values.add(s);
                    return s;    // short
                case 'Z':
                    boolean b1 = getBoolean();
                    values.add(b1);
                    return b1;    // boolean
                case 'L':
                    byte objectType = getByte();
                    if (objectType == ObjectStreamConstants.TC_REFERENCE) {
                        getShort(); // skip 2 bytes
                        return values.get(getShort());
                    } else {
                        short stringValueLength = getShort();
                        String string = getString(stringValueLength);
                        values.add(string);
                        return string;
                    }
            }
            throw new IllegalArgumentException();
        }
    
        public static void main(String[] args) {
            A a = new A();
            try {
                File file = new File("temp.out");
                try (FileOutputStream fos = new FileOutputStream(file);
                     ObjectOutputStream oos = new ObjectOutputStream(fos);) {
                    oos.writeObject(a);
                    oos.flush();
                    oos.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
                return;
            }
            try {
                try (FileInputStream fis = new FileInputStream("temp.out");
                     ObjectInputStream ois = new ObjectInputStream(fis);
                     ByteArrayOutputStream buffer = new ByteArrayOutputStream();) {
                    int cursor;
                    byte[] data = new byte[8192];
                    while ((cursor = fis.read(data, 0, data.length)) != -1) {
                        buffer.write(data, 0, cursor);
                    }
                    byte[] bytes = buffer.toByteArray();
    
                    List<List<Object>> result = new CustomDeserialization(bytes).parse();
                    result.forEach(list -> {
                        list.forEach(o -> System.out.print(o + " "));
                        System.out.println();
                    });
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }
    

    【讨论】:

    • 问题是关于如何在没有类的情况下读取数据。根据您指定的链接,有一种方法可以读取字段名称,而不仅仅是值。因此,应该有一种方法可以将序列化对象读入一些通用数据结构,如Map
    • 谢谢。我错过了这部分。我将编辑我的答案。
    • 您提供的信息本身很有用,但我试图找到一个成熟的反序列化库/解决方案。您自己说过您的概念验证代码仅支持原始类型和字符串。
    • 您期望什么行为?我们应该用 Properties 类的实例替换所有复杂的字段吗?然后它将是嵌套的 Properties 对象。
    • 我认为使用属性作为容器很难确定内部对象的类。我的意思是,您需要存储字段名称、其类型和值。如果将 map 用作容器,则可能有对“字段名称”-“值”(以及相应类型或映射的值)和复杂对象的“类”-“类名”之类的东西。将二进制数据读取到映射就足够了 - 之后很容易将其编写为 json 或 xml。如果我是你,我不会费心自己写这篇文章。
    【解决方案2】:

    我已经编译了所需类的旧版本并更改了 ClassLoader 以在升级时加载它们,使用 ObjectStream 读取对象并使用 XML 对其进行序列化。然后我为 XML 结构添加了修复。

    如果需要,我可以使用 ClassLoader hack 添加代码,但 AFAIR 它位于 Stack Overflow 上的某个位置。

    【讨论】:

    • 很高兴它有效,但作为批准的答案它没有回答问题
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2021-08-25
    • 1970-01-01
    • 2010-11-14
    • 2012-09-25
    • 2015-12-30
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多