打包非模块化 JavaFX 应用程序 [英] Package a non-modular JavaFX application

查看:50
本文介绍了打包非模块化 JavaFX 应用程序的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我有一个 Java 8 应用程序,它使用 JavaFX 并且主类扩展javafx.application.Application.目前,我将它作为一个胖 jar 交付,它在 Oracle Java 8 上运行良好.

现在我希望它能够在 OpenJDK 11 上运行.为了添加 JavaFX,我已经将 org.openjfx 中的工件添加到类路径中,并将它们包含在胖 jar 中.如果我从命令行启动我的 jar,我得到

错误:缺少 JavaFX 运行时组件,需要运行此组件应用

我找到了两种可能的方法来解决这个问题:

  1. 肮脏的:编写一个特殊的启动器,它不扩展应用程序并绕过模块检查.参见

    结论

    虽然您可以坚持使用经典的胖罐子,但在迁移到 Java 11 及更高版本时,一切都应该是模块化的.新的(即将推出的)可用工具和插件,包括 IDE 支持,在这一转变过程中起到了帮助作用.

    我知道我在这里展示了最简单的用例,并且在尝试更复杂的实际案例时,会出现几个问题……但我们应该更好地解决这些问题,而不是继续使用过时的解决方案.

    I have a Java 8 application, that uses JavaFX and where the main class extends javafx.application.Application . Currently, I deliver it as a fat jar and it runs fine on Oracle Java 8.

    Now I want it to be able to run on OpenJDK 11. To add JavaFX, I already added the artifacts from org.openjfx to the classpath and am including them in the fat jar. If I start my jar from the command line, I get

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

    I found two possible ways around this problem:

    1. The dirty one: Write a special launcher that does not extend Application and circumvent the module check. See http://mail.openjdk.java.net/pipermail/openjfx-dev/2018-June/021977.html
    2. The clean one: Add --module-path and --add-modules to my command line. The problem with this solution is, that I want my end users to be able to just launch the application by double clicking it.

    While I could go with 1. as a workaround, I wonder what is currently (OpenJDK 11) the intended way to build/deliver executable fat jars of non-modular JavaFX applications. Can anyone help?

    解决方案

    These are a few options for packaging/distributing a (non-modular) JavaFX 11 end application. Most of them are explained in the official OpenJFX docs.

    I'll use this sample as reference. I'll also use Gradle. Similar can be done with Maven (different plugins), and even without build tools (but this is not recommended...). Build tools are a must nowadays.

    Fat Jar

    This is still a valid option, but not the preferred one, as it breaks the modular design and bundles everything all together, and it is not cross-platform unless you take care of that.

    For the given sample, you have a build.gradle file like this:

    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) }
        }
    }
    

    Note the use of a Launcher class. As mentioned by the OP or explained here, a launcher class that not extends from Application is required now to create a fat jar.

    Running ./gradlew jar produces a fat jar (~8 MB) that includes the JavaFX classes, and the native libraries of your current platform.

    You can run java -jar build/libs/hellofx.jar as usual, but only in the same platform.

    As explained in the OpenJFX docs or here, you can still create a cross-platform jar.

    In this case, we can include the three graphics jars, as those are the ones that have platform-dependent code and libraries. Base, controls and fxml modules are the platform-independent.

    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 will produce a fat jar (19 MB) now that can be distributed to these three platforms.

    (Note Media and Web have also platform-dependent code/native libraries).

    So this works as it used to on Java 8. But as I said before, it breaks how modules work, and it doesn't align on how libraries and apps are distributed nowadays.

    And don't forget that the users of these jars will still have to install a JRE.

    jlink

    So what about distributing a custom image with your project, that already includes a native JRE and a launcher?

    You will say that if you have a non-modular project, that won't work. True. But let's examine two options here, before talking about jpackage.

    runtime-plugin

    The badass-runtime-plugin is a Gradle plugin that creates runtime images from non-modular projects.

    With this 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']
    }
    

    When you run ./gradlew runtime it will create a runtime, with its launcher, so you can run:

    cd build/image/hellofx/bin
    ./hellofx
    

    Note it relies on the shadow plugin, and it requires a Launcher class as well.

    If you run ./gradlew runtimeZip, you can get a zip for this custom image of about 32.5 MB.

    Again, you can distribute this zip to any user with the same platform, but now there is no need of an installed JRE.

    See targetPlatform for building images for other platforms.

    Going Modular

    We keep thinking that we have non-modular project, and that can't be changed... but what if we do change it?

    Going modular is not that big of a change: you add a module-info.java descriptor, and you include the required modules on it, even if those are non-modular jars (based on automatic names).

    Based on the same sample, I'll add a descriptor:

    module hellofx {
        requires javafx.controls;
    
        exports hellofx;
    }
    

    And now I can use jlink on command line, or use a plugin for it. The badass-gradle-plugin is a gradle plugin, from the same author as the one mentioned before, that allows creating a custom runtime.

    With this build file:

    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'
    

    you can run now:

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

    or ./gradlew jlinkZip for a zipped version (31 MB) that can be distributed and run in machines, of the same platform, even if there is no JRE installed.

    As you can see, no need for shadow plugin or Launcher class. You can also target other platforms, or include non-modular dependencies, like in this question.

    jpackage

    Finally, there is a new tool to create executable installers that you can use to distribute your application.

    So far there is no GA version yet (probably we'll have to wait for Java 13), but there are two options to use it now with Java 11 or 12:

    With Java/JavaFX 11 there is a back port from the initial work on the JPackager on Java 12 that you can find here. There is a nice article about using it here, and a gradle project to use it here.

    With Java/JavaFX 12 there is already a build 0 version of the jpackage tool that will be available with Java 13.

    This is a very preliminary use of the tool:

    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}"
    }
    

    Now running ./gradlew jpackage generates a dmg (65 MB), that I can distribute to be installed:

    Conclusion

    While you can stick to classic fat jars, when moving to Java 11 and beyond, everything is supposed to be modular. The new (soon to be) available tools and plugins, including the IDE support, are helping during this transition.

    I know I've presented here the simplest use case, and that when trying more complex real cases, there will be several issues... But we should better work on solving those issues rather than keep using outdated solutions.

    这篇关于打包非模块化 JavaFX 应用程序的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆