简体   繁体   中英

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 .

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.

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. It then calls lifecycle methods on that Application instance.

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. The init() method is executed prior to start() . It is executed on the main thread, but is guaranteed to complete prior to start() being invoked. The stop() method is invoked when the FX application exits. These methods are both invoked on the same Application instance as the start() method.


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. Here is an example with 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();
    }

}

An alternative approach is to bypass the standard JavaFX startup process. 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. 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. 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.

Here's an example of that approach, again using 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);
    }

}

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 ).


For completeness, here are the remaining classes, FXML files and configuration to make this a complete example. Either version of App above will work with these files:

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.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:

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

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

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

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