Понимание того, как основной класс влияет на 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: class 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, Так что либо я должен получить исключение в обоих случаях, либо ни в одном. В чем разница?

1 ответ

Решение

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

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

В ответе на Maven Shade отсутствуют компоненты среды выполнения JavaFX, я объяснил причину, почему при использовании 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");
}

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

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

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

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

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

В итоге:

  • Использование лаунчера необходимо для преодоления упомянутого sun.launcher.LauncherHelper вопрос.
  • Как плагин maven запускается в classpath, загружая все зависимости в изолированный поток, так и 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 в этом случае (где у вас нет контроля).

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

Maven Exec

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

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

Другие вопросы по тегам