¿Cómo puedo determinar la ruta correcta para archivos FXML, archivos CSS, imágenes y otros recursos que necesita mi aplicación JavaFX?

Resuelto James_D asked hace 4 años • 1 respuestas

Mi aplicación JavaFX necesita poder encontrar los archivos FXML para cargarlos con FXMLLoader, así como hojas de estilo (archivos CSS) e imágenes. Cuando intento cargarlos, a menudo obtengo errores o el elemento que intento cargar simplemente no se carga en tiempo de ejecución.

Para archivos FXML, el mensaje de error que veo incluye

Caused by: java.lang.NullPointerException: location is not set

Para imágenes, el seguimiento de la pila incluye

Caused by: java.lang.IllegalArgumentException: Invalid URL: Invalid URL or resource not found

¿Cómo puedo saber la ruta de recursos correcta para estos recursos?

James_D avatar May 01 '20 02:05 James_D
Aceptado

Versión corta de la respuesta:

  • Utilice getClass().getResource(...)o SomeOtherClass.class.getResource(...)para crear un URLrecurso
  • Pase una ruta absoluta (con un inicio /) o una ruta relativa (sin un inicio /) al getResource(...)método. La ruta es el paquete que contiene el recurso, .reemplazado por /.
  • No lo utilice .. en la ruta de recursos. Si la aplicación se incluye como un archivo jar, esto no funcionará. Si el recurso no está en el mismo paquete o en un subpaquete de la clase, utilice una ruta absoluta.
  • Para archivos FXML, pase URLdirectamente al archivo FXMLLoader.
  • Para imágenes y hojas de estilo, llame toExternalForm()a URLpara generar el archivo Stringpara pasarlo al Imageconstructor ImageViewo para agregarlo a la stylesheetslista.
  • Para solucionar el problema, examine el contenido de su carpeta de compilación (o archivo jar), no su carpeta de origen .
  • Colocarse srcen el camino cuando obtienes un recurso siempre es incorrecto. El srcdirectorio solo está disponible en tiempo de desarrollo y compilación, no en tiempo de implementación y ejecución.

Respuesta completa

Contenido

  1. Alcance de esta respuesta
  2. Los recursos se cargan en tiempo de ejecución.
  3. JavaFX usa URL para cargar recursos
  4. Recursos como flujos
  5. Reglas para nombres de recursos
  6. Crear una URL de recurso congetClass().getResource(...)
  7. Organización del código y los recursos.
  8. Diseños estándar de Maven (y similares)
  9. Solución de problemas

Alcance de esta respuesta

Tenga en cuenta que esta respuesta solo aborda la carga de recursos (por ejemplo, archivos FXML, imágenes y hojas de estilo) que forman parte de la aplicación y se incluyen con ella. Entonces, por ejemplo, cargar imágenes que el usuario elija del sistema de archivos en la máquina en la que se ejecuta la aplicación requeriría diferentes técnicas que no se tratan aquí.

Los recursos se cargan en tiempo de ejecución.

Lo primero que hay que entender acerca de la carga de recursos es que, por supuesto, se cargan en tiempo de ejecución. Normalmente, durante el desarrollo, una aplicación se ejecuta desde el sistema de archivos: es decir, los archivos de clase y los recursos necesarios para ejecutarla son archivos individuales en el sistema de archivos. Sin embargo, una vez creada la aplicación, normalmente se ejecuta desde un archivo jar. En este caso, los recursos como archivos FXML, hojas de estilo e imágenes ya no son archivos individuales en el sistema de archivos, sino entradas en el archivo jar. Por lo tanto:

El código no puede usar File, FileInputStreamo file:URL para cargar un recurso

JavaFX usa URL para cargar recursos

JavaFX carga hojas de estilo FXML, imágenes y CSS mediante URL.

Espera FXMLLoaderexplícitamente java.net.URLque se le pase un objeto (ya sea al static FXMLLoader.load(...)método, al FXMLLoaderconstructor o al setLocation()método).

Ambos Imagey Scene.getStylesheets().add(...)esperan Stringcorreos electrónicos que representen URL. Si las URL se pasan sin un esquema, se interpretan en relación con el classpath. Estas cadenas se pueden crear de URLforma robusta llamando toExternalForm()al archivo URL.

El mecanismo recomendado para crear la URL correcta para un recurso es utilizar Class.getResource(...), que se llama en una Classinstancia adecuada. Una instancia de clase de este tipo se puede obtener llamando getClass()(que proporciona la clase del objeto actual), o ClassName.class. El Class.getResource(...)método toma una Stringrepresentación del nombre del recurso.

Recursos como flujos

Si necesita el recurso como una secuencia, puede utilizar getClass().getResourceAsStream(...)o SomeOtherClass.class.getResourceAsStream(...)para crear una secuencia para los datos del recurso. Sin embargo, para la mayor parte del trabajo en JavaFX no necesita esto ya que las API de JavaFX funcionan principalmente con URL como entrada en lugar de transmisiones. Existen algunas API, como los constructores de imágenes y FXMLLoader, que pueden funcionar tanto con URL como con transmisiones. Para este tipo de API, suele ser mejor utilizar los formularios API basados ​​en URL en lugar de los formularios API basados ​​en secuencias. Esto permite que procesos como FXMLLoader encuentren URL relativas a recursos incluidos que de otro modo no podrían hacer si solo presentaran un InputStream sin formato.

Reglas para nombres de recursos

  • Los nombres de los recursos son /nombres de rutas separadas. Cada componente representa un componente de nombre de paquete o subpaquete.
  • Los nombres de los recursos distinguen entre mayúsculas y minúsculas.
  • Los componentes individuales en el nombre del recurso deben ser identificadores Java válidos.

El último punto tiene una consecuencia importante:

.y ..no son identificadores Java válidos, por lo que no se pueden utilizar en nombres de recursos .

En realidad, estos pueden funcionar cuando la aplicación se ejecuta desde el sistema de archivos, aunque en realidad esto es más bien un accidente de la implementación de getResource(). Fallarán cuando la aplicación esté empaquetada como un archivo jar.

De manera similar, si está ejecutando un sistema operativo que no distingue entre nombres de archivos que difieren solo en mayúsculas y minúsculas, entonces usar el caso incorrecto en un nombre de recurso puede funcionar mientras se ejecuta desde el sistema de archivos, pero fallará cuando se ejecuta desde un archivo jar.

Los nombres de recursos que comienzan con un interlineado /son absolutos : en otras palabras, se interpretan en relación con el classpath. Los nombres de recursos sin interlineado /se interpretan en relación con la clase a la que getResource()se llamó.

Una ligera variación de esto es usar getClass().getClassLoader().getResource(...). La ruta proporcionada a ClassLoader.getResource(...) no debe comenzar con a /y siempre es absoluta, es decir, relativa a la classpath. También cabe señalar que en las aplicaciones modulares, el acceso a los recursos que utilizan ClassLoader.getResource()está, en algunas circunstancias, sujeto a reglas de encapsulación fuerte y, además, el paquete que contiene el recurso debe abrirse incondicionalmente. Consulte la documentación para obtener más detalles.

Crear una URL de recurso congetClass().getResource()

Para crear una URL de recurso, utilice someClass.getResource(...). Por lo general, someClassrepresenta la clase del objeto actual y se obtiene usando getClass(). Sin embargo, este no tiene por qué ser el caso, como se describe en la siguiente sección.

  • Si el recurso está en el mismo paquete que la clase actual, o en un subpaquete de esa clase, use una ruta relativa al recurso:

     // FXML file in the same package as the current class:
     URL fxmlURL = getClass().getResource("MyFile.fxml");
     Parent root = FXMLLoader.load(fxmlURL);
    
     // FXML file in a subpackage called `fxml`:
     URL fxmlURL2 = getClass().getResource("fxml/MyFile.fxml");
     Parent root2 = FXMLLoader.load(fxmlURL2);
    
     // Similarly for images:
     URL imageURL = getClass().getResource("myimages/image.png");
     Image image = new Image(imageURL.toExternalForm());
    
  • Si el recurso está en un paquete que no es un subpaquete de la clase actual, utilice una ruta absoluta. Por ejemplo, si la clase actual está en el paquete org.jamesd.examples.viewy necesitamos cargar un archivo CSS style.cssque está en el paquete org.jamesd.examples.css, tenemos que usar una ruta absoluta:

     URL cssURL = getClass().getResource("/org/jamesd/examples/css/style.css");
     scene.getStylesheets().add(cssURL.toExternalForm());
    

    Vale la pena volver a enfatizar para este ejemplo que la ruta "../css/style.css"no contiene nombres de recursos Java válidos y no funcionará si la aplicación está empaquetada como un archivo jar.

Organización del código y los recursos.

Recomiendo organizar su código y recursos en paquetes determinados por la parte de la interfaz de usuario a la que están asociados. El siguiente diseño de código fuente en Eclipse ofrece un ejemplo de esta organización:

ingrese la descripción de la imagen aquí

Usando esta estructura, cada recurso tiene una clase en el mismo paquete, por lo que es fácil generar la URL correcta para cualquier recurso:

FXMLLoader editorLoader = new FXMLLoader(EditorController.class.getResource("Editor.fxml"));
Parent editor = editorLoader.load();
FXMLLoader sidebarLoader = new FXMLLoader(SidebarController.class.getResource("Sidebar.fxml"));
Parent sidebar = sidebarLoader.load();

ImageView logo = new ImageView();
logo.setImage(newImage(SidebarController.class.getResource("logo.png").toExternalForm()));

mainScene.getStylesheets().add(App.class.getResource("style.css").toExternalForm());

Si tiene un paquete con solo recursos y sin clases, por ejemplo, el imagespaquete en el diseño siguiente

ingrese la descripción de la imagen aquí

Incluso puede considerar la creación de una "interfaz de marcador" únicamente con el fin de buscar los nombres de los recursos:

package org.jamesd.examples.sample.images ;
public interface ImageLocation { }

que ahora te permite encontrar estos recursos fácilmente:

Image clubs = new Image(ImageLocation.class.getResource("clubs.png").toExternalForm());

Cargar recursos desde un subpaquete de una clase también es razonablemente sencillo. Dado el siguiente diseño:

ingrese la descripción de la imagen aquí

Podemos cargar recursos en la Appclase de la siguiente manera:

package org.jamesd.examples.resourcedemo;
import java.net.URL;
import javafx.application.Application;
import javafx.fxml.FXMLLoader;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.stage.Stage;

public class App extends Application {
    @Override
    public void start(Stage primaryStage) throws Exception {
                
        URL fxmlResource = getClass().getResource("fxml/MainView.fxml");
        
        Parent root = FXMLLoader.load(fxmlResource);
        Scene scene = new Scene(root);
        scene.getStylesheets().add(getClass().getResource("style/main-style.css").toExternalForm());
        primaryStage.setScene(scene);
        primaryStage.show();
    }
    
    public static void main(String[] args) {
        Application.launch(args);
    }
}

Para cargar recursos que no están en el mismo paquete, o en un subpaquete, de la clase desde la que los estás cargando, necesitas usar la ruta absoluta:

    URL fxmlResource = getClass().getResource("/org/jamesd/examples/resourcedemo/fxml/MainView.fxml");

Diseños estándar de Maven (y similares)

Maven y otras herramientas de creación y gestión de dependencias recomiendan un diseño de carpeta de origen en el que los recursos estén separados de los archivos de origen de Java, según el diseño de directorio estándar de Maven . La versión de diseño de Maven del ejemplo anterior tiene el siguiente aspecto:

ingrese la descripción de la imagen aquí

Es importante entender cómo se construye esto para ensamblar la aplicación:

  • *.javaLos archivos de la carpeta de origensrc/main/java se compilan en archivos de clase, que se implementan en la carpeta de compilación o en el archivo jar.
  • Los recursos de la carpeta de recursossrc/main/resources se copian en la carpeta de compilación o en el archivo jar.

En este ejemplo, debido a que los recursos están en carpetas que corresponden a subpaquetes de los paquetes donde se define el código fuente, la compilación resultante (que, de forma predeterminada con Maven, está en target/classes) consta de una única estructura.

Tenga en cuenta que ambos src/main/javay src/main/resourcesse consideran la raíz de la estructura correspondiente en la compilación, por lo que solo su contenido, no las carpetas en sí, son parte de la compilación. En otras palabras, no hay ninguna resourcescarpeta disponible en tiempo de ejecución. La estructura de compilación se muestra a continuación en la sección "solución de problemas".

Notice that the IDE in this case (Eclipse) displays the src/main/java source folder differently from the src/main/resources folder; in the first case it displays packages, but for the resource folder it displays folders. Make sure you know if you are creating packages (whose names are .-delimited) or folders (whose names must not contain ., or any other character not valid in a Java identifier) in your IDE.

If you are using Maven and decide that for ease of maintenance you'd rather keep your .fxml files next to the .java files that reference them (instead of sticking strictly to the Maven Standard Directory Layout), you can do so. Just tell Maven to copy these files to the same folder in your output directory that it will place the class files generated from those source files into, by including something like the following in your pom.xml file:

    <build>
        ...
        <resource>
            <directory>src/main/java</directory>
            <includes>
                <include>**/*.fxml</include>
                <include>**/*.css</include>
            </includes>
        </resource>
        ...
    </build>

If you do this, you can then use an approach like FXMLLoader.load(getClass().getResource("MyFile.fxml")) to have your classes load .fxml resources from the directory which contains their own .class files.

Troubleshooting

If you get errors you do not expect, first check the following:

  • Make sure you are not using invalid names for your resources. This includes using . or .. in the resource path.
  • Make sure you are using relative paths where expected, and absolute paths where expected. for Class.getResource(...) the path is absolute if it has a leading /, and relative otherwise. For ClassLoader.getResource(...), the path is always absolute, and must not start with a /.
  • Remember that absolute paths are defined relative to the classpath. Typically the root of the classpath is the union of all source and resource folders in your IDE.

If all this seems correct, and you still see errors, check the build or deployment folder. The exact location of this folder will vary by IDE and build tool. If you are using Maven, by default it is target/classes. Other build tools and IDEs will deploy to folders named bin, classes, build, or out.

Often, your IDE will not show the build folder, so you may need to check it with the system file explorer.

The combined source and build structure for the Maven example above is

ingrese la descripción de la imagen aquí

If you are generating a jar file, some IDEs may allow you to expand the jar file in a tree view to inspect its contents. You can also check the contents from the command line with jar tf file.jar:

$ jar -tf resource-demo-0.0.1-SNAPSHOT.jar 
META-INF/
META-INF/MANIFEST.MF
org/
org/jamesd/
org/jamesd/examples/
org/jamesd/examples/resourcedemo/
org/jamesd/examples/resourcedemo/images/
org/jamesd/examples/resourcedemo/style/
org/jamesd/examples/resourcedemo/fxml/
org/jamesd/examples/resourcedemo/images/so-logo.png
org/jamesd/examples/resourcedemo/style/main-style.css
org/jamesd/examples/resourcedemo/Controller.class
org/jamesd/examples/resourcedemo/fxml/MainView.fxml
org/jamesd/examples/resourcedemo/App.class
module-info.class
META-INF/maven/
META-INF/maven/org.jamesd.examples/
META-INF/maven/org.jamesd.examples/resource-demo/
META-INF/maven/org.jamesd.examples/resource-demo/pom.xml
META-INF/maven/org.jamesd.examples/resource-demo/pom.properties
$ 

If the resources are not being deployed, or are being deployed to an unexpected location, check the configuration of your build tool or IDE.

Example image loading troubleshooting code

This code is deliberately more verbose than is strictly necessarily to facilitate adding additional debugging information for the image loading process. It also uses System.out rather than a logger for easier portability.

String resourcePathString = "/img/wumpus.png";
Image image = loadImage(resourcePathString);

// ...

private Image loadImage(String resourcePathString) {
    System.out.println("Attempting to load an image from the resourcePath: " + resourcePathString);
    URL resource = HelloApplication.class.getResource(resourcePathString);
    if (resource == null) {
        System.out.println("Resource does not exist: " + resourcePathString);

        return null;
    }

    String path = resource.toExternalForm();
    System.out.println("Image path: " + path);

    Image image = new Image(path);
    System.out.println("Image load error?  " + image.isError());
    System.out.println("Image load exception? " + image.getException());

    if (!image.isError()) {
        System.out.println("Successfully loaded an image from " + resourcePathString);
    }

    return image;
}

External Tutorial Reference

A useful external tutorial for resource location is Eden coding's tutorial:

  • Where to put resource files in JavaFX.

The nice thing about the Eden coding tutorial is that it is comprehensive. In addition to covering the information on lookups from Java code which is in this question. The Eden tutorial covers topics such as locating resources that are encoded as urls in CSS, or resource references in FXML using an @ specifier or fx:include element (which are topics currently not directly covered in this answer).

James_D avatar Apr 30 '2020 19:04 James_D