Activité Premier serveur HTTP

Consigne

  1. La durée de l’activité est estimée à environ 180 minutes.
  2. Avant de commencer l’activité :
    • Lire la mise en situation, prendre connaissance des objectifs, du résultat attendu, et des ressources à disposition.
    • Lire une première fois en entier la section Mise en route.
  3. Effectuez le travail décrit dans la section Mise en route et effectuez les tâches demandées dans la section Tâches.

Mise en situation

Vous avez été chargé de réaliser une application de messagerie instantanée pour votre site web. L’entreprise développe en Java avec le framework Spring et Spring Boot. Comme vous n’avez jamais pratiqué le développement d’application web en Java et que vous ne connaissez pas le framework Spring, vous décidez de vous lancer dans une activité d’exploration afin d’acquérir quelques bases.

Objectifs

À la fin de ce travail, vous devez :

  1. Connaître la différence entre le framework Spring et Spring Boot.
  2. Connaître la notion d’annotation en Java.
  3. Connaître la structure d’une application Spring Boot.
  4. Connaître l’annotation @SpringBootApplication et l’instruction SpringApplication.run(App.class, args);.
  5. Connaître les annotations @Controller, @RequestMapping, et @RequestBody.
  6. Connaître les annotations @GetMapping, @PostMapping, @PutMapping, @DeleteMapping .
  7. Connaître les utilitaires curl et postman.

Résultat attendu

  • Un projet Java fonctionnel
  • Une section dans votre rapport

Ressources

Matériel :

  • Visual Studio Code
  • Docker Desktop, Colima, Rancher Desktop

Mise en route

Installer Docker

Dans ce module, nous aurons besoin de conteneurs pour exécuter un serveur HTTP, notre application, notre SGBD, etc.

Pour cela, nous avons besoin d’une distribution de docker compatible avec Visual Studio Code :

  • Docker Desktop : C’est la solution la plus simple sous Windows, macOS et Linux si vous disposez de votre propre matériel. Sur un PC d’entreprise, vous devez vous assurer que votre entreprise possède une licence pour Docker Desktop.
  • Rancher Desktop : C’est la solution recommandée sous Windows, macOS et Linux si vous disposez d’un PC d’entreprise et que votre entreprise ne possède pas de licence pour Docker Desktop.
  • Colima : C’est une solution libre et plus légère que Docker Desktop pour macOS.

Installez la solution la plus appropriée en utilisant votre gestionnaire de paquets (brew, choco, etc.)

Installer les packs d’extensions pour VSCode

Pour ce module nous avons besoin de plusieurs extensions pour Visual Studio Code (VSCode) :

  • Extension Pack for Java
  • Spring Boot Extension Pack
  • Editor config

Ouvrez VSCode et installez ces trois extensions.

Récupérer le projet depuis Gitlab

Pour récupérer le projet, restez dans VSCode, ouvrez une fenêtre de terminal intégré, rendez-vous dans votre répertoire de projet (cd $HOME/projects), créez un répertoire pour le module 295 (mkdir m295), rendez-vous dans ce répertoire (cd m295), et lancez les commandes suivantes :

1
2
git clone git@gitlab.epai-ict.ch:m295/java/firstserver.git
code -r firstserver

Si la commande git clone vous renvoie une erreur, cela signifie probablement que votre clé SSH n’est pas chargée, ou que vous ne l’avez pas ajouté à votre compte Gitlab. Si vous ne savez pas comment faire, demandez de l’aide à votre enseignant.

Lorsque le projet est chargé, assurez-vous que Docker Desktop fonctionne et tapez Shift-Command-P ou Shift-Ctrl-P pour ouvrir la liste des commandes de VSCode. Recherchez la commande dev containers: reopen in container dans la liste (tappez le début de la commande pour filtrer le contenu de la liste) et sélectionnez cette commande pour l’exécuter.

Lorsque le projet est ouvert dans le container, ouvez une fernêtre de terminal intégré et lancez la commande suivante pour vous assurer que les containers ont été correctement créés.

1
mysql -h db -u root -pepai123

La commande doit ouvrir l’interpréteur de commande du serveur mariadb. Si c’est bien le cas, quittez l’interpréteur et lancez les commandes ci-dessous pour builder le projet et exécuter le fichier .jar.

1
2
mvn verify
java -jar target/firstserver-0.0.1-SNAPSHOT.jar

La première commande devrait se terminer par un message qui indique “BUILD SUCCESS” et l’exécution du .jar devrait afficher le message “Hello, World!”.

N’allez pas plus loin tant que cela ne fonctionne pas et demandez de l’aide au besoin.

Spring et Spring Boot

Spring est un framework développé par VMware (Broadcom), depuis l’acquisition de SpringSource en 2009. Au moment de l’acquisition, SpringSource était l’un des principaux contributeurs du projet Apache Tomcat.

Spring Boot est une extension du framework Spring qui permet de produire une application serveur directement exécutable avec un serveur HTTP embarqué (typiquement Tomcat ou Jetty).

Pour réaliser un premier serveur HTTP avec Spring et Spring Boot, on a besoin des bibliothèques suivantes :

  • spring-boot-starter-web
  • spring-boot-starter-test (pour les tests unitaires)

Et pour produire un fichier jar exécutable avec un serveur HTTP embarqué, on a besoin du plugin suivant :

  • spring-boot-maven-plugin

Ces dépendances ont déjà été ajoutées dans le fichier pom.xml de votre projet.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
    ...
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</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>
    ...

Tâches

Créer le serveur HTTP

Pour réaliser un serveur HTTP avec le framework Spring, on comment par annoter la classe principale avec l’annotation @SpringBootApplication.

1
2
3
4
5
6
7
8
9
10
11
package ch.epai.ict.m295.firstserver;

import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class FirstserverApplication {

    public static void main(String[] args) {
        System.out.println("Hello, World!");
    }
}

Puis, dans la méthode main, on utilise la méthode SpringApplication.run pour instancier et démarrer le serveur HTTP.

1
2
3
4
5
6
7
8
9
10
11
12
package ch.epai.ict.m295.firstserver;

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

@SpringBootApplication
public class FirstserverApplication {

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

Nous pouvons maintenant lancer le projet pour vérifier qu’il fonctionne, en cliquant le petit run qui devrait se trouver juste au-dessus de la méthode main. Le démarrage du serveur devrait ressembler à cela :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
  .   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/

 :: Spring Boot ::                (v3.3.4)

2024-10-10T18:27:02.047+02:00  INFO 36322 --- [firstserver] [           main] c.e.i.m.f.FirstserverApplication         : Starting FirstserverApplication using Java 23 with PID 36322 (/Users/frossardj/projects/firstserver/target/classes started by frossardj in /Users/frossardj/projects/firstserver)
2024-10-10T18:27:02.050+02:00  INFO 36322 --- [firstserver] [           main] c.e.i.m.f.FirstserverApplication         : No active profile set, falling back to 1 default profile: "default"
2024-10-10T18:27:02.613+02:00  INFO 36322 --- [firstserver] [           main] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat initialized with port 8080 (http)
2024-10-10T18:27:02.622+02:00  INFO 36322 --- [firstserver] [           main] o.apache.catalina.core.StandardService   : Starting service [Tomcat]
2024-10-10T18:27:02.622+02:00  INFO 36322 --- [firstserver] [           main] o.apache.catalina.core.StandardEngine    : Starting Servlet engine: [Apache Tomcat/10.1.30]
2024-10-10T18:27:02.670+02:00  INFO 36322 --- [firstserver] [           main] o.a.c.c.C.[Tomcat].[localhost].[/]       : Initializing Spring embedded WebApplicationContext
2024-10-10T18:27:02.671+02:00  INFO 36322 --- [firstserver] [           main] w.s.c.ServletWebServerApplicationContext : Root WebApplicationContext: initialization completed in 579 ms
2024-10-10T18:27:02.944+02:00  INFO 36322 --- [firstserver] [           main] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat started on port 8080 (http) with context path '/'
2024-10-10T18:27:02.949+02:00  INFO 36322 --- [firstserver] [           main] c.e.i.m.f.FirstserverApplication         : Started FirstserverApplication in 1.157 seconds (process running for 1.473)

En parcourant ces informations de démarrage, on peut apprendre que le serveur Tomecat a démarré sur le port 8080. Ouvrez un navigateur et naviguez vers l’URI http://localhost:8080/.

Que constatez-vous ? Est-ce que le serveur HTTP fonctionne correctement selon-vous ?

Pour arrêter le serveur, placez le curseur dans la fenêtre de terminal où le serveur est lancé et pressez les touches Ctrl-c.

Créer un gestionnaire de requête

Pour créer un gestionnaire de requête, nous commençons par créer un nouveau fichier que nous appelons HomeController.java, dans le même répertoire que le fichier FirstserverApllication.java.

Ce fichier devrait avoir le contenu ci-dessous. Si ce n’est pas le cas, copiez ce code dans le fichier.

1
2
3
4
5
package ch.epai.ict.m295.firstserver;

public class HomeController {
    
}

Dans cette classe, nous allons ajouter une méthode handleGetHome et pour le moment, nous allons simplement écrire un message dans la sortie standard. Si ce message apparaît dans le terminal, nous saurons que la méthode a bien été exécutée.

1
2
3
4
5
6
7
8
9
10
11
12
13
package ch.epai.ict.m295.firstserver;

import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;

public class HomeController {

    public void handleGetHome(
            HttpServletRequest request, 
            HttpServletResponse response) {
        System.out.println("Home page requested");
    }
}

D’après ce nous avons vue dans la présentation sur les frameworks, nous devrions maintenant ajouter ce gestionnaire de requête à la liste des gestionnaires de requête du serveur HTTP. Avec Spring, cela ne se fait pas de manière impérative (avec des instructions), mais de manière déclarative avec des annotations.

Pour informer le framework que notre classe est un contrôleur qui contient des gestionnaires de requête, nous l’annotons avec @Controller. Pour indiquer que la méthode handeGetHome est le gestionnaire de requête pour l’URI racine / et pour la méthode GET, on utilise l’annotation @RequestMapping(value = "/", method = RequestMethod.GET) :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
package ch.epai.ict.m295.firstserver;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;

import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;

@Controller
public class HomeController {

    @RequestMapping(value = "/", method = RequestMethod.GET)
    public void handleGetHome(
            HttpServletRequest request, 
            HttpServletResponse response) {
        System.out.println("Home page requested");
    }
}

Nous pouvons maintenant relancer l’application de la même manière que précédemment, ouvrir un navigateur et naviguer sur l’URI http://localhost:8080/.

Cette fois, la page n’affiche rien du tout puisque nous n’avons rien écrit dans la réponse, mais on peut voir dans la fenêtre du terminal intégré le message qui nous indique que le gestionnaire de requête a bien été invoqué.

Écrire des données dans la réponse

On peut utiliser les méthodes de la réponse pour indiquer le code de statut et le type de contenu de la réponse, et le flux de sortie du socket attaché à la réponse pour envoyer le contenu.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
package ch.epai.ict.m295.firstserver;

import java.io.IOException;
import java.io.PrintWriter;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;

import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;

@Controller
public class HomeController {

    @RequestMapping(value = "/", method = RequestMethod.GET)
    public void handleGetHome(
            HttpServletRequest request,
            HttpServletResponse response) throws IOException {

        // Récupère des données d'une manière ou d'une autre
        String title = "Page d'accueil";
        String data = "Bienvenue sur la page d'accueil !";

        response.setContentType("text/html");
        response.setStatus(200);

        PrintWriter out = response.getWriter();
        out.println("<html>");
        out.println("<head>");
        out.println(String.format("<meta charset=\"UTF-8\">", title));
        out.println(String.format("<title>%s</title>", title));
        out.println("</head>");
        out.println("<body>");
        out.println(String.format("<h1>%s</h1>", title));
        out.println(String.format("<p>%s</p>", data));
        out.println("</body>");
        out.println("</html>");
        out.flush();
    }
}

On relance le serveur et cette fois le navigateur affiche un contenu en HTML lorsque l’on navigue vers l’URI http://localhost:8080/.

Créer la réponse avec un template

Cette manière de faire nous donnes un contrôle total sur la manière de traiter la requête. Toutefois, l’utilisation du writer pour créer la réponse est laborieuse et difficile à maintenir. De plus, comme le code HTML est imbriqué dans le code Java, il n’est pas possible de sous-traiter la réalisation du code HTML à des spécialistes.

Pour résoudre ces problèmes, on utilise généralement des templates (modèles). Un template est un fichier de texte qui contient des espaces réservés, souvent appelés placeholders, qui sont remplis avec des données dynamiques au moment de l’exécution.

Pour spécifier les espaces réservés, on utilise un langage de template. Un langage de template permet généralement spécifier des variables à remplacer, mais aussi des structure de choix (if-then-else) pour rendre contionnelle certaines partie du template, et des boucles (for-each) pour répéter un partie du template.

Dans notre projet, nous allons utiliser le langage de template FreeMarker. Pour cela, nous devons d’abord ajouter la bibliothèque spring-boot-starter-freemarker à la liste des dépendances dans le fichier pom.xml.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
    ...
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-freemarker</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>
    ...

Nous devons ensuite configurer Spring Boot pour lui indiquer où trouver les templates et configurer le moteur de template FreeMarker, en ajoutant les entrées ci-dessous dans le fichier /src/main/resources/application.properties de notre projet.

1
2
3
spring.freemarker.template-loader-path=classpath:/templates/
spring.freemarker.suffix=.ftl
spring.freemarker.charset=UTF-8

Pour créer le template de notre réponse, nous devons d’abord créer le répertoire templates dans le répertoire /srv/main/resources du projet, puis un fichier que nous appellerons home.ftl avec le code de la figure ci-dessous.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>${title}</title>
    <style>
        body { font-family: Arial, sans-serif; margin: 0; padding: 0; }
        header, footer { background: #333; color: #fff; padding: 10px; text-align: center; }
        main { padding: 20px; }
    </style>
</head>
<body>
    <header>
        <h1>Première application Spring Boot</h1>
    </header>

    <main>
        <h1>${title}</h1>
        <p>${mydata}</p>
    </main>

    <footer>
        <p>2024,Première application</p>
    </footer>
</body>
</html>

Les variables à remplacer sont spécifiées par leur nom entre accolades précédées d’un signe dolard (${...}).

Modifions maintenant notre gestionnaire de requête pour utiliser ce template. Dans ce nouveau gestionnaire, nous n’allons plus utiliser directement la requête et la réponse, nous laisserons cette tâche au framework. Nous allons utiliser un objet de type Model que le framework instancie pour nous et qui nous permet de spécifier la valeur des variables du templagte (dans notre cas, les variable title et mydata). Puis nous indiquerons au framework le template qu’il doit instancier en renvoyant son nom du template sans extention (dans notre cas, home).

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
package ch.epai.ict.m295.firstserver;

import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;

@Controller
public class HomeController {

    @RequestMapping(value = "/", method = RequestMethod.GET)
    public String handleGetHome(Model model) {

        // Récupère des données d'une manière ou d'une autre
        String title = "Page d'accueil";
        String data = "Bienvenue sur la page d'accueil !";

        model.addAttribute("title", title);
        model.addAttribute("mydata", data);
        return "home";
    }
}

Redémarrez le serveur et naviguez une nouvelle fois vers l’URI http://localhost:8080/ pour voir le résultat.

Page d'accueil avec template.

Répondre avec un contenu JSON

Par défaut, le framework suppose que la réponse contient une représentation HTML (text/html). Si l’on veut renvoyer une représentation JSON de la même ressource, on crée un deuxième gestionnaire de requête en indiquant que le contenu est de type application/json.

Pour cela, on utilise l’attribut produces de l’annotation @RequestMapping.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
package ch.epai.ict.m295.firstserver;

import java.io.IOException;
import java.io.PrintWriter;

import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;

import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;

@Controller
public class HomeController {

    @RequestMapping(value = "/", method = RequestMethod.GET)
    public String handleGetHome(Model model) {

        // Récupère des données d'une manière ou d'une autre
        String title = "Page d'accueil";
        String data = "Bienvenue sur la page d'accueil !";

        model.addAttribute("title", title);
        model.addAttribute("mydata", data);
        return "home";
    }

    @RequestMapping(value = "/", method = RequestMethod.GET, produces = "application/json")
    public void handleGetHome(
            HttpServletRequest request,
            HttpServletResponse response) throws IOException {

        response.setContentType("application/json");
        response.setStatus(200);

        PrintWriter out = response.getWriter();
        out.print("{");
        out.print("\"title\": \"Page d'accueil\",");
        out.print("\"content\": \"Bienvenue sur la page d'accueil !\"");
        out.print("}");
        out.flush();
    }
}

Redémarrez le serveur et navigez une nouvelle fois vers http://localhost:8080/. Vous remarquez que pour le navigateur le serveur renvoie une représentation HTML.

Pour obtenir une représentation JSON, on peut utiliser l’utilitaire Postman. Installez l’utilitaire et créez un compte si ce n’est pas déjà fait, puis créez une nouvelle requête HTTP pour l’URI http://localhost:8080/. Avant d’envoyer la requête, affichez les entêtes, désélectionnez l’entête Accept: */* et ajoutez un entête accept: application/json.

Page d'accueil avec template.

Des annotations qui simplifient le code

Le framework Spring propose des annotations qui permettent de simplifier le code.

Dans notre exemple, plutôt que d’utiliser l’objet response pour spécifier le code de statut, on peut utiliser l’annotation @ResponseStatus. Toutefois, dans le cas du code de statut 200, comme il s’agit du code de statut par défaut, l’annotation peut être omise.

De plus, puisque notre code se limite maintenant à écrire une chaîne de caractère dans le flux de sortie de l’objet response, on peut utiliser l’annotation @ResponseBody pour indiquer que la valeur de retour de la méthode doit être interprété comme le contenu de la réponse et pas comme le nom d’un template.

Et comme nous n’utilisons plus ni le paramètre request ni le paramètre response, nous pouvons purement et simplement les supprimer.

Pour finir, on peut encore éliminer un peu de code en utilisant l’annotation @GetMapping plutôt que l’annotation @RequestMapping avec l’attribut method.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
package ch.epai.ict.m295.firstserver;

import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.ResponseStatus;

@Controller
public class HomeController {

    @GetMapping(value = "/")
    @ResponseStatus(HttpStatus.OK) // peut être omise
    public String handleGetHome(Model model) {

        // Récupère des données d'une manière ou d'une autre
        String title = "Page d'accueil";
        String data = "Bienvenue sur la page d'accueil !";

        model.addAttribute("title", title);
        model.addAttribute("mydata", data);

        // La valeur de retour est le nom du template
        return "home";
    }

    @GetMapping(value = "/", produces = "application/json")
    @ResponseStatus(HttpStatus.OK) // peut être omise
    @ResponseBody
    public String handleGetHome() {
        StringBuilder sb = new StringBuilder();
        sb.append("{");
        sb.append("  \"title\" : \"Page d'accueil\"");
        sb.append("  \"content\" : \"Bienvenue sur la page d'accueil !\"");
        sb.append("}");

        // La valeur de retour est le contenu de la réponse (response body)
        return sb.toString();
    }
}

Redémarrez l’application et assurez-vous qu’elle fonctionne correctement dans le navigateur et dans Postman.

Produire du JSON directement avec des objets

Jusque là, nous avons produit la représentation JSON de la ressource directement sous la forme d’une chaîne de caractère. Cela fonctionne, mais ce n’est pas très pratique. Comme JSON est un moyen de représenter des objets et des tableaux, nous pouvons utiliser le framework pour produire une représentation JSON à partir d’un objet ou d’une liste Java.

Définissons par exemple, la classe Message:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
package ch.epai.ict.m295.firstserver;

public class Message {
    private String title;
    private String content;

    public Message(String title, String content) {
        this.title = title;
        this.content = content;
    }

    public String getTitle() {
        return title;
    }

    public String getContent() {
        return content;
    }
}

Nous pouvons maintenant renvoyer une instance de cette classe comme contenu de notre réponse et le framework se charge de produire une représentation JSON de cet objet en utilisant le nom des variables membres comme nom des attributs (ici, title et content).

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
package ch.epai.ict.m295.firstserver;

import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ResponseBody;

@Controller
public class HomeController {

    @GetMapping(value = "/")
    public String handleGetHome(Model model) {

        // Récupère des données d'une manière ou d'une autre
        String title = "Page d'accueil";
        String data = "Bienvenue sur la page d'accueil !";

        model.addAttribute("title", title);
        model.addAttribute("mydata", data);

        // La valeur de retour est le nom du template
        return "home";
    }

    @GetMapping(value = "/", produces = "application/json")
    @ResponseBody
    public Message handleGetHome() {
        Message message = new Message("Page d'accueil", "Bienvenue sur la page d'accueil !");
        return message;
    }
}

Redémarrez une dernière fois l’application et assurez-vous qu’elle fonctionne correctement dans le navigateur et dans Postman.