编译后没有反射的序列化 类
Serialization without reflection in compiled classes
由于客户端JVM 的限制,由于不支持反射,我无法使用任何流行的序列化程序。我正在寻找一种执行字节码操作的工具,通过将 writer 和 reader 方法注入已编译的 class 来实现序列化。
我需要字节码操作 java 代码来将其与我的构建过程代码绑定。
我一直在通过生成代码并将其注入源代码,然后编译以使用我的自定义序列化程序来执行此操作。我想避免这种方法,因为我不想让你以任何方式修改源文件。
我知道 Kryo 和其他 XML 和 JSON 序列化程序,但它们不符合我的需求。
谢谢。
您可以使用我的图书馆 Byte Buddy for this purpose. Byte Buddy is a byte code manipulation library and it allows you to easily add methods to any existing class. Furthermore, it allows you inject redefined code into a jar file。这样,您可以要求字节好友重新定义 classes 以添加所需的方法。但是,请注意,向 class 添加方法可能会更改其隐式序列化 uuid。
如果您的 classes 在您的应用程序启动之前不得加载,Byte Buddy 允许您重新定义 classes 而无需加载它们。为此,您可以使用 Byte Buddys 类型的池。
尝试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);
}
一些可能的选择:
- 如果你可以使用scala,这个项目会编译时生成序列化程序:
https://github.com/scala/pickling
- 这个Whosebug问题指出了使用aspectJ和注解处理的可能性:
AspectJ / Generate methods using compile-time reflection
顺便说一句,如果您的注释处理器声称处理“*”所有注释,则您的 类 不需要注释。
- Seren (SERialization ENhancer) 声称 "enhance your classes so that they are much quicker to serialize." 然而,像许多其他工具一样,它在加载时执行此操作......也许可以对其进行调整以执行构建时间工具,或向它的创建者建议。
- leshy,也进行运行时检测。同样的事情:适应编译时间或向作者建议。
由于客户端JVM 的限制,由于不支持反射,我无法使用任何流行的序列化程序。我正在寻找一种执行字节码操作的工具,通过将 writer 和 reader 方法注入已编译的 class 来实现序列化。 我需要字节码操作 java 代码来将其与我的构建过程代码绑定。
我一直在通过生成代码并将其注入源代码,然后编译以使用我的自定义序列化程序来执行此操作。我想避免这种方法,因为我不想让你以任何方式修改源文件。
我知道 Kryo 和其他 XML 和 JSON 序列化程序,但它们不符合我的需求。
谢谢。
您可以使用我的图书馆 Byte Buddy for this purpose. Byte Buddy is a byte code manipulation library and it allows you to easily add methods to any existing class. Furthermore, it allows you inject redefined code into a jar file。这样,您可以要求字节好友重新定义 classes 以添加所需的方法。但是,请注意,向 class 添加方法可能会更改其隐式序列化 uuid。
如果您的 classes 在您的应用程序启动之前不得加载,Byte Buddy 允许您重新定义 classes 而无需加载它们。为此,您可以使用 Byte Buddys 类型的池。
尝试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);
}
一些可能的选择:
- 如果你可以使用scala,这个项目会编译时生成序列化程序: https://github.com/scala/pickling
- 这个Whosebug问题指出了使用aspectJ和注解处理的可能性:
AspectJ / Generate methods using compile-time reflection
顺便说一句,如果您的注释处理器声称处理“*”所有注释,则您的 类 不需要注释。 - Seren (SERialization ENhancer) 声称 "enhance your classes so that they are much quicker to serialize." 然而,像许多其他工具一样,它在加载时执行此操作......也许可以对其进行调整以执行构建时间工具,或向它的创建者建议。
- leshy,也进行运行时检测。同样的事情:适应编译时间或向作者建议。