JavaFX Weaver: интеграция JavaFX и Spring Boot приложения

Автор оригинала: Vojtech Ruzicka
  • Перевод
Это заключительный пост в серии о JavaFX:

  1. Учебник по JavaFX: начало работы
  2. Учебник по JavaFX: Hello world!
  3. Учебник по JavaFX: FXML и SceneBuilder
  4. Учебник по JavaFX: основные макеты
  5. Учебник по JavaFX: расширенные макеты
  6. Учебник по JavaFX: CSS-стилизация
  7. JavaFX Weaver: интеграция JavaFX и Spring Boot приложения

JavaFX & Spring


В настоящее время Java разработчики, редко работают просто с Java. В большинстве проектов они обычно используют Spring Framework или Spring Boot. У этого подхода много преимуществ, так как эти проекты включают в себя много полезных функций.

Однако, когда вы разрабатываете приложения JavaFX, нет простого способа интегрировать его со Spring. Интеграция не работает «из коробки», так как приложения JavaFX имеют свой собственный жизненный цикл и обеспечивают создание экземпляров контроллеров.

Для этого поста есть репозиторий с примером, где вы можете посмотреть окончательный код проекта.

JavaFX-Weaver


JavaFX-Weaver — это проект Рене Гилена (Rene Gielen), целью которого является интеграция Spring и JavaFX. Настройка, к счастью, не так сложна.

Начнем


Давайте попробуем с самым простым проектом Spring Boot, и попробуем интегрировать в него JavaFX. Вы можете создать новый проект с помощью Spring Initializr. Вам не нужно добавлять какие-либо зависимости. Если вы хотите избежать настройки JavaFX в качестве зависимости, выберите версию более раннюю чем Java 11, поскольку она по-прежнему содержит JavaFX как часть JDK.

Добавление контроллера


Нам понадобится контроллер и сопутствующее представление .fxml, чтобы можно было проверить, правильно ли работает наше приложение как с Spring, так и с JavaFX. Давайте сначала создадим контроллер и пока оставим его пустым.

public class MyController {
}

Добавление представления


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

Например, предположим, что наш контроллер находится в пакете com.vojtechruzicka.javafxweaverexample. Файл .fxml должен быть размещен именно здесь:
src\main\resources\com\vojtechruzicka\javafxweaverexample

Давайте назовем наш файл main-scene.fxml.

Зависимости


Если вы используете обычный Spring, настройка немного отличается, но для Spring Boot вам просто нужно добавить следующую зависимость в ваш файл pom.xml:

<dependency>
    <groupId>net.rgielen</groupId>
    <artifactId>javafx-weaver-spring-boot-starter</artifactId>
    <version>1.3.0</version>
</dependency>

Или это, если вы используете Gradle:
implementation 'javafx-weaver-spring-boot-starter:1.3.0'

Класс Spring Boot Application


Когда мы генерировали наше базовое приложение Spring Boot, для нас также был создан основной класс приложения. Это класс, аннотированный @SpringBootApplication, используется в качестве точки входа для запуска всего приложения.

Но подождите! JavaFX также имеет свой основной класс приложения, который используется в качестве точки входа для запуска приложений JavaFX.

Это сбивает с толку. Итак, какой из них нужно использовать для запуска нашего приложения, как Spring Boot, так и JavaFX?

Мы все еще будем использовать наше @SpringBootApplication с небольшой модификацией. Вместо непосредственного запуска приложения Spring, мы будем использовать его для запуска нашего приложения JavaFX. Приложение JavaFX будет отвечать за правильный запуск контекста приложения Spring и интеграцию всего вместе с помощью JavaFX Weaver.

Сначала нам нужно убедиться, что приложение Spring Boot запускает наше приложение JavaFX.

import javafx.application.Application;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class SpringBootExampleApplication {

    public static void main(String[] args) {
        // This is how normal Spring Boot app would be launched
        // SpringApplication.run(SpringBootExampleApplication.class, args);

        // JavaFxApplication doesn't exist yet, 
        // we'll create it in the next step
        Application.launch(JavaFxApplication.class, args);
    }
}

Класс JavaFX Application


Теперь наше приложение @SpringBootApplication вызывает класс JavaFxApplication, который еще не существует. Давайте создадим его.

public class JavaFxApplication extends Application {

    private ConfigurableApplicationContext applicationContext;

    @Override
    public void init() {
        String[] args = getParameters().getRaw().toArray(new String[0]);

        this.applicationContext = new SpringApplicationBuilder()
                .sources(SpringBootExampleApplication.class)
                .run(args);
    }

}

Когда JavaFX инициализируется, он создает новый контекст приложения на основе конфигурации в нашем SpringBootExampleApplication — главном классе приложения Spring Boot, которое мы изменили на предыдущем шаге.

Теперь у нас есть приложение Spring Boot, работающее с нашим новым контекстом приложения. Но мы должны убедиться, что контекст правильно закрыт, когда приложение JavaFX завершается. Например, когда вы закрываете окно. Давайте сделаем это сейчас.

@Override
public void stop() {
    this.applicationContext.close();
    Platform.exit();
}

Последняя часть приложения — это создание нового окна (Stage) и его показ.

@Override
public void start(Stage stage) {
    FxWeaver fxWeaver = applicationContext.getBean(FxWeaver.class);
    Parent root = fxWeaver.loadView(MyController.class);
    Scene scene = new Scene(root);
    stage.setScene(scene);
    stage.show();
}

Это то место, где FxWeaver вступает в игру. Нам нужно получить его bean-компонент из контекста приложения, а затем использовать его для загрузки нашего FXML.

Контроллеры, управляемые Spring


Традиционно мы создавали бы нашу сцену, используя FXMLLoader, который загружал бы файл FXML и создавал экземпляр Controller, объявленный в нем для нас.

FXMLLoader loader = new FXMLLoader();
URL xmlUrl = getClass().getResource("/main-scene.fxml");
loader.setLocation(xmlUrl);
Parent root = loader.load();
Scene scene = new Scene(root);
stage.setScene(scene);
stage.show();

Так почему же мы используем FX Weaver? Проблема в том, что FXMLLoader создает для нас экземпляр контроллера. Это означает, что он не создан и не управляется Spring. Поэтому мы не можем использовать внедрение зависимостей и другие полезности Spring в наших контроллерах. И именно поэтому мы представили Spring в нашем JavaFX в первую очередь!

Но когда FX Weaver создает контроллер для нас, он создает его как управляемый Spring компонент, поэтому мы можем полностью использовать возможности Spring.

Включение Spring для контроллера


Первое, что нам нужно сделать — это аннотировать существующий контроллер JavaFX с помощью Component, чтобы Spring распознавал и управлял им. Затем нам нужно добавить аннотацию @FxmlView, чтобы она распознавалась FX Weaver.

import net.rgielen.fxweaver.core.FxmlView;
import org.springframework.stereotype.Component;

@Component
@FxmlView("main-stage.fxml")
public class MyController {
}

Обратите внимание на параметр @FxmlView («main-stage.fxml»). Он указывает имя вашего .fxml файла, который должен соответствовать контроллеру. Это необязательно. Если вы не укажете его, Fx Weaver будет использовать имя класса контроллера в качестве имени файла с расширением .fxml. Файл FXML должен находиться в том же пакете, что и контроллер, но в папке ресурсов.

Убедимся, что все работает


Теперь давайте удостоверимся, что все работает и хорошо интегрируется. Давайте запустим наше приложение @SpringBootApplication с его main методом. Вы должны увидеть простое окно с надписью, ничего особенного.

Хорошо, это означает, что приложение запускается, но мы не сделали ничего специфического для Spring в нашем контроллере. Нет внедрения зависимости или чего-либо еще. Давайте попробуем сделать это теперь.

Добавление сервиса


Чтобы убедиться, что интеграция Spring работает правильно, давайте создадим новый сервис, управляемый Spring. Позже мы добавим его в наш контроллер и будем использовать там.

import org.springframework.stereotype.Service;

@Service
public class WeatherService {

    public String getWeatherForecast() {
        return "It's gonna snow a lot. Brace yourselves, the winter is coming.";
    }
}

Ничего особенного, это сервис для прогнозирования погоды, который сейчас не очень динамичен, но этого будет достаточно для нашего примера.

Внедрение сервиса


Теперь давайте добавим наш новый сервис в наш существующий контроллер. Это обычный Spring, ничего особенного здесь нет.

@Component
@FxmlView("main-stage.fxml")
public class MyController {

    private WeatherService weatherService;

    @Autowired
    public MyController(WeatherService weatherService) {
        this.weatherService = weatherService;
    }
}

Загрузка прогноза


Теперь нам нужно как-то загрузить данные из нашего сервиса. Давайте изменим наше представление FMXL, так:

  1. Создадим кнопку, по нажатию на которую загружаются данные из WeatherService
  2. Загруженные данные отображаются в метке

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

<?import javafx.scene.control.*?>
<?import javafx.scene.layout.*?>

<VBox xmlns="http://javafx.com/javafx"
            xmlns:fx="http://javafx.com/fxml"
            fx:controller="com.vojtechruzicka.javafxweaverexample.MyController"
            prefHeight="100.0" prefWidth="350.0" spacing="10" alignment="CENTER">

    <Label fx:id="weatherLabel"/>
    <Button onAction="#loadWeatherForecast">Get weather</Button>
</VBox>

Обратите внимание на идентификатор $inline$fx:id = "weatherLabel"$inline$, мы будем использовать его для получения доступа к указанной метке в нашем контроллере, чтобы изменить ее текст.

onAction="# loadWeatherForecast" — это метод нашего контроллера, который следует вызывать при нажатии кнопки. Нам все еще нужно добавить его в контроллер. Давайте сделаем это.

Логика контроллера


Последний шаг — это изменить наш контроллер, чтобы он реагировал на нажатие кнопок в представлении, загружал данные прогноза погоды и устанавливал их для нашей метки.

Поэтому нам нужна ссылка на метку с нашей точки зрения, чтобы мы могли изменить ее текст. Нам нужно выбрать его имя, соответствующее $inline$fx:id = "weatherLabel"$inline$.

@FXML
private Label weatherLabel;

Теперь нам нужно добавить метод, который вызывается при нажатии кнопки — onAction="# loadWeatherForecast".

public void loadWeatherForecast(ActionEvent actionEvent) {
    this.weatherLabel.setText(weatherService.getWeatherForecast());
}

В этом методе мы берем прогноз погоды из сервиса и присваиваем его метке, которую мы определили ранее.

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



Доступ к компонентам из представления


Как и в обычном JavaFX, вы можете объявить компоненты из представления для внедрения в ваш контроллер, чтобы вы могли взаимодействовать с ними.

@FXML
private Label weatherLabel;

Мы уже видели, что все работает хорошо. Вам просто нужно быть осторожным с выбором времени. Наш контроллер аннотирован Component, так что это обычный bean-объект, управляемый Spring. Это означает, что он создается в Spring при запуске контекста приложения и внедрении всех зависимостей. Однако ткачество по FX Weaver происходит позже. И во время этого переплетения вставляются ссылки на компоненты.

Это имеет один смысл. В вашем конструкторе и @PostConstruct вы уже можете работать с внедренными зависимостями Spring как обычно. Однако следует помнить, что в течение этого времени ссылки на компоненты из представления еще не доступны и, следовательно, являются нулевыми.

Заключение


JavaFX Weaver предоставляет удобный и простой способ интеграции Spring с приложениями JavaFX. В остальном это не так просто, поскольку JavaFX управляет собственным жизненным циклом и жизненным циклом своих контроллеров. JavaFX Weaver делает интеграцию возможной и довольно простой, поэтому вы, наконец, можете использовать все интересные возможности Spring даже с JavaFX.

Комментарии 0

Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

Самое читаемое