Понимание того, как основной класс влияет на JPMS

У меня очень простое приложение JavaFX, которое работает безупречно, если класс Application не является классом Main:

import javafx.application.Application;
import javafx.fxml.FXMLLoader;
import javafx.stage.Stage;

public class Main {

    public static void main(String[] args) {
        Application.launch(App.class, args);
    }

}

public class App extends Application {

    @Override
    public void start(Stage primaryStage) {
        FXMLLoader loader = new FXMLLoader(); // works
    }

}

Однако, когда я объединяю их вместе (что рекомендуется в большинстве руководств, включая официальная документация OpenJFX), система модулей выдает IllegalAccessError (по крайней мере, в OpenJDK 11.0.2):

public class MainApp extends Application {

    @Override
    public void start(Stage primaryStage) {
        FXMLLoader loader = new FXMLLoader(); // throws IllegalAccessError
    }

    public static void main(String[] args) {
        launch(MainApp.class, args);
    }

}

Исключение составляют:

java.lang.IllegalAccessError: класс com.sun.javafx.fxml.FXMLLoaderHelper (в безымянном модуле @0x642c1a1b) не может получить доступ к классу com.sun.javafx.util.Utils (в модуле javafx.graphics), потому что модуль javafx.graphics не экспортирует com.sun.javafx.util в безымянный модуль @0x642c1a1b

Странно то, что я не активно использовал модульную систему. Я не добавлял module-info.java в свой проект. Итак, я предположил, что все должно быть экспортировано в какие-либо безымянные модули? Но дело даже не в этом.

Главный вопрос: почему один и тот же код ведет себя по-разному, если он распределен по двум классам? В обоих случаях FXMLLoader использует com.sun.javafx.fxml.FXMLLoaderHelper, который, в свою очередь, использует com.sun.javafx.util.Utils. Так что либо я должен получить исключение в обоих случаях, либо ни в каком. В чем разница?


person Sebastian S    schedule 18.02.2019    source источник
comment
Кажется странным. Пожалуйста, предоставьте для этого настоящий MCVE.   -  person flakes    schedule 19.02.2019
comment
@flakes Просто попробуйте запустить код. Вы можете получить демонстрационный проект на GitHub   -  person Sebastian S    schedule 19.02.2019
comment
идеально вот что я хотел. При изучении этого вопроса будут важны точные имена пакетов и инструменты сборки.   -  person flakes    schedule 19.02.2019


Ответы (1)


Уже опубликовано несколько ответов, которые могут частично относиться к вашим вопросам, но было бы удобно собрать их здесь и представить в виде полного ответа.

Класс приложения

В ответе на компоненты среды выполнения JavaFX Maven Shade отсутствуют Я объяснил причину, по которой, когда вы используете класс Application в качестве основного, ожидается, что вы будете использовать модульную систему.

В итоге:

Как вы можете прочитать здесь:

Эта ошибка возникает из sun.launcher.LauncherHelper в модуле java.base (ссылка).

Если основное приложение расширяет Application и имеет метод main, LauncherHelper проверяет, присутствует ли модуль javafx.graphics как именованный модуль:

Optional<Module> om = ModuleLayer.boot().findModule(JAVAFX_GRAPHICS_MODULE_NAME);
if (!om.isPresent()) {
    abort(null, "java.launcher.cls.error5");
}

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

Каждый jar-файл JavaFX 11 имеет файл module-info.class, поэтому по определению ожидается, что он будет добавлен в путь к модулю.

Но если вы не запускаете через класс Application, эта проверка не выполняется.

Основной класс

Этот другой ответ на Другое поведение между Maven и Eclipse для запуска приложения JavaFX 11 объясняет, почему оно работает без модульной системы, когда вы используете класс Launcher (основной класс, не расширяющий приложение) с подключаемым модулем Maven exec:java.

В итоге:

  • Для решения упомянутой sun.launcher.LauncherHelper проблемы требуется использование программы запуска.
  • Как плагин maven работает в пути к классам, загружая все зависимости в изолированный поток, так и IntelliJ в этом случае.

Если вы проверите командную строку при запуске Main.main():

/path/to/jdk-11.0.2.jdk/Contents/Home/bin/java \
    "-javaagent:/Applications/IntelliJ IDEA.app/Contents/lib/idea_rt.jar=60556:/Applications/IntelliJ IDEA.app/Contents/bin"  \
    -Dfile.encoding=UTF-8  \
    -classpath /path/to/so-question-54756176-master/target/classes:/path/to/.m2/repository/org/openjfx/javafx-base/11.0.2/javafx-base-11.0.2.jar:.../path/to/.m2/repository/org/openjfx/javafx-fxml/11.0.2/javafx-fxml-11.0.2-mac.jar  \
    Main

Все jar-файлы JavaFX из JavaFX SDK добавляются в путь к классам, и вы используете классический java -cp ... Main.

javafx.fxml отсутствует

Эти другие ответы на IntelliJ IDEA - Ошибка: компоненты среды выполнения JavaFX отсутствуют и необходимы для запуска этого приложения. объясняется ошибка, возникающая при запуске в модульной системе, но вы не добавляете javafx.fxml в параметр --add-modules.

Caused by: java.lang.IllegalAccessError: class com.sun.javafx.fxml.FXMLLoaderHelper (in unnamed module @0x5fce9dc5) cannot access class com.sun.javafx.util.Utils (in module javafx.graphics) because module javafx.graphics does not export com.sun.javafx.util to unnamed module @0x5fce9dc5
    at com.sun.javafx.fxml.FXMLLoaderHelper.<clinit>(FXMLLoaderHelper.java:38)
    at javafx.fxml.FXMLLoader.<clinit>(FXMLLoader.java:2056)

В вашей ошибке говорится, что вы используете FXML, но она не может быть разрешена в модуле-пути, поэтому он пытается получить доступ через отражение, и это не удается, поскольку вы не открыли этот javafx.graphics в своем безымянном модуле.

Итак, теперь вы спросите: я изначально не устанавливал javafx.graphics!

Что ж, вы этого не сделали, но IntelliJ сделал это за вас!

Проверьте командную строку при запуске MainApp.main():

/path/to/jdk-11.0.2.jdk/Contents/Home/bin/java \
    --add-modules javafx.base,javafx.graphics \
    --add-reads javafx.base=ALL-UNNAMED \
    --add-reads javafx.graphics=ALL-UNNAMED \
    "-javaagent:/Applications/IntelliJ IDEA.app/Contents/lib/idea_rt.jar=60430:/Applications/IntelliJ IDEA.app/Contents/bin" \
    -Dfile.encoding=UTF-8 \
    -classpath /path/to/so-question-54756176-master/target/classes:/path/to/.m2/repository/org/openjfx/javafx-base/11.0.2/javafx-base-11.0.2.jar:.../.m2/repository/org/openjfx/javafx-graphics/11.0.2/javafx-graphics-11.0.2-mac.jar \
    MainApp

Вы можете видеть, что IntelliJ по умолчанию добавляет javafx.base и javafx.graphics. Таким образом, отсутствует только javafx.fxml (и тогда вы, конечно, должны добавить путь к модулю).

Как вы отметили, рекомендуемое решение находится в документации:

Либо в командной строке, используя --module-path, чтобы указать путь к вашей папке lib JavaFX SDK, либо --add-modules, чтобы включить javafx.fxml в этом случае (когда у вас нет элементов управления).

Аргументы виртуальной машины IntelliJ

Или с помощью плагина Maven. В какой-то момент вам придется покинуть свою IDE, поэтому вам нужно будет использовать плагин для запуска приложения.

Maven exec

Последнее замечание по плагину Maven exec, если вы его используете:

Более того, рекомендуемое решение Maven, пока плагин exec:java не будет исправлен для модульной системы (и хорошие новости заключаются в том, что это done как мы говорим), вместо этого будет использоваться exec:exec, как описано в этом issue, поэтому вы можете указать оба аргумента vm.

person José Pereda    schedule 19.02.2019
comment
Вау, спасибо за подробный ответ! очень ценю все детали. Также это показывает, как много я еще не знаю об этих библиотеках. Поэтому при выходе из IDE и распространении приложения мне нужно добавить опцию «add-modules» для javafx, несмотря на то, что я сам не использую JPMS, верно? - person Sebastian S; 19.02.2019
comment
Вы все еще можете использовать класс запуска и текущий плагин exec:java, но это скоро изменится, и другой альтернативы не будет, поэтому вам придется использовать модульную систему, нравится вам это или нет ... - person José Pereda; 19.02.2019