Подтвердить что ты не робот

Управление несколькими версиями сериализованных объектов Java

Предположим, что у меня есть программа, которая по какой-то причине должна обрабатывать старые версии сериализованных объектов.

Например: при десериализации можно встретить одну из этих версий.

class Pet {
    private static final long serialVersionUID = 1L;
    int paws;
}

class Pet {
    private static final long serialVersionUID = 2L;
    long paws; // handle marsian centipedes
    boolean sharpTeeth;
}

Предположим, что он (логически) можно преобразовать старый объект в новый объект, используя некоторую умную стратегию для установки нестерических полей и т.д., но:

Как упорядочить исходный код? Мне бы, вероятно, понадобились обе версии в одном исходном дереве при написании конвертера, но как мне обрабатывать это, например, в eclipse.

Должен ли я выполнять десериализацию в одном загрузчике классов, и если это не удается, попробуйте использовать другой загрузчик классов, который использует более старую версию (и так далее), или есть ли лучшие способы?

Какая лучшая стратегия?

4b9b3361

Ответ 1

Предположим, что он (логически) можно преобразовать старый объект в новый объект, используя некоторую умную стратегию для установки неэксентирующих полей и т.д. и т.д. Как я могу упорядочить исходный код?

Я вижу два способа справиться с этим. Во-первых, вы никогда не должны менять serialVersionUID, если вы не хотите, чтобы InvalidClassException был сброшен. Второе правило состоит в том, чтобы не изменять типы полей, а только добавлять или удалять поля, которые автоматически обрабатываются сериализацией. Например, если сериализованный файл имеет версию класса с boolean sharpTeeth;, но класс не имеет этого поля, то при десериализации он будет игнорироваться. Если класс deserialized имеет поле sharpTeeth, но файл не будет тогда, он будет инициализирован по умолчанию, false в этом случае.

Это особенно важно для распределенных систем, в которых вы хотите попробовать как передовую, так и обратную совместимость. Вы не хотите обновлять версию приложения A и прерывать другое приложение B, которое зависит от A. Не изменяя serialVersionUID, а просто добавляя или удаляя поля, вы можете это сделать. Более поздние версии вашего объекта должны поддерживать более старые версии без значений в новых полях, но более старые объекты не будут возражать, если будут доступны новые поля. Это также означает, что вы также не должны изменять масштаб поля.

Сериализация довольно умна, но она не обрабатывает изменения типов в полях. Вы не должны просто изменять paws от int до long. Вместо этого я бы рекомендовал добавить long pawsLong или некоторые из них и написать свой код, чтобы обрабатывать возможность наличия int paws или long pawsLong со значением.

public long getPaws() {
    if (pawsLong > 0) {
        return pawsLong;
    } else {
        // paws used to be an integer
        return paws;
    }
}

Вы также можете написать свой собственный метод readObject для преобразования при де-сериализации:

private void readObject(java.io.ObjectInputStream in) {
    super.readObject(in);
    // paws used to be an integer
    if (pawsLong == 0 && paws != 0) {
        pawsLong = paws;
    }
}

Если это не сработает для вас, тогда обычная сериализация - это путь. Сначала вам нужно начинать с этого делать и определять собственные методы readObject(...) и writeObject(...) с внутренним идентификатором версии. Что-то вроде:

// never change this
private static final long serialVersionUID = 3375159358757648792L;
// only goes up
private static final int INTERNAL_VERSION_ID = 2;
...
// NOTE: in version #1, this was an int
private long paws;

private void readObject(java.io.ObjectInputStream in) {
    int version = in.readInt();
    switch (version) {
        case 1 :
            paws = in.readInt();
            ...
        case 2 :
            paws = in.readLong();
            ...

private void writeObject(java.io.ObjectOutputStream out) {
    out.writeInt(INTERNAL_VERSION_ID);
    out.writeLong(paws);
    ...

Но этот метод не поможет вам с форвардной совместимостью. Читатель версии 1 не понимает ввод сериализации версии 2.

Должен ли я выполнять десериализацию в одном загрузчике классов, и если это не удается, попробуйте использовать другой загрузчик классов, который использует более старую версию (и так далее), или есть ли лучшие способы?

Я бы не предложил ни один из этих методов. Звучит очень сложно.

Ответ 2

К сожалению, изменение типов полей недопустимо. Поддержка двух (десяти, сотен?) Разных версий будет слишком сложной. Таким образом, вы можете использовать метод readObject(ObjectInputStream in). И установите фиксированный serialVersionUID. Если вы не установили его на начальном этапе, используйте его IDE или JDK serialver, чтобы получить его, так что, похоже, у вас есть только одна версия класса.

Если вы хотите изменить тип поля, измените его имя. Например paws > pawsCount. Механизм десериализации даже не попадает в метод readObject(..), если в полях есть несоответствие типов.

В приведенном выше примере рабочим решением будет:

class Pet implements Serializable {
    private static final long serialVersionUID = 1L;
    long pawsCount; // handle marsian centipedes
    boolean sharpTeeth;

    private void readObject(java.io.ObjectInputStream in)
        throws IOException, ClassNotFoundException {

        in.defaultReadObject();
        GetField fields = in.readFields();
        int paws = fields.get("paws", 0); // the 0 is a default value 
        this.pawsCount = paws;
    }
}

Поля, добавленные позже, будут установлены в значения по умолчанию.

Btw, было бы немного проще использовать java.beans.XMLEncoder (если это не слишком поздно для вашего проекта)

Ответ 3

Должен ли я делать десериализацию в одном класс загрузчик, и если это не удается, попробуйте используя другой загрузчик классов, который использует более старая версия (и т.д.), или там лучше?

Какая лучшая стратегия?

Сериализация действительно не должна использоваться для долгосрочного хранения.

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

Это действительно лучший способ сохранить данные для долговременного хранения, а обновления старых объектов для заполнения нулевых полей очень просто.