当前位置: 首页 > 面试题库 >

如何在运行时发出和执行Java字节码?

巫马昆琦
2023-03-14
问题内容

我正在用Java为具有特定脚本功能的领域特定语言编写解释器。我已经实现了解析器,现在需要做一个后端。为此,我正在考虑编写自己的解释器(使用抽象语法树或某些自定义字节码)或目标JVM(在运行时发出并执行Java字节码)。

对此领域有更多经验的人可以说针对JVM的方法可行吗?您建议使用哪些库来发出Java字节码?


问题答案:

这是使用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[] {});
    }
}

为了生成代码,我发现了非常有用的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:如果不够清晰,我可以在代码中添加注释。我不是因为答案已经太长了。不过,我对您的建议是尝试对其进行调试。



 类似资料:
  • 问题内容: 我得到了一些在程序中生成的Java字节码(即已编译的Java源代码)。现在,我想将此字节代码加载到当前运行的Java- VM中并运行特定功能。我不确定如何做到这一点,我对Java类加载器进行了一些研究,但没有找到直接的方法。 我找到了一个在硬盘上接收类文件的解决方案,但是我得到的字节码在字节数组中,我不想将其写入磁盘,而是直接使用它。 谢谢! 问题答案: 您需要编写一个自定义的类加载器

  • 我正在尝试将我制作的两个简单程序合并为一个。罐子我两个都打包了。将jar装入新的jar并在运行时使用。getRuntime()。exec方法来执行它们。 代码: 问题是只执行proj1.jar,proj2.jar不运行。我是新来的java,不知道为什么会这样。我该怎么解决这个问题?我想要两个文件都被执行。

  • 问题内容: 我试图了解Python的工作原理(因为我一直都在使用它!)。据我了解,当您运行python script.py之类的脚本时,该脚本将转换为字节码,然后解释器/ VM / CPython(实际上只是一个C程序)读取python字节码并相应地执行该程序。 该字节码如何读入?它类似于在C语言中读取文本文件的方式吗?我不确定Python代码如何转换为机器代码。是否确实是Python解释器(CL

  • 问题内容: 我正在编写一个规则引擎,该引擎执行由条件构造确定的简单分配。规则必须为XML格式,这是项目的先决条件。我已经将XML模式建模为类似于简单的代码块。我希望解析XML,然后将其转换为Java代码。然后,我希望在运行时编译(并运行)此代码。这样做意味着我的规则引擎不再充当解释器,而是执行本机Java字节码。 我已经确定了解析阶段,或多或少的Java代码生成阶段。我现在想弄清楚最后一个阶段-运

  • 我正在尝试用Java编写RSA加密和解密类,用于客户端来回传递字符串的服务器。我为这些类编写了以下代码: 我有以下测试类代码: 然而,当我尝试运行测试类时,密钥生成和加密工作正常,但当我尝试解密时,会出现一个错误。错误是javax。加密。IllegalBlockSizeException:数据不能超过128字节,但我的数据肯定小于128字节。 我可以确认将公钥转换为字符串并返回相同的公钥。我也尝试

  • 字节码的解释执行和AST的解释执行有类似之处,而且更简单,因为树形结构已经展开成顺序了,以栈虚拟机为例,为方便起见,假设所有的指令都在一个指令数组里,每个元素是一个指令对象,有code和arg两个属性,解释器入口: Object execute(Inst[] inst_list, Object[] func_arg); 由于continue和break已经被jmp指令代替了,这里我们认为exec