JavaFX:自定义组件仅在 Runnable JAR 中抛出 LoadException

JavaFX: Custom Component throws LoadException only in Runnable JAR

我正在尝试从我正在处理的 JavaFX 项目生成一个可执行的 jar 文件,当通过 Eclipse [=40] 执行时 运行 ok =]. 但是,当从生成的 jar 文件执行时,当 FXML 尝试加载自定义组件(class 其中 extends JFX 组件 BorderPane)时会发生运行时错误。 怎么了?

这是 FXML 文件,其中使用了扩展 BorderPane 的自定义 class、CardPane

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

<?import com.tsi.ui.CardPane?>
<?import java.lang.*?>
<?import javafx.scene.layout.BorderPane?>
<?import javafx.scene.layout.ColumnConstraints?>
<?import javafx.scene.layout.GridPane?>
<?import javafx.scene.layout.HBox?>
<?import javafx.scene.layout.Pane?>
<?import javafx.scene.layout.RowConstraints?>
<?import javafx.scene.text.Font?>
<?import com.tsi.ui.CardPane?>

<BorderPane fx:id="mainPane" maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="500.0" prefWidth="400.0" style="-fx-background-color: #454449;" xmlns="http://javafx.com/javafx/8" xmlns:fx="http://javafx.com/fxml/1">
   <center>
      <GridPane fx:id="gridPane" opacity="0.99" prefHeight="386.0" prefWidth="380.0" BorderPane.alignment="CENTER">
        <columnConstraints>
            <ColumnConstraints hgrow="SOMETIMES" minWidth="10.0" prefWidth="100.0" />
          <ColumnConstraints hgrow="SOMETIMES" minWidth="10.0" prefWidth="100.0" />
          <ColumnConstraints hgrow="SOMETIMES" minWidth="10.0" prefWidth="100.0" />
        </columnConstraints>
        <rowConstraints>
          <RowConstraints minHeight="10.0" prefHeight="30.0" vgrow="SOMETIMES" />
          <RowConstraints minHeight="10.0" prefHeight="30.0" vgrow="SOMETIMES" />
          <RowConstraints minHeight="10.0" prefHeight="30.0" vgrow="SOMETIMES" />
        </rowConstraints>
         <children>
           <!-- 
              The below command (referenced in the stack trace as line 40, 
              which here is not precise, since I've deleted some useless 
              stuff for the post propose) is where the error occurs
            -->
            <CardPane fx:id="pane00" prefHeight="200.0" prefWidth="200.0" />
            <CardPane fx:id="pane01" prefHeight="200.0" prefWidth="200.0" GridPane.columnIndex="1" />
            <CardPane fx:id="pane02" prefHeight="200.0" prefWidth="200.0" GridPane.columnIndex="2" />
            <CardPane fx:id="pane10" prefHeight="200.0" prefWidth="200.0" GridPane.rowIndex="1" />
            <CardPane fx:id="pane11" prefHeight="200.0" prefWidth="200.0" GridPane.columnIndex="1" GridPane.rowIndex="1" />
            <CardPane fx:id="pane12" prefHeight="200.0" prefWidth="200.0" GridPane.columnIndex="2" GridPane.rowIndex="1" />
            <CardPane fx:id="pane20" prefHeight="200.0" prefWidth="200.0" GridPane.rowIndex="2" />
            <CardPane fx:id="pane21" prefHeight="200.0" prefWidth="200.0" GridPane.columnIndex="1" GridPane.rowIndex="2" />
            <CardPane fx:id="pane22" prefHeight="200.0" prefWidth="200.0" GridPane.columnIndex="2" GridPane.rowIndex="2" />
         </children>

      </GridPane>
   </center>

<!-- I will show only the interesting point -->
...

</BorderPane>

CardPane 仅用作扩展 BorderPane 的 class,其布局位置已经填充了一些其他节点。在此代码中,我们使用其中一些填充 GridPane 单元格的内容。

这里有几行来说明它是如何定义的:

public class CardPane extends BorderPane {

    private Card card;
    private Label cardName;
    private Label cardValue;
    private ImageView valueIcon;
    private HBox hBox;
    private Node armaSprite;

    public CardPane() {
        hBox = new HBox();
        cardName = new Label();
        cardValue = new Label();
        valueIcon = new ImageView(new Image(Sprite.CAMINHO + File.separator + "CoracaoIcon.png", 27, 22, false, false));
    }

老实说,我不知道这种方法(扩展 BorderPane class,以这种方式制作我自己的组件)是否是一个好的做法。如果没有,如果有人能让我知道应该避免什么,我会很高兴。 但最重要的是,要点是 Eclipse 中的一切都运行良好,但在导出项目时却不行。为什么?

这是 Windows 控制台上打印的异常。同样,它仅在使用 java.exe -jar '.\Dungeon Cards.jar' 执行 jar 时发生。永远不会在 IDE.

javafx.fxml.LoadException:
file:/C:/Users/ramon/Desktop/Dungeon%20Cards.jar!/com/tsi/ui/fxml/game.fxml:40

        at javafx.fxml.FXMLLoader.constructLoadException(FXMLLoader.java:2601)
        at javafx.fxml.FXMLLoader.loadImpl(FXMLLoader.java:2579)
        at javafx.fxml.FXMLLoader.loadImpl(FXMLLoader.java:2441)
        at javafx.fxml.FXMLLoader.loadImpl(FXMLLoader.java:3214)
        at javafx.fxml.FXMLLoader.loadImpl(FXMLLoader.java:3175)
        at javafx.fxml.FXMLLoader.loadImpl(FXMLLoader.java:3148)
        at javafx.fxml.FXMLLoader.loadImpl(FXMLLoader.java:3124)
        at javafx.fxml.FXMLLoader.loadImpl(FXMLLoader.java:3104)
        at javafx.fxml.FXMLLoader.load(FXMLLoader.java:3097)
        at com.tsi.app.Jogo.<init>(Jogo.java:40)
        at com.tsi.app.DungeonCards.iniciarJogo(DungeonCards.java:138)
        at com.tsi.ui.Instrucoes.esconder(Instrucoes.java:72)
        at com.tsi.ui.Instrucoes.handle(Instrucoes.java:52)
        at com.tsi.ui.Instrucoes.handle(Instrucoes.java:1)
        at com.sun.javafx.event.CompositeEventHandler$NormalEventHandlerRecord.handleBubblingEvent(CompositeEventHandler.java:218)
        at com.sun.javafx.event.CompositeEventHandler.dispatchBubblingEvent(CompositeEventHandler.java:80)
        at com.sun.javafx.event.EventHandlerManager.dispatchBubblingEvent(EventHandlerManager.java:238)
        at com.sun.javafx.event.EventHandlerManager.dispatchBubblingEvent(EventHandlerManager.java:191)
        at com.sun.javafx.event.CompositeEventDispatcher.dispatchBubblingEvent(CompositeEventDispatcher.java:59)
        at com.sun.javafx.event.BasicEventDispatcher.dispatchEvent(BasicEventDispatcher.java:58)
        at com.sun.javafx.event.EventDispatchChainImpl.dispatchEvent(EventDispatchChainImpl.java:114)
        at com.sun.javafx.event.BasicEventDispatcher.dispatchEvent(BasicEventDispatcher.java:56)
        at com.sun.javafx.event.EventDispatchChainImpl.dispatchEvent(EventDispatchChainImpl.java:114)
        at com.sun.javafx.event.EventUtil.fireEventImpl(EventUtil.java:74)
        at com.sun.javafx.event.EventUtil.fireEvent(EventUtil.java:54)
        at javafx.event.Event.fireEvent(Event.java:198)
        at javafx.scene.Scene$KeyHandler.process(Scene.java:3964)
        at javafx.scene.Scene$KeyHandler.access00(Scene.java:3910)
        at javafx.scene.Scene.impl_processKeyEvent(Scene.java:2040)
        at javafx.scene.Scene$ScenePeerListener.keyEvent(Scene.java:2501)
        at com.sun.javafx.tk.quantum.GlassViewEventHandler$KeyEventNotification.run(GlassViewEventHandler.java:217)
        at com.sun.javafx.tk.quantum.GlassViewEventHandler$KeyEventNotification.run(GlassViewEventHandler.java:149)
        at java.security.AccessController.doPrivileged(Native Method)
        at com.sun.javafx.tk.quantum.GlassViewEventHandler.lambda$handleKeyEvent2(GlassViewEventHandler.java:248)
        at com.sun.javafx.tk.quantum.QuantumToolkit.runWithoutRenderLock(QuantumToolkit.java:389)
        at com.sun.javafx.tk.quantum.GlassViewEventHandler.handleKeyEvent(GlassViewEventHandler.java:247)
        at com.sun.glass.ui.View.handleKeyEvent(View.java:546)
        at com.sun.glass.ui.View.notifyKey(View.java:966)
        at com.sun.glass.ui.win.WinApplication._runLoop(Native Method)
        at com.sun.glass.ui.win.WinApplication.lambda$null7(WinApplication.java:177)
        at java.lang.Thread.run(Unknown Source)
Caused by: java.lang.IllegalArgumentException: Invalid URL: Invalid URL or resource not found
        at javafx.scene.image.Image.validateUrl(Image.java:1118)
        at javafx.scene.image.Image.<init>(Image.java:659)
        at com.tsi.ui.CardPane.<init>(CardPane.java:42)
        at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
        at sun.reflect.NativeConstructorAccessorImpl.newInstance(Unknown Source)
        at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(Unknown Source)
        at java.lang.reflect.Constructor.newInstance(Unknown Source)
        at java.lang.Class.newInstance(Unknown Source)
        at sun.reflect.misc.ReflectUtil.newInstance(Unknown Source)
        at javafx.fxml.FXMLLoader$InstanceDeclarationElement.constructValue(FXMLLoader.java:1009)
        at javafx.fxml.FXMLLoader$ValueElement.processStartElement(FXMLLoader.java:746)
        at javafx.fxml.FXMLLoader.processStartElement(FXMLLoader.java:2707)
        at javafx.fxml.FXMLLoader.loadImpl(FXMLLoader.java:2527)
        ... 39 more
Caused by: java.lang.IllegalArgumentException: Invalid URL or resource not found
        at javafx.scene.image.Image.validateUrl(Image.java:1110)
        ... 51 more

@Zephyr 指出我在编写 post 时如何错过 StackTrace 上的 "Caused By:" 语句。它清楚地表明 ImageView 由于 URL 格式错误而无法实例化。

诀窍是我有以下常量指向我的图像资源所在的根路径:

public static final String CAMINHO = "/com/tsi/sprites"; 

URL 被定义为 CAMINHO + File.separator + [image_file].png

结果:"/com/tsi/sprites\[image_file].png"

用于构造函数连接的File.separator持有文件分隔的Windows模式(反斜杠),而常量值CAMINHO 使用 普通斜杠 (Linux 模式)。 不知何故,Eclipse 可以抽象出那个放错地方的组合,但是 运行 在它外面,Windows 却不能。

为了解决,我只是把File.separator改成了"/"

this article 中所述,在使用资源时,应避免 File.separator

"[...] 仅在处理文件和向用户显示路径时使用 system-specific 文件分隔符。对于所有其他情况,使用正斜杠。