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

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/21544446/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-13 09:17:04  来源:igfitidea点击:

How do you dynamically compile and load external java classes?

javadynamiccompilationloadexternal

提问by Shadowtrot

(This question is similar to many questions I have seen but most are not specific enough for what I am doing)

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

Background:

背景:

The purpose of my program is to make it easy for people who use my program to make custom "plugins" so to speak, then compile and load them into the program for use (vs having an incomplete, slow parser implemented in my program). My program allows users to input code into a predefined class extending a compiled class packaged with my program. They input the code into text panes then my program copies the code into the methods being overridden. It then saves this as a .java file (nearly) ready for the compiler. The program runs javac (java compiler) with the saved .java file as its input.

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

My question is, how do I get it so that the client can (using my compiled program) save this java file (which extends my InterfaceExample) anywhere on their computer, have my program compile it (without saying "cannot find symbol: InterfaceExample") then load it and call the doSomething() method?

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

I keep seeing Q&A's using reflection or ClassLoader and one that almost described how to compile it, but none are detailed enough for me/I do not understand them completely.

我一直看到使用反射或类加载器的问答,以及几乎描述了如何编译它的问答,但对我来说都不够详细/我不完全理解它们。

采纳答案by MadProgrammer

Take a look at JavaCompiler

看一眼 JavaCompiler

The following is based on the example given in the JavaDocs

以下基于JavaDocs中给出的示例

This will save a Filein the testcompiledirectory (based on the packagename requirements) and the compile the Fileto a Java class...

这将Filetestcompile目录中保存一个(基于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();
    }

}

Now updated to include suppling a classpath for the compiler and loading and execution of the compiled class!

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

回答by Peter Lawrey

I suggest using the Java Runtime Compilerlibrary. You can give it a String in memory and it will compile and load the class into the current class loader (or one of your choice) and return the Class loaded. Nested classes are also loaded. Note: this works entirely in memory by default.

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

e.g.

例如

 // 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();