Image-Pfad Probleme nach Export aus Eclipse

Fiedlerdan

Mitglied
Hallo zusammen,

nun melde ich mich nach einigen Monaten doch noch zu Wort...

Ich lerne Java seit Februar 2019 nach langen eingeschlafenen Vorkenntnissen in HTML, CSS und JavaScript.
Gelernt habe ich über einen VideoKurs von Udemy und seit März sitze ich an einem eigenen Projekt.

Bisher konnte ich mir mit Google und dem Umbau des Codes gut selbst helfen, wenn Fehler auftauchten, aber jetzt komm ich leider nicht weiter.

Es handelt sich um ein mit FXML und Scene Builder erstelltes Programm.
Wenn ich dieses in Eclipse starte, läuft es perfekt. Aber sobald ich dieses in eine Executable JAR exportiere und (vom Schreibtisch aus beispielsweise) starte, schliesst sich diese wieder von selbst.

Ich habe die JAR vom Terminal gestartet und erhalte folgender Exception:

Code:
Exception in Application constructor
java.lang.reflect.InvocationTargetException
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:498)
    at com.sun.javafx.application.LauncherImpl.launchApplicationWithArgs(LauncherImpl.java:389)
    at com.sun.javafx.application.LauncherImpl.launchApplication(LauncherImpl.java:328)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:498)
    at sun.launcher.LauncherHelper$FXHelper.main(LauncherHelper.java:767)
Caused by: java.lang.RuntimeException: Unable to construct Application instance: class de.morpheus.init.Main
    at com.sun.javafx.application.LauncherImpl.launchApplication1(LauncherImpl.java:907)
    at com.sun.javafx.application.LauncherImpl.lambda$launchApplication$154(LauncherImpl.java:182)
    at java.lang.Thread.run(Thread.java:748)
Caused by: java.lang.reflect.InvocationTargetException
    at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
    at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:62)
    at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45)
    at java.lang.reflect.Constructor.newInstance(Constructor.java:423)
    at com.sun.javafx.application.LauncherImpl.lambda$launchApplication1$160(LauncherImpl.java:819)
    at com.sun.javafx.application.PlatformImpl.lambda$runAndWait$174(PlatformImpl.java:326)
    at com.sun.javafx.application.PlatformImpl.lambda$null$172(PlatformImpl.java:295)
    at java.security.AccessController.doPrivileged(Native Method)
    at com.sun.javafx.application.PlatformImpl.lambda$runLater$173(PlatformImpl.java:294)
    at com.sun.glass.ui.InvokeLaterDispatcher$Future.run(InvokeLaterDispatcher.java:95)
Caused by: java.lang.NullPointerException: Input stream must not be null
    at javafx.scene.image.Image.validateInputStream(Image.java:1128)
    at javafx.scene.image.Image.<init>(Image.java:706)
    at de.morpheus.controller.ImageContainer.<init>(ImageContainer.java:7)
    at de.morpheus.controller.DialogController.<init>(DialogController.java:21)
    at de.morpheus.init.Main.<init>(Main.java:24)
    ... 10 more
Exception running application de.morpheus.init.Main

Es betrifft hier wohl offensichtlich meine Bild-Pfade.
Ich nutze hier zum schnellen einfügen der Bilder eine Klasse Namens ImageContainer:

Code:
package de.morpheus.controller;

import javafx.scene.image.Image;

public class ImageContainer {
    
    public Image dbError32 = new Image(getClass().getResourceAsStream("../images/dbError_32.png"));
    public Image dbOk32 = new Image(getClass().getResourceAsStream("../images/dbOk_32.png"));
    public Image dbUnknown32 = new Image(getClass().getResourceAsStream("../images/dbunknown_32.png"));
    public Image Error48 = new Image(getClass().getResourceAsStream("../images/Error_48.png"));
    public Image Dummy48 = new Image(getClass().getResourceAsStream("../images/Dummy_48.png"));
    public Image InfoY48 = new Image(getClass().getResourceAsStream("../images/InfoY_48.png"));
    public Image InfoB48 = new Image(getClass().getResourceAsStream("../images/InfoB_48.png"));
    public Image Ask48 = new Image(getClass().getResourceAsStream("../images/Ask_48.png"));
    public Image Success48 = new Image(getClass().getResourceAsStream("../images/Success_48.png"));
    public Image Key48 = new Image(getClass().getResourceAsStream("../images/key_48.png"));
}

Hier noch die aufrufende Main:

Code:
package de.morpheus.init;

import de.morpheus.controller.DialogController;
import de.morpheus.controller.SystemX;
import javafx.application.Application;
import javafx.fxml.FXMLLoader;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.stage.Stage;

public class Main extends Application {
    
    DialogController dialogController = new DialogController();
    @Override
    public void start(Stage primaryStage) {
        
        SystemX.consoleSplash();

        try {

            Parent root = FXMLLoader.load(getClass().getResource("../modules/dash/Dashboard.fxml"));

            Scene scene = new Scene(root);

            primaryStage.setScene(scene);
            primaryStage.setTitle("Dashboard " + SystemX.APP_VERSION);
            primaryStage.setResizable(true);
            primaryStage.show();

            

        } catch (Exception e) {
            dialogController.displayError("FXML Error: start", e, Main.class.getName());
            e.printStackTrace();
        }

    }

    public static void main(String[] args) {
        launch(args);
    }
}

diese erstellt dann ein Objekt des DialogControllers, der die Bilder lädt:

Code:
package de.morpheus.controller;

import javafx.event.EventHandler;
import javafx.geometry.Insets;
import javafx.geometry.Orientation;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.control.Separator;
import javafx.scene.image.ImageView;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.AnchorPane;
import javafx.scene.layout.HBox;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;

public class DialogController{

    ImageContainer imageContainer = new ImageContainer();

    // Voller Dialog

    public void display(String _type, String _header, String _context) {

        Stage stage = new Stage();

        ImageView image = new ImageView();
        image.setFitHeight(48);
        image.setFitWidth(48);
        image.setLayoutX(25);
        image.setLayoutY(25);

        switch (_type) {
        case "notify":
            System.out.println("*DialogController* open Dialog >notify<");
            stage.setTitle("Hinweis");
            image.setImage(imageContainer.InfoB48);
            break;
        case "success":
            System.out.println("*DialogController* open Dialog >success<");
            stage.setTitle("Erfolg");
            image.setImage(imageContainer.Success48);
            break;
        }

        Label header = new Label(_header);
        header.setLayoutX(90);
        header.setLayoutY(25);

        Label context = new Label(_context);
        context.setLayoutX(90);
        context.setLayoutY(55);

        Separator separator = new Separator(Orientation.HORIZONTAL);
        separator.setPrefWidth(350);
        separator.setLayoutY(100);

        Button button = new Button("OK");
        button.setPrefWidth(100);
        button.setLayoutX(225);
        button.setLayoutY(125);
        button.setOnMouseClicked(new EventHandler<MouseEvent>() {
            @Override
            public void handle(MouseEvent event) {
                stage.close();
            }
        });

        AnchorPane root = new AnchorPane();
        root.getChildren().addAll(image, header, context, separator, button);

        Scene scene = new Scene(root, 350, 175);

        stage.setScene(scene);
        stage.showAndWait();

    }

    // Voller Dialog bei Fehler

    public void displayError(String _header, Exception _e, String _url) {

        Stage stage = new Stage();
        stage.setTitle("Java Fehler");
        System.out.println("*DialogController* open ErrorDialog");

        ImageView image = new ImageView();
        image.setFitHeight(48);
        image.setFitWidth(48);
        image.setLayoutX(25);
        image.setLayoutY(25);
        image.setImage(imageContainer.Error48);

        Label header = new Label(_header);
        header.setLayoutX(90);
        header.setLayoutY(25);

        Label context = new Label(_e.getMessage());
        context.setLayoutX(90);
        context.setLayoutY(55);

        Label url = new Label(_url);
        url.setLayoutX(90);
        url.setLayoutY(85);

        Separator separator = new Separator(Orientation.HORIZONTAL);
        separator.setPrefWidth(550);
        separator.setLayoutY(130);

        Button button = new Button("OK");
        button.setPrefWidth(100);
        button.setLayoutX(425);
        button.setLayoutY(155);
        button.setOnMouseClicked(new EventHandler<MouseEvent>() {
            @Override
            public void handle(MouseEvent event) {
                stage.close();
            }
        });

        AnchorPane root = new AnchorPane();
        root.getChildren().addAll(image, header, context, url, separator, button);

        Scene scene = new Scene(root, 550, 205);

        stage.setScene(scene);
        stage.showAndWait();

    }

    public void SimpleDialog(String _type, String _message) {

        Stage stage = new Stage();
        ImageView image = new ImageView();
        image.setFitHeight(48);
        image.setFitWidth(48);

        switch (_type) {
        case "notify":
            System.out.println("*DialogController* open SimpleDialog >notify<");
            stage.setTitle("Hinweis");
            image.setImage(imageContainer.InfoB48);
            break;
        case "success":
            System.out.println("*DialogController* open SimpleDialog >success<");
            stage.setTitle("Erfolg");
            image.setImage(imageContainer.Success48);
            break;
        case "error":
            System.out.println("*DialogController* open SimpleDialog >error<");
            stage.setTitle("Fehler");
            image.setImage(imageContainer.Error48);
            break;
        }

        Label message = new Label(_message);
        message.setPrefWidth(350);
        Button button = new Button("OK");

        button.setOnMouseClicked(new EventHandler<MouseEvent>() {

            @Override
            public void handle(MouseEvent event) {
                stage.close();
            }
        });

        VBox root = new VBox(10);
        HBox sub = new HBox(10);
        HBox interaction = new HBox(10);

        root.getChildren().addAll(sub, interaction);
        root.setAlignment(Pos.CENTER_RIGHT);
        root.setPadding(new Insets(20));

        sub.getChildren().addAll(image, message);
        sub.setAlignment(Pos.CENTER_RIGHT);

        interaction.getChildren().add(button);
        interaction.setAlignment(Pos.CENTER_RIGHT);

        Scene scene = new Scene(root, 450, 150);

        stage.setScene(scene);
        stage.showAndWait();

    }

}

Nach vielerlei Umbau und Probieren habe ich festgestellt, dass wenn ich die JAR in den SRC Ordner des Projektes verschiebe, die App läuft.
Dementsprechend habe ich hier ein Problem mit den relativen Pfaden, und mir fällt einfach keine saubere Lösung ein.

Ich bin mir bewusst, dass mein Code noch sehr umständlich ist und nicht unbedingt die Konventionen erfüllt, aber auch hier bin ich um Tipps dankbar 🙂

Ich hoffe, dass hier jemand für euch eine Idee für mich hat.

LG Daniel
 
getResourceAsStream sucht nicht im Dateisystem sondern im Classpath. Du musst sicherstellen, dass sich die Ressourcen an entsprechender Stelle im Jar befinden.
 
Ja genau so ist es auch gedacht. Ich habe die Bilder alle in einem separaten Image Package. Normalerweise müsste der Pfad doch immer passen, egal wohin ich die jar packe. Die Bilder inklusive den Pfaden müssten doch mit in die jar exportiert werden... er findet die Pfade aber nur wenn ich die jar in das src Verzeichnis des Projektes packe und von dort aus starte... es kommt mir vor wie ein Fehlverhalten...
 
Hab gerade nachgesehen, die Bilder sind im Archiv drin.

Hab auch nochmal nen neuen Test gemacht, Projekt als JAR exportiert - im Ordner Schreibtisch Exception und in den Ordner des Projektes kopiert läuft sie einwandfrei.
 
Laut der Exception liegt die Ursache in Zeile 7 des Image Containers. Hier wird ja auch das erste Bild initialisiert. Er kommt dort also mit dem Pfad nicht klar. Aber nach meiner Ansicht ist der Code korrekt -.-
 
Stimmt mit der Hierarchie in Eclipse überein...
 

Anhänge

  • Bildschirmfoto 2019-05-19 um 20.24.14.png
    Bildschirmfoto 2019-05-19 um 20.24.14.png
    69,1 KB · Aufrufe: 46
  • Bildschirmfoto 2019-05-19 um 20.25.05.png
    Bildschirmfoto 2019-05-19 um 20.25.05.png
    23,7 KB · Aufrufe: 53
Mal zwei Verzweiflungstaten:
1. Auf der Befehlszeile
Code:
jar -tf /pfad/zur/jardatei.jar de/morpheus/images/dbError_32.png

Was gibt das aus?

(Alternativ kannst Du de/morpheus/images/dbError_32.png auch weglassen, dann wird die Ausgabe aber ggf. recht lang)

2. Was passiert, wenn Du
Java:
    public Image dbError32 = new Image(getClass().getResourceAsStream("../images/dbError_32.png"));
durch
Java:
    public Image dbError32 = new Image(getClass().getResourceAsStream("/de/morpheus/images/dbError_32.png"));
ersetzt?
 
Danke, werde gleich mal die Vorschläge testen, mir ist jetzt allerdings etwas aufgefallen:
Ich habe mal aus Spaß die JAR in einen leeren Ordner kopiert, und habe den Ordner "de"(...) mit in den Ordner reinkopiert. Die JAR läuft, was mir zeigt, dass sich das laufende Programm alle Ressourcen aus den Ordnern zieht und nicht aus dem JAR selbst.
Sehr seltsam.
 

Anhänge

  • Bildschirmfoto 2019-05-19 um 20.54.52.png
    Bildschirmfoto 2019-05-19 um 20.54.52.png
    43,3 KB · Aufrufe: 35
Was gibt das aus?

Die Ausgabe lautet "de/morpheus/images/dbError_32.png"

2. Was passiert, wenn Du
Java:
public Image dbError32 = new Image(getClass().getResourceAsStream("../images/dbError_32.png"));
durch
Java:
public Image dbError32 = new Image(getClass().getResourceAsStream("/de/morpheus/images/dbError_32.png"));
ersetzt?

Hat nichts geändert. Die App stürzt wieder ab / Identische Exception
 
So, auf die eine Art könnte man dich jetzt dafür in die Luft heben, auf die andere Art schäme ich mich jetzt für meine Blindheit.
Im image Container waren viele kleine Groß/Kleinschreibungsfehler. Diese habe ich korrigiert und plötzlich läuft es.

Ich verstehe allerdings nicht ganz, warum mir Eclipse beim Run keine Exceptions geworfen hat...
 
aber wo es jetzt läuft würde mich mal folgendes interessieren: kann ich beim Image das .getClass... auch weglassen und nur den Pfad angeben? Dann ließen sich die Bilder nämlich static machen und ich müsste nicht immer ein Objekt vom Image Container erstellen... das wäre doch auch Performanceseitig besser oder?
 
Dann ließen sich die Bilder nämlich static machen und ich müsste nicht immer ein Objekt vom Image Container erstellen... das wäre doch auch Performanceseitig besser oder?
Das sind jetzt ein paar Dinge auf einmal:
1. Du kannst Dir das Klassenobjekt auch ohne Objekt über Klasse.class holen ImageContainer.class.getResourceAsStream(...)
2. static ist nur als Konstante eine gute Idee. In dem Fall könnte man darüber nachdenken.
3. Man muss aufpassen, was man static verwendet. "The same Image instance can be displayed by multiple ImageViews." scheint ok zu sein.
 
Ok super. Vielen Dank für die schnelle Hilfe, ohne den Hinweis mit Zeile 9 wäre ich wohl erst in Tagen auf den kleinen Fehler gekommen 🙂

Und static ist nur als Konstante ratsam?
Ich habe einige observablelists im Hauptfenster static gemacht (diese Stellen Daten für choiceboxen bereit), um nicht unnötig viele observableLists für jedes unterfenster machen zu müssen, da ich dachte dies ginge zu Lasten der Performance.

Sollte das Vorgehen unsinnig oder problematisch sein würde ich das lieber ändern und neue lists für jedes neue Fenster als Objekt erstellen.

Dies würde aber bedeuten, dass zu gewissen Laufzeiten einige mit Objekten gefüllten oL gleichzeitig im Arbeitsspeicher wären
 
Und static ist nur als Konstante ratsam?
Static und Objektorientierung passen nicht zusammen (fehlende Objekte/Kapselung).

Ich habe einige observablelists im Hauptfenster static gemacht (diese Stellen Daten für choiceboxen bereit), um nicht unnötig viele observableLists für jedes unterfenster machen zu müssen, da ich dachte dies ginge zu Lasten der Performance.
Das ist keine gute Idee, denn damit wird der Code der Unterfenster abhängig vom Hauptfenster.

Sollte das Vorgehen unsinnig oder problematisch sein würde ich das lieber ändern und neue lists für jedes neue Fenster als Objekt erstellen.

Dies würde aber bedeuten, dass zu gewissen Laufzeiten einige mit Objekten gefüllten oL gleichzeitig im Arbeitsspeicher wären
Grundsätzlich übergibt man Objekte einfach. An der Performance ändert sich dabei praktisch nichts, da hier nur eine Referenz (Speicheradresse) kopiert wird.

Bei GUI-Anwendungen trennt man die Darstellung vom Kern der Anwendung. Man hat dann für die Anwendung bzw. Teile der Anwendung ein (Objekt-)Modell, das zwischen den Views herumgereicht wird. In JavaFX ist das im Zusammenspiel mit FXML nur nicht ganz so einfach, weil das Framework den Controller instantiiert. Das kann man mit einer Factory lösen.

Bei einfachen "Anwendungen" könnte das Modell z. B. aus einer ObservableList bestehen. Dann wird die Liste (= Referenz auf die Liste) herumgereicht und alle Views arbeiten mit dem selben Modell. Im Hauptspeicher werden dabei keine Kopien der Liste angelegt.
 
Bei einfachen "Anwendungen" könnte das Modell z. B. aus einer ObservableList bestehen. Dann wird die Liste (= Referenz auf die Liste) herumgereicht und alle Views arbeiten mit dem selben Modell. Im Hauptspeicher werden dabei keine Kopien der Liste angelegt.

Dazu muss ich noch einmal nerven 🙂

Ich habe bisher immer alle observable lists im controller angelegt, und im Modell nur die Datenbank querys angelegt... ist wohl besser die oLs auch in das Modell zu verlegen oder?
 
Zuletzt bearbeitet von einem Moderator:
Das kann man pauschal nicht sagen: die Frage ist, ob die List aufgrund des User Interfaces existiert (was wahrscheinlich ist) oder ob sie tatsächlich Teil des Problembereichs ist. Meist hat man ein UI-Modell und ein Domänenmodell, wobei das UI-Modell nur dazu dient, das Modell der Domäne an das UI anzubinden.
 

Neue Themen


Zurück
Oben