Загадочная ошибка при запуске модульного приложения JavaFX из командной строки без maven или IDE

У меня есть клон JavaFX Tetris, который я изначально создал, используя ide и maven для управления зависимостями. Все работает отлично, однако я хочу знать, как все работает «под капотом», поэтому я пытаюсь скомпилировать и запустить приложение в своем терминале Ubuntu, не используя ide или maven.

У меня есть все мои зависимости javafx и jar-файлы драйвера sqlite-jdbc/slj4 в структуре моего проекта.

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

  1. cd в корневой каталог проекта ~/Tetris, который содержит каталоги lib, bin и src. Lib содержит модули javafx, bin предназначен для вывода файлов .class и ресурсов проекта (файлы изображений, звуковые файлы, база данных для лучших результатов), а src содержит исходный код.

  2. скомпилируйте программу:

    javac --module-path lib --add-modules javafx.base,javafx.controls,javafx.graphics,javafx.media -d bin src/main/java/com/example/tetris/*.java

До этого момента все работает нормально.

  1. Запустите программу:

    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. Я искренне озадачен. Спасибо всем, кто увидит и ответит.

Вы хотели запустить файл .java напрямую? (вероятно, нет, потому что тогда вам не понадобится этап компиляции в вашем вопросе). Это поддерживается JEP 330 , но его возможности ограничены по сравнению с запуском предварительно скомпилированного кода. Я советую сначала скомпилировать код (с помощью javac, с помощью инструмента сборки, такого как Maven или Gradle), а затем запустить скомпилированный код. Инструкции для этого находятся на сайте openjfx.io.

jewelsea 06.05.2024 21:33
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
В компьютерном программировании биты играют важнейшую роль в представлении и манипулировании данными на двоичном уровне. Побитовые операции...
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Приходилось ли вам сталкиваться с требованиями, в которых вас могли попросить поднять тревогу или выдать ошибку, когда метод Java занимает больше...
Полный курс Java для разработчиков веб-сайтов и приложений
Полный курс Java для разработчиков веб-сайтов и приложений
Получите сертификат Java Web и Application Developer, используя наш курс.
5
1
83
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Эта ошибка (что я считаю) ошибкой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

Обратите внимание на несколько вещей:

  1. Последний бит — это полное имя основного класса (вместо пути к исходному файлу). Этот класс будет расположен на пути к классам.

  2. В javafx.controls необходимо включить только javafx.media и --add-modules, поскольку остальные необходимые модули JavaFX будут неявно подтянуты теми двумя, которые им требуются. Хотя явное добавление каждого отдельного модуля JavaFX не является неправильным, а просто ненужным.

  3. Вы можете использовать -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 — под модульным я просто имел в виду, что это многоклассовая программа, которая запускается одним классом, расширяющим приложение.

DH615 06.05.2024 22:26

Причина, по которой я явно указал путь к sl4j в пути к классам, заключается в том, что я получал сообщение об ошибке, сообщающее, что драйвер sqlite-jdbc восстановил его, и его не удалось найти.

DH615 06.05.2024 22:28

ОБНОВЛЕНИЕ: теперь у меня все работает. Благодаря помощи с этим сообщением об ошибке я смог выяснить причину настоящей проблемы. Большое спасибо, что нашли время, чтобы помочь мне. Хорошего дня.

DH615 06.05.2024 22:59

«Причина, по которой я явно указал путь sl4j в пути к классам, заключается в том, что я получал сообщение об ошибке, в котором говорилось, что драйвер sqlite-jdbc восстановил его, и его не удалось найти» - при запуске вашего кода из пути к классам это правильно делать. Мои примечания о необходимости использования org.slf4j применимы только в том случае, если ваш код имеет дескриптор информации о модуле и вы запускаете свое приложение из пути к модулю.

Slaw 06.05.2024 23:10

«ОБНОВЛЕНИЕ: теперь у меня все работает». -> что именно вы сделали, чтобы все заработало?

jewelsea 07.05.2024 08:54

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