Компилировать Java файлы программно

Я знаю, что это было задано и много ответа, но у меня все еще нет хорошего решения, и я до сих пор не понимаю некоторых частей. Поэтому у меня есть требование скомпилировать программные файлы *.java.

JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();

- это то, что я использую и (как и ожидалось) компилятор null. Теперь я знаю, что я должен использовать JDK, а не JRE как "runtime", но вот что-то я не понимаю: недостаточно ли просто поместить tools.jar в путь к классам приложения и затем получить доступ к API компилятора Java? Если это так, существует (я думаю, есть) разница между автономным Java-приложением и веб-приложениями. На самом деле, я пытаюсь вызвать JavaCompiler из веб-приложения PlayFramework, поэтому я понял, могут ли эти решения (включая tools.jar) работать только для автономных приложений?

Я также попытался создать собственный ClassLoader и вызвать вышеупомянутый метод с отражением, но все, что я получаю, это null для объекта compiler:

ClassLoader classloader = app.classloader();
File file = new File("lib/tools.jar");          
URL url = file.toURI().toURL();
URL[] urls = new URL[]{url};
ClassLoader newCL = new URLClassLoader(urls, classloader) {
};
Class<?> loadClass = newCL.loadClass("javax.tools.ToolProvider");
Method method = loadClass.getMethod("getSystemJavaCompiler", null);             
Object object = method.invoke(null);
System.out.println("Object: " + object); // NULL

Объяснение для приведенного выше кода:

  • Я просто не использовал try/catch для простоты.
  • app.classloader() - это метод воспроизведения, который возвращает ClassLoader приложения
  • tools.jar включен в мою папку lib в проекте Play (это означает, что он находится в пути к классу проекта - в соответствии с документацией Play)

Я уверен, что перед тем, как Play сможет загрузить класс компилятора Java, я просто не знаю, что мне не хватает.

Мне известны опции, такие как Runtime.exec("javac myFile.java") и компилятор Eclipse JDT, но это не то, что я ищу.

О, и что-то вроде System.setProperty("java.home", "PATH_TO_YOUR_JDK");, а затем ToolProvider.getSystemJavaCompiler(); работает, но я считаю это решение таким уродливым.

С наилучшими пожеланиями

EDIT: (чтобы предоставить дополнительную информацию и отобразить последний статус) Это базовое представление структуры web-app:

myApp
|-conf\...
|-lib\MyJar.jar
|-lib\tools.jar
|-logs\...
|-...

В MyJar.jar теперь есть файл META-INF/MANIFEST.MF со следующим содержимым:

Manifest-Version: 1.0
Sealed: true
Main-Class: here.comes.my.main.class
Class-Path: tools.jar

Без запуска приложения Play я пытаюсь (в папке lib): java -jar MyJar.jar - мой простой метод main пытается вызвать компилятор (ToolProvider.getSystemJavaCompiler();) и возвращает null. Таким образом, это заставляет меня поверить, что проблема не имеет ничего общего с Play - я даже не могу получить компилятор при нормальной работе Jar!

Ответ 1

Нет никакой разницы между веб-приложениями и отдельными приложениями.

Вы не должны упаковывать tools.jar в свой путь к классу webapp. Классный загрузчик на Java обычно работает только снизу вверх. Таким образом, ToolProvider, который является частью jdk, не увидит ваши tools.jar в пути к классу webapp (он не может смотреть вниз в путь класса webapp).

Решение: используйте JDK и убедитесь, что вы указали на него или поместите tools.jar в каталог Java ext. Вы можете переопределить ext dir, установив свойство -Djava.ext.dir в любой каталог, который вам нравится.

Ответ 2

В documentation говорится, что To run the Play framework, you need JDK 6 or later. Как вам удалось запустить его с помощью jre?

В любом случае, если getSystemJavaCompiler возвращает null, это означает, что у вас нет tools.jar в вашем пути к классам или он поврежден. Если это Java/Java, убедитесь, что в нем есть класс com.sun.tools.javac.api.JavacTool.

И если вы хотите использовать пользовательский загрузчик классов, это класс JavacTool, который вам нужно загрузить, а не ToolProvider. Посмотрите, как это делается в java 6:

        URL[] urls = {file.toURI().toURL()};
        ClassLoader cl = URLClassLoader.newInstance(urls);
        cl.setPackageAssertionStatus("com.sun.tools.javac", true);
        return Class.forName(defaultJavaCompilerName, false, cl);

где defaultJavaCompilerName = "com.sun.tools.javac.api.JavacTool"

Подкласс к JavaCompiler и получить новый экземпляр - у вас будет ваш компилятор.