| Главная » Статьи » Java » Java Ranger Basic |
День 19/21
| Serialization Serialization В Джаве есть Serialization API – это конкретный API, где все сосредоточено в двух интерфейсах java.io.ObjectInput и java.io.ObjectOutput и конкретно в классах ObjectInputStream и ObjectOutputStream. Это классы, которые в конструкторе принимают InputStream и OutputStream соответственно, и у них есть методы readObject() и writeObject(), которые принимают любой Object и превращают его в байтовый массив. Есть несколько требований: класс объекта должен имплементить интерфейс Serializable; на обеих сторонах должны быть не сильно отличные версии JVM (не 15-летняя), та же самая версия класса. И по этим сырым байтам восстанавливается объект прямо в Heap (создается экземпляр объекта, но при это не вызывается конструктор. Потому что, если у объекта нет конструктора по умолчанию, а есть какой-нибудь другой, с аргументами, то JVM не знает, какой конструктор вызвать, какие аргументы передать). Когда говорят сериализовать/десериализовать, имеют в виду, что какой-то объект в Heap преобразовать в какой-то формат, более универсальный, например, просто в набор байт, и послать пакетом по TCP. Или еще лучше – преобразовать в XML – тогда его можно даже сохранить в какую-то базу данных, поддерживающую XML, делать по нему SELECT. Более того, теоретически можно сериализовать в XML в одном языке, а десериализовать – в другом. В современном мире это очень популярно – конкретно, у компании Google есть такой протокол protobuf (Protocol Buffers), у компании FaceBook есть такой протокол thrift (Apache Thrift), еще есть протокол Apache Avro, протокол Hessian (Hessian Binary Web Service Protocol). Это именно протоколы, которые описывают формат файла (он бинарный, или какой-то текстовый типа XML), не привязанный к конкретному языку. В данном случае Google и Facebook написали себе сериализатор и десериализатор для нескольких своих популярных языков, потому что они часто из Java вызывают сервер на C++, Python или PHP. При этом пришлось ввести свои типы данных (может оказаться, что long в Java – в Python ничему не соответствует). И поэтому эти процессы называются сериализация / десериализация. То есть, сериализация – это процесс преобразование объекта из Heap в какой-то внешний формат, который существует за пределами нашей виртуальной машины. serialVersionUID В Джаве при сериализации есть механизм проверки версии класса. Он выглядит так: когда просят сериализировать наш класс, мы делаем new ObjectOutputStream и указываем ему writeObject(), помещая в него нашу штуку. Они у этой штуки кроме того, что хотят записать его поля, спрашивают, есть ли у него такое поле: private static final long serialVersionUID = -9023781642162374579L; Это сказано в спецификации сериализации. То есть они это находят по рефлексии. Это поле должно быть приватное, статическое (потому что оно описывает версию класса, а не версию экземпляра) с именем serialVersionUID типа long. Если они находят, то записывают туда 1. Если не находят, то запускается достаточно дорогостоящий процесс, который по классу исследует рефлексией и строит какой-то long, например, -9023781642162374579L – это какая-то большая хэш-сумма из имен всех методов, сигнатур всех методов, имен всех полей (что именно – определено в спецификации). Сериализация берет данные, не вызывая методы, а прямо залазит внутрь по рефлексии, находит поля и их считывает. Когда же мы получили эти сырые байты из ObjectOutputStream, мы от них строим ObjectInputStream, и делаем readObject(). ObjectInputStream залазит в массив байт, в массиве байт указано имя класса. readObject() пытается локально загрузить такой класс (сделать типа Class.forName()). Если в системе такого класса нет, то вылетит какой-нибудь ClassNotFoundException. Например, мы на сервере сериализировали объект, и он случайно своим полем захватил log4j, мы пытаемся десериализировать, а локально у нас никакого log4j нет. Суть в том, что классы не сериализируются, сериализируются данные экземпляра и просто имя класса. Если при десериализации такой класс в локальном CLASSPATH не найден, то сериализация падает по ClassNotFoundException. Когда класс найден, создается экземпляр этого класса. Потом у того класса, который в CLASSPATH, берут этот serialVersionUID, если он есть захардкоженый – берут его, если нет – считают достаточно дорогостоящим алгоритмом. И потом смотрят версию класса, который в CLASSPATH и того, который в массиве байт – если они разные, то мы падаем по какому-то специальному исключению сериализации. Это означает, что если у нас, например, был User, у которого было три поля (float a, float b, float c), мы его сериализировали и он теперь лежит в виде файла на диске. И мы взяли и в классе User сделали два поля double k и long g, и хотим десериализировать то, что мы сериализировали. У сериализованного класса другая структура, при вычитке механизм сравнит и вылетит исключение, потому что изменилась структура класса. Соответственно у всех нормальных бизнес-систем (а сериализация пытается к ним двигаться) существует чудовищная проблема с согласованием версий. Как только что-то хранится в БД, в файлах, а мы класс-файлы меняем, то новые сущности должны подхватывать старые данные (где не хватает каких-то полей или типы меняются и т.д.). В сериализации есть некоторые хинты, их можно вставить, и таки-можно будет читать классы, даже если версии не совпадают. Фактически нам дадут сырой массив байт и номер версии – восстанавливай, как хочешь, по номеру версии ты должен знать, чем оно было. Соответственно получается, что если мы serialVersionUID не забьем и будем сериализировать, то при каждой сериализации и десериализации мы будем запускать тяжелый алгоритм вычисления этого числа. По старой структуре класса строят хэш, по новой строят хэш, и сравнивают хэши. В следующей теме мы рассмотрим: JDK, Hardware Источник: http://becomejavasenior.com/courses/?utm_source=Java+Email+Courses&utm_campaign=aa710df388-JavaRangerBasicIntro&utm_medi | |
| Просмотров: 375 | | |
| Всего комментариев: 0 | |