web-dev-qa-db-ja.com

外部のJavaクラスを動的にコンパイルおよびロードするにはどうすればよいですか?

(この質問は私が見た多くの質問に似ていますが、ほとんどは私がやっていることに対して十分に具体的ではありません)

バックグラウンド:

私のプログラムの目的は、私のプログラムを使用する人々がカスタムの「プラグイン」を簡単に作成できるようにすることです。私のプログラムでは、ユーザーはプログラムにパッケージされたコンパイル済みクラスを拡張する定義済みクラスにコードを入力できます。彼らはコードをテキストペインに入力し、私のプログラムはオーバーライドされているメソッドにコードをコピーします。次に、これをコンパイラーの(ほぼ)準備ができた.Javaファイルとして保存します。プログラムは、保存された.Javaファイルを入力としてjavac(Javaコンパイラー)を実行します。

私の質問は、クライアントが(コンパイルされたプログラムを使用して)コンピューターのどこにでもこのJavaファイル)を保存し、プログラムをコンパイルできるようにする方法です( 「シンボルが見つかりません:InterfaceExample」と言わずに)それをロードし、doSomething()メソッドを呼び出しますか?

リフレクションまたはClassLoaderを使用しているQ&Aと、それをコンパイルする方法をほとんど説明しているQ&Aが表示され続けますが、十分な詳細はありません/それらを完全に理解していません。

42
Shadowtrot

JavaCompiler を見てください

以下は、JavaDocsに記載されている例に基づいています。

これにより、Fileディレクトリにtestcompileが保存され(package名前の要件に基づいて)、FileがJava class ...

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") + ";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();
    }

}

コンパイラーのクラスパスの供給と、コンパイルされたクラスのロードと実行を含むように更新されました!

63
MadProgrammer

Java Runtime Compiler ライブラリを使用することをお勧めします。メモリ内の文字列を渡すと、クラスをコンパイルして現在のクラスローダー(または選択したクラスローダー)に読み込み、読み込んだクラスを返します。ネストされたクラスもロードされます。注:これはデフォルトで完全にメモリ内で機能します。

例えば.

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