Сериализация без отражения в скомпилированных классах

Из-за ограничения на клиентской JVM я не могу использовать ни один из популярных сериализаторов из-за того, что не поддерживается отражение. Я ищу инструмент, который выполняет манипуляции с байт-кодом для сериализации путем внедрения методов записи и чтения в уже скомпилированный класс. Мне нужен java-код для манипулирования байт-кодом, чтобы связать его с моим кодом для процесса сборки.

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

Я знаю о Kryo и других сериализаторах XML и JSON, но они не соответствуют моим потребностям.

Спасибо.


person Juan Garcia    schedule 23.04.2015    source источник
comment
Может ли downvoter оставить комментарий?   -  person Juan Garcia    schedule 23.04.2015
comment
Какой клиент vm вы используете? Это ГВТ?   -  person slartidan    schedule 01.05.2015
comment
Я перехожу с GWT на TeaVM.   -  person Juan Garcia    schedule 01.05.2015
comment
Если вы пытаетесь написать сериализатор для TeaVM, может быть, вам следует обратиться к его автору по электронной почте или в группе проекта google?   -  person Alexey Andreev    schedule 07.05.2015
comment
@Alexey Я взломал вывод JavaScript для компилятора TeaVM, и у меня есть поля для объекта, однако он использовал для них автоматически сгенерированные имена сортировки, и после многих компиляций и попыток я понял, что поля никогда не следовали предсказуемому шаблону: они ни в алфавитном порядке имен полей, ни в порядке их объявления, ни даже в порядке использования метода отражения getDeclaredField. И даже если я сериализую их в TeaVM, мне нужно будет использовать другую реализацию для серверной JVM.   -  person Juan Garcia    schedule 07.05.2015
comment
@Alexey Я пытаюсь сделать его переносимым, чтобы он работал также для Dragome и Bck2Brwsr, Android, любой стандартной JVM или Java ME, а также путем внедрения исходного кода в GWT.   -  person Juan Garcia    schedule 07.05.2015


Ответы (3)


Попробуйте javassist. Это, вероятно, самая простая библиотека для генерации байт-кода для вашего конкретного проекта.

Это потому, что вы сможете повторно использовать часть вашего текущего генератора кода, поскольку javassist может анализировать некоторые простые формы кода Java.

Например, вы можете сделать:

CtClass clazz = ...;
CtMethod m = CtNewMethod.make(
    "public void serialize(java.io.DataOutput out) {  out.writeInt(x); }",
     clazz);

Если вы хотите углубиться в создание байт-кода, вы можете попробовать asm. Asm позволит вам писать байт-код напрямую, но это кажется излишним для вашей проблемы.

Реализация с помощью javassist

Вот базовый скелет, чтобы сделать это с помощью javassist:

Path inputDir = Paths.get("target/classes");
Path outputDir = Paths.get("target/classes2");

ClassPool classPool = new ClassPool(true);
classPool.appendClassPath(inputDir.toString());

// get all class names from a certain directory
String[] classNames = Files.walk(inputDir)
        .filter(p -> (!Files.isDirectory(p)) && p.toString().endsWith(".class"))
        .map(p -> inputDir.relativize(p).toString())
        .map(s -> s.substring(0, s.length() - 6).replace(File.separatorChar, '.'))
        .toArray(size -> new String[size]);

for (String className : classNames) {
    CtClass clazz = classPool.get(className);
    // add further filtering to select the classes you want.

    // ex: "public void serializer(java.io.DataOutput out) { out.writeInt(x); } }"
    String serializerBody = generateSerializer(clazz);
    clazz.addMethod(CtNewMethod.make(serializerBody, clazz));

    // ex: "public void deserializer(java.io.DataInput in) { x = in.readInt(); } }";
    String deserializerBody = generateDeserializer(clazz);
    clazz.addMethod(CtNewMethod.make(deserializerBody, clazz));

    // save the modified class
    clazz.setModifiers(clazz.getModifiers() & ~Modifier.ABSTRACT);
    byte[] bytes = clazz.toBytecode();
    Path outFile = outputDir.resolve(className.replace('.', '/') + ".class");
    Files.createDirectories(outFile.getParent());
    Files.write(outFile, bytes);
}

Зависимость: org.javassist:javassist:3.19.0-GA

person Daniel Sperry    schedule 02.05.2015
comment
Это то, что я делал, портируя свой сериализатор на Javassist. Я жду, чтобы увидеть, есть ли проект, который уже делает это, если нет, это будет принятый ответ. - person Juan Garcia; 03.05.2015
comment
Было бы любезно с вашей стороны упомянуть об этом в вопросе. Из того, что вы сказали, можно только сделать вывод, что вы генерировали только исходный код. Два ответа, которые вы получили до сих пор, являются свидетельством этого. - person Daniel Sperry; 03.05.2015
comment
Я не упомянул об этом, потому что я только что узнал о Javassist после того, как задал этот вопрос. - person Juan Garcia; 03.05.2015
comment
Круто :) Кстати, я думаю, вы можете найти здесь что-то новое. Не могу найти никаких упоминаний о том, что кто-то делает то же, что и вы, в java. Я надеюсь, что вы открываете его. - person Daniel Sperry; 05.05.2015
comment
Конечно, я хотел бы выпустить его с открытым исходным кодом, когда он будет завершен, но у меня не будет времени вести документацию. - person Juan Garcia; 05.05.2015
comment
Если вы хотите знать, что делает мой сериализатор, он сериализует объект в строку UTF-8 в очень компактном формате. Он жертвует временем обработки для уменьшения размера, а также делает много предположений, поэтому он не сможет десериализоваться при изменении класса. С другой стороны, это формат, предназначенный для быстрой связи клиент-сервер. Я разрабатываю свой собственный протокол RPC вокруг него. - person Juan Garcia; 05.05.2015
comment
Полученную строку можно безопасно встраивать в XML или JSON, поскольку она не создает никаких символов, которые необходимо экранировать. Во многих случаях он может быть очень маленьким по размеру, так как маленькое длинное поле сериализовано в один единственный однобайтовый символ UTF-8. Это позволяет быстро сократить время работы сети, даже если обработка занимает больше времени, чем, скажем, запись и чтение потоков туда-сюда. - person Juan Garcia; 05.05.2015
comment
Я бы не стал писать свой собственный сериализатор, если бы что-то подобное уже было сделано. GWT сделал это автоматически, но работает только с GTW. Мой может быть независимым от платформы и скомпилирован статически. Если вам интересно узнать больше или вы хотите сотрудничать в поддержании его как проекта с открытым исходным кодом, свяжитесь со мной. - person Juan Garcia; 05.05.2015
comment
Давай поговорим :) Я не смог найти способ связаться с тобой из твоего профиля stackoverflow. Это мой профиль на github, и я доступен на гиттер: - person Daniel Sperry; 08.05.2015

Для этой цели вы можете использовать мою библиотеку Byte Buddy. Byte Buddy — это библиотека для работы с байтовым кодом, которая позволяет легко добавлять методы в любой существующий класс. Кроме того, он позволяет вставлять переопределенный код в файл jar. Таким образом, вы можете попросить Byte Buddy переопределить классы, чтобы добавить необходимые методы. Однако обратите внимание, что добавление методов к классу может изменить его UUID неявной сериализации.

Если ваши классы не должны быть загружены, пока ваше приложение не запустится, Byte Buddy позволяет вам переопределить классы, не загружая их. Для этого вы можете использовать пул типа Byte Buddys.

person Rafael Winterhalter    schedule 01.05.2015
comment
Ваша библиотека звучит великолепно. Однако я ищу библиотеку сериализации. Если мне нужно реализовать это самому, в этом случае Javassist будет лучше, так как он легко позволит мне повторно использовать мой собственный сериализатор исходного кода и работать с файлами классов, которые я мог бы подключить к компоновщику для Eclise. - person Juan Garcia; 03.05.2015

Некоторые возможные альтернативы:

person Daniel Sperry    schedule 04.05.2015
comment
Я посмотрел на все из них, к сожалению, я не мог использовать ни один из них. JVM — это компилятор AOT. Но это был полезный ответ, так как теперь я начал понимать, что то, что мне нужно, еще не сделано, поэтому я продолжу работу над своим сериализатором. - person Juan Garcia; 05.05.2015