JavaFX and Spring – How to Integrate

Posted on May 25, 2014
Share article:

JavaFX and Spring? Why? Well, IMHO, JavaFX offers the best features in terms of Java-based GUI and Spring offers the best dependency injection features. What better way to develop java desktop applications?

Post Purpose

– Quickly learn how to integrate JavaFX and Spring
– Set a base for an extensible GUI framework based on JavaFX and Spring for easy dependency injection and inversion of control.

SpringFX Application

The SpringFX application is a simple, two-view application that illustrates how the navigation between views can be done. The models and controllers are injected via Spring wherever they are needed. The main goal of Spring in this application is dependency injection. A popup will be used to change the model, making the modification visible in the second view. Thus, it is clear that dependency injection and binding work as planned.

Some nice-to-haves are internationalization to easily allow the application to change the interface language on the fly and the CSS styling for skinning.

You can download the source code for this sample at the end of this post. Before trying it out, please make sure you’ve followed the pre requisites section below.

Prerequisites

Features

  • Uses Spring AnnotationConfigApplicationContext
  • Allows dependency injection in JavaFX controllers
  • Internationalization
  • Easy to extend, modify and build upon
  • CSS

Sample Code Installation

In Eclipse, import a java project from the sources provided. At first, Maven might not find the dependencies, so you should right-click on the project->Maven->Update Project (Alt+F5) and Maven will automatically configure the project using the .pom file provided.

Tried the same in IntellijIdea and it works like a charm.

Notes

Throughout the example, I have used ‘Presentation’ to denote FXML controllers. Because I’ve used Swiz on my Flex projects, I am keen on the idea of having a clear divider between the view and the rest of the application. Swiz calls this ‘presentation layer‘. If you do not like the term, you may rename those classes to ‘Controller‘ or whatever you are most familiar with.

Take great care when choosing package names for JavaFX related classes and resources. For simplicity and standardization, I have used ro.stancalau.springfx.gui both in the src and resources, for all view-related classes and references. If you choose to use multiple packages, you will need to change the referencing string when loading the FXML files ( getClass().getResource(“/my/full/package/View.fxml”)  – the first slash denotes absolute path). It is not mandatory to isolate the FXML files in a resource folder, but I find it useful to keep things clean and separate java code from FXML, CSS, lang files, photos, etc.

Tricky Bits

Below we have the entry point in our application, the Main class which extends javafx.application.Application. It tells the JRE we are going to use JavaFX. Then we create a context for Spring to do its magic with our two configuration classes: AppConfig and ScreensConfig. AppConfig will hold application beans such as controllers, models, services, and repositories. Whereas ScreensConfig will hold view-related beans such as presentations and views. ScreensConfig is, in essence, our main view controller and all navigation will originate there.

package ro.stancalau.springfx.gui;

public class Main extends Application {

    public static void main(String[] args) {
        launch(args);
    }

    @Override
    public void start(Stage stage) throws Exception {
        Platform.setImplicitExit(true);

        ApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
        ScreensConfig screens = context.getBean(ScreensConfig.class);
        LanguageModel lang = context.getBean(LanguageModel.class);
        
        screens.setLangModel(lang);
        screens.setPrimaryStage(stage);
        screens.showMainScreen();
        screens.loadFirst();
    }
}

AppConfig just defines a few beans that will be injected elsewhere later on:

package ro.stancalau.springfx.config;

@Configuration
@Import(ScreensConfig.class)
public class AppConfig {

    @Bean
    LanguageModel languageModel() {
        return new LanguageModel();
    }
    
    @Bean
    LanguageController languageController() {
        return new LanguageController(languageModel());
    }

    @Bean
    MessageModel messageModel() {
        return new MessageModel();
    }
}

ScreensConfig is altogether more complex. Some of the things that happen here:

  1. Initializing the stage with style sheets
  2. Observing the language model and reloading the first view with the new Locale
  3. Define presentation beans (view controllers) as prototypes (new instance for every loaded or re-loaded view)
  4. Implement public API for navigation (e.g.: loadFirst(), loadPopup(), etc.)
  5. Dynamically load view resources and define the control factory via the getNode(…) method
package ro.stancalau.springfx.gui;

@Configuration
@Lazy
public class ScreensConfig implements Observer{
    private static Logger logger = LogManager.getLogger(ScreensConfig.class);
    
    public static final int WIDTH = 480;
    public static final int HEIGHT = 320;    
    public static final String STYLE_FILE = "main.css";
    
    private Stage stage;
    private Scene scene;
    private LanguageModel lang;
    private StackPane root;    

    public void setPrimaryStage(Stage primaryStage) {
        this.stage = primaryStage;
    }

    public void setLangModel(LanguageModel lang) {
        if (this.lang!=null){
            this.lang.deleteObserver(this);
        }
        lang.addObserver(this);
        this.lang = lang;
    }

    public ResourceBundle getBundle() {
        return lang.getBundle();
    }

    public void showMainScreen() {
        root = new StackPane();
        root.getStylesheets().add(STYLE_FILE);
        root.getStyleClass().add("main-window");
        stage.setTitle("SpringFX");
        scene = new Scene(root, WIDTH, HEIGHT);
        stage.setScene(scene);
        stage.setOnHiding(new EventHandler<WindowEvent>() {
            public void handle(WindowEvent event) {
                System.exit(0);
            }
        });
        stage.show();
    }

    private void setNode(Node node) {
        root.getChildren().setAll(node);
    }

    public void removeNode(Node node) {
        root.getChildren().remove(node);
    }

    void loadFirst() {
        setNode(getNode(firstPresentation(), getClass().getResource("First.fxml")));
    }
    
    void loadSecond() {
        setNode(getNode(secondPresentation(), getClass().getResource("Second.fxml")));
    }
    
    void loadPopup() {
        ModalDialog modal = new ModalDialog(popupPresentation(), getClass().getResource("Popup.fxml"), stage.getOwner(), lang.getBundle());
        modal.setTitle( lang.getBundle().getString("popup.title") );
        modal.getStyleSheets().add(STYLE_FILE);
        modal.show(); 
    }

    @Bean
    @Scope("prototype")
    FirstPresentation firstPresentation() {
        return new FirstPresentation(this);
    }
    
    @Bean
    @Scope("prototype")
    SecondPresentation secondPresentation() {
        return new SecondPresentation(this);
    }
    
    @Bean
    @Scope("prototype")
    PopupPresentation popupPresentation() {
        return new PopupPresentation(this);
    }

    private Node getNode(final Presentation control, URL location) {
        FXMLLoader loader = new FXMLLoader(location, lang.getBundle());
        loader.setControllerFactory(new Callback<Class<?>, Object>() {
            public Object call(Class<?> aClass) {
                return control;
            }
        });   
        try {
            return (Node) loader.load();
        } catch (Exception e) {
            logger.error("Error casting node", e);
            return null;
        }
    }

    public Stage getStage() {
        return stage;
    }

    public void update(Observable o, Object arg) {
        loadFirst();        
    }
}

Download Source Code

You can find the git repo here. Note that there are two branches, the master is now built using Gradle and the maven branch is build using Maven.
Feel free to do pull requests if you do some cool change and you think it would add value to the project. I will give credit to all contributors right here on the post after any successful merge.

Updates:
07.03.2015  |  Refactored project structure to Maven standards
07.03.2015  |  Created a separate Maven branch
07.03.2015  |  Migrated build script to Gradle on master

For further reading, I invite you to check out a Pong Game made using JavaFX and Spring.

Share article:

5 Replies to "JavaFX and Spring - How to Integrate"

  • Alex
    October 6, 2019 (13:08)
    Reply

    1. Nice tutorial *thumbs up*
    2. In the main spring class you are getting the beans directly from the context, which is considered to be a bad practice because it defeats the Spring’s dependency injection purpose:
    ScreensConfig screens = context.getBean(ScreensConfig.class);
    LanguageModel lang = context.getBean(LanguageModel.class);

    How would you tackle this problem? As I am facing the same problem and I can’t find an alternate solution.
    Thanks!

  • HOUSSAM KOURDACHE
    December 6, 2016 (10:32)
    Reply

    Thanks for this tutorial it’s really useful et productive. I’am facing to a problem of nested controllers in my app, the included controller in main controller is null.
    I wonder if it does not come from the fact that I use spring injections !! What do you think ? Thanks again 🙂

    • Cristian S.
      December 11, 2016 (13:10)
      Reply

      Hi there!
      Could you explain what you are trying to do? Also, it would be useful to know if you have errors in the Spring logs.
      Cheers!

  • Sylwia
    January 22, 2015 (14:47)
    Reply

    Thanks for yours tutorial and help 🙂 It works. It was very useful !

  • JavaFX links of the week, May 26 // JavaFX News, Demos and Insight // FX Experience
    May 26, 2014 (02:09)
    Reply

    […] Christian Stancalau has a post on integrating JavaFX and Spring. […]


What do you think? Share your thoughts!

69 + = 75