web-dev-qa-db-de.com

Wie kompiliert und lädt man dynamisch externe Java Klassen?

(Diese Frage ähnelt vielen Fragen, die ich gesehen habe, aber die meisten sind nicht spezifisch genug für das, was ich tue.)

Hintergrund:

Der Zweck meines Programms ist es, es Leuten, die mein Programm verwenden, sozusagen zu erleichtern, benutzerdefinierte "Plugins" zu erstellen, diese dann zu kompilieren und zur Verwendung in das Programm zu laden (im Gegensatz dazu, wenn ein unvollständiger, langsamer Parser in meinem Programm implementiert ist). Mit meinem Programm können Benutzer Code in eine vordefinierte Klasse eingeben, die eine mit meinem Programm kompilierte Klasse erweitert. Sie geben den Code in Textfenster ein, dann kopiert mein Programm den Code in die Methoden, die überschrieben werden. Es speichert diese dann als .Java-Datei (fast) bereit für den Compiler. Das Programm führt javac (Java-Compiler) mit der gespeicherten .Java-Datei als Eingabe aus.

Meine Frage ist, wie bekomme ich es, damit der Client (mit meinem kompilierten Programm) diese Java -Datei (die mein InterfaceExample erweitert) irgendwo auf seinem Computer speichern kann, damit mein Programm es kompiliert ( ohne zu sagen "Symbol kann nicht gefunden werden: InterfaceExample") dann laden und die Methode doSomething () aufrufen?

Ich sehe immer wieder Fragen und Antworten, die Reflection oder ClassLoader verwenden, und eine, die beinahe das Kompilieren beschrieben hat, aber keine ist detailliert genug für mich/ich verstehe sie nicht vollständig.

42
Shadowtrot

Schauen Sie sich JavaCompiler an

Das Folgende basiert auf dem in den JavaDocs angegebenen Beispiel

Dies speichert ein File im testcompile Verzeichnis (basierend auf den package Namensanforderungen) und kompiliert das File in ein Java Klasse ...

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

}

Jetzt aktualisiert, um einen Klassenpfad für den Compiler und das Laden und Ausführen der kompilierten Klasse zu ergänzen!

63
MadProgrammer

Ich schlage vor, die Bibliothek Java Runtime Compiler zu verwenden. Sie können ihm einen String in den Speicher geben, und er kompiliert und lädt die Klasse in das aktuelle Klassenladeprogramm (oder eines Ihrer Wahl) und gibt die geladene Klasse zurück. Geschachtelte Klassen werden ebenfalls geladen. Hinweis: Dies funktioniert standardmäßig vollständig im Speicher.

z.B.

 // 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