JavaFX + FXML + webstart:为什么这不起作用?

seb*_*kem 4 java javafx java-web-start fxmlloader

让我们使用带有FXML的JavaFX 8创建最简单的Hello World应用程序:

src/application/Main.java:

package application;

import javafx.application.Application;
import javafx.fxml.FXMLLoader;
import javafx.stage.Stage;
import javafx.scene.Parent;
import javafx.scene.Scene;

public class Main extends Application {
    @Override
    public void start(Stage stage) {
        try {
            System.out.println("Main.start()");
            FXMLLoader fxml_loader = new FXMLLoader();
            fxml_loader.setLocation(getClass().getResource("Sample.fxml"));
            System.out.println("FXML resource URL = " + getClass().getResource("Sample.fxml"));
            Parent root = fxml_loader.load(); 
            Scene scene = new Scene(root, 300, 200);
            stage.setScene(scene);
            stage.setTitle("JFX HW");
            stage.show();
        } catch(Exception e) {
            e.printStackTrace();
        }
    }

    public static void main(String[] args) {
        launch(args);
    }
}
Run Code Online (Sandbox Code Playgroud)

src/application/Sample.fxml:

<?xml version="1.0" encoding="UTF-8"?>

<?import java.lang.*?>
<?import javafx.scene.control.*?>
<?import javafx.scene.layout.*?>
<?import javafx.scene.layout.StackPane?>

<StackPane xmlns:fx="http://javafx.com/fxml/1" xmlns="http://javafx.com/javafx/8">
   <children>
      <Label text="Hello World" />
   </children>
</StackPane>
Run Code Online (Sandbox Code Playgroud)

工作流程

使用您喜欢的IDE,将all编译到一个bin文件夹中:

$ find bin
bin
bin/application
bin/application/Main.class
bin/application/Sample.fxml
Run Code Online (Sandbox Code Playgroud)

然后创建一个jar:

$ javapackager -createjar -appclass application.Main -srcdir bin -outdir compiled -outfile jfxhw -v -manifestAttrs "Application-Name=JFXHW,Permissions=sandbox,Codebase=*"
Run Code Online (Sandbox Code Playgroud)

这里可以验证jar文件是否正常工作java -jar jfxhw.jar.

我们来签名吧:

$ jarsigner compiled/jfxhw.jar MYALIAS
Run Code Online (Sandbox Code Playgroud)

部署:

$ javapackager -deploy -appclass application.Main -srcdir compiled -outdir deployed -outfile index -width 300 -height 200 -name JFXHW -v

$ find deployed
deployed/
deployed/jfxhw.jar
deployed/index.jnlp
deployed/index.html
Run Code Online (Sandbox Code Playgroud)

结果

命令:

javaws index.jnlp
Run Code Online (Sandbox Code Playgroud)

失败了(你需要让控制台看到这个):

Main.start()
FXML resource URL = null
java.lang.IllegalStateException: Location is not set.
    at javafx.fxml.FXMLLoader.loadImpl(FXMLLoader.java:2438)
    at javafx.fxml.FXMLLoader.load(FXMLLoader.java:2413)
    at application.Main.start(Main.java:18)
    :
Run Code Online (Sandbox Code Playgroud)

我究竟做错了什么?

aw-*_*ink 5

问题

应用程序需要足够的权限来调用fxml文件.您尚未在部署步骤中设置任何权限,因此在生成的jnlp文件中省略了安全性部分.

有一个已知的错误,它计划在Java 9中修复.这个错误与你的问题有关,但不是根本原因.

https://bugs.openjdk.java.net/browse/JDK-8088866

对于jarsign和javapackager -signjar的不一致.

我试图摆脱它:

  1. 做像你这样的事情,以不可运行的相同jnlp结束
  2. 切换到JConsole和Tracing以获取更多细节,但又失败了
  3. 尝试javapackager -signjar命令,而不是像你这样的jarsigner,但再次失败
  4. 在bugs.openjdk.java.net上搜索了Bug-Database,它向我展示了验证由javapackager -signjar创建的签名jar的问题
  5. 搜索了有关applet,jnlp和签名的更多复杂情况,发现了这个:https://weblogs.java.net/blog/cayhorstmann/archive/2014/01/16/still-using-applets-sign-them-or-else
  6. 尝试使用-allpermissions创建部署,以部署javapackager的步骤.这适用于自签名证书的限制!
  7. 尝试使用嵌入的"普通"signjar任务创建javafx ant任务.这有效!(顺便说一句:如果您在项目属性下选中"请求不受限访问(启用签名)",这就是Netbeans的工作方式.

使用javapackager解决方案

-allpermissions参数添加到命令行:

javapackager -deploy -allpermissions -appclass application.Main -srcdir compiled -outdir deployed -outfile index -width 300 -height 200 -name JFXHW -v
Run Code Online (Sandbox Code Playgroud)

但是有一个小问题:您的manifest.mf将不包含空类Class-Path标记,因为这是由ant脚本添加的.将弹出Java安全提示,并且在将证书添加到操作系统中受信任的根证书存储区之前,您无法运行该应用程序.

使用ant脚本解决方案

首先,我更改了部署任务中的权限以将其设置为提升:

<fx:permissions elevated="true" cacheCertificates="true"/>
Run Code Online (Sandbox Code Playgroud)

其次,我创建了以下build.xml文件并将其放在项目的根目录中.你的jnlp将在文件夹dist中创建.您必须为密钥库设置storepass.和正确的别名和密钥库文件名.如果您不在Windows上,"env"可能无法正常工作.把它设置为你的jdk很难.

只需在项目的根目录中使用"ant default"调用它即可.

build.xml文件

<?xml version="1.0" encoding="UTF-8"?>

<project name="App" default="default" basedir="."
         xmlns:fx="javafx:com.sun.javafx.tools.ant">

  <property environment="env"/>
  <property name="build.src.dir" value="src"/>
  <!-- <property name="build.resources.dir" value="${build.src.dir}/resources"/> -->
  <property name="build.classes.dir" value="classes"/>
  <property name="build.dist.dir" value="dist"/>


  <target name="default" depends="clean,compile">

    <!-- defines the classpath -->
    <path id="cp">
      <filelist>
        <file name="${env.JAVA_HOME}/lib/ant-javafx.jar"/>
        <file name="${basedir}" />
      </filelist>
    </path>

    <!-- defines the task with a reference to classpath -->
    <taskdef resource="com/sun/javafx/tools/ant/antlib.xml"
             uri="javafx:com.sun.javafx.tools.ant"
             classpathref="cp"/>


    <fx:application id="appId"
                    name="jfxHw"
                    mainClass="application.Main"/>


    <!-- Defines the resources needed by the application -->
    <fx:resources id="appRes">
      <fx:fileset dir="${build.dist.dir}" includes="jfxHw.jar"/>
      <!-- <fx:fileset dir="${build.dist.dir}" includes="bsigned_jfxHw.jar"/> -->
    </fx:resources>

    <!-- Create a jar file -->
    <fx:jar destfile="${build.dist.dir}/jfxHw.jar">
      <fx:application refid="appId"/>
      <fx:resources refid="appRes"/>
      <fileset dir="${build.classes.dir}"/>
      <fileset dir="${build.src.dir}">
        <exclude name="**/*.java"/>
      </fileset>
      <manifest>
        <attribute name="Permission" value="all-permissions"/>
      </manifest>
    </fx:jar>

    <signjar alias="myAlias" keystore="myKeystore.jks"
             storepass="******"
             preservelastmodified="true">
      <path>
        <fileset dir="${build.dist.dir}" includes="**/*.jar" />
      </path>
    </signjar>


    <fx:deploy width="600" height="400" outdir="${basedir}//${build.dist.dir}/"
               outfile="jfxhw" verbose="true">
      <fx:info title="jfxHw"/>
      <fx:application refid="appId"/>
      <fx:resources refid="appRes"/>
      <fx:permissions elevated="true" cacheCertificates="true"/>
    </fx:deploy>

  </target>

  <!-- Removes the folders of previous runs -->
  <target name="clean">
    <mkdir dir="${build.classes.dir}"/>
    <mkdir dir="${build.dist.dir}"/>

    <delete>
      <fileset dir="${build.classes.dir}" includes="**/*"/>
      <fileset dir="${build.dist.dir}" includes="**/*"/>
    </delete>
  </target>

  <!-- Compiles the sources -->
  <target name="compile" depends="clean">
    <javac includeantruntime="false"
           srcdir="${build.src.dir}"
           destdir="${build.classes.dir}"
           fork="yes"
           executable="${env.JAVA_HOME}/bin/javac"
           source="1.8"
           debug="on">
    </javac>
  </target>

</project>
Run Code Online (Sandbox Code Playgroud)

MANIFEST.MF

一切都完成后创建.请注意,我将清单权限设置为all-permissios以进行调试.您可以安全地将其设置为沙箱.

Manifest-Version: 1.0
JavaFX-Version: 8.0
Permission: all-permissions
Class-Path: 
Created-By: JavaFX Packager
Main-Class: application.Main

Name: application/Sample.fxml
SHA-256-Digest: eT8+7c2XeVhURexj5X9Y1xAP2H8YIMcieeySOmgOPZw=

Name: application/Main.class
SHA-256-Digest: Md+alMOmoQpslZIgLwbmPFAI8axSKBVvReXZFgoKJ6A=
Run Code Online (Sandbox Code Playgroud)

jfxhw.jnlp

观察安全部分:

<?xml version="1.0" encoding="utf-8"?>
<jnlp spec="1.0" xmlns:jfx="http://javafx.com" href="jfxhw.jnlp">
  <information>
    <title>jfxHw</title>
    <vendor>Unknown vendor</vendor>
    <description>Sample JavaFX 2.0 application.</description>
    <offline-allowed/>
  </information>
  <resources>
    <j2se version="1.6+" href="http://java.sun.com/products/autodl/j2se"/>
    <jar href="jfxHw.jar" size="3089" download="eager" />
  </resources>
<security>
  <all-permissions/>
</security>
  <jfx:javafx-desc  width="600" height="400" main-class="application.Main"  name="jfxHw" />
  <update check="background"/>
</jnlp>
Run Code Online (Sandbox Code Playgroud)