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>
Application.init()method, keeping the context in an instance variable, and shut it down in theApplication.stop()method.