Como você compila e carrega dinamicamente as classs java externas?

(Esta pergunta é semelhante a muitas perguntas que tenho visto, mas a maioria não é específica o suficiente para o que estou fazendo)

Fundo:

O objective do meu programa é tornar mais fácil para as pessoas que usam o meu programa fazerem “plugins” personalizados por assim dizer, então compilá-los e carregá-los no programa para uso (tendo um parser incompleto e lento implementado em meu programa). Meu programa permite que os usuários insiram código em uma class predefinida, estendendo uma class compilada empacotada com meu programa. Eles inserem o código em painéis de texto e meu programa copia o código para os methods que estão sendo substituídos. Em seguida, ele salva isso como um arquivo .java (quase) pronto para o compilador. O programa executa o javac (compilador java) com o arquivo .java salvo como sua input.

Minha pergunta é: como faço para que o cliente possa (usando meu programa compilado) salvar este arquivo java (que estende meu InterfaceExample) em qualquer lugar em seu computador, ter meu programa compilado (sem dizer “não pode encontrar símbolo: InterfaceExample” então carregue-o e chame o método doSomething ()?

Eu continuo vendo Q & A’s usando reflection ou ClassLoader e um que quase descreveu como compilá-lo, mas nenhum é detalhado o suficiente para mim / eu não os entendo completamente.

Dê uma olhada no JavaCompiler

O seguinte é baseado no exemplo dado nos JavaDocs

Isso salvará um File no diretório testcompile (baseado nos requisitos do nome do package ) e compilará o File em uma class 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 diagnostics = new DiagnosticCollector(); 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 optionList = new ArrayList(); optionList.add("-classpath"); optionList.add(System.getProperty("java.class.path") + ";dist/InlineCompiler.jar"); Iterable 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 // classs, 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 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(); } } 

Agora atualizado para include o fornecimento de um caminho de class para o compilador e o carregamento e a execução da class compilada!

Eu sugiro usar a biblioteca Java Runtime Compiler . Você pode dar a ela um String na memory e ele irá compilar e carregar a class no carregador de classs atual (ou um de sua escolha) e retornar a Classe carregada. As classs aninhadas também são carregadas. Nota: isso funciona inteiramente na memory por padrão.

por exemplo

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