jeudi 20 septembre 2018

JavaFX + Spring Boot + Maven

Préambule


Dans mon travail de tous les jours je développe des applications web avec des technologies comme JSF + Spring + Hibernate, Angular + Spring Boot + Spring Data / Hibernate, etc...
Lorsque je me suis intéressé à JavaFX pour un petit projet personnel, j'ai commencé à développer mon application en utilisant le plugin e(fx)clipse afin de générer le projet.
Ensuite j'ai voulu ajouter une base de données fichiers pour sauvegarder les données de l'application, ajouter des librairie pour générer du PDF, etc... Et la je me suis rendu compte que toutes les choses bien pratiques que j'utilisais tous les jours dans le cadre du développement d'applications web (tel que l'IoC, l'ORM, la gestion des dépendances, ...) je devais les gérer ou les ajouter manuellement.
Partant de ce constat je me suis dit que j'allais faire de mon projet JavaFX un projet Maven et ensuite je me suis demandé s'il ne serait pas possible de marier JavaFX et Spring Boot pour profiter des avantages de ce dernier.
Et nous voilà donc dans cet article pour voir comment tout cela s'articule. Bon allé j'arrête de raconter ma vie, fort intéressante vous en conviendrez, et passons au coté technique.


Technos


Tout d'abord voici la liste des technologies utilisées dans cet article et leurs version au moment de l'écriture.
  • JavaFX 8 (version de la JRE 8 en l’occurrence)
  • Maven 3.x (on pourrait aussi bien utiliser Gradle)
  • Spring Boot 2.0.5


Tutoriel


Tout d'abord je tiens à préciser que cet article n'a pas pour but d'apprendre le développement d'une application JavaFX, il y a déjà pléthore d'articles sur le net pour cela, je vous laisse rechercher ça dans votre moteur de recherche favoris.


Spring Boot 


Pour commencer nous allons générer une application Spring Boot, pour cela personnellement j'utilise Spring Initializr qui permet de choisir ce que vous voulez ajouter dans votre application.

Choisissez Maven project, remplissez Group et Artifact, puis générez le projet.


Une fois le fichier zip téléchargé décompressez le et copiez le dans votre workspace afin de l'ouvrir avec votre IDE favoris (Pour ma part Eclipse).
Vous devriez vous retrouver avec quelque chose comme ça :



Pour le moment le fichier pom.xml ressemble à ce qui suit :
<?xml version="1.0" encoding="UTF-8"?>
<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/xsd/maven-4.0.0.xsd">
 <modelVersion>4.0.0</modelVersion>

 <groupId>fr.jbe</groupId>
 <artifactId>jfxspringboot</artifactId>
 <version>0.0.1-SNAPSHOT</version>
 <packaging>jar</packaging>

 <name>jfxspringboot</name>
 <description>Demo project for Spring Boot</description>

 <parent>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-parent</artifactId>
  <version>2.0.5.RELEASE</version>
  <relativePath/> <!-- lookup parent from repository -->
 </parent>

 <properties>
  <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
  <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
  <java.version>1.8</java.version>
 </properties>

 <dependencies>
  <dependency>
   <groupId>org.springframework.boot</groupId>
   <artifactId>spring-boot-starter</artifactId>
  </dependency>

  <dependency>
   <groupId>org.springframework.boot</groupId>
   <artifactId>spring-boot-starter-test</artifactId>
   <scope>test</scope>
  </dependency>
 </dependencies>

 <build>
  <plugins>
   <plugin>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-maven-plugin</artifactId>
   </plugin>
  </plugins>
 </build>

</project>

Et le main de l'application devrait ressembler à quelque chose comme ça :



package fr.jbe.jfxspringboot;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class JfxspringbootApplication {

 public static void main(String[] args) {
  SpringApplication.run(JfxspringbootApplication.class, args);
 }
}

Nous avons maintenant notre main classe avec l'annotation @SpringBootApplication qui permet de pré-configurer ce qui est nécessaire pour utiliser Spring. Bien sûr comme pour JavaFX je ne vais pas entrer dans le détail du fonctionnement de Spring Boot car ce n'est pas le sujet de cet article. Vous pouvez vous renseigne directement sur le site de Spring https://spring.io/projects/spring-boot.



JavaFX


Nous pouvons maintenant intégrer JavaFX à notre application Spring Boot. Pour cela il faut commencer par faire étendre Application.java à notre main classe.
Puis implémenter la méthode start() de l'application JavaFX dans laquelle on va construire la fenêtre principal de l'application puis l'afficher.


package fr.jbe.jfxspringboot;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

import javafx.application.Application;
import javafx.stage.Stage;

@SpringBootApplication
public class JfxspringbootApplication extends Application {

 public static void main(String[] args) {
  SpringApplication.run(JfxspringbootApplication.class, args);
 }

 @Override
 public void start(Stage primaryStage) throws Exception {
  // TODO Auto-generated method stub
  
 }
}

Ensuite afin de mieux intégrer Spring Boot à l'applicarion JavaFX nous allons récupérer le contexte Spring qui nous permettra plus tard d'injecter les contrôleurs lors du chargement des vue FXML.
Il faut donc ajouter la variable de contexte à notre classe, puis implémenter la méthode init() de JavaFX pour démarrer Spring Boot via la méthode run() et récupérer le contexte. La main méthode appelle maintenant la méthode launch() de JavaFX pour lancer l'application.


 private ConfigurableApplicationContext context;
 
 public static void main(String[] args) {
  launch(JfxspringbootApplication.class ,args);
 }
 
 @Override
 public void init() throws Exception {
  context = SpringApplication.run(JfxspringbootApplication.class);
 }


Voilà nous avons notre contexte prêt à être utilisé. Je prends le parti ici d'utiliser la création d'interfaces graphiques via les fichiers FXML pour profiter de l'utilisation de l'éditeur SceneBuilder qui est bien pratique. Mais bien sûr ce n'est pas obligatoire et on peut très bien construire les IHMs programmatiquement.



FMXL Loader


Afin de tirer partie de l'utilisation de Spring et de son contexte nous allons créer un loader pour notre application qui va intégrer le contexte au loader FMXL de JavaFX.


package fr.jbe.jfxspringboot.view;

import java.net.URL;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.stereotype.Component;

import javafx.fxml.FXMLLoader;

@Component
public class AppFXMLLoader {

 @Autowired
 private ConfigurableApplicationContext context;

 public FXMLLoader getLoader(URL url) {
  FXMLLoader loader = new FXMLLoader(url);
  loader.setControllerFactory(context::getBean);
  return loader;
 }
 
 public FXMLLoader getLoader(String fxmlPath) {
  return getLoader(this.getClass().getResource(fxmlPath));
 }

}


Comme on peut le voir ce loader créé une instance FXMLLoader en lui donnant le context::getBean comme controllerFactory ce qui lui permet d'utiliser le contexte Spring pour demander une instance du contrôleur associé à la vue FXML. Nous pouvons constater au passage que nous utilisons l'annotation @Autowired pour injecter le contexte.
Nous disposons maintenant d'un composant que nous allons pouvoir utiliser pour construire nos vue et notre scène. 

Fichier FXML


Tout d'abord il nous faut créer le FXML qui va correspondre à la fenêtre principale de notre application. faisons quelque chose de simple.

<?xml version="1.0" encoding="UTF-8"?>

<?import javafx.geometry.Insets?>
<?import javafx.scene.control.Button?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.control.TextField?>
<?import javafx.scene.layout.BorderPane?>
<?import javafx.scene.layout.ColumnConstraints?>
<?import javafx.scene.layout.GridPane?>
<?import javafx.scene.layout.RowConstraints?>


<BorderPane maxHeight="-Infinity" maxWidth="-Infinity" prefHeight="200.0" prefWidth="400.0" xmlns:fx="http://javafx.com/fxml/1" xmlns="http://javafx.com/javafx/8.0.141" fx:controller="fr.jbe.jfxspringboot.view.controller.MainController">
   <center>
      <GridPane hgap="10.0" BorderPane.alignment="CENTER">
        <columnConstraints>
          <ColumnConstraints hgrow="SOMETIMES" minWidth="10.0" prefWidth="100.0" />
          <ColumnConstraints hgrow="SOMETIMES" minWidth="10.0" prefWidth="100.0" />
        </columnConstraints>
        <rowConstraints>
          <RowConstraints maxHeight="-Infinity" minHeight="10.0" prefHeight="30.0" vgrow="SOMETIMES" />
          <RowConstraints maxHeight="-Infinity" minHeight="10.0" prefHeight="30.0" vgrow="SOMETIMES" />
          <RowConstraints maxHeight="-Infinity" minHeight="10.0" prefHeight="30.0" vgrow="SOMETIMES" />
        </rowConstraints>
         <children>
            <TextField fx:id="name" />
            <Button mnemonicParsing="false" onAction="#handleHello" text="Hello" GridPane.columnIndex="1" />
            <Label text="Bonjour" GridPane.halignment="RIGHT" GridPane.rowIndex="1" />
            <Label fx:id="displayName" text="Label" GridPane.columnIndex="1" GridPane.rowIndex="1" />
         </children>
         <BorderPane.margin>
            <Insets bottom="10.0" left="10.0" right="10.0" top="10.0" />
         </BorderPane.margin>
      </GridPane>
   </center>
</BorderPane>

Voilà juste un textfield, un bouton et un label pour afficher le résultat.


FXML controller


Il faut maintenant associé un contrôleur à notre vue FXML afin de réagir à l'appui sur le bouton. C'est ce contrôleur qui sera gérer via le contexte Spring.

package fr.jbe.jfxspringboot.view.controller;

import org.springframework.stereotype.Component;

import javafx.fxml.FXML;
import javafx.scene.control.Label;
import javafx.scene.control.TextField;

@Controller
public class MainController {

 @FXML
 private TextField name;
 
 @FXML
 private Label displayName;
 
 @FXML
 private void initialize() {
  name.clear();
  displayName.setText("");
 }
 
 @FXML
 public void handleHello() {
  if (!name.getText().isEmpty()) {
   displayName.setText(name.getText());
  }
 }
 
}

Notez bien de la classe du contrôleur est annotée @Controller afin d'être prise en compte par l'IoC de Spring.


Créer la vue


Je vais maintenant créer une classe pour la création de la vue principale de l'application. Cette classe a pour but de charger la vue FXML principale et d'effectuer les opérations nécessaires, ici pas grand chose mais on pourrait imaginer setter des données sur le contrôleur. Elle permet aussi d'initialiser l'utilisation de l'IoC car comme vous le savez si vous instanciez à la main une classe elle ne peut plus bénéficier du DI.

package fr.jbe.jfxspringboot.view;

import java.io.IOException;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import javafx.fxml.FXMLLoader;
import javafx.scene.Scene;
import javafx.scene.layout.BorderPane;

@Component
public class MainView {

 @Autowired
 AppFXMLLoader appFXMLLoader;
 
 public Scene createScene() {
  Scene scene = null;
  try {
   FXMLLoader loader = appFXMLLoader.getLoader(MainView.class.getResource("fxml/MainLayout.fxml"));
   BorderPane borderPane = loader.load();
   scene = new Scene(borderPane);
  } catch (IOException e) {
   e.printStackTrace();
  }
  return scene;
 }
 
}

Il ne nous reste plus qu'à implémenter la méthode start() de notre application.


Démarrons l'application


Voici l'intégration de tous ce que nous avons parlé au-dessus. Dans la classe JfxspringbootApplication.java il faut implémenter la méthode start() comme suit.

@Override
 public void start(Stage primaryStage) throws Exception {
  primaryStage.setTitle("Hello guys !");
  
  MainView mainView = context.getBean(MainView.class);
  primaryStage.setScene(mainView.createScene());
  primaryStage.show();
 }

Maintenant si nous lançons l'application nous devrions voir quelque chose qui ressemble à ce qui suit.

  .   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/
 :: Spring Boot ::        (v2.0.5.RELEASE)

2018-09-21 17:05:09.419  INFO 13800 --- [JavaFX-Launcher] o.s.boot.SpringApplication               : Starting application on NUANDA-PC with PID 13800 (started by nuanda in D:\Dev\workspace\workspace_oxygen\jfxspringboot)
2018-09-21 17:05:09.422  INFO 13800 --- [JavaFX-Launcher] o.s.boot.SpringApplication               : No active profile set, falling back to default profiles: default
2018-09-21 17:05:09.459  INFO 13800 --- [JavaFX-Launcher] s.c.a.AnnotationConfigApplicationContext : Refreshing org.springframework.context.annotation.AnnotationConfigApplicationContext@79e35f75: startup date [Fri Sep 21 17:05:09 CEST 2018]; root of context hierarchy
2018-09-21 17:05:09.885  INFO 13800 --- [JavaFX-Launcher] o.s.j.e.a.AnnotationMBeanExporter        : Registering beans for JMX exposure on startup
2018-09-21 17:05:09.896  INFO 13800 --- [JavaFX-Launcher] o.s.boot.SpringApplication               : Started application in 0.707 seconds (JVM running for 1.206)

Avec la fenêtre de l'application.



Conclusion


Voilà pour les grandes lignes de la création d'une application JavaFX, Spring Boot et Maven. Comme vous avez pu le voir il n'y a rien de très compliqué, on peut bien sûr ajouter bien d'autre choses tel qu'une datasource pour se connecter à une base de données par exemple, mais cela sera peut être le sujet d'un prochain article.

J'espère que cet article vous aura intéressé et n’hésitez pas à laisser un commentaire si vous avez des questions ou des remarques constructives.
Merci d'avoir pris le temps de lire cet article et à bientôt.

Retrouvez les sources sur github.com

Voir l'article suivant : JavaFX + Spring Boot + Database