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.