Comment compiler et charger dynamiquement des classes java externes? [dupliquer]

cette question a déjà une réponse ici:

  • comment fournir une interface à JavaCompiler lors de la compilation dynamique d'un fichier source? 3 réponses

(cette question est similaire à beaucoup de questions que j'ai vu mais la plupart ne sont pas assez spécifiques pour ce que je fais)

Contexte:

le but de mon programme est de rendre facile pour les personnes qui utilisent mon programme de faire des" plugins " personnalisés pour ainsi dire, puis compiler et charger dans le programme pour utilisation (vs avoir un parser lent incomplet mis en œuvre dans mon programme). Mon programme permet aux utilisateurs d'entrer du code dans une classe prédéfinie en étendant une classe compilée avec mon programme. Ils saisir le code dans le texte volets puis mon programme copie le code dans les méthodes remplacer. Il sauve alors ceci comme un .fichier java (presque) prêt pour le compilateur. Le programme exécute javac (java compiler) avec le sauvegardé .java fichier en entrée.

ma question Est, Comment puis-je l'obtenir pour que le client puisse (en utilisant mon programme compilé) sauvegarder ce fichier java (qui étend mon exemple D'InterfaceExample) n'importe où sur leur ordinateur, faire compiler mon programme (sans dire "ne trouve pas le symbole: InterfaceExample") puis le charger et appeler la méthode doSomething ()?

je continue à voir Q&A en utilisant reflection ou ClassLoader et un qui a presque décrit comment le compiler, mais aucun ne sont assez détaillés pour moi/Je ne les comprends pas complètement.

34
demandé sur caucow 2014-02-04 09:14:34

2 réponses

regardez JavaCompiler

ce qui suit est basé sur l'exemple donné dans le JavaDocs

cela sauvegardera un File dans le répertoire testcompile (basé sur les exigences de nom package ) et compilera le File dans une classe Java...

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

}

maintenant mis à jour pour inclure le suppling d'un chemin de classe pour le compilateur et le chargement et l'exécution de la classe compilée!

50
répondu MadProgrammer 2015-03-10 02:24:03

je suggère d'utiliser la bibliothèque Java Runtime Compiler . Vous pouvez lui donner une chaîne de caractères en mémoire et il compilera et chargera la classe dans le chargeur de classe courant (ou l'un de vos choix) et retournera la classe chargée. Les classes imbriquées sont également chargées. Remarque: cela fonctionne entièrement dans la mémoire par défaut.

p.ex.

 // 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();
24
répondu Peter Lawrey 2018-06-14 09:32:51