如何在运行时发出和执行 Java 字节码?
Posted
技术标签:
【中文标题】如何在运行时发出和执行 Java 字节码?【英文标题】:How to emit and execute Java bytecode at runtime? 【发布时间】:2011-04-30 07:12:43 【问题描述】:我正在用 Java 为具有一些脚本功能的特定领域语言编写解释器。我已经实现了一个解析器,现在需要做一个后端。为此,我正在考虑编写自己的解释器(使用抽象语法树或使用一些自定义字节码)或目标 JVM(在运行时发出并执行 Java 字节码)。
在这方面有更多经验的人能否说一下以 JVM 为目标的方法的可行性以及您推荐使用哪些库来发出 Java 字节码?
【问题讨论】:
是否已经设计/指定了 DSL?还是您正在与解析器和解释器一起开发 DSL? @Stobor: 语言已经指定并且解析器已经写好了。 【参考方案1】:这是一个使用ObjectWeb ASM(我推荐的库)制作的“hello world”:
package hello;
import java.lang.reflect.Method;
import org.objectweb.asm.ClassWriter;
import org.objectweb.asm.Label;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;
public class HelloWorldASM implements Opcodes
public static byte[] compile(String name)
ClassWriter cw = new ClassWriter(0);
MethodVisitor mv;
cw.visit(V1_6, ACC_PUBLIC + ACC_SUPER, "hello/HelloWorld", null,
"java/lang/Object", null);
cw.visitSource("HelloWorld.java", null);
mv = cw.visitMethod(ACC_PUBLIC, "<init>", "()V", null, null);
mv.visitCode();
Label l0 = new Label();
mv.visitLabel(l0);
mv.visitLineNumber(4, l0);
mv.visitVarInsn(ALOAD, 0);
mv.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "<init>",
"()V");
mv.visitInsn(RETURN);
Label l1 = new Label();
mv.visitLabel(l1);
mv.visitLocalVariable("this", "Lhello/HelloWorld;", null, l0, l1,
0);
mv.visitMaxs(1, 1);
mv.visitEnd();
mv = cw.visitMethod(ACC_PUBLIC + ACC_STATIC, "main",
"([Ljava/lang/String;)V", null, null);
mv.visitCode();
Label l0 = new Label();
mv.visitLabel(l0);
mv.visitLineNumber(7, l0);
mv.visitFieldInsn(GETSTATIC, "java/lang/System", "out",
"Ljava/io/PrintStream;");
mv.visitLdcInsn(String.format("Hello, %s!", name));
mv.visitMethodInsn(INVOKEVIRTUAL, "java/io/PrintStream", "println",
"(Ljava/lang/String;)V");
Label l1 = new Label();
mv.visitLabel(l1);
mv.visitLineNumber(8, l1);
mv.visitInsn(RETURN);
Label l2 = new Label();
mv.visitLabel(l2);
mv.visitLocalVariable("args", "[Ljava/lang/String;", null, l0, l2,
0);
mv.visitMaxs(2, 1);
mv.visitEnd();
cw.visitEnd();
return cw.toByteArray();
public static class DynamicClassLoader extends ClassLoader
public Class<?> define(String className, byte[] bytecode)
return super.defineClass(className, bytecode, 0, bytecode.length);
;
public static void main(String[] args) throws Exception
DynamicClassLoader loader = new DynamicClassLoader();
Class<?> helloWorldClass = loader.define("hello.HelloWorld",
compile("Test"));
Method method = helloWorldClass.getMethod("main", String[].class);
method.invoke(null, (Object) new String[] );
要生成代码,我发现Bytecode Outline for Eclipse 插件非常有用。虽然您可以像这样使用 ASMifier(包含在 ASM 中):
ClassReader cr = new ClassReader(new FileInputStream("HelloWorld.class"));
cr.accept(new ASMifierClassVisitor(new PrintWriter(System.out)), 0);
在运行时,如果您需要为创建的类获取Class
对象,您可以通过扩展类加载器并发布(例如通过另一种方法)defineClass
方法并提供类来加载您的类作为一个字节数组,如示例中所列。
您还可以使用接口处理创建的类,如下例所示:
package hello;
import org.objectweb.asm.ClassWriter;
import org.objectweb.asm.Label;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;
public class HelloWorldPlugin implements Opcodes
public static interface Plugin
void sayHello(String name);
public static byte[] compile()
ClassWriter cw = new ClassWriter(0);
MethodVisitor mv;
cw.visit(V1_6, ACC_PUBLIC + ACC_SUPER, "hello/MyClass", null,
"java/lang/Object",
new String[] "hello/HelloWorldPlugin$Plugin" );
cw.visitInnerClass("hello/HelloWorldPlugin$Plugin",
"hello/HelloWorldPlugin", "Plugin", ACC_PUBLIC + ACC_STATIC
+ ACC_ABSTRACT + ACC_INTERFACE);
mv = cw.visitMethod(ACC_PUBLIC, "<init>", "()V", null, null);
mv.visitCode();
Label l0 = new Label();
mv.visitLabel(l0);
mv.visitLineNumber(5, l0);
mv.visitVarInsn(ALOAD, 0);
mv.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "<init>",
"()V");
mv.visitInsn(RETURN);
Label l1 = new Label();
mv.visitLabel(l1);
mv.visitLocalVariable("this", "Lhello/MyClass;", null, l0, l1, 0);
mv.visitMaxs(1, 1);
mv.visitEnd();
mv = cw.visitMethod(ACC_PUBLIC, "sayHello",
"(Ljava/lang/String;)V", null, null);
mv.visitCode();
Label l0 = new Label();
mv.visitLabel(l0);
mv.visitLineNumber(9, l0);
mv.visitFieldInsn(GETSTATIC, "java/lang/System", "out",
"Ljava/io/PrintStream;");
mv.visitTypeInsn(NEW, "java/lang/StringBuilder");
mv.visitInsn(DUP);
mv.visitLdcInsn("Hello, ");
mv.visitMethodInsn(INVOKESPECIAL, "java/lang/StringBuilder",
"<init>", "(Ljava/lang/String;)V");
mv.visitVarInsn(ALOAD, 1);
mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/StringBuilder",
"append", "(Ljava/lang/String;)Ljava/lang/StringBuilder;");
mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/StringBuilder",
"toString", "()Ljava/lang/String;");
mv.visitMethodInsn(INVOKEVIRTUAL, "java/io/PrintStream", "println",
"(Ljava/lang/String;)V");
Label l1 = new Label();
mv.visitLabel(l1);
mv.visitLineNumber(10, l1);
mv.visitInsn(RETURN);
Label l2 = new Label();
mv.visitLabel(l2);
mv.visitLocalVariable("this", "Lhello/MyClass;", null, l0, l2, 0);
mv.visitLocalVariable("name", "Ljava/lang/String;", null, l0, l2,
1);
mv.visitMaxs(4, 2);
mv.visitEnd();
cw.visitEnd();
return cw.toByteArray();
public static class DynamicClassLoader extends ClassLoader
public DynamicClassLoader(ClassLoader parent)
super(parent);
public Class<?> define(String className, byte[] bytecode)
return super.defineClass(className, bytecode, 0, bytecode.length);
;
public static void main(String[] args) throws Exception
DynamicClassLoader loader = new DynamicClassLoader(Thread
.currentThread().getContextClassLoader());
Class<?> helloWorldClass = loader.define("hello.MyClass", compile());
Plugin plugin = (Plugin) helloWorldClass.newInstance();
plugin.sayHello("Test");
玩得开心。
PS:如果不够清楚,我可以在代码中添加 cmets。我没有,因为答案已经太长了。不过,我给你的建议是尝试调试它。
【讨论】:
是的,我知道。不客气。但我不会把功劳归于原作者安德烈·洛斯库托夫。【参考方案2】:我可以建议你看看这些库:
CGLIB BCEL asm Javassist【讨论】:
感谢您的链接。你用过这些吗?你会推荐哪一个? 我使用过 Javassist 和 CGLIB,但用于比您需要的更简单的任务。需要注意的一点是,BCEL 使用的是 CGLIB,因此它可能具有更大的功能。【参考方案3】:查看Jetbrains MPS。由为我们带来 IDEA 的人打造。
【讨论】:
【参考方案4】:从另一个角度,我问你是否考虑使用XText。这旨在使您能够创建 DSL、具有代码完成功能的代码编辑器、编译器、代码生成器等。我认为这真的很酷,并且有一个很好的documentation。值得一看。您可以基于它为您的 DSL 轻松创建编译器。
【讨论】:
感谢您的链接。看来使用 XText 我将不得不重新实现我想避免的前端。 是的,我明白了。这取决于您的团队规模和公司规模。我认为有时有必要完全放弃专有的东西来使用别人也使用的东西。以上是关于如何在运行时发出和执行 Java 字节码?的主要内容,如果未能解决你的问题,请参考以下文章