Главная
Блог разработчиков phpBB
 
+ 17 предустановленных модов
+ SEO-оптимизация форума
+ авторизация через соц. сети
+ защита от спама

Сериализация в Java: как заглянуть вовнутрь черного ящика

Anna | 4.06.2014 | нет комментариев

Испокон веку в Java есть Удивительный механизм сериализации, тот, что разрешает, не прилагая специальных умственных усилий, сберегать в виде последовательности байт сколь желательно трудные графы объектов. Формат хранения отлично документирован, есть куча примеров, сериализованные объекты «весят» абсолютно себе немножко, пересылаются по сети на раз, есть куча вероятностей для кастомизации… Все это звучит восхитительно, но только до тех пор, пока вы не останетесь один на один каким-нибудь многомегабайтным бинарным файлом, содержащим дюже-дюже дорогие и надобные именно теперь данные.

Как голыми руками забраться в данный файл, и осознать, что же хранится внутри этого громадного сериализованного графа объектов, не имея начального кода? На эти и многие другие вопросы может ответитьSerialysis – библиотека, которая дозволит вам подробно проанализировать сериализованные java-объекты (сериализованные объекты — это мой вариант перевода выражения serial forms, решил не уходить вдалеке от оригинала). Таким образом дозволено получить информацию об объекте, которая не доступна через его публичный API. Библиотека также является пригодным инструментом при тестировании сериализации ваших собственных классов.

От переводчика:
Суббота. Вечер. Ничто не предвещало работу в данный день, но внезапно я припоминаю, что хорошо бы проверить, как поживают наши джобы на hadoop-кластере, легко так, для успокоения совести — чай задача-то была решена…

<Лирическое отхождение>
В последние несколько дней достаточно много задач стали завершаться с OutOfMemoryError на нашем hadoop-кластере в production-е, наращивать объем выделяемой памяти вероятности огромнее не было, и мы с IT-отделом потратили довольное число времени на попытки обнаружить причину. Закончилось это тем, что наш заокеанский сотрудник раздумчиво посмотрел на конфиги, исправил пару строчек, и сказал что задача решена.
И подлинно, в пятницу все стало отлично, и мы в следующий раз порадовались наличию Cloudera Certified Developer-а в команде.
</Лирическое отхождение>

Но не здесь-то было!
Хадуп показывал, что ни одна задача в эту злополучную субботу не выполнилась.
Повод падений несколько отличалась от бывшей: task tracker не мог запустить задачу, потому что ему не хватало памяти, Дабы загрузить xml-файл конфигурации задачи.

Мне, безусловно, сразу стало увлекательно, что же за монструозные конфигурации там хранятся? Увы, огромную часть занимал сериализованный блоб на полсотни мегабайт. Блоб состоит из графа объектов десятка различных классов, для которых у меня безусловно же нет исходников.

Что же дозволено сделать с этим многомегабайтным бинарником вечером субботы с поддержкой одних лишь подручных средств?

Здесь на сцену выходит мой спаситель: Serialysis. Пара строк кода — и у меня на руках есть полный дамп внутренностей сериализованного объекта, с именами классов и полей. Имея на руках полный дамп, нахожу задачу, включаю gzip компрессию для словаря строк, патчу классы с поддержкой JBE. Вуаля — задача решена!

Это хак, безусловно, но изредка без хаков — никуда.

P.S. Библиотека давняя, но в данный момент оказалась как невозможно кстати. Сознаться, часть использований, которые обнаружил для библиотечки автор, мне кажутся крайне необычными. Ради Всевышнего, ну не дали узнать порт, значит и не дюже нужно!

Собственно, статья:

Когда публичного API не довольно

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

Скажем, у вас есть заглушка (stub) для удаленного объекта RMI, и вы хотите узнать, через какой адрес либо порт она будет подключаться, либо какую фабрику сокетов RMI (RMIClientSocketFactory) будет применять. Типовой API RMI не дает метода извлечь информацию из заглушки. Дабы заглушка могла работать позже десериализации, эта информация должна присутствовать в сериализованной форме. Следственно мы могли бы получить нужную информацию, если б только удалось каким-то образом разобрать сериализованную заглушку.

2-й пример взят из JMX API. Запросы к серверу MBean представлены интерфейсом QueryExp. Примеры QueryExp возведены на применении способов Query class. Если ваш объект принадлежит к QueryExp, как вы узнаете, какие запросы он исполняет? JMX API не предлагает никакого пути это узнать. Информация должна быть представлена в сериализованной форме так, Дабы, когда заказчик делает запрос к удаленному серверу, её допустимо было восстановить на сервере. Если мы можем увидеть сериализованную форму, мы сумеем и определить, какой был запрос.

2-й пример как раз и всподвиг меня написать эту библиотеку. Существующие типовые JMX-коннекторы основываются на Java-сериализации, следственно в них не требуется каким-либо специальным образом обрабатывать QueryExps. Но в новом Web Services
Connector введенном в JSR 262 применяется XML для сериализации. Как исследовать QueryExp, Дабы после этого преобразовать его в XML? Результат примитивен: WS-коннектор использует версию данной библиотеки, Дабы заглянуть вовнутрь сериализованных QueryExp.

Все эти примеры объединяет одно: они показывают пробелы в соответствующих интерфейсах API. А значит, необходимы способы, дозволяющие извлекать информацию из RMI-заглушки. Равно как необходим метод преобразовать QueryExp обратно к начальному способу Query, тот, что его породил. (Довольно даже стандартного итога toString(), пригодного к разбору). Но таких способов теперь нет, и если мы хотим код, тот, что будет трудиться с этими API в их текущем виде, нам необходим иной подход.

Проникаем в приватные поля объектов

Если у вас есть начальный код волнующих вас классов, то огромен соблазн легко влезть и взять желанные данные. В примере с заглушкой RMI, мы путем эксперимента можем узнать что способ заглушки getRef()возвращает sun.rmi.server.UnicastRef, и изучив исходники JDK, мы узнаем, что данный класс содержит поле ref типа sun.rmi.transport.LiveRef, как раз с той информацией, которая нам необходима. Так что получим приблизительно такой код (но скажу предварительно, не стоит этого делать):

import sun.rmi.server.*;
import sun.rmi.transport.*;
import java.rmi.*;
import java.rmi.server.*;

public class StubDigger {
    public static getPort(RemoteStub stub) throws Exception {
        RemoteRef ref = stub.getRef();
    	UnicastRef uref = (UnicastRef) ref;
    	Field refField = UnicastRef.class.getDeclaredField("ref");
    	refField.setAccessible(true);
    	LiveRef lref = (LiveRef) refField.get(uref);
    	return lref.getPort();
    }
}

Допустимо, итог вас абсолютно устроит, но, повторяю, делать так не советую — данный код никуда не годится. Во-первых, никогда не используйте связанность от классов sun.*, потому что никто не может гарантировать, что они не изменятся до неузнаваемости при любом обновлении JDK, к тому же ваш код однозначно будет нелегко портировать на другие платформы JDK. Во-вторых, когда вы видите что-то как быField.setAccessible, то вам следует воспринимать это, как знак стоп. Это обозначает, что ваш код зависит от недокументированных полей, которые могут меняться от релиза к релизу либо, того дрянней, которые могут сохраниться, но с измененной семантикой.

(Данный код был написан для JDK 5. Как оказалось, в JDK 6 LiveRef купил публичный способ getPort(), следственно вам огромнее не необходим Field.setAccessible. Но в любом случае, не стоит зависеть от sun.* классов.)

Безусловно изредка не получится обнаружить лучшего решения. Но если те классы, которыми вы серьезно заинтересовались, оказались сериализуемыми, то абсолютно допустимо, вам это удастся. Дело в том, что сериализованная форма класса является частью его контракта. Если API не вовсе пропащий, то его внешний контракт будет совместим с его предыдущими версиями. Это дюже главное условие, в частности для платформы JDK.

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

Изложение сериализованной формы включается в Javadoc в разделе «See Also» для всякого сереализируемого кtOutputStream, унаследованным от DataOutput (writeIntwriteUTF и так дальше) представляется как SBlockData. Сериализованный поток не разрешает выделить отдельные элементы внутри этого куска; эта информация – соглашение между писателем и читателем, задокументированое в тэге @serialData.

Базируясь на документации ArrayList, мы можем получить размер массива таким образом:

SObject slist = (SObject) SerialScan.examine(list);
	List<SEntity> writeObjectData = slist.getAnnotations();
	SBlockData data = (SBlockData) writeObjectData.get(0);
	DataInputStream din = data.getDataInputStream();
	int alen = din.readInt();
	System.out.println("Array length: "   alen);

 

Как Serialysis решает мои тестовые задачи

Спуская полный начальный код, приведу только очерк решения к задаче QueryExp, о которой я говорил в начале. Представим, у меня QueryExp строится вот так:

QueryExp query =
    Query.or(Query.gt(Query.attr("Version"), Query.value(5)),
	     Query.eq(Query.attr("SupportsSpume"), Query.value(true)));

Это обозначает: «дай мне MBean-ы с признаком Version огромнее 5 либо признаком SupportsSpume, равным true». toString() для этого запроса в JDK выглядит так:

((Version) > (5)) or ((SupportsSpume) = (true))

А так выглядит итог SerialScan.examine:

SObject(javax.management.OrQueryExp){
  exp1 = SObject(javax.management.BinaryRelQueryExp){
    relOp = SPrim(int){0}
    exp1 = SObject(javax.management.AttributeValueExp){
      attr = SString(String){"version"}
    }
    exp2 = SObject(javax.management.NumericValueExp){
      val = SObject(java.lang.Long){
        value = SPrim(long){5}
      }
    }
  }
  exp2 = SObject(javax.management.BinaryRelQueryExp){
    relOp = SPrim(int){4}
    exp1 = SObject(javax.management.AttributeValueExp){
      attr = SString(String){"supportsSpume"}
    }
    exp2 = SObject(javax.management.BooleanValueExp){
      val = SPrim(boolean){true}
    }
  }
}

Легко представить себе код, тот, что погружается в эту конструкцию, создавая XML-эквивалент. От всякой совместимой реализации JMX API требуется создавать верно такую же сериализованную форму, следственно анализирующий её код гарантированно будет трудиться где желательно.

Сейчас код, тот, что решает загвоздку номера порта в RMI заглушке:

 public static int getPort(RemoteStub stub) throws IOException {
	SObject sstub = (SObject) SerialScan.examine(stub);
	List<SEntity> writeObjectData = sstub.getAnnotations();
	SBlockData sdata = (SBlockData) writeObjectData.get(0);
	DataInputStream din = sdata.getDataInputStream();
	String type = din.readUTF();
	if (type.equals("UnicastRef"))
	    return getPortUnicastRef(din);
	else if (type.equals("UnicastRef2"))
	    return getPortUnicastRef2(din);
	else
	    throw new IOException("Can't handle ref type "   type);
    }

    private static int getPortUnicastRef(DataInputStream din) throws IOException {
	String host = din.readUTF();
	return din.readInt();
    }

    private static int getPortUnicastRef2(DataInputStream din) throws IOException {
	byte hasCSF = din.readByte();
	String host = din.readUTF();
	return din.readInt();
    }

Дабы разобраться в нем, взгляните на изложение сериализованной формы RemoteObject.

Данный код, безусловно, сложный, но но он легко портируем и перспективен в применении. Думаю, нет смысла пояснять, как извлекать из RMI-заглушек все остальные данные — используйте данный же метод.

Завершение

Скорее каждого, вы не захотите копаться в сериализованных формах до тех пор, пока не возникнет серьезная надобность. Но если без этого не обойтись, Serialysis горазд значительно упростить вам задачу.

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

Скачать библиотеку Serialysis дозволено отсель: http://weblogs.java.net/blog/emcmanus/serialysis.zip.

Источник: programmingmaster.ru

Оставить комментарий
БАЗА ЗНАНИЙ
СЛУЧАЙНАЯ СТАТЬЯ
СЛУЧАЙНЫЙ БЛОГ
СЛУЧАЙНЫЙ МОД
СЛУЧАЙНЫЙ СКИН
НОВЫЕ МОДЫ
НОВЫЕ СКИНЫ
НАКОПЛЕННЫЙ ОПЫТ
Форум phpBB, русская поддержка форума phpBB
Рейтинг@Mail.ru 2008 - 2017 © BB3x.ru - русская поддержка форума phpBB