Скомпилируйте класс Java во время выполнения с зависимостями от вложенной банки

В приложении с весенней загрузкой я делаю следующее во время выполнения:

  1. Создание класса Java
  2. Компиляция
  3. Доступ к некоторым статическим полям скомпилированного класса с использованием отражения.

Я основывал свой код на этот пост и возникла проблема с компиляцией сгенерированного класса во время выполнения. При запуске в IDE компиляция работает нормально, но при запуске из компиляции spring-boot jar происходит сбой, говоря, что символы отсутствуют или какой-то пакет не существует. Класс, который я компилирую, имеет зависимости от других классов, которые находятся в банке под \BOOT-INF\lib\, и кажется, что компилятору не удается загрузить эти классы с помощью существующего classLoader.

Я следил за этим сообщением которые предполагают решение этой конкретной проблемы, но я получил UnsupportedOperationException из метода

default Iterable<Set<Location>> listLocationsForModules(Location location) throws IOException {
    throw new UnsupportedOperationException();
}

интерфейса JavaFileManager.

Я столкнулся с другим возможным решением, приведенным здесь, но я не ясно именно с полной реализацией.

Это кажется хорошо известной проблемой при компиляции класса во время выполнения, есть ли какое-либо четкое решение для этого?

Обновление: в настоящее время я использую java 10.0.2.


person forhas    schedule 26.12.2018    source источник


Ответы (1)


Хотя вы не упомянули об этом явно, я думаю, что вы используете версию Java с modules (JDK 9+), но руководства, которым вы следовали, относятся к более ранним версиям, начиная с Java 6. Вот почему вы получаете сообщение об ошибке о неподдерживаемом listLocationsForModules, потому что разработчики JDK модернизировали FileManager с с методами по умолчанию, которые выдают UnsupportedOperationException.

Если вы на самом деле не хотите использовать версию Java выше 8, я бы остановился на JDK8, это будет намного проще!

Я продолжу предполагать, что вы хотите использовать Java 9 и выше (протестировал мой код на Java 11), однако:

Для работы с модулями достаточно, чтобы ваш файловый менеджер делегировал стандартному файловому менеджеру:

@Override
public Location getLocationForModule(Location location, String moduleName) throws IOException {
    return standardFileManager.getLocationForModule(location, moduleName);
}

@Override
public Location getLocationForModule(Location location, JavaFileObject fo) throws IOException {
    return standardFileManager.getLocationForModule(location, fo);
}

@Override
public Iterable<Set<Location>> listLocationsForModules(Location location) throws IOException {
    return standardFileManager.listLocationsForModules(location);
}

@Override
public String inferModuleName(Location location) throws IOException {
    return standardFileManager.inferModuleName(location);
}

Я также счел необходимым изменить код Atamur, чтобы явно проверять базовый модуль java (чтобы мы могли разрешать java.lang в Java 9+!) и делегировать стандартному файловому менеджеру, как вы бы сделали для пути к классу платформы в предыдущем версии:

@Override
public Iterable<JavaFileObject> list(Location location, String packageName, Set<JavaFileObject.Kind> kinds, boolean recurse) throws IOException {
    boolean baseModule = location.getName().equals("SYSTEM_MODULES[java.base]");
    if (baseModule || location == StandardLocation.PLATFORM_CLASS_PATH) { // **MODIFICATION CHECK FOR BASE MODULE**
        return standardFileManager.list(location, packageName, kinds, recurse);
    } else if (location == StandardLocation.CLASS_PATH && kinds.contains(JavaFileObject.Kind.CLASS)) {
        if (packageName.startsWith("java") || packageName.startsWith("com.sun")) {
            return standardFileManager.list(location, packageName, kinds, recurse);
        } else { // app specific classes are here
            return finder.find(packageName);
        }
    }
    return Collections.emptyList();

}

Обновления

Некоторые другие моменты:

Извлечение встроенных классов загрузки Spring:

Получите jarUri, найдя последний индекс '!' в каждом URL-адресе packageFolder, как в Комментарий Тэюн Ким и не первый, как в исходном примере.

 private List<JavaFileObject> processJar(URL packageFolderURL) {
  List<JavaFileObject> result = new ArrayList<JavaFileObject>();
  try {
    // Replace:
    // String jarUri = packageFolderURL.toExternalForm().split("!")[0];
    // With:
    String externalForm = packageFolderURL.toExternalForm();
    String jarUri = externalForm.substring(0, externalForm.lastIndexOf('!'));


    JarURLConnection jarConn = (JarURLConnection) packageFolderURL.openConnection();
    String rootEntryName = jarConn.getEntryName();
    int rootEnd = rootEntryName.length()+1;
    // ...

Это позволяет пакету PackageInternalsFinder возвращать CustomJavaFileObject с полными URI классам во встроенных банках spring (под BOOT-INF/lib), которые затем разрешаются с помощью spring boot jar Обработчик URI, который регистрируется аналогично тому, как описано в этом ответе. Обработка URI должна происходить автоматически через весеннюю загрузку.

person Frank Wilson    schedule 31.12.2018
comment
Спасибо, я использую Java 10, поэтому ваше решение ко мне не относится? я бы подумал о переходе на 11 - person forhas; 31.12.2018
comment
Этот ответ должен применяться к Java с 9 по 11. Поэтому мой ответ должен применяться в вашем случае. Не должно быть необходимости обновляться до Java 11. - person Frank Wilson; 31.12.2018
comment
Вы уверены, что это не подходит для Java 10? я должен дать ему шанс? - person forhas; 31.12.2018
comment
Это стоит попробовать. Не должно быть проблем! Я изменил свой ответ, чтобы было понятно, что он будет работать на Java 9+. - person Frank Wilson; 31.12.2018
comment
Отлично, скоро попробую - person forhas; 31.12.2018
comment
С компиляцией вашего предложения по-прежнему происходит сбой, теперь со следующим сообщением: ›не удается получить доступ к файлу плохого класса org.web3j.abi.TypeReference: файл:/C:/dev/project-folder/maven-parent/My-Project/target/App -spring-boot.jar!/org/web3j/abi/TypeReference.class не может получить доступ к файлу: java.io.FileNotFoundException: запись JAR org/web3j/abi/TypeReference.class не найдена в C:\dev\project-folder \maven-parent\My-Project\target\App-spring-boot.jar Удалите или убедитесь, что он отображается в правильном подкаталоге пути к классам.] - person forhas; 31.12.2018
comment
TypeReference — это класс, который я использую в компилируемом классе, он находится в банке с именем abi-3.5.0.jar. исполняемый jar-файл spring boot помещает этот jar-файл по пути C:\dev\project-folder\maven-parent\My-Project\target\App-spring-boot.jar\BOOT-INF\lib\abi-3.5.0.jar - person forhas; 31.12.2018
comment
PackageInternalsFinder.processJar несет ответственность за создание CustomJavaFileObject для каждого класса во встроенной зависимости весенней загрузки. В статье комментатора Атамура (Тэюн Ким) предлагается решение, позволяющее создавать объекты для весенней загрузки. Вы его применили? - person Frank Wilson; 31.12.2018
comment
Да, я почти уверен, что вы не применили исправление, потому что ошибка ссылается на C:/dev/project-folder/maven-parent/My-Project/target/App-spring-boot.jar!/org/web3j/abi/TypeReference.class, тогда как полный путь к классу — C:/dev/project-folder/maven-parent/My-Project/target/App-spring-boot.jar!/BOOT-INF/lib/abi-3.5.0!/org/web3j/abi/TypeReference.class. Если вы отрежете его последним !, а не первым, он сохранит полный путь к встроенному JAR. - person Frank Wilson; 31.12.2018
comment
Это работает, в PackageInternalsFinder.processJar я изменил способ извлечения пути jar: int endIndex = packageFolderURL.toExternalForm().lastIndexOf(!); Строка jarUri = packageFolderURL.toExternalForm().substring(0, endIndex); - person forhas; 31.12.2018