简体   繁体   English

使用依赖注入配置 JavaFX

[英]Configuring JavaFX using Dependency Injection

I have an application that I want to configure (set up database, initialize a worker pool) then launch my JavaFX Application with the configuration injected in. Unfortunately, every DI framework for Java wants to be able to construct the objects being injected into but the only way to run a JavaFX Application is to start it using Application.launch .我有一个要配置的应用程序(设置数据库,初始化工作池),然后启动我的 JavaFX 应用程序并注入配置。不幸的是,Java 的每个 DI 框架都希望能够构造注入的对象,但是运行 JavaFX 应用程序的唯一方法是使用Application.launch启动它。

The only solution that I have been able to come up with is to start my DI framework in my Application constructor, but that makes it impossible to configure the DI before the Application starts or clean up when the Application exits.我能够想出的唯一解决方案是在我的应用程序构造函数中启动我的 DI 框架,但这使得无法在应用程序启动之前配置 DI 或在应用程序退出时清理。

Application.launch() will start the JavaFX runtime, the JavaFX Application Thread, and, as noted in the question, will create an instance of the Application subclass. Application.launch()将启动 JavaFX 运行时、JavaFX 应用程序线程,并且如问题中所述,将创建Application子类的实例。 It then calls lifecycle methods on that Application instance.然后它在该Application实例上调用生命周期方法。

Most examples only use the Application.start() method, which is executed on the JavaFX Application Thread, but there are other lifecycle methods (which by default are no-ops) which are also executed.大多数示例仅使用Application.start()方法,该方法在 JavaFX 应用程序线程上执行,但还有其他生命周期方法(默认为无操作)也被执行。 The init() method is executed prior to start() . init()方法在start()之前执行。 It is executed on the main thread, but is guaranteed to complete prior to start() being invoked.它在主线程上执行,但保证在start()被调用之前完成。 The stop() method is invoked when the FX application exits. FX 应用程序退出时调用stop()方法。 These methods are both invoked on the same Application instance as the start() method.这些方法都在与start()方法相同的Application实例上调用。


One solution is to leverage these lifecycle methods to start, configure, and cleanup a dependency-injection framework.一种解决方案是利用这些生命周期方法来启动、配置和清理依赖注入框架。 So here we essentially use the JavaFX lifecycle, and hook into it to manually control the DI framework lifecycle.所以这里我们本质上是使用 JavaFX 生命周期,并挂钩到它来手动控制 DI 框架生命周期。 Here is an example with Spring Boot:这是 Spring 引导的示例:

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

}

An alternative approach is to bypass the standard JavaFX startup process.另一种方法是绕过标准的 JavaFX 启动过程。 JavaFX 9 and later have a Platform.startup() method that "manually" starts the JavaFX runtime, launches the FX Application Thread, and invokes the provided Runnable on that thread. JavaFX 9 和更高版本具有Platform.startup()方法,该方法“手动”启动 JavaFX 运行时,启动 FX 应用程序线程,并在该线程上调用提供的Runnable By bypassing the usual JavaFX startup process, you can use your DI framework's startup process, and invoke Platform.startup() as needed at an appropriate point in that process.通过绕过通常的 JavaFX 启动过程,您可以使用 DI 框架的启动过程,并在该过程中的适当位置根据需要调用Platform.startup() This in a sense is the opposite approach than the previous one: we use the DI Framework lifecycle, and hook into it to manually control the FX application lifecycle.这在某种意义上是与前一种相反的方法:我们使用 DI 框架生命周期,并将其挂钩以手动控制 FX 应用程序生命周期。

Here's an example of that approach, again using Spring Boot:这是该方法的一个示例,再次使用 Spring 引导:

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

}

Note that in this approach, Spring Boot is controlling its own lifecycle, so it will "know" to call any @PreDestroy -annotated methods at shutdown (the example code below contains such a method in the MessageBean ).请注意,在这种方法中,Spring Boot 正在控制自己的生命周期,因此它将“知道”在关机时调用任何@PreDestroy注释的方法(下面的示例代码在MessageBean中包含这样的方法)。


For completeness, here are the remaining classes, FXML files and configuration to make this a complete example.为了完整起见,这里是其余的类、FXML 文件和配置,以使其成为一个完整的示例。 Either version of App above will work with these files:上述任一版本的App都可以使用这些文件:

org.jamesd.examples.springfx.Config: 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: 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: 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.fxml: org.jamesd.examples.springfx.Main.fxml:

<?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

module-info.java:模块信息.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;
}

pom.xml pom.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>

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM