如何签署 JavaFX 应用程序并将其部署到单个 .JAR 中?

Posted

技术标签:

【中文标题】如何签署 JavaFX 应用程序并将其部署到单个 .JAR 中?【英文标题】:How can I sign and deploy a JavaFX application into a single .JAR? 【发布时间】:2014-09-03 20:09:19 【问题描述】:

我仍在学习 JavaFX 的来龙去脉。一个主要区别是,在 dist 文件夹(除了库)中,除了 .jar 文件之外,我还发现了一个 html 文件和一个 JNLP 文件,这对我来说都没有任何用处(这将是一个桌面应用程序)。

我发现了以下内容(由于敏感/不相关信息而省略了属性):

<delete dir="$store.dir"/>
<mkdir dir="$store.dir"/>

<jar destfile="$store.dir/temp_final.jar" filesetmanifest="skip">
    <zipgroupfileset dir="dist" includes="*.jar"/>
    <zipgroupfileset dir="dist/lib" includes="*.jar"/>

    <manifest>
        <attribute name="Main-Class" value="$main.class"/>
    </manifest>
</jar>

<zip destfile="$store.jar">
    <zipfileset src="$store.dir/temp_final.jar"
    excludes="META-INF/*.SF, META-INF/*.DSA, META-INF/*.RSA"/>
</zip>
    <delete file="$store.dir/temp_final.jar"/>

    <delete     dir = "$build.output.dir"/>
    <mkdir      dir = "$build.output.dir"/>

    <signjar
                jar = "$store.jar"
          signedjar = "$build.output.dir/$FileName"
              alias = "$comodo.key.alias"
          storepass = "$comodo.key.storepass"
           keystore = "$comodo.key.store"
          storetype = "PKCS12"
            keypass = "$comodo.key.pass"
             tsaurl = "$timestamp.url"/> 

这是为了将“可执行”的 MAIN JAR 和所有依赖库构建到一个可以从任何位置运行的单个 .JAR 中,然后对该 JAR 进行签名并将其移动到“签名”目录中。

这适用于任何 JAR 库或 Swing GUI 应用程序 JAR,但是当我用 JavaFX 应用程序尝试同样的事情时,它就失败了:

Error: Could not find or load main class com.javafx.main.Main

发现它不起作用我并不完全惊讶,但这有点问题。我对可能部署“自包含”应用程序进行了一些研究,但这不符合我们的需求。我的雇主花了一些钱购买了 Comodo 证书,我很快就撞到了一堵墙,似乎因为 Apple 的恶作剧(除非我在这个假设中弄错了),你必须以 100 美元的低价加入他们的开发者俱乐部年(未发生)。我并没有专门为苹果平台做开发。我做Java开发。无论如何,如果我是正确的,那将不适合我们,因此 Windows、Linux 和 Mac 的独立部署已经结束(再次,如果我是正确的)。

我希望这很简单。

那么,如何将我创建的用于将包含所有依赖 LIB 的 JAR 编译为代码签名 JAR 的 ANT 脚本应用于 JavaFX 应用程序?

编辑 1:好的,所以肯定说得太早了。不是很接近答案。我试图将我所知道的关于编译和签名常规 JAR 文件的知识告诉 frankenstein,以便包含所有库,并且签名成功地融入了我能够从 manual 中挑选出来的内容。我所拥有的是大杂烩和失败。我得到一个 .JAR 文件,其中包含除主 .JAR 文件之外的所有内容。无论如何,这是 ANT 脚本代码:

属性:

<property  name = "name"
          value = "APPNAME"/>

<property  name = "file"
          value = "APPJAR"/>

<property  name = "MC" 
          value = "MAINPACKAGE.MAINCLASS" />

<property  name = "released.dir"
          value = "released"/>

<property  name = "compiled.dir"
          value = "$released.dir/compiled"/>

<property  name = "stored.dir"
          value = "$released.dir/stored"/>

<property  name = "signed.dir"
          value = "$released.dir/signed"/>

其他:

<delete dir = "$released.dir"/>
        <mkdir dir = "$compiled.dir"/>

        <fx:jar destfile = "dist/compiled.jar">
            <fx:platform javafx = "8.0+" j2se = "8.0"/>
            <fx:application      name = "$name"
                            mainClass = "$MC"/>

            <fileset dir = "build/classes"/>

            <fx:resources>
                <fx:fileset dir = "dist" includes = "lib/*.jar"/>
            </fx:resources>
        </fx:jar>

        <fx:signjar keystore = "$comodo.key.store"
                       alias = "$comodo.key.alias"
                   storetype = "PKCS12"
                     keypass = "$comodo.key.pass"
                   storepass = "$comodo.key.storepass"
                         jar = "dist/compiled.jar"
                     destdir = "$compiled.dir"/>

        <mkdir dir = "$stored.dir"/>

        <jar       destFile = "$stored.dir/temp_final.jar"
            filesetmanifest = "skip" >
            <zipgroupfileset      dir = "$compiled.dir"
                             includes = "compiled.jar"/>

            <zipgroupfileset      dir = "dist/lib"
                             includes = "*.jar"/>

            <manifest>
                <attribute name = "Main-Class"
                          value = "$main.class"/>
            </manifest>
        </jar>

        <zip destfile = "$stored.dir/$file">
            <zipfileset src = "$stored.dir/temp_final.jar"
                   excludes = "META-INF/*.SF, META-INF/*.DSA, META-INF/*.RSA"/>
        </zip>

        <delete file="$stored.dir/temp_final.jar"/>

        <mkdir dir = "$signed.dir"/>

        <signjar
            keystore = "$comodo.key.store"
               alias = "$comodo.key.alias"
           storetype = "PKCS12"
              tsaurl = "$timestamp.url"
             keypass = "$comodo.key.pass"
           storepass = "$comodo.key.storepass"
                 jar = "$stored.dir/$file"
             destdir = "$signed.dir"/>

这就是我必须在我的时间展示的东西。如果有人能从中收集到一些可能使我走上正轨的东西(如果我什至接近,我觉得我不是),那将是非常棒的。

【问题讨论】:

可能已经找到了查阅文档的答案。不要以为事情会那么容易...... 【参考方案1】:

在把我的头骨和里面的东西打成一团难以辨认的连贯思想后,我设法编写了一个脚本来完成我打算完成的任务。其中大部分来自其他人的贡献和咨询docs。另一个主要贡献是 here (该线程的最后一条评论,它链接到另一个 SO 答案,但那个 SO 答案对我没有多大帮助)。感谢我能够从中提取解决方案部分的一切。我希望这对其他需要/想要完成这项任务的人有所帮助。

无论如何:第一件事:属性:

<property name = "JFXProject.name"
         value = "PROJECT"/> <!-- Put your own project name here -->
<property name = "JFXMainClass"
         value = "MAINPACKAGE.MAINCLASS"/> <!--Put your own main class here -->
    <!-- don't edit below this line -->
<property name = "JFX.src.dir"
         value = "src"/>
<property name = "JFX.lib.dir"
         value = "dist/lib"/>
<property name = "JFX.build.dir"
         value = "release/JFXBuild"/>
<property name = "JFX.sign.dir"
         value = "release/JFXSign"/>
<property name = "store.dir"
         value = "release/store"/>
<property name = "sign.dir"
         value = "release/sign"/>
<property name = "comodo.key.store"
         value = "PATH-TO-YOUR-CERTIFICATE" /> <!-- You can name this what you like, but you want to make sure the value points to your certificate or JKS file -->

<property name = "comodo.key.storepass"
         value = "PASSWORD"/> <!-- Above applies here. Make sure it's the right password -->

<property name = "comodo.key.alias"
         value = "ALIAS"/> <!-- Make sure it's your right alias. You can find out how to find that information from [here][3] -->

<property name = "comodo.key.pass"
         value = "PASSWORD"/> <!-- In my own experience this was the same as the storepass but it may be different for you -->

<property name = "timestamp.url"
         value = "TIMESTAMPURL"/> <!-- This will vary for you depending on your certificate. -->

如果需要,您可以将属性名称更改为对您更有意义的名称,但请确保它们在整个脚本中保持一致。

接下来,我们要清理最后一次构建:

<target name = "JFXClean">
    <echo>Cleaning $JFX.build.dir...</echo>
    <delete dir = "$JFX.build.dir"/>
    <delete dir = "$JFX.sign.dir"/>
    <delete dir = "$store.dir"/>
    <delete dir = "$sign.dir"/>
</target>

然后我们要为新的干净构建重新创建目录...

<target name = "JFXInit" depends = "JFXClean">
    <echo>Creating the build directory...</echo>
    <mkdir dir = "$JFX.build.dir"/>
    <mkdir dir = "$JFX.sign.dir"/>
    <mkdir dir = "$store.dir"/>
    <mkdir dir = "$sign.dir"/>
</target>

这部分对于获得正常运行的 JavaFX JAR 文件至关重要:

<target name = "JFXBuild" depends = "jar, JFXInit">
    <fx:jar destfile = "$JFX.build.dir/$JFXProject.name.jar">
        <fx:application name = "$JFXProject.name"
                   mainClass = "$JFXMainClass"/>
        <fileset dir = "build/classes"/>
    </fx:jar>
</target>

这会将所有部分正确存储到 JAR 中的位置(包括您可能拥有的任何 CSS 和 JFXML 文件。如果您不创建 JFXML 应用程序,这可能没有必要。我不知道我没有测试了一下。

然后我们要签署 JavaFX JAR:

<target name = "JFXSign" depends = "JFXBuild">
    <fx:signjar  keystore = "$comodo.key.store"
                    alias = "$comodo.key.alias"
                storetype = "PKCS12" <!-- This is necessary for me, but may not be for you if you are not using a PFX file -->
                  keypass = "$comodo.key.pass"
                storepass = "$comodo.key.storepass"
                      jar = "$JFX.build.dir/$JFXProject.name.jar"
                  destdir = "$JFX.sign.dir"/>
</target>

之后,还有 2 个目标处理采购 zip,然后设置该 zip,还有一个用于唱最后一个 jar 的 final:

<target name = "build" depends = "JFXSign">
    <jar destfile = "$store.dir/temp_final.jar"
  filesetmanifest = "skip">
        <zipgroupfileset dir = "$JFX.build.dir"
                     includes = "*.jar"/>
        <zipgroupfileset dir = "$JFX.lib.dir"
                    includes = "*.jar"/>
        <manifest>
            <attribute name = "Main-Class"
                      value = "$JFXMainClass"/>
        </manifest>
    </jar>
</target>

<target name = "store" depends = "build">
    <zip destfile = "$store.dir/$JFXProject.name.jar">
        <zipfileset src = "$store.dir/temp_final.jar"
               excludes = "META-INF/*sf, META-INF/*.DSA, META-INF/*RSA"/>
    </zip>
    <delete file = "$store.dir/temp_final.jar"/>
</target>

<target name = "BuildStoreAndSign" depends = "store">
    <signjar
         keystore = "$comodo.key.store"
            alias = "$comodo.key.alias"
        storetype = "PKCS12" <!-- This is necessary for me, but may not be for you if you are not using a PFX file -->
           tsaurl = "$timestamp.url"
          keypass = "$comodo.key.pass"
        storepass = "$comodo.key.storepass"
              jar = "$store.dir/$JFXProject.name.jar"
          destdir = "$sign.dir"/>
    <delete dir = "$JFX.compile.dir"/>
    <delete dir = "$JFX.build.dir"/>
    <delete dir = "$JFX.sign.dir"/>
    <delete dir = "$store.dir"/>
</target>

我无法真正解释这一点,因为我远不是这方面的专家。我基本上能够从示例代码和我找到的源代码中提取我所看到的内容,并将它们放在一起得到这个: 最后一点半有用的信息是this,但请注意这不使用一个罐子(我用一个罐子试过,但没有用。它没有引入 CSS 或应用它)。

还有一点警告:This worked for me。我不能保证它会适合你,但我想修改它会产生类似于我的结果(一个可以在你放置的任何地方运行的单个 .JAR JavaFX 应用程序)。

【讨论】:

期待您的回答@Will。考虑将其更改为您的答案下方的评论,然后在您弄清楚时发布您的完整答案。 很抱歉,但好消息是我有一个可行的解决方案!【参考方案2】:

神谕说:http://www.oracle.com/technetwork/articles/javase/single-jar-141905.html

需要修改

<manifest>
    <attribute name="Main-Class" value="$main.class"/>
</manifest>

并将其更改为

<manifest>
    CHANGE application.MainClass to 
    <attribute name="JavaFX-Application-Class" value="application.MainClass" />
    <attribute name="Main-Class" value="com/javafx/main/Main" />
</manifest>

但是听了上面的 Oracle,我得到了一个带有外部 jar 的单一 JavaFX。

【讨论】:

【参考方案3】:

我尝试了您的 build.xml 示例,但未能使其正常工作。

不过,我确实找到了使用 Eclipse 的替代方法。

    在 Eclipse 中打开您的 JavaFX 项目。 右键单击您的项目并将其导出为可运行的 JAR 文件。 将所需的库提取到生成的 JAR 中。

    使用标准 jar 签名器为您的可运行 JAR 签名。

为此,您需要先创建一个密钥库:

keytool -genkey -keyalg RSA -alias Aubrey -keystore keystore.jks -storepass YourPassword

然后像这样签名:

jarsigner -keystore c:\path\to\your\keystore.jks -storepass YourPassword c:\path\to\your\file.jar Aubrey
    最后,创建一个 javafx jnlp 文件。并将您签名的 jar 和 jnlp 上传到您的服务器。

例如:

<?xml version="1.0" encoding="utf-8"?>
<jnlp spec="1.0" xmlns:jfx="http://javafx.com" codebase="http://aubreigo.info/java/" href="FX-People.jnlp">
<information>
<title>FX-People</title>
<vendor>aubrey</vendor>
<description>Google Contacts Viewer</description>
<offline-allowed/>
</information>

<security>
<all-permissions/>
</security>

<resources>
<j2se version="1.6+" href="http://java.sun.com/products/autodl/j2se"/>
<jar href="FX-People.jar" size="109545" download="eager" />
</resources>

<applet-desc    main-class="com.javafx.main.NoJavaFXFallback"  name="FX-People" >
<param name="requiredFXVersion" value="8.0+"/>
</applet-desc>
<jfx:javafx-desc    main-class="fx.contacts.FXContacts"  name="FX-People" />
<update check="always"/>
</jnlp>

【讨论】:

以上是关于如何签署 JavaFX 应用程序并将其部署到单个 .JAR 中?的主要内容,如果未能解决你的问题,请参考以下文章

JavaFX应用程序 - 需要签名的内容?

我在哪里可以找到有关 SP 如何使用其私钥签署身份验证请求并将其发送到 IdP 的 URL 的信息?

签署小程序并部署到内网

使用 maven 部署 javafx hsqldb 项目

如何使用 JavaFX 将选择框、复选框和文本字段实现为一个按钮 [关闭]

JavaFX - 分页:如何隐藏底部(控制)面板并用页面占据其区域?