Skip to content
Ken Davidson

Java FX and Spring Boot playground

Technology, Java, JavaFX, Spring Boot4 min read

Time to throw my hat into the ring with a JavaFX and Spring Boot article. This information exists elsewhere, but I've accumulated and added what I've learned in the hopes:

  • that I'll be able to remember it going forward
  • learn a little about spring boot
  • help someone with the same pains

As always, feel free to add some comments or push me in the right direction if you've come across this and see that I'm going in the completely wrong way.

The project (in all it's glory) can be found at https://github.com/kenjdavidson/gspro-connector

Project Details

If it's not apparent, I love golf. Which means in the winter I love virtual golf. The last winter I've been playing E6 Connect using the SLX Micro Simulator and it's gotten me by. Although now that the primary game that I've been playing has disallowed the SLX Micro Sim, I figured I'd see if I could give GS Pro a shot; it seems to have substantially better reviews in terms of graphics and game play, just not internal simulator support.

It does offer the GS Pro Connect API which already have some plugins available for different monitors. Not that I have (nor maybe will ever have) the SLX Micro library, but here's dreaming. This project should be a fully working, pluginable, desktop application that will provide the UI and GS Pro Connect functionality to any willing to create a library plugin.

The source for this can be found at https://github.com/kenjdavidson/gspro-connect.

Development Environment

Initialize the Project

The project is initialized pretty quickly:

  1. Open up Visual Studio Code
  2. Ctrl-P to open up the palette and choose Remote Container: Add Development Container Configuration Files
  3. Configure the newly generated .devcontainer with the following Java related extensions
1"extensions": [
2 "vscjava.vscode-java-pack",
3 "Pivotal.vscode-boot-dev-pack",
4 "GabrielBB.vscode-lombok",
5 "redhat.vscode-xml"
6 ],
  1. Startup the devcontainer with Ctrl-P > Remote Containers: Rebuild and Run Container. This is where I started development on the API side of things (non-spring, non-fx).

Just a note - Java and VSCode still still seem to have some issues, specifically regarding the Java Language Server and other Java Language plugins. Particularly when the devcontainer is first built.

I've found a couple things that help:

  • Adding mvn clean install -DskipTests to the container post build command.
  • When rebuilding the container, you may need to restart the Java language service after mvn install runs.

Project

The project consists of a maven parent module gspro-connector and a number of sub modules:

  • gspro-api which contains the api data and connection functionality
  • gspro-client (deprecated) wrapping the api connection
  • gspro-app which is the JavaFX/Spring Boot application. The application contains a basic form based launch monitor which can be used if no other plugin(s) are provided.
  • gspro-api-garmin-r10 which is a sample plugin, the Python and Javascript version were first developed by Travis Lang and converted to Java to showcase how a plugin is used.

Desktop in Devcontainer

Now that the API is pretty good, it's time to start on the interface portion. I know what you're thinking, this could have been a web app (desktop apps are uncool now); but realistically there is just much less to do:

  • The code is self contained in Java, no HTML or JavaScript to need to worry about.
  • The communication code doesn't need to be added, no Websockets, etc to fool around with.
  • For the purpose of this it just made sense to take out the extra layer

But the main issue is Devcontainers have no desktop!!!

But thanks to a number of people smarter than I, this is manageable using the the Visual Studio Code feature desktop-lite. There are a number of great tutorials on how to get this working, so I'll just provide the short form here:

  1. Configure your .devcontainer with the required feature, desktop-lite, which as described

Adds a lightweight Fluxbox based desktop to the container that can be accessed using a VNC viewer or the web. GUI-based commands executed from the built-in VS code terminal will open on the desktop automatically.

which is what we want:

1"features": {
2 // Install desktop-lite (Fluxbox) on devcontainer
3 // When starting GsProConnectApplicationBoot application loads into desktop
4 // https://lucasjellema.medium.com/run-and-access-gui-inside-vs-code-devcontainers-b572643d0d2a
5 "desktop-lite": {
6 "password": "vscode",
7 "webPort": 6080,
8 "vncPort": 5901
9 }
10 },
  1. Add the appropriate port forwarding. Here there are two options:
  • Web
  • VNC

Both are configured in this case, but I generally just use the web desktop:

1// Use 'forwardPorts' to make a list of ports inside the container available locally.
2 "forwardPorts": [
3 6080, // desktop-lite web
4 5901, // desktop-lite vnc
5 ],

Once the devcontainer is started, you'll be able to login to http://localhost:6080 with the password you provided (vscode in my case). Now when you start up your JavaFX application it will be run in the context of the VNC desktop.

Desktop Lite Web VNC

JavaFX with Spring Boot

The connect application provides a standardized GS Pro Connector interface and the ability to choose from a number of installed LaunchMonitor(s) using the LaunchMonitorProvider interface. By default there is only the FormLaunchMonitor installed, but these are easy to implement and add to the application by placing the compiled JAR in the class path.

GS Pro Connect JavaFX

Two Applications/One Stone

One of the major issues run into across the web is getting the Spring Boot Application to play nicely with the JavaFX Application. There are a number of great tutorials on this:

Following a combination of these tutorials the resulting Application classes look like this:

Spring Boot

The @SpringBootApplication launches the GsProConnectApplication (FX Application) during the boot process:

1@SpringBootApplication
2public class GsProConnectApplicationBoot {
3 public static void main(String[] args) {
4 Application.launch(GsProConnectApplication.class, args);
5 }
6}

JavaFX Application

The Java FX Application peforms a couple key functions:

1/**
2 * Starts the application and fires off an `ApplicationStartupEvent` (which is responsible for building
3 * the primary stage) and displaying it.
4 */
5 @Override
6 public void start(Stage stage) throws Exception {
7 logger.debug("Starting GsProConnectApplication");
8
9 applicationContext.publishEvent(new ApplicationStartupEvent(this, stage));
10 }
11
12 /**
13 * Initialize is responsible for completing the configuration of the `ApplicationContext`. During this process
14 * a number of additional beans are applied using the `initializers()`:
15 */
16 @Override
17 public void init() {
18 logger.debug("Initializing Spring ApplicationContext");
19
20 applicationContext = new SpringApplicationBuilder(GsProConnectApplicationBoot.class)
21 .sources(GsProConnectApplicationBoot.class)
22 .initializers(initializers())
23 .run(getParameters().getRaw().toArray(new String[0]));
24 }
25
26 /**
27 * Return an `ApplicationContextInitializer` which provides some of the specific JavaFX application
28 * related beans: `Application`, `Parameters` and `HostServices`. This allows `Controller`(s) to
29 * be injected with application related items.
30 *
31 * For example `HostServices` would allow a controller access to disk, etc.
32 */
33 ApplicationContextInitializer<GenericApplicationContext> initializers() {
34 return ac -> {
35 ac.registerBean(Application.class, () -> GsProConnectApplication.this);
36 ac.registerBean(Parameters.class, this::getParameters);
37 ac.registerBean(HostServices.class, this::getHostServices);
38 };
39 }

Controllers (FXML)

The FXML controllers are implemented through the #setControllerFactory providing the applicationContext::getBean method. Controllers are setup as prototype scope; this isn't really required (based on your application) but just be aware that if you are working with singleton controllers and attempting to display multiple Views you're run into issues.

Stolen from another project the ViewManager provides the direct management of loading FXML views.

1public <T> T load(String view, Stage stage) throws IOException {
2 String resource = validateViewPath(view);
3 FXMLLoader loader = new FXMLLoader(getClass().getResource(resource), ResourceBundle.getBundle("i18n"));
4
5 loader.setControllerFactory(applicationContext::getBean);
6
7 // Need to find a way to make an fxml scope so that controller and builder will return the same
8 // controller from the context, instead of needing the controller to be a singleton (bad) or
9 // having two different instances of the same class (also bad); since all examples for use of
10 // fx:root use #setRoot(this); #setController(this); when performing the load.
11 //loader.setBuilderFactory(builderFactory);
12
13 T parent = loader.load();
14
15 if (loader.getController() instanceof StageAware) {
16 ((StageAware)loader.getController()).setStage(stage);
17 }
18
19 return parent;
20}

This could have been more easily done with FXWeaver, which is on the todo list for implementation.

Services

Services are designed pretty much how they would be normally.

Issues

One of the main issues that I ran into while getting this project going was the inability to use fx:root to build the FXML components. There are a few ways around this that I found online, while JavaFX allows you to set the FXMLLoader#setControlllerFactory and FXMLLoader#setBuilderFactory to the context::getBean this will result in two separate version of the class (ie. Not the same instance of Controller being the root). This can be worked around by using singleton scope for your highest level components (application scene, etc) but that also has limitations (only a single window available, which might work for the majority of applications).

As long as you're following a pretty standard structure of:

  • singleton Service(s) which are pretty standard
  • prototype View Controllers which get injected with singleton services
  • fx:root Which are just display Views (no injected services)
1@Scope('prototype')
2public class Controller {
3 @Inject DataService dataService;
4
5 void buildList() {
6 // MyCustomList created with `fx:root` extending the ListView
7 ListView list = new MyCustomList(dataService.getItems());
8 }
9}

Web App / Running as Service

The cool thing about backing JavaFX with Spring Boot is that if you'd like to run the application as a service, you have the option of quickly adding some Web Controllers and modifying the Spring Boot startup so that instead of running the JavaFX Application it runs the Web Application.

Updating the Spring Boot Application

Check out one of the next articles

Starting the Application in Service Mode

Check out one of the next articles

© 2023 by Ken Davidson. All rights reserved.