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

How do I programmatically compile and instantiate a Java class?

Как мне программно скомпилировать и создать экземпляр класса Java?

У меня есть имя класса, сохраненное в файле свойств. Я знаю, что хранилище классов реализует IDynamicLoad . Как мне создать экземпляр класса динамически?

Прямо сейчас у меня есть

     Properties foo = new Properties();
foo.load(new FileInputStream(new File("ClassName.properties")));
String class_name = foo.getProperty("class","DefaultClass");
//IDynamicLoad newClass = Class.forName(class_name).newInstance();

Загружает ли newInstance только скомпилированные файлы .class? Как мне загрузить класс Java, который не скомпилирован?

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

Как мне загрузить класс Java, который не скомпилирован?


Сначала вам нужно его скомпилировать. Это можно сделать программно с помощью javax.tools API. Для этого требуется только, чтобы JDK был установлен на локальном компьютере поверх JRE.

Вот базовый пример запуска (оставляя в стороне очевидную обработку исключений):

// Prepare source somehow.
String source = "package test; public class Test { static { System.out.println(\"hello\"); } public Test() { System.out.println(\"world\"); } }";

// Save source in .java file.
File root = Files.createTempDirectory("java").toFile();
File sourceFile = new File(root, "test/Test.java");
sourceFile.getParentFile().mkdirs();
Files.write(sourceFile.toPath(), source.getBytes(StandardCharsets.UTF_8));

// Compile source file.
JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
compiler.run(null, null, null, sourceFile.getPath());

// Load and instantiate compiled class.
URLClassLoader classLoader = URLClassLoader.newInstance(new URL[] { root.toURI().toURL() });
Class<?> cls = Class.forName("test.Test", true, classLoader); // Should print "hello".
Object instance = cls.getDeclaredConstructor().newInstance(); // Should print "world".
System.out.println(instance); // Should print "test.Test@hashcode".

Что дает следующее

hello
world
test.Test@cafebabe

Дальнейшее использование было бы проще, если бы у этих классов implements был определенный интерфейс, который уже есть в classpath.

SomeInterface instance = (SomeInterface) cls.getDeclaredConstructor().newInstance();

В противном случае вам необходимо задействовать Reflection API для доступа и вызова (неизвестных) методов / полей.


Это сказано выше и не имеет отношения к реальной проблеме:

properties.load(new FileInputStream(new File("ClassName.properties")));

Позволить java.io.File полагаться на текущий рабочий каталог - это путь к проблемам с переносимостью. Не делайте этого. Поместите этот файл в classpath и используйте ClassLoader#getResourceAsStream() с относительным путем classpath.

properties.load(Thread.currentThread().getContextClassLoader().getResourceAsStream("ClassName.properties"));
Ответ 2

В том же духе, что и в ответе BalusC, но в этом фрагменте кода из моего дистрибутива kilim представлена немного более автоматическая оболочка. https://github.com/kilim/kilim/blob/master/src/kilim/tools/Javac.java

Он принимает список строк, содержащих исходный код Java, извлекает имена пакета и общедоступного класса / интерфейса и создает соответствующую иерархию каталогов / файлов в каталоге tmp. Затем он запускает над ним компилятор java и возвращает список пар name, classfile (структура ClassInfo).

Разберитесь с кодом самостоятельно. Он лицензирован MIT.

Ответ 3

Ваш прокомментированный код верен, если вы знаете, что у класса есть открытый конструктор без аргументов. Вам просто нужно привести результат, поскольку компилятор не может знать, что класс фактически реализует IDynamicLoad. Итак:

   IDynamicLoad newClass = (IDynamicLoad) Class.forName(class_name).newInstance();

Конечно, класс должен быть скомпилирован и указан в classpath, чтобы это работало.

Если вы хотите динамически скомпилировать класс из исходного кода, это совсем другое дело.

2023-10-16 23:07 java reflection