Понимание того, как основной класс влияет на 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.