Как определить правильный путь для файлов FXML, файлов CSS, изображений и других ресурсов, необходимых моему приложению JavaFX?

Мое приложение JavaFX должно иметь возможность находить файлы FXML, чтобы загружать их с помощью FXMLLoader, а также таблицы стилей (файлы CSS) и изображения. Когда я пытаюсь загрузить их, я часто получаю ошибки или элемент, который я пытаюсь загрузить, просто не загружается во время выполнения.

Для файлов FXML я вижу сообщение об ошибке:

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

Для изображений трассировка стека включает

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

Как мне определить правильный путь к этим ресурсам?

2 ответа

Решение

Краткая версия ответа:

  • Использовать getClass().getResource(...) или SomeOtherClass.class.getResource(...) создать URL к ресурсу
  • Передайте либо абсолютный путь (с ведущим /) или относительный путь (без ведущего /) к getResource(...)метод. Путь - это пакет, содержащий ресурс, с. заменен на /.
  • Не использовать ..в пути к ресурсу. Если и когда приложение объединено как файл jar, это не сработает. Если ресурс не находится в том же пакете или подпакете класса, используйте абсолютный путь.
  • Для файлов FXML передайте URL прямо в FXMLLoader.
  • Для изображений и таблиц стилей звоните toExternalForm() на URL для создания String перейти к Image или ImageView конструктор, или добавить в stylesheets список.
  • Для устранения неполадок изучите содержимое папки buid (или файла jar), а не исходной папки.

Полный ответ

Содержание

  1. Сфера этого ответа
  2. Ресурсы загружаются во время выполнения
  3. JavaFX использует URL-адреса для загрузки ресурсов
  4. Правила для имен ресурсов
  5. Создание URL ресурса с getClass().getResource(...)
  6. Организация кода и ресурсов
  7. Стандартные макеты Maven (и аналогичные)
  8. Исправление проблем

Сфера этого ответа

Обратите внимание, что этот ответ касается только ресурсов загрузки (например, файлов FXML, изображений и таблиц стилей), которые являются частью приложения и связаны с ним. Так, например, загрузка изображений, которые пользователь выбирает из файловой системы на машине, на которой запущено приложение, потребует различных методов, которые здесь не рассматриваются.

Ресурсы загружаются во время выполнения

Первое, что нужно понять при загрузке ресурсов, это то, что они, конечно, загружаются во время выполнения. Обычно во время разработки приложение запускается из файловой системы: то есть файлы классов и ресурсы, необходимые для его запуска, представляют собой отдельные файлы в файловой системе. Однако, как только приложение создано, оно обычно запускается из файла jar. В этом случае ресурсы, такие как файлы FXML, таблицы стилей и изображения, больше не являются отдельными файлами в файловой системе, а являются записями в файле jar. Следовательно:

Код не может использовать File, FileInputStream, или file: URL-адреса для загрузки ресурса

JavaFX использует URL-адреса для загрузки ресурсов

JavaFX загружает таблицы стилей FXML, изображения и CSS с помощью URL-адресов.

В FXMLLoader явно ожидает java.net.URL объект, который будет передан ему (либо в static FXMLLoader.load(...) метод, чтобы FXMLLoader конструктор, или в setLocation() метод).

Обе Image а также Scene.getStylesheets().add(...) ожидать Strings, которые представляют URL-адреса. Если URL-адреса передаются без схемы, они интерпретируются относительно пути к классам. Эти строки могут быть созданы изURL надежно, позвонив toExternalForm() на URL.

Рекомендуемый механизм для создания правильного URL-адреса для ресурса - использовать Class.getResource(...), который вызывается на соответствующем Classпример. Такой экземпляр класса можно получить, вызвавgetClass() (который дает класс текущего объекта), или ClassName.class. ВClass.getResource(...) метод требует String представляющий имя ресурса.

Правила для имен ресурсов

  • Названия ресурсов /-разделенные путевые имена. Каждый компонент представляет собой компонент имени пакета или подпакета.
  • Имена ресурсов чувствительны к регистру.
  • Отдельные компоненты в имени ресурса должны быть действительными идентификаторами Java.

Последний пункт имеет важное следствие:

. а также ..не являются допустимыми идентификаторами Java, поэтому их нельзя использовать в именах ресурсов.

На самом деле они могут работать, когда приложение запускается из файловой системы, хотя на самом деле это скорее случайность реализации getResource(). Они завершатся ошибкой, если приложение объединено в виде файла jar.

Точно так же, если вы работаете в операционной системе, которая не различает имена файлов, которые различаются только регистром, то использование неправильного регистра в имени ресурса может работать при запуске из файловой системы, но не сработает при запуске из файла jar.

Имена ресурсов, начинающиеся с ведущей /являются абсолютными: другими словами, они интерпретируются относительно пути к классам. Имена ресурсов без ведущего/ интерпретируются относительно класса, на котором getResource() назывался.

Небольшой вариант - использовать getClass().getClassLoader().getResource(...). Путь кClassLoader.getResource(...)это всегда является абсолютным, т.е. по отношению к классам.

Создание URL ресурса с getClass().getResource()

Чтобы создать URL-адрес ресурса, используйте someClass.getResource(...). Как правило,someClass представляет класс текущего объекта и получается с использованием getClass(). Однако это не обязательно, как описано в следующем разделе.

  • Если ресурс находится в том же пакете, что и текущий класс, или в подпакете этого класса, используйте относительный путь к ресурсу:

     // 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());
    
  • Если ресурс находится в пакете, который не является подпакетом текущего класса, используйте абсолютный путь. Например, если текущий класс находится в пакетеorg.jamesd.examples.view, и нам нужно загрузить файл CSS style.css что в пакете org.jamesd.examples.css, мы должны использовать абсолютный путь:

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

    В этом примере стоит еще раз подчеркнуть, что путь "../css/style.css"не содержит действительных имен ресурсов Java и не будет работать, если приложение объединено в виде файла jar.

Организация кода и ресурсов

Я рекомендую организовать ваш код и ресурсы в пакеты, определяемые частью пользовательского интерфейса, с которой они связаны. Следующий макет исходного кода в Eclipse дает пример этой организации:

Используя эту структуру, каждый ресурс имеет класс в одном пакете, поэтому легко создать правильный URL-адрес для любого ресурса:

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());

Если у вас есть пакет только с ресурсами и без классов, например, images пакет в макете ниже

вы даже можете рассмотреть возможность создания "интерфейса маркера" исключительно для целей поиска имен ресурсов:

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

что теперь позволяет легко находить эти ресурсы:

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

Загрузка ресурсов из подпакета класса также довольно проста. Учитывая следующий макет:

мы можем загружать ресурсы в App класс следующим образом:

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");
        
        
        FXMLLoader loader = new FXMLLoader();
        loader.setLocation(fxmlResource);
        Parent root = loader.load();
        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);
    }

}

Чтобы загрузить ресурсы, которые не находятся в том же пакете или подпакете класса, из которого вы их загружаете, вам необходимо использовать абсолютный путь:

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

Стандартные макеты Maven (и аналогичные)

Maven и другие инструменты для управления зависимостями и сборки рекомендуют макет исходной папки, в котором ресурсы отделены от исходных файлов Java. Версия макета Maven из предыдущего примера выглядит так:

Для сборки приложения важно понимать, как это устроено:

  • *.javaфайлы в исходной папкеsrc/main/java компилируются в файлы классов, которые развертываются в папку сборки или файл jar.
  • Ресурсы в папке ресурсовsrc/main/resourcesкоторые копируются в папку сборки или файл банка.

В этом примере, поскольку ресурсы находятся в папках, соответствующих подпакетам пакетов, в которых определен исходный код, итоговая сборка (которая по умолчанию с Maven находится в target/classes) состоит из единой конструкции.

Обратите внимание, что оба src/main/java а также src/main/resourcesсчитаются корнем для соответствующей структуры в сборке, поэтому только их содержимое, а не сами папки, являются частью сборки. Другими словами, нетresourcesпапка доступна во время выполнения. Структура сборки показана ниже в разделе "Устранение неполадок".

Обратите внимание, что в этом случае IDE (Eclipse) отображает src/main/java исходная папка отличается от src/main/resourcesпапка; в первом случае он отображает пакеты, но для папки ресурсов он отображает папки. Убедитесь, что вы знаете, создаете ли вы пакеты (чьи имена.-delimited) или папок (имена которых не должны содержать .или любой другой символ, недопустимый в идентификаторе Java) в вашей среде IDE.

Исправление проблем

Если вы получаете неожиданные ошибки, сначала проверьте следующее:

  • Убедитесь, что вы не используете недопустимые имена для своих ресурсов. Это включает использование. или .. в пути к ресурсу.
  • Убедитесь, что вы используете относительные пути там, где ожидается, и абсолютные пути, где ожидается. заClass.getResource(...) путь является абсолютным, если у него есть ведущий /, и относительно в противном случае. ЗаClassLoader.getResource(...), путь всегда абсолютен.
  • Помните, что абсолютные пути определяются относительно пути к классам. Обычно корень пути к классам представляет собой объединение всех папок источников и ресурсов в вашей среде IDE.

Если все это кажется правильным, но ошибки по-прежнему появляются, проверьте папку сборки или развертывания. Точное расположение этой папки зависит от IDE и инструмента сборки. Если вы используете Maven, по умолчанию этоtarget/classes. Другие инструменты сборки и IDE будут развернуты в папках с именемbin, classes, build, или out.

Часто ваша IDE не отображает папку сборки, поэтому вам может потребоваться проверить ее с помощью системного файлового проводника.

Комбинированная структура источника и сборки для примера Maven выше:

Если вы создаете файл jar, некоторые IDE могут позволить вам развернуть файл jar в древовидном представлении, чтобы проверить его содержимое. Вы также можете проверить содержимое из командной строки с помощьюjar tffile.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
$ 

Если ресурсы не развертываются или развертываются в неожиданном месте, проверьте конфигурацию вашего инструмента сборки или IDE.

В "Структура проекта"> "Проект" убедитесь, что Project compiler output:заполнено значение. В моем случае это не так. Укажите на ./target или же ./bin (все, что у вас есть) в вашем проекте.

Другие вопросы по тегам