У меня есть клон JavaFX Tetris, который я изначально создал, используя ide и maven для управления зависимостями. Все работает отлично, однако я хочу знать, как все работает «под капотом», поэтому я пытаюсь скомпилировать и запустить приложение в своем терминале Ubuntu, не используя ide или maven.
У меня есть все мои зависимости javafx и jar-файлы драйвера sqlite-jdbc/slj4 в структуре моего проекта.
Вот шаги, которые я предпринимаю, чтобы попытаться скомпилировать и запустить приложение:
cd в корневой каталог проекта ~/Tetris, который содержит каталоги lib, bin и src. Lib содержит модули javafx, bin предназначен для вывода файлов .class и ресурсов проекта (файлы изображений, звуковые файлы, база данных для лучших результатов), а src содержит исходный код.
скомпилируйте программу:
javac --module-path lib --add-modules javafx.base,javafx.controls,javafx.graphics,javafx.media -d bin src/main/java/com/example/tetris/*.java
До этого момента все работает нормально.
Запустите программу:
java --module-path lib --add-modules javafx.base,javafx.controls,javafx.graphics,javafx.media -cp bin:lib/sqlite-jdbc-3.45.3.0.jar:lib/sl4j-api-1.7 .36.jar src/main/java/com/example/tetris/Main.java
В этой команде я добавляю драйвер sqlite и sl4j в путь к классам и пытаюсь запустить программу. Сначала я получаю вывод на консоль, означающий, что программа запущена: Таблица «top_players» успешно создана. Количество строк в таблице: 3 Н: ААА, С: 10000 Н: ВВВ, С: 5000
Но вскоре после этого происходит сбой, и я получаю загадочную ошибку:
Exception in Application start method
Exception in thread "main" java.lang.IllegalArgumentException: 0 > -4
at java.base/java.util.Arrays.copyOfRange(Arrays.java:3782)
at java.base/java.util.Arrays.copyOfRange(Arrays.java:3742)
at jdk.compiler/com.sun.tools.javac.launcher.Main.execute(Main.java:431)
at jdk.compiler/com.sun.tools.javac.launcher.Main.run(Main.java:192)
at jdk.compiler/com.sun.tools.javac.launcher.Main.main(Main.java:132)
Это все, что он мне дает, никакой другой информации. Кто-нибудь знает, что я могу упустить или сделать неправильно?
Если это поможет, я использую систему Ubuntu. У меня есть jdk и javafx, добавленные в мой PATH. Я искренне озадачен. Спасибо всем, кто увидит и ответит.
Эта ошибка (что я считаю) ошибкой1 в реализации JEP 330: Запуск однофайловых программ с исходным кодом (и усовершенствования JEP 458: Запуск многофайловых программ с исходным кодом). Его можно воспроизвести с помощью следующего:
import javafx.application.Application;
import javafx.stage.Stage;
public class Main extends Application {
@Override
public void start(Stage primaryStage) {
throw new RuntimeException("test");
}
public static void main(String[] args) {
launch(Main.class, args);
}
}
Командная строка:
java -p <path-to-javafx> --add-modules javafx.graphics Main.java
Выход:
Exception in Application start method
Exception in thread "main" java.lang.IllegalArgumentException: 0 > -2
at java.base/java.util.Arrays.copyOfRange(Arrays.java:3807)
at java.base/java.util.Arrays.copyOfRange(Arrays.java:3767)
at jdk.compiler/com.sun.tools.javac.launcher.SourceLauncher.execute(SourceLauncher.java:273)
at jdk.compiler/com.sun.tools.javac.launcher.SourceLauncher.run(SourceLauncher.java:153)
at jdk.compiler/com.sun.tools.javac.launcher.SourceLauncher.main(SourceLauncher.java:78)
Источником этой загадочной ошибки, по-видимому, является обработка исключения InvoctionTargetException (ссылка на исходный код Java 22) в JEP 330/458. Насколько я понимаю, этот код пытается скрыть методы SourceLauncher
из трассировки стека, когда main
выдает исключение. По большей части это работает нормально. Но это терпит неудачу, если исключение, выброшенное из main
, имеет трассировку стека менее примерно 5 элементов. И насколько я могу судить, это тот случай, описанный выше, потому что исключение, выброшенное из launch
, было создано в другом потоке, отличном от того, который вызвал main
, и этот другой поток не имеет такого глубокого стека вызовов в тот момент.
В любом случае, сначала вы компилируете свою программу, поэтому вам не следует использовать функцию JEP 330 с самого начала. Это означает, что вы должны передавать имя класса java
, а не исходный файл. На основании информации, которую вы предоставили в своем вопросе, ваша команда должна быть такой:
java --module-path lib \
--add-modules javafx.controls,javafx.media \
--class-path bin:lib/sqlite-jdbc-3.45.3.0.jar:lib/sl4j-api-1.7.36.jar \
com.example.tetris.Main
Обратите внимание на несколько вещей:
Последний бит — это полное имя основного класса (вместо пути к исходному файлу). Этот класс будет расположен на пути к классам.
В javafx.controls
необходимо включить только javafx.media
и --add-modules
, поскольку остальные необходимые модули JavaFX будут неявно подтянуты теми двумя, которые им требуются. Хотя явное добавление каждого отдельного модуля JavaFX не является неправильным, а просто ненужным.
Вы можете использовать -cp
вместо --class-path
и -p
вместо --module-path
.
Тем не менее, ваше приложение все равно может выдать исключение. Но теперь вы сможете увидеть настоящее исключение, а не загадочное IllegalArgumentException
.
В заголовке вы упоминаете, что используете «модульное приложение». Если под этим вы подразумеваете, что ваш собственный код является модульным (т. е. у вас есть файл module-info.java
), то ваша команда на самом деле должна быть такой:
java --module-path lib:bin --module app/com.example.tetris.Main
Note: You can use -p
instead of --module-path
and -m
instead of --module
.
Предположим, что ваш дескриптор модуля выглядит примерно так:
module app {
requires java.sql;
requires javafx.controls;
requires javafx.media;
exports com.example.tetris to
javafx.graphics;
}
Note: Include requires org.slf4j
if your own code uses it. Otherwise, the sqlite-jdbc driver requires it already. Similarly, you can requires org.xerial.sqlitejdbc
if you use it directly, otherwise you should let it be found via the service-provider mechanism (the java.sql
module uses
a service that the org.xerial.sqlitejdbc
module provides
).
1. I submitted a bug for this once, but it was rejected as "not reproducible". The comment explained what they did to try to reproduce it and they failed to use JEP 330. My follow up received no response. So, as far as I know, the OpenJDK developers remain unaware of this issue.
Ух ты, спасибо за подробный ответ, добрый незнакомец, это потрясающе. Приношу извинения за, возможно, неправильное использование термина «модульный» — я использовал файл Module-info.java, который был сгенерирован intellij, когда я изначально создавал игру в IDE, но я хотел научиться запускать программу, просто используя терминал, поэтому В этой версии я не использую файл mod-info.java — под модульным я просто имел в виду, что это многоклассовая программа, которая запускается одним классом, расширяющим приложение.
Причина, по которой я явно указал путь к sl4j в пути к классам, заключается в том, что я получал сообщение об ошибке, сообщающее, что драйвер sqlite-jdbc восстановил его, и его не удалось найти.
ОБНОВЛЕНИЕ: теперь у меня все работает. Благодаря помощи с этим сообщением об ошибке я смог выяснить причину настоящей проблемы. Большое спасибо, что нашли время, чтобы помочь мне. Хорошего дня.
«Причина, по которой я явно указал путь sl4j в пути к классам, заключается в том, что я получал сообщение об ошибке, в котором говорилось, что драйвер sqlite-jdbc восстановил его, и его не удалось найти» - при запуске вашего кода из пути к классам это правильно делать. Мои примечания о необходимости использования org.slf4j применимы только в том случае, если ваш код имеет дескриптор информации о модуле и вы запускаете свое приложение из пути к модулю.
«ОБНОВЛЕНИЕ: теперь у меня все работает». -> что именно вы сделали, чтобы все заработало?
Вы хотели запустить файл
.java
напрямую? (вероятно, нет, потому что тогда вам не понадобится этап компиляции в вашем вопросе). Это поддерживается JEP 330 , но его возможности ограничены по сравнению с запуском предварительно скомпилированного кода. Я советую сначала скомпилировать код (с помощьюjavac
, с помощью инструмента сборки, такого как Maven или Gradle), а затем запустить скомпилированный код. Инструкции для этого находятся на сайте openjfx.io.