IdentifiantMot de passe
Loading...
Mot de passe oublié ?Je m'inscris ! (gratuit)

Tutoriel pour apprendre à développer les Microcservices REST avec Spring Boot et Spring RestTemplate

Projet Spring Boot Image non disponible


précédentsommairesuivant

II. Consommation des services

II-A. Introduction

Dans cette deuxième partie du tutoriel, je vais développer un client REST en utilisant le Framework Spring RestTemplate et SpringMVC. Le service à consommer par le client a été développé dans la première partie et est disponible ici.

Spring RestTemplate est un Framework de Spring qui permet d'établir une communication entre un client et un serveur REST, ceci grâce aux requêtes HTTP.

SpringMVC permet de faire le lien entre le contrôleur et les pages JSP grâce aux mappings des objets Models (Model, Map, ModelAndView).

Le contrôleur intercepte toutes les requêtes et les transmet (grâce à Spring RestTemplate) ensuite au service REST qui lui renvoie une réponse, et c'est cette réponse qui est remise au Model à travers une variable pour affichage dans une page JSP.

II-B. Création du projet client

Le socle projet sera créé par génération depuis le site web de Spring Boot dont voici le lien : https://start.spring.io/. Vous pouvez néanmoins créer manuellement un projet Maven classique et le compléter. Avant de créer le socle projet, nous savons que notre application est une application web.

Voici donc les dépendances nécessaires :

  • Web : permet à Spring Boot de rapatrier toutes les dépendances pour une application web (SpringMVC, SpringContext etc.).
  • Packaging : War, car l'application sera déployée sur un serveur d'application.
  • Java Version 8.
  • Pour accéder à tous les modules fournis par Spring Boot, cliquez sur Switch to full version.
Génération Spring Boot Client Rest
Génération Spring Boot Client Rest

Le résultat de la génération est un fichier zip springboot-restclient.zip que vous devez décompresser et importer dans votre IDE préféré (dans mon cas, il s'agit d'Eclipse) en suivant la procédure suivante :

Fichier --> Import … --> Existing Maven Projects --> Next --> Indiquer le chemin au pom.xml --> Finish

Structure projet client généré par Spring Boot
Structure projet client généré par Spring Boot

Voici le contenu des deux classes générées :

ServletInitializer
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
package com.bnguimgo.springbootrestclient;

import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.boot.web.support.SpringBootServletInitializer;

public class ServletInitializer extends SpringBootServletInitializer {

    @Override
    protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {
        return application.sources(SpringbootRestclientApplication.class);
    }
}

La classe ServletInitializer étend SpringBootServletInitializer qui permet de déployer l'application en tant qu'application web. Elle initialise également la classe de démarrage.

SpringbootRestclientApplication
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
package com.bnguimgo.springbootrestclient;

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

@SpringBootApplication
public class SpringbootRestclientApplication {

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

La classe SpringbootRestclientApplication est le point d'entrée de l'application, et possède à cet effet la méthode main(String[] args) qui exécute l'application au démarrage

Il faut compléter le pom.xml avec les propriétés et dépendances manquantes. Dans la partie propriété, j'ai indiqué quel est le point d'entrée de l'application, et la version de Tomcat. Ces deux configurations restent optionnelles, car Spring boot sait se débrouiller tout seul. Mais, comme je vais utiliser un Tomcat externe, il faut renseigner la version de Tomcat. Vous pouvez donc supprimer la dépendance de Tomcat ajoutée par défaut lors de la génération.

 
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
<properties>
<start-class>com.bnguimgo.springbootrestclient.SpringbootRestclientApplication</start-class><!-- cette déclaration est optionnelle  -->
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
    <java.version>1.8</java.version>
    <tomcat.version>8.0.41</tomcat.version><!-- Permet de modifier la version de Tomcat embarquée  -->
</properties>

Comme on va développer les pages web en utilisant la technologie JSP, il faut ajouter la dépendance jstl, car Spring Boot ne sait pas d'avance quel client web il va supporter. Cependant, Spring Boot intègre un Framework de Template Thymeleaf que je ne vais pas utiliser dans le cadre de ce projet.
On va donc ajouter cette dépendance :

 
Sélectionnez
1.
2.
3.
4.
<dependency>
    <groupId>javax.servlet</groupId>
    <artifactId>jstl</artifactId>
</dependency>

Pensez à faire de temps en temps une synchronisation des dépendances dans Eclipse en faisant un clic droit --> Maven --> Update Projects …

J'ai supprimé les dépendances spring-boot-starter-tomcat et spring-boot-starter-test puisque nous n'en aurons pas besoin. Il faut aussi supprimer le package de tests. Pour les tests unitaires et tests d'intégration, se référer à la première partie de ce tutoriel.
Voici le pom.xml final de l'application :

pom.xml final
Sélectionnez
<?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>com.bnguimgo</groupId>
    <artifactId>springboot-restclient</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <packaging>war</packaging>

    <name>springboot-restclient</name>
    <description>Client REST utilisant le framework Spring Boot</description>

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

    <properties>
        <start-class>com.bnguimgo.springbootrestclient.SpringbootRestclientApplication</start-class><!-- cette déclaration est optionnelle  -->
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
        <java.version>1.8</java.version>
        <tomcat.version>9.0.24</tomcat.version><!-- optionnelle, permet de modifier la version de Tomcat embarquée  -->
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <!-- obligatoire: JSTL pour les pages JSP, car Spring Boot ne sais pas quel type de client vous allez utiliser (JSP, AngularJs, VueJs, etc.) -->
        <dependency>
            <groupId>javax.servlet</groupId>
            <artifactId>jstl</artifactId>
        </dependency>
<!-- Dépendances pour les tests uniquement -->
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-test</artifactId>
		</dependency>
        <dependency>
            <groupId>com.jayway.jsonpath</groupId>
            <artifactId>json-path</artifactId>
            <scope>test</scope>
        </dependency>        
    </dependencies>

    <build>
    <finalName>springboot-restclient</finalName>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>                
            </plugin>
        </plugins>
    </build>
</project>

En résumé, le pom.xml ne contient que deux dépendances :

  • Une dépendance spring-boot-starter-web pour la partie web.
  • Une dépendance JSTL pour les servlets et pages JSP.

Il faut exécuter un premier build pour vérifier que la configuration est bonne.

II-C. Création du contrôleur

Je vais créer un contrôleur par défaut dont le rôle est d'envoyer une requête ping au serveur pour vérifier si le serveur est démarré et donc disponible, sinon, une page d'erreur est affichée.

II-C-1. Contrôleur par défaut

C'est le contrôleur qui s'écutera en premier. Dès le chargement de l'application,il va autommatiquement contacter le serveur Rest.

Créez un package controller et ajoutez la classe WelcomeController.

contrôleur WelcomeController
Sélectionnez
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.
45.
46.
47.
48.
49.
50.
51.
52.
53.
54.
package com.bnguimgo.springbootrestclient.controller;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.client.RestTemplate;
import org.springframework.web.servlet.ModelAndView;

@Controller
public class WelcomeController {

	private static Logger logger = LoggerFactory.getLogger(WelcomeController.class);

	@Autowired
	private RestTemplate restTemplate;

	@Autowired
	private PropertiesConfigurationService configurationService;

	@GetMapping(value = "/")
	ModelAndView ping(ModelAndView modelAndView) {
		// On vérifie directement la disponibilité du serveur au lancement de l'application
		ResponseEntity<String> reponseServeur = restTemplate.getForEntity(configurationService.getUrl(), String.class);
		int codeReponseServeur = reponseServeur.getStatusCodeValue();
		String reponsePing = "";
		if (codeReponseServeur != 200) {
			logger.error("Réponse du serveur: " + codeReponseServeur+ " ==> Serveur indisponible, votre application ne fonctionnera pas correctement");
			reponsePing = configurationService.getPingServeurKo();
		} else {
			reponsePing = configurationService.getPingServeurOk();
			logger.info(configurationService.getPingServeur(), reponsePing);
		}
		// construction de la vue
		modelAndView.setViewName("welcome");
		modelAndView.addObject("urlServeur", configurationService.getUrl());
		modelAndView.addObject("pingServeur", reponsePing);
		modelAndView.addObject("profileActif", configurationService.getProfileActif());
		return modelAndView;
	}

	@GetMapping(value = "/error")
	public String error() {
		return "error";
	}

	@GetMapping("/next")
	ModelAndView next(ModelAndView modelAndView) {
		modelAndView.setViewName("next");
		modelAndView.addObject("message", configurationService.getMessage());
		return modelAndView;
	}
}

L'injection par @Autowired de RestTemplate va permettre de construire les requêtes HTTP nécessaires pour établir la communication avec le serveur.

Grâce à l'annotation @GetMapping(value="/"), la méthode ModelAndView ping(ModelAndView modelAndView) {xxx } envoie une requête "ping" au serveur à l'adresse app.serveur.url = http://localhost:8080/springboot-restserver/. Cette requête est construite avec RestTemplate

Si la réponse reçue correspond au code HttpStatus = 200, c'est que le serveur est disponible. On construit ensuite la vue welcome.jsp grâce à la classe ModelAndView fournie par Spring en lui passant en paramètres la page à afficher, l'URL du serveur, la réponse du serveur, et enfin le profil utilisateur.

Si le serveur ne répond pas, une page d'erreur error.jsp sera construite et renvoyée grâce à la méthode :

code de la page d'erreur
Sélectionnez
1.
2.
3.
4.
@GetMapping(value = "/error")
    public String error() {
        return "error";
    }

L'adresse du serveur est configurée dans le fichier application.properties et accessible grâce à la classe utilitaire ci-dessous.

Classe utilitaire PropertiesConfigurationService
Sélectionnez
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.
45.
46.
47.
48.
49.
50.
51.
package com.bnguimgo.springbootrestclient.controller;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;

@Service
public class PropertiesConfigurationService{
 
    @Value("${app.serveur.url}") // injection via application.properties
    private String url="";
    
    @Value("${app.serveur.pingServeur}")
    private String pingServeur;
    
    @Value("${app.serveur.ok}")
    private String pingServeurOk;
    
    @Value("${app.serveur.ko}")
    private String pingServeurKo;
    
    @Value("${nextpage.message}")
    private String message;
    
    @Value("${spring.profiles.active}")
    private String profileActif;
    // getters, setters
    
    public String getUrl() {
        return url;
    }

    public String getPingServeur() {
        return pingServeur;
    }
    
    public String getPingServeurOk() {
        return pingServeurOk;
    }
    
    public String getPingServeurKo() {
        return pingServeurKo;
    }

    public String getMessage() {
        return message;
    }

    public String getProfileActif() {
        return profileActif;
    }
}

Cette classe utilitaire PropertiesConfigurationService permet de récupérer l'URL du serveur stockée dans le fichier de configuration application.properties. Cette classe permet également de récupérer toutes les autres variables du fichier de configuration. Le principal avantage de cette classe, c'est qu'elle permet d'éviter qu'une même variable soit injectée dans plusieurs classes, ce qui rend la maintenance facile.

Voici les trois fichiers .properties à ajouter dans source/main/resources/

Le fichier application.properties contient les propriétés globales de l'application
Sélectionnez
1.
2.
3.
4.
#Au démarrage de l'application, en cas d'erreur
server.error.whitelabel.enabled = false
#Chargement du profil par defaut dev
spring.profiles.active=dev
Le fichier application-dev-properties contient la configuration nécessaire en phase de développement
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
#URL du serveur REST
app.serveur.url = http://localhost:8080/springboot-restserver/
nextpage.message = Salut vous &ecirc;tes en profile dev
app.serveur.ok = disponible
app.serveur.ko = indisponible
app.serveur.pingServeur = Le serveur est {}
compte.user.exist = Un utilisateur est d&eacute;j&agrave; enregistr&eacute; avec ce compte

#GESTION DES LOGS
logging.level.org.springframework.web=DEBUG
logging.level.com.bnguimgo.restclient=DEBUG

# Pattern impression des logs consoles
logging.pattern.console= %d{yyyy-MM-dd HH:mm:ss} - %msg%n

# Pattern impression des logs dans un fichier
logging.pattern.file= %d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger{36} - %msg%n
# Redirection des logs vers un fichier du repertoire Temp, exemple sur windows: C:\Users\UnserName\AppData\Local\Temp\
logging.file= ${java.io.tmpdir}\logs\\restClient\\applicationRestClient.log
Le fichier application-prod-properties contient la configuration nécessaire en phase de production
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
logging.level.org.springframework.web=ERROR
logging.level.com.bnguimgo.restclient=ERROR
#
# Pattern impression des logs console
logging.pattern.console= %d{yyyy-MM-dd HH:mm:ss} - %msg%n

# Pattern impression des logs dans un fichier
logging.pattern.file= %d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger{36} - %msg%n
# Redirection des logs vers un fichier du repertoire Temp, exemple sur windows: C:\Users\UnserName\AppData\Local\Temp\
logging.file=${java.io.tmpdir}/logs/rest/applicationRest.log

II-C-2. Contrôleur LoginController

Ce contrôleur gère tous les services CRUD relatifs au compte utilisateur.

LoginController
Sélectionnez
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.
45.
46.
47.
48.
49.
50.
51.
52.
53.
54.
55.
56.
57.
58.
59.
60.
61.
62.
63.
64.
65.
66.
67.
68.
69.
70.
71.
72.
73.
74.
75.
76.
77.
78.
79.
80.
81.
82.
83.
84.
85.
86.
87.
88.
89.
90.
91.
92.
93.
94.
95.
96.
97.
98.
99.
100.
101.
102.
103.
104.
105.
106.
107.
108.
109.
110.
111.
112.
113.
114.
115.
116.
117.
118.
119.
120.
121.
122.
123.
124.
125.
126.
127.
128.
129.
130.
131.
132.
133.
134.
135.
136.
137.
138.
139.
140.
141.
142.
143.
144.
145.
146.
147.
148.
149.
150.
151.
152.
153.
154.
155.
156.
157.
158.
159.
160.
161.
162.
163.
164.
165.
166.
167.
168.
169.
170.
171.
172.
173.
174.
175.
176.
177.
178.
179.
180.
181.
182.
183.
184.
185.
186.
187.
188.
189.
190.
package com.bnguimgo.springbootrestclient.controller;

import java.util.HashMap;
import java.util.Map;
import java.util.Set;

import javax.validation.Valid;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpMethod;
import org.springframework.http.ResponseEntity;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.HttpClientErrorException;
import org.springframework.web.client.HttpServerErrorException;
import org.springframework.web.client.RestTemplate;
import org.springframework.web.servlet.ModelAndView;

import com.bnguimgo.springbootrestclient.dto.RestClientExceptionDTO;
import com.bnguimgo.springbootrestclient.dto.UserDto;
import com.bnguimgo.springbootrestclient.entities.User;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.JsonMappingException;
import com.fasterxml.jackson.databind.ObjectMapper;

@RestController
public class LoginController {

	private static Logger logger = LoggerFactory.getLogger(LoginController.class);

	@Autowired
	private RestTemplate restTemplate;

	@Autowired
	private PropertiesConfigurationService configurationService;

	@GetMapping(value = "/login") // initialisation du login
	public ModelAndView loginView(ModelAndView modelAndView) {
		modelAndView.setViewName("loginForm");
		UserDto userDto = new UserDto();
		modelAndView.addObject("userForm", userDto);
		return modelAndView;
	}

	@PostMapping(value = "/login")
	public ModelAndView login(@Valid @ModelAttribute("userForm") UserDto userForm, BindingResult bindingResult,
			ModelAndView modelAndView) throws JsonMappingException, JsonProcessingException {

		modelAndView.setViewName("loginForm");
		if (bindingResult.hasErrors()) {
			modelAndView.addObject("saveError", "Erreur de saisie");
			return modelAndView;
		}
		try {
			ResponseEntity<User> userExists = restTemplate
					.postForEntity(configurationService.getUrl() + "user/users/login", new User(userForm), User.class);

			modelAndView.addObject("currentUser", new UserDto(userExists.getBody()));
			modelAndView.setViewName("loginSuccess");
			return modelAndView;

		} catch (HttpClientErrorException | HttpServerErrorException ex) {
			ObjectMapper objectMapper = new ObjectMapper();
			RestClientExceptionDTO restClientExceptionDTO = objectMapper.readValue(ex.getResponseBodyAsString(),
					new TypeReference<RestClientExceptionDTO>() {
					});
			modelAndView.setViewName("loginForm");
			modelAndView.addObject("saveError", restClientExceptionDTO.getErrorMessage());
			modelAndView.addObject("createAccount", " Créez votre compte !");
			return modelAndView;
		} catch (Exception ex) {
			modelAndView.setViewName("loginForm");
			modelAndView.addObject("saveError", ex.getMessage());
			modelAndView.addObject("createAccount", " Créez votre compte !");
			return modelAndView;
		}

	}

	@GetMapping(value = "/registration") // initialisation du formulaire de création du compte
	public ModelAndView registrationView(ModelAndView modelAndView) {
		modelAndView.setViewName("registrationForm");
		UserDto userRegistrationForm = new UserDto();
		modelAndView.addObject("registrationForm", userRegistrationForm);
		return modelAndView;
	}

	@PostMapping(value = "/registration")
	public ModelAndView saveUser(@Valid @ModelAttribute("registrationForm") UserDto userDtoForm,
			BindingResult bindingResult, ModelAndView modelAndView)
			throws JsonMappingException, JsonProcessingException {
		modelAndView.setViewName("registrationForm");
		if (bindingResult.hasErrors()) {
			return modelAndView;
		}
		try {
			ResponseEntity<User> userEntitySaved = restTemplate.postForEntity(configurationService.getUrl() + "user/users", new User(userDtoForm), User.class);
			User user = userEntitySaved.getBody();
			modelAndView.addObject("currentUser", new UserDto(user));
			modelAndView.setViewName("loginSuccess");
			return modelAndView;

		} catch (HttpClientErrorException | HttpServerErrorException ex) {
			ObjectMapper objectMapper = new ObjectMapper();
			RestClientExceptionDTO restClientExceptionDTO = objectMapper.readValue(ex.getResponseBodyAsString(),
					new TypeReference<RestClientExceptionDTO>() {
					});
			modelAndView.addObject("saveError", restClientExceptionDTO.getErrorMessage());
			bindingResult.rejectValue("login", "error.user", restClientExceptionDTO.getErrorMessage());
			// login doit être un attribut réel du formulaire UserDto
			return modelAndView;

		} catch (Exception ex) {
			logger.info("Erreur technique de création du compte", ex);
			modelAndView.addObject("saveError", "erreur technique de création du compte");
			return modelAndView;

		}
	}
	
	@GetMapping(value = "/{id}")
	public ModelAndView findUserById(@PathVariable(value = "id") Long id, ModelAndView modelAndView) throws JsonMappingException, JsonProcessingException {
		modelAndView.setViewName("updateUserForm");
		Map<String, Long> variables = new HashMap<>(1);
        variables.put("id", new Long(id));
		try {
			ResponseEntity<User> userEntityFound = restTemplate.getForEntity(configurationService.getUrl() + "user/users/"+String.valueOf(id), User.class);
			UserDto userDto = new UserDto(userEntityFound.getBody());
		modelAndView.addObject("currentUserForm", userDto);
		return modelAndView;
		} catch (HttpClientErrorException | HttpServerErrorException ex) {
			ObjectMapper objectMapper = new ObjectMapper();
			RestClientExceptionDTO restClientExceptionDTO = objectMapper.readValue(ex.getResponseBodyAsString(),
					new TypeReference<RestClientExceptionDTO>() {
					});
			modelAndView.addObject("userNotFoundError", restClientExceptionDTO.getErrorMessage());
			return modelAndView;
		} catch (Exception ex) {
			logger.info("Erreur technique de recherche utilisateur", ex);
			modelAndView.addObject("saveError", "Erreur technique de recherche utilisateur");
			return modelAndView;

		}
	}
	
	@PostMapping(value = "/updateuser")
	public ModelAndView updateUser(@Valid @ModelAttribute("currentUserForm") UserDto userDtoForm, @RequestParam(value ="roles", required=false) Set<String>roles,
			BindingResult bindingResult, ModelAndView modelAndView)
			throws JsonMappingException, JsonProcessingException {
		modelAndView.setViewName("updateUserForm");
		if (bindingResult.hasErrors()) {
			return modelAndView;
		}
		try {
			HttpEntity<User> requestEntity = new HttpEntity<User>(new User(userDtoForm));
			ResponseEntity<User> userEntityUpdated = restTemplate
					.exchange(configurationService.getUrl() + "user/users", HttpMethod.PUT, requestEntity, User.class);
			UserDto userDto = new UserDto(userEntityUpdated.getBody());
			modelAndView.addObject("currentUserForm", userDto);
			modelAndView.addObject("updateSucess", "Mise à jour Ok !");
			modelAndView.setViewName("updateUserForm");
			return modelAndView;

		} catch (HttpClientErrorException | HttpServerErrorException ex) {
			ObjectMapper objectMapper = new ObjectMapper();
			RestClientExceptionDTO restClientExceptionDTO = objectMapper.readValue(ex.getResponseBodyAsString(),
					new TypeReference<RestClientExceptionDTO>() {
					});
			modelAndView.addObject("saveError", restClientExceptionDTO.getErrorMessage());
			bindingResult.rejectValue("login", "error.user", restClientExceptionDTO.getErrorMessage());// login doit être un attribut réel du formulaire UserDto
			return modelAndView;

		} catch (Exception ex) {
			logger.info("Erreur technique de mise à jour", ex);
			modelAndView.addObject("saveError", "erreur technique de mise à jour.");
			return modelAndView;

		}

	}
}

II-C-3. Rôle de chaque méthode

Il faut deux méthodes pour gérer le login: une pour initialiser l'objet qui va contenir les informations de login, une autre pour gérer le login proprement dit.

Méthode d'initialisation du login

Méthode d'initialisation du login
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
	@GetMapping(value = "/login")
	public ModelAndView loginView(ModelAndView modelAndView) {
		modelAndView.setViewName("loginForm");
		UserDto userDto = new UserDto();
		modelAndView.addObject("userForm", userDto);
		return modelAndView;
	}
La méthode ci-dessus initialise la vue jsp qui sera renvoyée
  • @GetMapping(value = "/login") permet d'intercepter toutes les requêtes HTTP GET de la forme: http://localhost:8080/springboot-restclient/login.
  • C'est cette requête qui déclenche l'initialisation de la vue.
  • La vue va s'appeler loginForm.jsp.
  • Les informations de login seront stockées dans userDto.
  • L'ensemble loginForm + userDto sera stocké dans un objet modèle spring modelAndView et renvoyer comme réponse à la réquête http://localhost:8080/springboot-restclient/login.
  • La classe UserDto et le formulaire loginForm.jsp sont disponibles en téléchargementTutoSpringBoot_et_REST.zip.

Méthode de gestion du formulaire de connexion:

Méthode de gestion du formulaire du login
Sélectionnez
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.
@PostMapping(value = "/login")
public ModelAndView login(@Valid @ModelAttribute("userForm") UserDto userForm, BindingResult bindingResult,
		ModelAndView modelAndView) throws JsonMappingException, JsonProcessingException {

	modelAndView.setViewName("loginForm");
	if (bindingResult.hasErrors()) {
		modelAndView.addObject("saveError", "Erreur de saisie");
		return modelAndView;
	}
	try {
		ResponseEntity<User> userExists = restTemplate
				.postForEntity(configurationService.getUrl() + "user/users/login", new User(userForm), User.class);

		modelAndView.addObject("currentUser", new UserDto(userExists.getBody()));
		modelAndView.setViewName("loginSuccess");
		return modelAndView;

	} catch (HttpClientErrorException | HttpServerErrorException ex) {
		ObjectMapper objectMapper = new ObjectMapper();
		RestClientExceptionDTO restClientExceptionDTO = objectMapper.readValue(ex.getResponseBodyAsString(),
				new TypeReference<RestClientExceptionDTO>() {
				});
		modelAndView.setViewName("loginForm");
		modelAndView.addObject("saveError", restClientExceptionDTO.getErrorMessage());
		modelAndView.addObject("createAccount", " Créez votre compte !");
		return modelAndView;
	} catch (Exception ex) {
		modelAndView.setViewName("loginForm");
		modelAndView.addObject("saveError", ex.getMessage());
		modelAndView.addObject("createAccount", " Créez votre compte !");
		return modelAndView;
	}

}
La méthode ci-dessus traite les données du formulaire loginForm.jsp
  • @PostMapping(value = "/login") permet d'intercepter toutes les requêtes HTTP POST de la forme: http://localhost:8080/springboot-restclient/login.
  • C'est cette requête qui déclenche l'appel du service Rest.
  • L'annotation @Valid permet de vérifier et de valider les champs obligatoires de l'objet userForm. Il faut ouvrir la classe UserDto pour voir ces champs à valider, c'est le cas du champ password.
  • L'annotation @ModelAttribute("userForm") de spring crée la liaison entre le formulaire et l'objet UserDto.
  • La classe BindingResult collecte et gère les erreurs du formulaire en relation avec l'objet UserDto.
  • S'il y a une erreur, on la stocke dans la variable saveError qu'on affichera à travers un label dans l'IHM
  • On construit la requête HTTP POST grâce à la classe RestTemplate, en lui passant l'URL, le contenu du formulaire, et le type de réponse attendu.
  • La réponse à la requête sera stockée dans la variable objet currentUser.
  • Si tout se passe bien, une nouvelle page loginSuccess.jsp est construite et renvoyée.
  • En cas d'erreur, on parse l'erreur et on stocke le résultat dans l'objet restClientExceptionDTO, puis on renvoie la page initiale loginForm.jsp et on propose à l'utilisateur de créer son compte.

Pour enregistrer un utilisateur, il faut suivre la même démarche ci-dessus, à savoir, initialiser d'abord un objet userRegistrationForm, puis faire la sauvegarde après contrôle des champs obligatoires.

Pour la mise à jour, on utilise plutôt restTemplate.exchange(xxx), car avec restTemplate.put(xxx), on n'a pas assez de flexibilités pour gérer les réponses HTTP

II-C-4. Configuration de l'application

Dans cette section, je vais créer une classe de configuration MvcConfiguration juste pour indiquer à Spring Boot où se trouvent les pages web à charger, puisque je ne vais pas utiliser la configuration par défaut qui consiste à mettre toutes les pages statiques dans le dossier source/main/resources/static généré. Créez un package config et ajoutez cette classe:

MvcConfiguration
Sélectionnez
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.
45.
46.
47.
package com.bnguimgo.springbootrestclient.config;

import java.time.Duration;

import org.springframework.boot.web.client.RestTemplateBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.client.RestTemplate;
import org.springframework.web.servlet.config.annotation.DefaultServletHandlerConfigurer;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.ViewResolverRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import org.springframework.web.servlet.view.InternalResourceViewResolver;
import org.springframework.web.servlet.view.JstlView;

@Configuration
@EnableWebMvc
@ComponentScan
public class MvcConfiguration implements WebMvcConfigurer {
	
    @Override
    public void configureViewResolvers(ViewResolverRegistry registry) {
        InternalResourceViewResolver resolver = new InternalResourceViewResolver();
        resolver.setPrefix("/WEB-INF/views/");
        resolver.setSuffix(".jsp");
        resolver.setOrder(1);
        resolver.setViewClass(JstlView.class);
        registry.viewResolver(resolver);
    }
    @Override
    public void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) {
        configurer.enable();
    }
    
	@Bean
	public RestTemplate restTemplate(RestTemplateBuilder builder) {
		builder.setConnectTimeout(Duration.ofSeconds(10));//set timeout connection
		return builder.build();
	}
	//Permet de spécifier le chemin d'accès aux fichiers de resources statiques comme le CSS, les images etc.
	@Override
	public void addResourceHandlers(final ResourceHandlerRegistry registry) {
        registry.addResourceHandler("/resources/**").addResourceLocations("/resources/");
    }
}

D'après cette configuration, toutes les pages web (vues) seront dans le dossier "/WEB-INF/views/".

L'annotation @Configuration indique que cette classe peut être utilisée par le conteneur Spring comme une source de définition des beans. On constate par ailleurs que le bean restTemplate y a été initialisé.

Si on ne déclarait pas le bean restTemplate, il serait impossible d'injecter RestTemplate par @Autowired dans le contrôleur WelcomeController, à défaut il faut faire un new RestTemplate() à chaque appel de RestTemplate.

Depuis Spring-4, on ne peut plus injecter directement RestTemplate par @Autowired car, la classe RestTemplate a été est migrée dans RestTemplateBuilder qui offre plus de méthodes, comme la configuration du timeout, etc.

L'annotation @EnableWebMvc est l'équivalent de mvc:annotation-driven si on utilisait la configuration XML. Cette annotation active le contexte SpringMVC, et permet d'utiliser les annotations @Controller.

@ComponentScan scanne par défaut toutes les classes du même package pour trouver les classes annotées.

II-C-5. Création des pages web

Créez les dossiers WEB-INF/views/ dans webapp et ajoutez les vues.jsp. Toutes les vues que j'ai créées sont disponibles en téléchargement dans le répertoire WEB-INF/views/.

Les vues (pages web) et toutes les sources sont disponibles en téléchargementTutoSpringBoot_et_REST.zip.

II-D. Test manuel de l'application

Commencer par démarrer le serveur, ensuite le client (clic droit sur le projet --> Run As --> Run On Server --> Choisir Tomcat-8). Voici l'URL de test : http://localhost:8080/springboot-restclient/.

II-D-1. Serveur démarré

Serveur REST démarré et à l'écoute
Serveur REST démarré et à l'écoute

II-D-2. Serveur arrêté

Si on démarre le client alors que le serveur est arrêté, on aura l'erreur ci-dessous:

Serveur REST arrêté
Serveur REST arrêté

II-D-3. Création d'un utilisateur

Ecran création compte utilisateur
Écran création compte utilisateur

II-D-4. Test de connexion

Ecran de connexion
Écran de connexion

II-D-5. Page de connexion réussie

connexion avec succès
Connexion avec succès

précédentsommairesuivant

Les sources présentées sur cette page sont libres de droits et vous pouvez les utiliser à votre convenance. Par contre, la page de présentation constitue une œuvre intellectuelle protégée par les droits d'auteur. Copyright © 2018 Nguimgo Bertrand. Aucune reproduction, même partielle, ne peut être faite de ce site ni de l'ensemble de son contenu : textes, documents, images, etc. sans l'autorisation expresse de l'auteur. Sinon vous encourez selon la loi jusqu'à trois ans de prison et jusqu'à 300 000 € de dommages et intérêts.