Using readClassDescriptor() and maybe resolveClass() to permit Serialization versioning

自古美人都是妖i 提交于 2019-11-29 04:44:54

I had same problems with flexibility like you and I found the way. So here my version of readClassDescriptor()

    static class HackedObjectInputStream extends ObjectInputStream
{

    /**
     * Migration table. Holds old to new classes representation.
     */
    private static final Map<String, Class<?>> MIGRATION_MAP = new HashMap<String, Class<?>>();

    static
    {
        MIGRATION_MAP.put("DBOBHandler", com.foo.valueobjects.BoardHandler.class);
        MIGRATION_MAP.put("DBEndHandler", com.foo.valueobjects.EndHandler.class);
        MIGRATION_MAP.put("DBStartHandler", com.foo.valueobjects.StartHandler.class);
    }

    /**
     * Constructor.
     * @param stream input stream
     * @throws IOException if io error
     */
    public HackedObjectInputStream(final InputStream stream) throws IOException
    {
        super(stream);
    }

    @Override
    protected ObjectStreamClass readClassDescriptor() throws IOException, ClassNotFoundException
    {
        ObjectStreamClass resultClassDescriptor = super.readClassDescriptor();

        for (final String oldName : MIGRATION_MAP.keySet())
        {
            if (resultClassDescriptor.getName().equals(oldName))
            {
                String replacement = MIGRATION_MAP.get(oldName).getName();

                try
                {
                    Field f = resultClassDescriptor.getClass().getDeclaredField("name");
                    f.setAccessible(true);
                    f.set(resultClassDescriptor, replacement);
                }
                catch (Exception e)
                {
                    LOGGER.severe("Error while replacing class name." + e.getMessage());
                }

            }
        }

        return resultClassDescriptor;
    }

The problem is that readClassDescriptor is supposed to tell the ObjectInputStream how to read the data which is currently in the stream you are reading. if you look inside a serialized data stream, you will see that it not only stores the data, but lots of metadata about exactly what fields are present. this is what allows serialization to handle simple field additions/removals. however, when you override that method and discard the info returned from the stream, you are discarding the info about what fields are in the serialized data.

i think the solution to the problem would be to take the value returned by super.readClassDescriptor() and create a new class descriptor which returns the new class name, but otherwise returns the info from the old descriptor. (although, in looking at ObjectStreamField, it may be more complicated than that, but that is the general idea).

This is what writeReplace() and readResolve() are for. You're making it much more complicated than it really is. Note that you can define these methods either in the two objects concerned or in subclasses of thee Object stream classes.

I haven't tinkered with class descriptors much enough, but if your problem is just about renaming and repackaging there is a much easier solution for that. You could just simply edit your serialized data file with a text editor and just replace your old names with new ones. it's there in a human-readable form. for example suppose we have this OldClass placed inside oldpackage and containing an oldField, like this:

package oldpackage;

import java.io.Serializable;

public class OldClass implements Serializable
{
    int oldField;
}

Now when we serialize an instance of this class and get something like this:

¬í sr oldpackage.OldClasstqŽÇ§Üï I oldFieldxp    

Now if we want to change class's name to NewClass and put inside newpackage and change its field's name to newField, I simply rename it the file, like this:

¬í sr newpackage.NewClasstqŽÇ§Üï I newFieldxp    

and define the appropriate serialVersionUID for the new class.

That's all. No extending and overriding required.

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!