web-dev-qa-db-fra.com

Comment compiler et charger dynamiquement des classes externes Java?

(Cette question est similaire à de nombreuses questions que j'ai vues, mais la plupart ne sont pas suffisamment spécifiques à ce que je fais)

Contexte:

Le but de mon programme est de permettre aux gens qui utilisent mon programme de créer des "plugins" personnalisés pour ainsi dire, puis de les compiler et de les charger dans le programme pour les utiliser (vs avoir un analyseur lent et incomplet implémenté dans mon programme). Mon programme permet aux utilisateurs de saisir du code dans une classe prédéfinie étendant une classe compilée empaquetée avec mon programme. Ils saisissent le code dans des volets de texte, puis mon programme copie le code dans les méthodes en cours de substitution. Il l'enregistre ensuite en tant que fichier .Java (presque) prêt pour le compilateur. Le programme exécute javac (compilateur Java) avec le fichier .Java enregistré comme entrée.

Ma question est de savoir comment l'obtenir pour que le client puisse (à l'aide de mon programme compilé) enregistrer ce fichier Java (qui étend mon InterfaceExample) n'importe où sur son ordinateur, demander à mon programme de le compiler ( sans dire "ne peut pas trouver le symbole: InterfaceExample") puis le charger et appeler la méthode doSomething ()?

Je continue de voir les questions et réponses à l'aide de la réflexion ou de ClassLoader et celle qui décrit presque comment le compiler, mais aucune n'est suffisamment détaillée pour moi/je ne les comprends pas complètement.

42
Shadowtrot

Jetez un oeil à JavaCompiler

Ce qui suit est basé sur l'exemple donné dans les JavaDocs

Cela permettra d'économiser un File dans le répertoire testcompile (basé sur les exigences de nom package) et de compiler le File dans un Java classe ...

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 la fourniture d'un chemin de classe pour le compilateur et le chargement et l'exécution de la classe compilée!

63
MadProgrammer

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

par exemple.

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