Вопрос-ответ

How do you dynamically compile and load external java classes? [duplicate]

Как вы динамически компилируете и загружаете внешние классы Java?

(Этот вопрос похож на многие вопросы, которые я видел, но большинство из них недостаточно конкретны для того, что я делаю)

Справочная информация:

Цель моей программы - упростить для людей, использующих мою программу, создание пользовательских, так сказать, "плагинов", а затем скомпилировать и загрузить их в программу для использования (по сравнению с неполным и медленным анализатором, реализованным в моей программе). Моя программа позволяет пользователям вводить код в заранее определенный класс, расширяющий скомпилированный класс, входящий в комплект моей программы. Они вводят код в текстовые области, затем моя программа копирует код в переопределяемые методы. Затем это сохраняется как файл .java (почти) готовый для компиляции. Программа запускает javac (компилятор Java) с сохраненным файлом .java в качестве входных данных.

Мой вопрос в том, как мне сделать так, чтобы клиент мог (используя мою скомпилированную программу) сохранить этот java-файл (который расширяет мой InterfaceExample) в любом месте на своем компьютере, чтобы моя программа скомпилировала его (не говоря "не удается найти symbol: InterfaceExample"), затем загрузить его и вызвать метод doSomething()?

Я продолжаю видеть вопросы и ответы, использующие reflection или ClassLoader, и один, в котором почти описано, как его скомпилировать, но ни один из них не является достаточно подробным для меня / я не понимаю их полностью.

Переведено автоматически
Ответ 1

Взгляните на JavaCompiler

Следующее основано на примере, приведенном в JavaDocs

Это сохранит File в testcompile каталоге (в зависимости от package требований к имени) и скомпилирует File в класс Java...

package inlinecompiler;

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

}

Теперь обновлено, чтобы включать добавление пути к классу для компилятора, а также загрузку и выполнение скомпилированного класса!

Ответ 2

Я предлагаю использовать библиотеку 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();
2023-05-18 14:15 java