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.
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
Voici le contenu des deux classes générées :
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.
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.
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 :
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 :
<?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.
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 :
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.
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/
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
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] %-
5
level %
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
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] %-
5
level %
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.
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
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;
}
- @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:
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;
}
}
- @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:
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é▲
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: