Let Spring Be Your JavaFX Controller Factory

One of the best things to come out of the Spring ecosystem is Spring Boot. I use it quite a bit for web development (when Grails isn't the best fit). Recently I was playing around with JavaFX and I wondered if there was a way to use Spring with JavaFX; specifically I really wanted to take advantage of Spring's dependency injection and Spring Data. Turns out, it is pretty simple.

Like most of my Spring Boot applications, I started by going to https://start.spring.io/ and adding JPA, Validation and H2 as dependencies. I then generated a Gradle project for Spring Boot 1.5.1. You can name the Group and Artifact whatever you want and use Maven if you prefer.

What you'll have now is a standard Spring Boot application with a Gradle build. Since JavaFX is part of Java 8, there's no additional dependencies to add. The main change required is how the Spring Boot Application boots up along side the JavaFX bits.

The changes need to be made to the Spring Boot Application entry point; in my case it was com.example.DemoApplication. Here's the full file and then I'll explain it afterwards:

@SpringBootApplication
public class DemoApplication extends Application {

    private ConfigurableApplicationContext springContext;
    private Parent root;

    @Override
    public void init() throws Exception {
        springContext = SpringApplication.run(DemoApplication.class);
        FXMLLoader fxmlLoader = new FXMLLoader(getClass().getResource("/sample.fxml"));
        fxmlLoader.setControllerFactory(springContext::getBean);
        root = fxmlLoader.load();
    }

    @Override
    public void start(Stage primaryStage) throws Exception {
        primaryStage.setTitle("Hello World");
        Scene scene = new Scene(root, 800, 600);
        primaryStage.setScene(scene);
        primaryStage.show();
    }

    @Override
    public void stop() throws Exception {
        springContext.stop();
    }


    public static void main(String[] args) {
        launch(DemoApplication.class, args);
    }
}

First, we need to extend JavaFX's Application class. Then in the init() method we need to get an instance of the ConfigurableApplicationContext. Generally, a Spring Boot application simply has SpringApplication.run(DemoApplication.class); and we don't care about the context, but it does return a ConfigurableApplicationContext and we can utilize that.

We go ahead and create the initial FXMLLoader from an entry point JavaFX fxml resource then the magic happens. We have to tell the FXMLLoader that we want Spring to be the controller factory.

fxmlLoader.setControllerFactory(springContext::getBean);

Now we can do Spring'y stuff. I created an Entity:

@Entity
public class Article {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(nullable = false, columnDefinition = "BIGINT")
    private Long id;

    @Column(nullable = false)
    private String content;

    // getter and setters omitted
}

And a Repository for said Entity:

@Repository
public interface ArticleRepository extends JpaRepository<Article, Long> {  
}

My sample fxml file contains the following:

<BorderPane fx:controller="com.example.controller.Controller"  
            xmlns:fx="http://javafx.com/fxml"
<!-- more stuff here -->  
</BorderPane>  

And then my com.example.controller.Controller:

@Component
public class Controller {

    @Autowired
    private ArticleRepository articleRepository;

    // constructor injection also works
    // code here to use the injected articleRepository

}

When JavaFX's launch(DemoApplication.class, args); is called, the init method kicks off Spring Boot and wires up all the beans. And that's it! You can now start adding whatever additional Spring functionality you might need. In my use case, I needed a pretty simple way of managing a local data store for offline mode. Spring Data made that a piece of cake.