Вопрос по serialization, deserialization, java, version-control, version – Использование readClassDescriptor () и, возможно, resolClass () для разрешения управления версиями сериализации

5

Я изучаю различные варианты в механизме Java Serialization, чтобы обеспечить гибкость в наших структурах классов для хранилища с поддержкой версий (и выступая за другой механизм, вам не нужно говорить мне).

Например, механизм сериализации по умолчанию может обрабатывать как добавление, так и удаление полей, если требуется только обратная совместимость.

Однако переименовать класс или переместить его в другой пакет оказалось гораздо сложнее. Я нашел вэтот вопрос что я смог сделать простое переименование класса и / или переместить пакет, создав подкласс ObjectInputStream и переопределив readClassDescriptor ():

    if (resultClassDescriptor.getName().equals("package.OldClass"))
        resultClassDescriptor = ObjectStreamClass.lookup(newpackage.NewClass.class);

Это хорошо для простых переименований. Но если вы затем попытаетесь добавить или удалить поле, вы получите исключение java.io.StreamCorruptedException. Хуже того, это происходит, даже если поле было добавлено или удалено, иthen вы переименовываете класс, что может вызвать проблемы с несколькими разработчиками или несколькими проверками.

Основываясь на чтении, которое я сделал, я немного поэкспериментировал с переопределением resolClass (), с идеей, что мы правильно переназначаем имя новому классу, но не загружаем сам старый класс и бомбим изменения полей. Но это происходит из-за очень смутного понимания некоторых деталей механизма сериализации, и я не уверен, что я даже лаю на правильное дерево.

Итак, 2 точных вопроса:

Why is repointing the class name using readClassDescriptor() causing deserialization to fail on normal, compatible class changes? Is there a way using resolveClass() or another mechanism to get around this and allow classes to both evolve (add and remove fields) and be renamed/repackaged?

Я осмотрелся и не смог найти эквивалентный вопрос по SO. Во что бы то ни стало, укажите мне на такой вопрос, если он существует, но, пожалуйста, прочитайте его достаточно внимательно, чтобы вы не закрыли меня, если другой вопрос не ответит на мой точный вопрос.

@orbfish, пожалуйста, поделитесь своим решением, если вы обнаружите, что Girish
@ enthu-man Почему-то я пропустил закрытие, и это было давно, и у меня больше нет кода с проблемой. Здесь есть 3 красивых решения, я бы попробовал их, и если вы найдете одно, я его приму;) orbfish
Вы нашли решение? gaponov

Ваш Ответ

4   ответа
1

Проблема в том, что readClassDescriptor должен сообщать ObjectInputStream, как читать данные, которыеcurrently в потоке вы читаете. если вы заглянете внутрь сериализованного потока данных, вы увидите, что он не только хранит данные, но и множество метаданных о том, какие именно поля присутствуют. это то, что позволяет сериализации обрабатывать простые добавления / удаления полей. однако, когда вы переопределяете этот метод и отбрасываете информацию, возвращаемую из потока, выdiscarding информация о том, какие поля находятся в сериализованных данных.

яthink решение проблемы заключается в том, чтобы взять значение, возвращаемое super.readClassDescriptor (), и создатьnew дескриптор класса, который возвращает имя нового класса, но в противном случае возвращает информацию из старого дескриптора. (хотя, глядя на ObjectStreamField, он может быть более сложным, чем это, но это общая идея).

8

У меня были такие же проблемы с гибкостью, как у вас, и я нашел способ. Так вот моя версия 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;
    }
0

Я не достаточно возился с дескрипторами классов, но если ваша проблема заключается только в переименовании и переупаковке, есть гораздо более простое решение для этого. Вы можете просто отредактировать ваш сериализованный файл данных с помощью текстового редактора и просто заменить ваши старые имена новыми. это там в удобочитаемой форме. например, предположим, у нас есть этоOldClass помещен внутриoldpackage и содержащийoldField, как это:

package oldpackage;

import java.io.Serializable;

public class OldClass implements Serializable
{
    int oldField;
}

Теперь, когда мы сериализуем экземпляр этого класса и получим что-то вроде этого:

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

Теперь, если мы хотим изменить имя класса наNewClass и положить внутрьnewpackage и измените имя его поля наnewFieldЯ просто переименую его в файл, например так:

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

и определите соответствующий serialVersionUID для нового класса.

Вот и все. Не требует расширения и переопределения.

1- переименование или переупаковка класса или переименование его полей. 2- Блокнот ++ 5.9.6.2 3- Windows 7 4 & 5- Я сделал приведенный выше пример так же, как я описал. Я пробовал это в нескольких проектах, которые у меня были. Никогда не было проблем. Была ли у вас проблема при тестировании?
Я проверил это. Вы можете попробовать это.
Сериализованные данные являются двоичными. Вы можете повредить его с помощью текстового редактора. Не практичный ответ.
Вы что именно проверяли? С каким текстовым редактором? На какой системе? С какими сериализованными данными? Содержащие какие двоичные данные?
1

Вот для чего предназначены writeReplace () и readResolve (). Ты делаешь это намного сложнее, чем есть на самом деле. Обратите внимание, что вы можете определить эти методы либо в двух соответствующих объектах, либо в подклассах классов потоков объектов.

Я не думаю, что это делает это, но, как я сказал выше, у меня больше нет кода для тестирования. Я попробую, если увижу проблему снова, спасибо! orbfish

Похожие вопросы