web-dev-qa-db-fra.com

Empaqueter une application JavaFX non modulaire

J'ai une application Java 8, qui utilise JavaFX et où la classe principale s'étend javafx.application.Application . Actuellement , Je le livre comme un gros pot et il fonctionne très bien sur Oracle Java 8.

Maintenant, je veux qu'il puisse fonctionner sur OpenJDK 11. Pour ajouter JavaFX, j'ai déjà ajouté les artefacts d'org.openjfx au chemin de classe et je les inclue dans le gros pot. Si je démarre mon pot à partir de la ligne de commande, je reçois

Error: JavaFX runtime components are missing, and are required to run this
application

J'ai trouvé deux façons possibles de contourner ce problème:

  1. Le sale: Écrivez un lanceur spécial qui n'étend pas l'application et contourne la vérification du module. Voir http://mail.openjdk.Java.net/pipermail/openjfx-dev/2018-June/021977.html
  2. Le plus propre: ajoutez --module-path et --add-modules à ma ligne de commande. Le problème avec cette solution est que je veux que mes utilisateurs finaux puissent simplement lancer l'application en double-cliquant dessus.

Bien que je pourrais utiliser 1. comme solution de contournement, je me demande quelle est actuellement (OpenJDK 11) le moyen prévu de créer/livrer des gros pots exécutables d'applications JavaFX non modulaires. Quelqu'un peut-il aider?

15
taranion

Ce sont quelques options pour empaqueter/distribuer une application finale JavaFX 11 (non modulaire). La plupart d'entre eux sont expliqués dans le OpenJFX officiel docs .

Je vais utiliser cet exemple comme référence. J'utiliserai également Gradle. La même chose peut être faite avec Maven (différents plugins), et même sans outils de construction (mais ce n'est pas recommandé ...). Les outils de construction sont un must de nos jours.

Fat Jar

C'est toujours une option valide, mais pas la préférée, car elle casse la conception modulaire et regroupe tout, et elle n'est pas multiplateforme à moins que vous ne vous en occupiez.

Pour l'exemple donné, vous avez un fichier build.gradle comme celui-ci:

plugins {
    id 'application'
    id 'org.openjfx.javafxplugin' version '0.0.5'
}

repositories {
    mavenCentral()
}

dependencies {
}

javafx {
    modules = [ 'javafx.controls' ]
}

mainClassName = 'hellofx.HelloFX'

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

Notez l'utilisation d'une classe Launcher. Comme mentionné par l'OP ou expliqué ici , une classe de lancement qui ne s'étend pas de Application est maintenant requise pour créer un gros pot.

Fonctionnement ./gradlew jar produit un gros pot (~ 8 Mo) qui inclut les classes JavaFX et les bibliothèques natives de votre plate-forme actuelle.

Tu peux courir Java -jar build/libs/hellofx.jar comme d'habitude, mais uniquement sur la même plateforme.

Comme expliqué dans les documents OpenJFX ou ici , vous pouvez toujours créer un pot multi-plateforme.

Dans ce cas, nous pouvons inclure les trois fichiers graphiques, car ce sont ceux qui ont du code et des bibliothèques dépendant de la plate-forme. La base, les contrôles et les modules fxml sont indépendants de la plate-forme.

dependencies {
    compile "org.openjfx:javafx-graphics:11.0.1:win"
    compile "org.openjfx:javafx-graphics:11.0.1:linux"
    compile "org.openjfx:javafx-graphics:11.0.1:mac"
}

./gradlew jar produira maintenant un gros pot (19 Mo) qui pourra être distribué sur ces trois plates-formes.

(Remarque: les médias et le Web ont également du code dépendant de la plateforme/des bibliothèques natives).

Donc, cela fonctionne comme auparavant Java 8. Mais comme je l'ai déjà dit, cela rompt le fonctionnement des modules et ne correspond pas à la façon dont les bibliothèques et les applications sont distribuées de nos jours.

Et n'oubliez pas que les utilisateurs de ces pots devront toujours installer un JRE.

jlink

Alors qu'en est-il de la distribution d'une image personnalisée avec votre projet, qui comprend déjà un JRE natif et un lanceur?

Vous direz que si vous avez un projet non modulaire, cela ne fonctionnera pas. Vrai. Mais examinons deux options ici, avant de parler de jpackage.

plugin d'exécution

badass-runtime-plugin est un plugin Gradle qui crée des images d'exécution à partir de projets non modulaires.

Avec ce build.gradle:

plugins {
    id 'org.openjfx.javafxplugin' version '0.0.5'
    id 'org.beryx.runtime' version '1.0.0'
    id "com.github.johnrengelman.shadow" version "4.0.3"
}

repositories {
    mavenCentral()
}

dependencies {
}

javafx {
    modules = [ 'javafx.controls' ]
}

mainClassName = 'hellofx.Launcher'

runtime {
    options = ['--strip-debug', '--compress', '2', '--no-header-files', '--no-man-pages']
}

Lorsque vous exécutez ./gradlew runtime il créera un runtime, avec son lanceur, vous pourrez donc exécuter:

cd build/image/hellofx/bin
./hellofx

Notez qu'il repose sur le plugin shadow et qu'il nécessite également une classe Launcher.

Si vous exécutez ./gradlew runtimeZip, vous pouvez obtenir un Zip pour cette image personnalisée d'environ 32,5 Mo.

Encore une fois, vous pouvez distribuer ce Zip à n'importe quel utilisateur avec la même plate-forme, mais maintenant il n'y a plus besoin d'un JRE installé.

Voir targetPlatform pour construire des images pour d'autres plates-formes.

Devenir modulaire

Nous continuons de penser que nous avons un projet non modulaire, et qui ne peut pas être changé ... mais que faire si nous le changeons?

Devenir modulaire n'est pas si important que ça: vous ajoutez un module-info.Java descripteur, et vous y incluez les modules requis, même si ce sont des pots non modulaires (basés sur des noms automatiques).

Sur la base du même échantillon, je vais ajouter un descripteur:

module hellofx {
    requires javafx.controls;

    exports hellofx;
}

Et maintenant, je peux utiliser jlink en ligne de commande, ou utiliser un plugin pour cela. Le badass-gradle-plugin est un plugin gradle, du même auteur que celui mentionné précédemment, qui permet de créer un runtime personnalisé.

Avec ce fichier de construction:

plugins {
    id 'org.openjfx.javafxplugin' version '0.0.5'
    id 'org.beryx.jlink' version '2.3.0'
}

repositories {
    mavenCentral()
}

dependencies {
}

javafx {
    modules = [ 'javafx.controls' ]
}

mainClassName = 'hellofx/hellofx.HelloFX'

vous pouvez exécuter maintenant:

./gradlew jlink
cd build/image/bin/hellofx
./hellofx

ou ./gradlew jlinkZip pour une version zippée (31 Mo) pouvant être distribuée et exécutée sur des machines, de la même plateforme, même si aucun JRE n'est installé.

Comme vous pouvez le voir, pas besoin de plugin shadow ou de classe Launcher. Vous pouvez également cibler d'autres plates-formes, ou inclure des dépendances non modulaires, comme dans cette question .

jpackage

Enfin, il existe un nouvel outil pour créer des programmes d'installation exécutables que vous pouvez utiliser pour distribuer votre application.

Jusqu'à présent, il n'y a pas encore de version GA (probablement nous devrons attendre Java 13), mais il y a deux options pour l'utiliser maintenant avec = Java 11 ou 12:

Avec Java/JavaFX 11, il y a un port de retour du travail initial sur le JPackager sur Java 12 que vous pouvez trouver ici . Il y a un bel article sur son utilisation) ici , et un projet gradle pour l'utiliser ici .

Avec Java/JavaFX 12, il existe déjà un build 0 version de l'outil jpackage qui sera disponible avec Java 13.

Il s'agit d'une utilisation très préliminaire de l'outil:

plugins {
    id 'org.openjfx.javafxplugin' version '0.0.5'
}

repositories {
    mavenCentral()
}

dependencies {
}

javafx {
    version = "12-ea+5"
    modules = [ 'javafx.controls' ]
}

mainClassName = 'hellofx/hellofx.HelloFX'

def Java_home = '/Users/<user>/Downloads/jdk-12.jdk/Contents/Home'
def installer = 'build/installer'
def appName = 'HelloFXApp'

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

task jpackage(type: Exec) {
    dependsOn 'clean'
    dependsOn 'copyDependencies'

    commandLine "${Java_home}/bin/jpackage", 'create-installer', "dmg",
            '--output', "${installer}", "--name", "${appName}",
            '--verbose', '--echo-mode', '--module-path', 'build/libs',
            '--add-modules', "${moduleName}", '--input', 'builds/libraries',
            '--class', "${mainClassName}", '--module', "${mainClassName}"
}

En cours d'exécution ./gradlew jpackage génère un dmg (65 Mo), que je peux distribuer pour être installé:

installer

Conclusion

Bien que vous puissiez vous en tenir aux gros pots classiques, lorsque vous passez à Java 11 et au-delà, tout est censé être modulaire. Les nouveaux (et bientôt) outils et plugins disponibles, y compris le IDE support, aide pendant cette transition.

Je sais que j'ai présenté ici le cas d'utilisation le plus simple, et que lors de l'essai de cas réels plus complexes, il y aura plusieurs problèmes ... Mais nous devrions mieux travailler à résoudre ces problèmes plutôt que de continuer à utiliser des solutions obsolètes.

29
José Pereda