你如何动态编译和加载外部的Java类?

29 浏览
0 Comments

你如何动态编译和加载外部的Java类?

这个问题已经有了答案
当动态编译源文件时,如何为Java编译器提供接口?

社区在5个月前回顾了是否重新打开这个问题(原文为2022-10-28),并将其关闭:

原关闭原因尚未解决

(这个问题类似于我见过的许多问题,但大多数都不够具体)

背景:

我的程序的目的是让使用我的程序的人很容易制作自定义的“插件”,然后将它们编译并加载到程序中以供使用(而不是在程序中实现一个不完整、慢的解析器)。我的程序允许用户输入代码到一个预定义的类,扩展了一个打包在我的程序中的已编译类。他们将代码输入到文本面板,然后我的程序将代码复制到被覆盖的方法中。然后将其保存为一个(几乎)准备好编译器的.java文件。该程序使用保存的.java文件作为输入输入运行javac(Java编译器)。

我的问题是,如何让客户端使用我的编译程序在他们的计算机上任意保存这个扩展我的InterfaceExample的Java文件,然后编译它(不会提示“找不到符号:InterfaceExample”),然后加载它并调用doSomething()方法?

我一直看到使用反射或ClassLoader的问答,几乎有一个描述如何编译它的,但没有一个对我来说足够详细/我完全不理解它们。

admin 更改状态以发布 2023年5月21日
0
0 Comments

我建议使用Java Runtime Compiler库。您可以将String放入内存中,它将编译并加载类到当前类加载器(或您选择的一个)中,并返回加载的类。嵌套类也会被加载。注意:默认情况下,这完全在内存中进行工作。

例如:

 // dynamically you can call
 String className = "mypackage.MyClass";
 String javaCode = "package mypackage;\n" +
                  "public class MyClass implements Runnable {\n" +
                  "    public void run() {\n" +
                  "        System.out.println(\"Hello World\");\n" +
                  "    }\n" +
                  "}\n";
 Class aClass = CompilerUtils.CACHED_COMPILER.loadFromJava(className, javaCode);
 Runnable runner = (Runnable) aClass.newInstance();
 runner.run();

0
0 Comments

看一下JavaCompiler

以下是以JavaDocs中给出的示例为基础

这将会在testcompile目录下(基于package名称要求)保存一个File并编译这个File为Java类......

package inlinecompiler;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.io.Writer;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import javax.tools.Diagnostic;
import javax.tools.DiagnosticCollector;
import javax.tools.JavaCompiler;
import javax.tools.JavaFileObject;
import javax.tools.StandardJavaFileManager;
import javax.tools.ToolProvider;
public class InlineCompiler {
    public static void main(String[] args) {
        StringBuilder sb = new StringBuilder(64);
        sb.append("package testcompile;\n");
        sb.append("public class HelloWorld implements inlinecompiler.InlineCompiler.DoStuff {\n");
        sb.append("    public void doStuff() {\n");
        sb.append("        System.out.println(\"Hello world\");\n");
        sb.append("    }\n");
        sb.append("}\n");
        File helloWorldJava = new File("testcompile/HelloWorld.java");
        if (helloWorldJava.getParentFile().exists() || helloWorldJava.getParentFile().mkdirs()) {
            try {
                Writer writer = null;
                try {
                    writer = new FileWriter(helloWorldJava);
                    writer.write(sb.toString());
                    writer.flush();
                } finally {
                    try {
                        writer.close();
                    } catch (Exception e) {
                    }
                }
                /** Compilation Requirements *********************************************************************************************/
                DiagnosticCollector diagnostics = new DiagnosticCollector();
                JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
                StandardJavaFileManager fileManager = compiler.getStandardFileManager(diagnostics, null, null);
                // This sets up the class path that the compiler will use.
                // I've added the .jar file that contains the DoStuff interface within in it...
                List optionList = new ArrayList();
                optionList.add("-classpath");
                optionList.add(System.getProperty("java.class.path") + File.pathSeparator + "dist/InlineCompiler.jar");
                Iterable compilationUnit
                        = fileManager.getJavaFileObjectsFromFiles(Arrays.asList(helloWorldJava));
                JavaCompiler.CompilationTask task = compiler.getTask(
                    null, 
                    fileManager, 
                    diagnostics, 
                    optionList, 
                    null, 
                    compilationUnit);
                /********************************************************************************************* Compilation Requirements **/
                if (task.call()) {
                    /** Load and execute *************************************************************************************************/
                    System.out.println("Yipe");
                    // Create a new custom class loader, pointing to the directory that contains the compiled
                    // classes, this should point to the top of the package structure!
                    URLClassLoader classLoader = new URLClassLoader(new URL[]{new File("./").toURI().toURL()});
                    // Load the class from the classloader by name....
                    Class loadedClass = classLoader.loadClass("testcompile.HelloWorld");
                    // Create a new instance...
                    Object obj = loadedClass.newInstance();
                    // Santity check
                    if (obj instanceof DoStuff) {
                        // Cast to the DoStuff interface
                        DoStuff stuffToDo = (DoStuff)obj;
                        // Run it baby
                        stuffToDo.doStuff();
                    }
                    /************************************************************************************************* Load and execute **/
                } else {
                    for (Diagnostic diagnostic : diagnostics.getDiagnostics()) {
                        System.out.format("Error on line %d in %s%n",
                                diagnostic.getLineNumber(),
                                diagnostic.getSource().toUri());
                    }
                }
                fileManager.close();
            } catch (IOException | ClassNotFoundException | InstantiationException | IllegalAccessException exp) {
                exp.printStackTrace();
            }
        }
    }
    public static interface DoStuff {
        public void doStuff();
    }
}

现在更新了,提供了编译器的classpath,并加载和执行编译的类!

0