Настройка JavaFX с использованием внедрения зависимостей - PullRequest
1 голос
/ 07 мая 2020

У меня есть приложение, которое я хочу настроить (настроить базу данных, инициализировать рабочий пул), а затем запустить мое приложение JavaFX с введенной конфигурацией. К сожалению, каждая структура DI для Java хочет иметь возможность создавать объекты вводится, но единственный способ запустить приложение JavaFX - запустить его с использованием Application.launch.

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

1 Ответ

1 голос
/ 07 мая 2020

Application.launch() запустит среду выполнения JavaFX, поток приложения JavaFX и, как указано в вопросе, создаст экземпляр подкласса Application. Затем он вызывает методы жизненного цикла для этого экземпляра Application.

В большинстве примеров используется только метод Application.start(), который выполняется в потоке приложения JavaFX, но есть и другие методы жизненного цикла (которые по умолчанию не- ops), которые также выполняются. Метод init() выполняется до start(). Он выполняется в основном потоке, но гарантированно завершится до вызова start(). Метод stop() вызывается при выходе из приложения FX. Оба эти метода вызываются в том же экземпляре Application, что и метод start().


Одним из решений является использование этих методов жизненного цикла для запуска, настройки и очистки инфраструктуры внедрения зависимостей. Итак, здесь мы по существу используем жизненный цикл JavaFX и подключаемся к нему для ручного управления жизненным циклом инфраструктуры DI. Вот пример с Spring Boot:

package org.jamesd.examples.springfx;

import java.io.IOException;
import java.util.List;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ConfigurableApplicationContext;

import javafx.application.Application;
import javafx.fxml.FXMLLoader;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.stage.Stage;

@SpringBootApplication
public class App extends Application {

    private ConfigurableApplicationContext context ;

    @Override
    public void init() {
        List<String> rawParams = getParameters().getRaw() ;
        String[] args = rawParams.toArray(new String[rawParams.size()]) ;
        context = SpringApplication.run(getClass(), args);
        // further configuration on context as needed
    }


    @Override
    public void start(Stage stage) throws IOException {

        FXMLLoader loader = new FXMLLoader(getClass().getResource("Main.fxml"));
        loader.setControllerFactory(context::getBean);
        Parent root = loader.load();
        Scene scene = new Scene(root);

        stage.setScene(scene);
        stage.show();
    }

    @Override
    public void stop() {
        context.close();
    }


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

}

Альтернативный подход - обойти стандартный процесс запуска JavaFX. JavaFX 9 и более поздние версии имеют метод Platform.startup(), который «вручную» запускает среду выполнения JavaFX, запускает поток приложения FX и вызывает предоставленный Runnable в этом потоке. Обходя обычный процесс запуска JavaFX, вы можете использовать процесс запуска вашей инфраструктуры DI и вызывать Platform.startup() по мере необходимости в соответствующем месте этого процесса. В некотором смысле это подход, противоположный предыдущему: мы используем жизненный цикл DI Framework и подключаемся к нему для ручного управления жизненным циклом приложения FX.

Вот пример такого подхода, снова с использованием Spring Boot:

package org.jamesd.examples.springfx;

import java.io.IOException;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ConfigurableApplicationContext;

import javafx.application.Platform;
import javafx.fxml.FXMLLoader;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.stage.Stage;

@SpringBootApplication
public class App implements CommandLineRunner {

    @Autowired
    private ConfigurableApplicationContext context ;

    private void startUI()  {

        try {
            FXMLLoader loader = new FXMLLoader(getClass().getResource("Main.fxml"));
            loader.setControllerFactory(context::getBean);
            Parent root = loader.load();
            Scene scene = new Scene(root);
            Stage stage = new Stage();
            stage.setScene(scene);
            stage.show();
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }



    public static void main(String[] args) {
        SpringApplication.run(App.class, args) ;
    }


    @Override
    public void run(String... args) throws Exception {
        // perform additional configuration on context, as needed

        Platform.startup(this::startUI);
    }

}

Обратите внимание, что в этом подходе Spring Boot управляет своим собственным жизненным циклом, поэтому он будет «знать», что нужно вызывать любые @PreDestroy -аннотированные методы при завершении работы (приведенный ниже пример кода содержит такой метод в the MessageBean).


Для полноты, вот оставшиеся классы, файлы F XML и конфигурация, чтобы сделать этот пример полным. Любая из указанных выше версий App будет работать с этими файлами:

org.jamesd.examples.springfx.Config:

package org.jamesd.examples.springfx;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class Config {

    @Bean
    public MessageBean messageBean() {
        return new MessageBean();
    }

    @Bean
    public Controller controller() {
        return new Controller();
    }
}

org.jamesd.examples.springfx.Controller:

package org.jamesd.examples.springfx;

import org.springframework.beans.factory.annotation.Autowired;

import javafx.fxml.FXML;
import javafx.scene.control.Label;

public class Controller {

    @Autowired
    private MessageBean messageBean ;

    @FXML
    private Label welcomeLabel ;

    @FXML
    private void initialize() {
        welcomeLabel.setText(messageBean.getMessage());
    }
}

org.examples.jamesd.springfx.MessageBean:

package org.jamesd.examples.springfx;

import javax.annotation.PreDestroy;

import org.springframework.boot.context.properties.ConfigurationProperties;

@ConfigurationProperties(prefix = "springfx")
public class MessageBean {

    private String message ;

    public String getMessage() {
        return message;
    }

    public void setMessage(String message) {
        this.message = message;
    }

    @PreDestroy
    public void dispose() {
        System.out.println("Closing");
    }

}

org.jamesd.examples.springfx.Main.f xml:

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

<?import javafx.scene.layout.VBox?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.control.Button?>
<?import javafx.geometry.Insets?>

<VBox alignment="CENTER" minWidth="200" minHeight="200" xmlns="http://javafx.com/javafx/8.0.171" xmlns:fx="http://javafx.com/fxml/1" fx:controller="org.jamesd.examples.springfx.Controller">
    <Label fx:id="welcomeLabel" />
</VBox>

application.properties:

springfx.message=Spring Boot JavaFX Application

информация о модуле. java:

module org.jamesd.examples.springfx {
    requires transitive javafx.controls;
    requires javafx.fxml;
    requires spring.boot;
    requires spring.beans;
    requires spring.context;
    requires spring.core ;
    requires spring.boot.autoconfigure;
    requires java.annotation;

    opens org.jamesd.examples.springfx to javafx.fxml, spring.context, spring.beans, spring.core ;
    exports org.jamesd.examples.springfx;
}

пом. xml

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <groupId>org.jamesd.examples</groupId>
    <artifactId>springfx</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <maven.compiler.source>14</maven.compiler.source>
        <maven.compiler.target>14</maven.compiler.target>
    </properties>
    <dependencies>
        <dependency>
            <groupId>org.openjfx</groupId>
            <artifactId>javafx-controls</artifactId>
            <version>14</version>
        </dependency>
        <dependency>
            <groupId>org.openjfx</groupId>
            <artifactId>javafx-fxml</artifactId>
            <version>14</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter</artifactId>
            <version>2.2.5.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-beans</artifactId>
            <version>5.2.4.RELEASE</version>
        </dependency>
    </dependencies>
    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.8.0</version>
                <configuration>
                    <release>11</release>
                </configuration>
            </plugin>
            <plugin>
                <groupId>org.openjfx</groupId>
                <artifactId>javafx-maven-plugin</artifactId>
                <version>0.0.1</version>
                <configuration>
                    <mainClass>org.jamesd.examples.springfx.App</mainClass>
                </configuration>
            </plugin>
        </plugins>
    </build>
</project>
...