Почему Alibaba требует, чтобы программисты тщательно изменили значение поля serialVersionud

Java

Сериализация — это средство сохранения объекта. Он широко используется в сетевой передаче, RMI и других сценариях. класс путем реализацииjava.io.Serializableинтерфейс, чтобы включить его возможности сериализации.

В моем блоге на самом деле много статей о сериализации.Друзья, которые недостаточно разбираются в основах сериализации, могут обратиться к следующим статьям:

Сериализация и десериализация объектов Java Углубленный анализ сериализации и десериализации JavaЭти вещи о синглтонах и сериализации

В этих статьях я представил классы и интерфейсы, участвующие в сериализации, как настроить стратегию сериализации, временные ключевые слова и взаимосвязь между сериализацией и т. д., а также глубоко изучил исходный код реализации сериализации, изучив ArrayList.serialized. А также расширили анализ влияния сериализации на синглтоны.

Однако есть еще один пункт знаний, который не был введен, а именно оserialVersionUID. Для чего именно используется это поле? Что делать, если он не установлен? Почему в Руководстве по разработке Java для Alibaba есть следующее положение:

жизненный опыт

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

Сериализуемый и экстернализируемый

Классы Java реализованыjava.io.Serializableинтерфейс, чтобы включить его возможности сериализации.Классы, которые не реализуют этот интерфейс, не смогут сериализовать или десериализовать.Все подтипы сериализуемого класса сами сериализуемы.

Если читатель виделSerializableисходный код, вы обнаружите, что это просто пустой интерфейс, в котором ничего нет.Интерфейс Serializable не имеет методов или полей и используется только для идентификации сериализуемой семантики.Однако, если класс не реализует этот интерфейс и хочет сериализоваться, он выдастjava.io.NotSerializableExceptionаномальный.

Как это гарантирует, что только методы, реализующие интерфейс, могут быть сериализованы и десериализованы?

Причина в том, что в процессе сериализации выполняется следующий код:

if (obj instanceof String) {    writeString((String) obj, unshared);} else if (cl.isArray()) {    writeArray(obj, desc, unshared);} else if (obj instanceof Enum) {    writeEnum((Enum<?>) obj, desc, unshared);} else if (obj instanceof Serializable) {    writeOrdinaryObject(obj, desc, unshared);} else {    if (extendedDebugInfo) {        throw new NotSerializableException(            cl.getName() + "\n" + debugInfoStack.toString());    } else {        throw new NotSerializableException(cl.getName());    }}скопировать код

При сериализации он будет определять, является ли сериализуемый классEnum,ArrayиSerializableтипа, если это ни то, ни другое, кидайте сразуNotSerializableException.

Java также предоставляетExternalizableинтерфейс, который также может быть реализован для предоставления возможностей сериализации.

Externalizableунаследовано отSerializable, в этом интерфейсе определены два абстрактных метода:writeExternal()иreadExternal().

когда используешьExternalizableКогда интерфейс используется для сериализации и десериализации, разработчикам необходимо его переписать.writeExternal()иreadExternal()метод. В противном случае значения всех переменных станут значениями по умолчанию.

transient

transientФункция ключевого слова – управлять сериализацией переменных. Добавление этого ключевого слова перед объявлением переменной может предотвратить сериализацию переменной в файл. После десериализацииtransientЗначение переменной устанавливается равным начальному значению, например 0 для типа int и null для типа объекта.

настраиваемая стратегия сериализации

Во время сериализации, если сериализованный класс определяетwriteObjectиreadObjectметод, виртуальная машина пытается вызвать метод в классе объектаwriteObjectиreadObjectметод пользовательской сериализации и десериализации.

Если такого метода нет, вызов по умолчаниюObjectOutputStreamизdefaultWriteObjectметод иObjectInputStreamизdefaultReadObjectметод.

определяемые пользователемwriteObjectиreadObjectЭтот метод позволяет пользователю управлять процессом сериализации, например, динамически изменять сериализованное значение во время процесса сериализации.

Поэтому, когда вам нужно определить стратегию сериализации для некоторых специальных полей, вы можете рассмотреть возможность использования временной модификации и переписать ее самостоятельно.writeObjectиreadObjectтакие методы, какjava.util.ArrayListЕсть такая реализация.

Вышеизложенное — это знания, которые некоторым читателям необходимо освоить и связанные с сериализацией.

Случайным образом находим в Java несколько классов, реализующих интерфейс сериализации, таких как String, Integer и т. д., можем найти деталь, то есть помимо реализации этих классовSerializableКроме того, он также определяетserialVersionUID

Итак, что именноserialVersionUIDШерстяная ткань? Зачем задавать такое поле?

что такое serialVersionUID

Сериализация — это процесс преобразования информации о состоянии объекта в форму, которую можно хранить или передавать. Все мы знаем, что объекты Java хранятся в куче памяти JVM, то есть, если кучи JVM не существует, объекты также исчезнут.

Сериализация предоставляет решение, позволяющее сохранять объекты, даже когда JVM не работает. Так же, как диск U, который мы обычно используем. Сериализация объектов Java в форму, которую можно хранить или передавать (например, в двоичный поток), например в файл. Таким образом, когда объект снова понадобится, двоичный поток считывается из файла, а объект десериализуется из двоичного потока.

Разрешает ли виртуальная машина десериализацию, зависит не только от согласованности пути к классам и кода функции, но также очень важным моментом является то, согласованы ли идентификаторы сериализации двух классов., этот так называемый идентификатор сериализации — это то, что мы определяем в кодеserialVersionUID.

Что делать, если serialVersionUID изменится

Давайте возьмем пример и посмотрим, еслиserialVersionUIDЧто произойдет, если его изменить?

public class SerializableDemo1 {    public static void main(String[] args) {        //Initializes The Object        User1 user = new User1();        user.setName("hollis");        //Write Obj to File        ObjectOutputStream oos = null;        try {            oos = new ObjectOutputStream(new FileOutputStream("tempFile"));            oos.writeObject(user);        } catch (IOException e) {            e.printStackTrace();        } finally {            IOUtils.closeQuietly(oos);        }    }}class User1 implements Serializable {    private static final long serialVersionUID = 1L;    private String name;    public String getName() {        return name;    }    public void setName(String name) {        this.name = name;    } }скопировать код

Сначала мы выполняем приведенный выше код, чтобы записать объект User1 в файл. Затем мы модифицируем класс User1 и поместимserialVersionUIDЗначение изменяется на2L.

class User1 implements Serializable {    private static final long serialVersionUID = 2L;    private String name;    public String getName() {        return name;    }    public void setName(String name) {        this.name = name;    }}скопировать код

Затем выполните следующий код, чтобы десериализовать объект в файле:

public class SerializableDemo2 {    public static void main(String[] args) {        //Read Obj from File        File file = new File("tempFile");        ObjectInputStream ois = null;        try {            ois = new ObjectInputStream(new FileInputStream(file));            User1 newUser = (User1) ois.readObject();            System.out.println(newUser);        } catch (IOException e) {            e.printStackTrace();        } catch (ClassNotFoundException e) {            e.printStackTrace();        } finally {            IOUtils.closeQuietly(ois);            try {                FileUtils.forceDelete(file);            } catch (IOException e) {                e.printStackTrace();            }        }    }}скопировать код

Результат выполнения следующий:

java.io.InvalidClassException: com.hollis.User1; local class incompatible: stream classdesc serialVersionUID = 1, local class serialVersionUID = 2скопировать код

Можно обнаружить, что приведенный выше код выдаетjava.io.InvalidClassException, и указываетserialVersionUIDнепоследовательный.

Это связано с тем, что при десериализации JVMserialVersionUIDс локальным соответствующим классом сущностейserialVersionUIDДля сравнения, если они совпадают, то считаются непротиворечивыми и могут быть десериализованы, в противном случае произойдет исключение несогласованных сериализованных версий, чтоInvalidCastException.

Это же оговорено в "Руководстве по разработке Java для Alibaba", в апгрейде совместимости при модификации класса не модифицироватьserialVersionUIDпричина.если только две версии полностью несовместимы. так,serialVersionUIDНа самом деле, это проверка согласованности версий.

Если читателю интересно, вы можете взглянуть на код JDK каждой версии, а также на коды классов, совместимых с предыдущими версиями.serialVersionUIDне изменился. например, строкаserialVersionUIDВсегда-6849794470754667710L.

Однако автор считает, что на самом деле эта спецификация может быть более строгой, то есть в ней оговариваются:

Если класс реализуетSerializableинтерфейс, необходимо вручную добавитьprivate static final long serialVersionUIDпеременная и установить начальное значение.

Почему следует четко установить serialVersionUID

Если мы не явно не определяем один в классеserialVersionUIDЕсли да, то посмотрите, что произойдет.

Попробуйте изменить демонстрационный код выше, сначала используйте следующий класс для определения объекта, который не определен в этом классе.serialVersionUID, запишите его в файл.

class User1 implements Serializable {    private String name;    public String getName() {        return name;    }    public void setName(String name) {        this.name = name;    } }скопировать код

Затем мы модифицируем класс User1, чтобы добавить к нему свойство. Попытка прочитать его из файла и десериализовать.

class User1 implements Serializable {    private String name;    private int age;    public String getName() {        return name;    }    public void setName(String name) {        this.name = name;    }    public int getAge() {        return age;    }    public void setAge(int age) {        this.age = age;    } }скопировать код

Результаты:

java.io.InvalidClassException: com.hollis.User1; local class incompatible: stream classdesc serialVersionUID = -2986778152837257883, local class serialVersionUID = 7961728318907695402скопировать код

Так же, метаниеInvalidClassException, и указать дваserialVersionUIDразные, соответственно-2986778152837257883и7961728318907695402.

Отсюда видно, что система добавилаserialVersionUID.

Итак, как только класс реализуетSerializable, рекомендуется четко определитьserialVersionUID. В противном случае при модификации класса возникнет исключение.

serialVersionUIDСуществует два метода генерации отображения: Один из них — 1 л по умолчанию, например:

private static final long serialVersionUID = 1L;   скопировать код

Другой способ — создать 64-битное хэш-поле на основе имени класса, имени интерфейса, метода-члена и атрибута, например:  

private static final  long   serialVersionUID = xxxxL;скопировать код

Последний метод можно сгенерировать с помощью IDE, которая будет представлена ​​позже.

Принцип, лежащий в основе

Зная это, чтобы узнать почему, давайте посмотрим на исходный код и проанализируем, почемуserialVersionUIDВыбросит исключение при изменении? При отсутствии явного определения по умолчаниюserialVersionUIDКак это произошло?

Чтобы упростить размер кода, цепочка вызовов десериализации выглядит следующим образом:

ObjectInputStream.readObject -> readObject0 -> readOrdinaryObject -> readClassDesc -> readNonProxyDesc -> ObjectStreamClass.initNonProxyскопировать код

существуетinitNonProxy, код ключа выглядит следующим образом:

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

Посмотрите поближеgetSerialVersionUIDметод:

public long getSerialVersionUID() {    // REMIND: synchronize instead of relying on volatile?    if (suid == null) {        suid = AccessController.doPrivileged(            new PrivilegedAction<Long>() {                public Long run() {                    return computeDefaultSUID(cl);                }            }        );    }    return suid.longValue();}скопировать код

в неопределенномserialVersionUIDВ то время его будет называтьсяcomputeDefaultSUIDметод, который генерирует значение по умолчаниюserialVersionUID.

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

ИДЕЯ Советы

Чтобы убедиться, что мы не забыли определитьserialVersionUID, вы можете настроить конфигурацию Intellij IDEA, в реализацииSerializableПосле интерфейса, если он не определенserialVersionUIDЕсли это так, IDEA (например, eclipse) предложит:

И может сгенерировать его одним щелчком мыши:

Конечно, эта конфигурация не действует по умолчанию, вам нужно установить ее вручную в IDEA:

В месте с пометкой 3 на рисунке (Конфигурация класса Serializable без serialVersionUID) отметьте его и сохраните.

Суммировать

serialVersionUIDОн используется для проверки согласованности версий. такПри обновлении совместимости не меняйте классserialVersionUIDзначение .

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

Если класс реализует интерфейс Serializable, обязательно определитеserialVersionUID, иначе возникнет исключение. Вы можете установить его в IDE, пусть он подскажет, а может быстро сгенерировать в один кликserialVersionUID.

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

Статья в публичном аккаунте Hollis уполномочила осуществлять защиту оригинальных прав.Во избежание ненужных проблем с ответственностью за авторские права просьба указывать источник для перепечатки!

В последний месяц 2018 года у Hollis's Knowledge Planet действует ограниченная по времени скидка.Глубокое понимание параллельного программирования на Java: что такое безопасность потоков?Добро пожаловать присоединиться.

 

Столкнувшись с проблемой Java 197: почему символы искажены?

Road to God Issue 015: Углубленное изучение IO в Java

Глубокое погружение в проблему параллелизма 004: несколько способов реализации потоков

- ЕЩЕ | Другие интересные статьи -

Если вам нравится эта статья.

Пожалуйста, нажмите и удерживайте QR-код, чтобы подписаться на Холлис

Пересылка круга друзей - самая большая поддержка для меня.