java - 如何动态编译和加载外部 java 类?

标签 java dynamic compilation load external

(这个问题与我见过的许多问题相似,但大多数问题对我正在做的事情不够具体)

背景:

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

我的问题是,我如何获取它以便客户端可以(使用我编译的程序)将此 java 文件(扩展我的 InterfaceExample)保存在他们计算机上的任何位置,让我的程序编译它(不说“找不到符号: InterfaceExample") 然后加载它并调用 doSomething() 方法?

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

最佳答案

看看JavaCompiler

以下内容基于JavaDocs中给出的示例

这将在 testcompile 目录中保存一个 File(基于 package 名称要求)并编译 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<JavaFileObject> diagnostics = new DiagnosticCollector<JavaFileObject>();
                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<String> optionList = new ArrayList<String>();
                optionList.add("-classpath");
                optionList.add(System.getProperty("java.class.path") + File.pathSeparator + "dist/InlineCompiler.jar");

                Iterable<? extends JavaFileObject> 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<? extends JavaFileObject> 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();
    }

}

现在更新为包括为编译器提供类路径以及加载和执行已编译的类!

关于java - 如何动态编译和加载外部 java 类?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/21544446/

相关文章:

c - 当隐式规则没有说明可执行文件时,make 如何从 C 源文件构建可执行文件?

c - 是否可以在 linux 上为许多体系结构编译 c?

java - 如何在LDAP中获取当前登录用户信息

java 。 Swing 。更改容器中组件的顺序

java - java中动态多态性的实时示例是什么

vba - 在数据透视 + VBA + 动态解决方案中运行所有可能的页面过滤器组合

java - 消息气泡自定义绘图

java - 确定 Java Eclipse 中方法调用的顺序

python - 创建基于动态更改列的公式以在 Pandas Dataframe 列中设置值

linux - 为什么一个剥离的二进制文件在反汇编文件中仍然可以有库调用信息?