JavaFX 11: создайте JAR-файл с помощью Gradle

Я пытаюсь обновить проект JavaFX с версии 8 Java до версии 11. Это работает, когда я использую задачу "Выполнить " Gradle ( я следовал учебному руководству Openjfx), но когда я собираю (с помощью задачи "jar" Gradle) и выполняю (с "java -jar") файл jar, появляется сообщение "Ошибка: Отсутствуют компоненты среды выполнения JavaFX, и они необходимы для запуска этого приложения ".

Вот мой файл build.gradle:

group 'Project'
version '1.0'
apply plugin: 'java'
sourceCompatibility = 1.11

repositories {
    mavenCentral()
}

def currentOS = org.gradle.internal.os.OperatingSystem.current()
def platform
if (currentOS.isWindows()) {
    platform = 'win'
} else if (currentOS.isLinux()) {
    platform = 'linux'
} else if (currentOS.isMacOsX()) {
    platform = 'mac'
}
dependencies {
    compile "org.openjfx:javafx-base:11:${platform}"
    compile "org.openjfx:javafx-graphics:11:${platform}"
    compile "org.openjfx:javafx-controls:11:${platform}"
    compile "org.openjfx:javafx-fxml:11:${platform}"
}

task run(type: JavaExec) {
    classpath sourceSets.main.runtimeClasspath
    main = "project.Main"
}

jar {
    manifest {
        attributes 'Main-Class': 'project.Main'
    }
    from {
        configurations.compile.collect { it.isDirectory() ? it : zipTree(it) }
    }
}

compileJava {
    doFirst {
        options.compilerArgs = [
                '--module-path', classpath.asPath,
                '--add-modules', 'javafx.controls,javafx.fxml'
        ]
    }
}

run {
    doFirst {
        jvmArgs = [
                '--module-path', classpath.asPath,
                '--add-modules', 'javafx.controls,javafx.fxml'
        ]
    }
}

Ты знаешь, что я должен делать?

2 ответа

Решение

Если кому-то интересно, я нашел способ создания jar-файлов для проекта JavaFX11 (с модулями Java 9). Я тестировал его только на Windows (если приложение также для Linux, я думаю, что мы должны сделать те же шаги, но на Linux, чтобы получить JavaFX jar для Linux).

У меня есть модуль "Project.main" (созданный IDEA, когда я создал проект Gradle):

 src
 +-- main
 |   +-- java
     |   +-- main
         |   +-- Main.java (from the "main" package, extends Application)
     |   +-- module-info.java
 build.gradle
 settings.gradle
 ...

Файл module-info.java:

module Project.main {
    requires javafx.controls;
    exports main;
}

Файл build.gradle:

plugins {
    id 'java'
}

group 'Project'
version '1.0'
ext.moduleName = 'Project.main'
sourceCompatibility = 1.11

repositories {
    mavenCentral()
}

def currentOS = org.gradle.internal.os.OperatingSystem.current()
def platform
if (currentOS.isWindows()) {
    platform = 'win'
} else if (currentOS.isLinux()) {
    platform = 'linux'
} else if (currentOS.isMacOsX()) {
    platform = 'mac'
}
dependencies {
    compile "org.openjfx:javafx-base:11:${platform}"
    compile "org.openjfx:javafx-graphics:11:${platform}"
    compile "org.openjfx:javafx-controls:11:${platform}"
}

task run(type: JavaExec) {
    classpath sourceSets.main.runtimeClasspath
    main = "main.Main"
}

jar {
    inputs.property("moduleName", moduleName)
    manifest {
        attributes('Automatic-Module-Name': moduleName)
    }
}

compileJava {
    inputs.property("moduleName", moduleName)
    doFirst {
        options.compilerArgs = [
                '--module-path', classpath.asPath,
                '--add-modules', 'javafx.controls'
        ]
        classpath = files()
    }
}

task createJar(type: Copy) {
    dependsOn 'jar'
    into "$buildDir/libs"
    from configurations.runtime
}

Файл settings.gradle:

rootProject.name = 'Project'

И команды Gradle:

#Run the main class
gradle run

#Create the jars files (including the JavaFX jars) in the "build/libs" folder
gradle createJar

#Run the jar file
cd build/libs
java --module-path "." --module "Project.main/main.Main"

В Java/JavaFX 11 теневая / жирная банка не будет работать.

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

Эта ошибка исходит от sun.launcher.LauncherHelper в модуле java.base. Причина в том, что приложение Main расширяется Application и имеет основной метод. Если это так, то LauncherHelper проверит наличие модуля javafx.graphics в качестве именованного модуля:

Optional<Module> om = ModuleLayer.boot().findModule(JAVAFX_GRAPHICS_MODULE_NAME);

Если этот модуль отсутствует, запуск отменяется. Следовательно, использование библиотек JavaFX в качестве jar-файлов на пути к классам в этом случае не допускается.

Более того, каждая банка JavaFX 11 имеет module-info.class файл, на корневом уровне.

Когда вы объединяете все содержимое банок в одну толстую банку, что происходит с этими файлами с тем же именем и в том же месте? Даже если толстая банка хранит их все, как это идентифицировать как единый модуль?

Есть запрос на поддержку этого, но он еще не был обработан: http://openjdk.java.net/projects/jigsaw/spec/issues/

Предоставьте средство для создания исполняемого модульного "uber-JAR", который содержит более одного модуля, сохраняя идентификаторы и границы модулей, так что все приложение может быть доставлено как один артефакт.

Плагин shadow по-прежнему имеет смысл объединить все остальные зависимости в один jar, но в конце концов вам придется запустить что-то вроде:

java --module-path <path-to>/javafx-sdk-11/lib \
   --add modules=javafx.controls -jar my-project-ALL-1.0-SNAPSHOT.jar

Это означает, что, в конце концов, вам придется установить JavaFX SDK (для каждой платформы), чтобы запустить тот jar, который использовал зависимости JavaFX от maven central.

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

Также вы можете использовать Javapackager для генерации установщика для каждой платформы. См. http://openjdk.java.net/jeps/343 который создаст упаковщик для Java 12.

Наконец, есть экспериментальная версия Javapackager, которая работает с Java 11 / JavaFX 11: http://mail.openjdk.java.net/pipermail/openjfx-dev/2018-September/022500.html

РЕДАКТИРОВАТЬ

Поскольку средство запуска Java проверяет, расширяется ли основной класс javafx.application.Applicationи в этом случае требуется, чтобы среда выполнения JavaFX была доступна в виде модулей (не в виде jar-файлов), возможный обходной путь, чтобы заставить его работать, - добавление нового класса Main, который будет основным классом вашего проекта, и этот класс будет тот, который вызывает ваш класс JavaFX Application.

Если у тебя есть javafx11 пакет с классом приложения:

public class HelloFX extends Application {

    @Override
    public void start(Stage stage) {
        String javaVersion = System.getProperty("java.version");
        String javafxVersion = System.getProperty("javafx.version");   
        Label l = new Label("Hello, JavaFX " + javafxVersion + ", running on Java " + javaVersion + ".");
        Scene scene = new Scene(new StackPane(l), 400, 300);
        stage.setScene(scene);
        stage.show();
    }

    public static void main(String[] args) {
        launch(args);
    }

}

Затем вы должны добавить этот класс в этот пакет:

public class Main {

    public static void main(String[] args) {
        HelloFX.main(args);
    }
}

И в вашем файле сборки:

mainClassName='javafx11.Main'
jar {
    manifest {
        attributes 'Main-Class': 'javafx11.Main'
    }
    from {
        configurations.compile.collect { it.isDirectory() ? it : zipTree(it) }
    }
}

Теперь вы можете запустить:

./gradlew run

или же

./gradlew jar
java -jar build/libs/javafx11-1.0-SNAPSHOT.jar

JavaFX из баночки FAT

Конечная цель состоит в том, чтобы модули JavaFX были названы как модули на пути к модулю, и это выглядит как быстрый / безобразный обходной путь для тестирования вашего приложения. Для распространения я бы все же предложил вышеупомянутые решения.

В последних версиях JavaFX вы можете использовать два плагина Gradle для простого распространения вашего проекта (javafxplugin и jlink).

С помощью этих плагинов вы можете:

  • Создайте распространяемый zip-файл, содержащий все необходимые файлы jar: для его выполнения требуется JRE (с помощью bash или пакетного сценария)
  • Создайте собственное приложение с Jlink для данной ОС: JRE не требуется для его выполнения, поскольку Jlink включает в себя "легкую" JRE (включая только необходимые java-модули и зависимости) в папке распространения.

Я сделал пример на битбакете, если вам нужен пример.

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