Spring est l’un des frameworks Java EE les plus largement utilisés. Nous avons déjà vu comment utiliser Spring MVC pour créer des applications web basées sur Java. Aujourd’hui, nous allons apprendre à créer des services web REST Spring en utilisant Spring MVC, puis les tester avec le client Rest. En fin de compte, nous examinerons également comment invoquer le service web REST Spring en utilisant l’API Spring RestTemplate.
REST Spring
Nous utiliserons la dernière version de Spring 4.0.0.RELEASE et utiliserons l’intégration Spring Jackson JSON pour envoyer une réponse JSON dans la réponse de l’appel REST. Le tutoriel est développé dans l’IDE Spring STS pour créer facilement du code squelette Spring MVC, puis étendu pour implémenter une architecture Restful. Créez un nouveau projet Spring MVC dans le STS, notre projet final ressemblera à l’image ci-dessous. Nous examinerons chaque composant un par un.
Fichiers de configuration XML Spring REST
Notre fichier pom.xml ressemble à ceci.
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="https://maven.apache.org/POM/4.0.0" xmlns:xsi="https://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="https://maven.apache.org/POM/4.0.0 https://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.journaldev</groupId>
<artifactId>SpringRestExample</artifactId>
<name>SpringRestExample</name>
<packaging>war</packaging>
<version>1.0.0-BUILD-SNAPSHOT</version>
<properties>
<java-version>1.6</java-version>
<org.springframework-version>4.0.0.RELEASE</org.springframework-version>
<org.aspectj-version>1.7.4</org.aspectj-version>
<org.slf4j-version>1.7.5</org.slf4j-version>
<jackson.databind-version>2.2.3</jackson.databind-version>
</properties>
<dependencies>
<!-- Jackson -->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>${jackson.databind-version}</version>
</dependency>
<!-- Spring -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>${org.springframework-version}</version>
<exclusions>
<!-- Exclude Commons Logging in favor of SLF4j -->
<exclusion>
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>${org.springframework-version}</version>
</dependency>
<!-- AspectJ -->
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjrt</artifactId>
<version>${org.aspectj-version}</version>
</dependency>
<!-- Logging -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>${org.slf4j-version}</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>jcl-over-slf4j</artifactId>
<version>${org.slf4j-version}</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
<version>${org.slf4j-version}</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.15</version>
<exclusions>
<exclusion>
<groupId>javax.mail</groupId>
<artifactId>mail</artifactId>
</exclusion>
<exclusion>
<groupId>javax.jms</groupId>
<artifactId>jms</artifactId>
</exclusion>
<exclusion>
<groupId>com.sun.jdmk</groupId>
<artifactId>jmxtools</artifactId>
</exclusion>
<exclusion>
<groupId>com.sun.jmx</groupId>
<artifactId>jmxri</artifactId>
</exclusion>
</exclusions>
<scope>runtime</scope>
</dependency>
<!-- @Inject -->
<dependency>
<groupId>javax.inject</groupId>
<artifactId>javax.inject</artifactId>
<version>1</version>
</dependency>
<!-- Servlet -->
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>servlet-api</artifactId>
<version>2.5</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>javax.servlet.jsp</groupId>
<artifactId>jsp-api</artifactId>
<version>2.1</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>jstl</artifactId>
<version>1.2</version>
</dependency>
<!-- Test -->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.7</version>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<artifactId>maven-eclipse-plugin</artifactId>
<version>2.9</version>
<configuration>
<additionalProjectnatures>
<projectnature>org.springframework.ide.eclipse.core.springnature</projectnature>
</additionalProjectnatures>
<additionalBuildcommands>
<buildcommand>org.springframework.ide.eclipse.core.springbuilder</buildcommand>
</additionalBuildcommands>
<downloadSources>true</downloadSources>
<downloadJavadocs>true</downloadJavadocs>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>2.5.1</version>
<configuration>
<source>1.6</source>
<target>1.6</target>
<compilerArgument>-Xlint:all</compilerArgument>
<showWarnings>true</showWarnings>
<showDeprecation>true</showDeprecation>
</configuration>
</plugin>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>exec-maven-plugin</artifactId>
<version>1.2.1</version>
<configuration>
<mainClass>org.test.int1.Main</mainClass>
</configuration>
</plugin>
</plugins>
</build>
</project>
L’outil STS génère le fichier pom.xml pour nous. Cependant, j’ai mis à jour la version de Spring Framework, AspectJ, SLF4J et Jackson à la dernière version en date. La plupart de la partie est commune et générée automatiquement, le point important à noter est que j’ai ajouté les bibliothèques Jackson JSON dans les dépendances car nous les utiliserons pour convertir des objets en JSON et vice versa.
<?xml version="1.0" encoding="UTF-8"?>
<web-app version="2.5" xmlns="https://java.sun.com/xml/ns/javaee"
xmlns:xsi="https://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="https://java.sun.com/xml/ns/javaee https://java.sun.com/xml/ns/javaee/web-app_2_5.xsd">
<!-- The definition of the Root Spring Container shared by all Servlets and Filters -->
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/spring/root-context.xml</param-value>
</context-param>
<!-- Creates the Spring Container shared by all Servlets and Filters -->
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<!-- Processes application requests -->
<servlet>
<servlet-name>appServlet</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/spring/appServlet/servlet-context.xml</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>appServlet</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
</web-app>
Ce fichier est généré automatiquement et je n’ai rien modifié. Cependant, si vous souhaitez modifier les fichiers de configuration de contexte et leur emplacement, vous pouvez le faire dans le fichier web.xml.
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="https://www.springframework.org/schema/beans"
xmlns:xsi="https://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="https://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd">
<!-- Root Context: defines shared resources visible to all other web components -->
</beans>
Ce fichier contient les ressources partagées qui seront visibles par tous les composants web. Nous développerons un service REST simple et c’est pourquoi je n’ai rien modifié ici.
<?xml version="1.0" encoding="UTF-8"?>
<beans:beans xmlns="https://www.springframework.org/schema/mvc"
xmlns:xsi="https://www.w3.org/2001/XMLSchema-instance"
xmlns:beans="https://www.springframework.org/schema/beans"
xmlns:context="https://www.springframework.org/schema/context"
xsi:schemaLocation="https://www.springframework.org/schema/mvc https://www.springframework.org/schema/mvc/spring-mvc.xsd
https://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd
https://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd">
<!-- DispatcherServlet Context: defines this servlet's request-processing infrastructure -->
<!-- Enables the Spring MVC @Controller programming model -->
<annotation-driven />
<!-- Handles HTTP GET requests for /resources/** by efficiently serving up static resources in the ${webappRoot}/resources directory -->
<resources mapping="/resources/**" location="/resources/" />
<!-- Resolves views selected for rendering by @Controllers to .jsp resources in the /WEB-INF/views directory -->
<beans:bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<beans:property name="prefix" value="/WEB-INF/views/" />
<beans:property name="suffix" value=".jsp" />
</beans:bean>
<!-- Configure to plugin JSON as request and response in method handler -->
<beans:bean class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter">
<beans:property name="messageConverters">
<beans:list>
<beans:ref bean="jsonMessageConverter"/>
</beans:list>
</beans:property>
</beans:bean>
<!-- Configure bean to convert JSON to POJO and vice versa -->
<beans:bean id="jsonMessageConverter" class="org.springframework.http.converter.json.MappingJackson2HttpMessageConverter">
</beans:bean>
<context:component-scan base-package="com.journaldev.spring.controller" />
</beans:beans>
La majeure partie est générée automatiquement et contient des configurations de base. Cependant, il est important de noter l’élément annotation-driven pour prendre en charge la configuration basée sur les annotations et l’intégration du convertisseur MappingJackson2HttpMessageConverter
dans le RequestMappingHandlerAdapter
messageConverters
afin que l’API Jackson entre en jeu et convertisse JSON en objets Java et vice versa. Grâce à cette configuration, nous utiliserons JSON dans le corps de la requête et nous recevrons des données JSON en réponse.
Classes de modèle Spring REST
Écrivons une simple classe POJO qui servira d’entrée et de sortie pour nos méthodes de service web RESTful.
package com.journaldev.spring.model;
import java.io.Serializable;
import java.util.Date;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import com.fasterxml.jackson.databind.ser.std.DateSerializer;
public class Employee implements Serializable{
private static final long serialVersionUID = -7788619177798333712L;
private int id;
private String name;
private Date createdDate;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@JsonSerialize(using=DateSerializer.class)
public Date getCreatedDate() {
return createdDate;
}
public void setCreatedDate(Date createdDate) {
this.createdDate = createdDate;
}
}
Le seul point important à noter est l’utilisation de l’annotation @JsonSerialize
pour utiliser la classe DateSerializer
pour la conversion de date du type Java au format JSON et vice versa.
Points de terminaison de service Web RESTful Spring
Nous aurons les points de terminaison de services Web REST suivants.
Sl. No | URI | HTTP Method | Details |
---|---|---|---|
1 | /rest/emp/dummy | GET | Health Check service, to insert a dummy data in the Employees data storage |
2 | /rest/emp/{id} | GET | To get the Employee object based on the id |
3 | /rest/emps | GET | To get the list of all the Employees in the data store |
4 | /rest/emp/create | POST | To create the Employee object and store it |
5 | /rest/emp/delete/{id} | PUT | To delete the Employee object from the data storage based on the id |
Nous avons une classe définissant toutes ces URI en tant que constantes de chaîne.
package com.journaldev.spring.controller;
public class EmpRestURIConstants {
public static final String DUMMY_EMP = "/rest/emp/dummy";
public static final String GET_EMP = "/rest/emp/{id}";
public static final String GET_ALL_EMP = "/rest/emps";
public static final String CREATE_EMP = "/rest/emp/create";
public static final String DELETE_EMP = "/rest/emp/delete/{id}";
}
Classe de contrôleur de service Web RESTful Spring
Notre classe EmployeeController publiera tous les points de terminaison de service Web mentionnés ci-dessus. Examinons le code de la classe, puis nous en apprendrons davantage sur chacune des méthodes en détail.
package com.journaldev.spring.controller;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseBody;
import com.journaldev.spring.model.Employee;
/**
* Handles requests for the Employee service.
*/
@Controller
public class EmployeeController {
private static final Logger logger = LoggerFactory.getLogger(EmployeeController.class);
//Map pour stocker les employés, idéalement nous devrions utiliser une base de données
Map empData = new HashMap();
@RequestMapping(value = EmpRestURIConstants.DUMMY_EMP, method = RequestMethod.GET)
public @ResponseBody Employee getDummyEmployee() {
logger.info("Start getDummyEmployee");
Employee emp = new Employee();
emp.setId(9999);
emp.setName("Dummy");
emp.setCreatedDate(new Date());
empData.put(9999, emp);
return emp;
}
@RequestMapping(value = EmpRestURIConstants.GET_EMP, method = RequestMethod.GET)
public @ResponseBody Employee getEmployee(@PathVariable("id") int empId) {
logger.info("Start getEmployee. ID="+empId);
return empData.get(empId);
}
@RequestMapping(value = EmpRestURIConstants.GET_ALL_EMP, method = RequestMethod.GET)
public @ResponseBody List getAllEmployees() {
logger.info("Start getAllEmployees.");
List emps = new ArrayList();
Set empIdKeys = empData.keySet();
for(Integer i : empIdKeys){
emps.add(empData.get(i));
}
return emps;
}
@RequestMapping(value = EmpRestURIConstants.CREATE_EMP, method = RequestMethod.POST)
public @ResponseBody Employee createEmployee(@RequestBody Employee emp) {
logger.info("Start createEmployee.");
emp.setCreatedDate(new Date());
empData.put(emp.getId(), emp);
return emp;
}
@RequestMapping(value = EmpRestURIConstants.DELETE_EMP, method = RequestMethod.PUT)
public @ResponseBody Employee deleteEmployee(@PathVariable("id") int empId) {
logger.info("Start deleteEmployee.");
Employee emp = empData.get(empId);
empData.remove(empId);
return emp;
}
}
Pour simplifier, je stocke toutes les données des employés dans la HashMap empData. L’annotation @RequestMapping est utilisée pour mapper l’URI de la requête à la méthode du gestionnaire. Nous pouvons également spécifier la méthode HTTP qui devrait être utilisée par l’application cliente pour invoquer la méthode REST. L’annotation @ResponseBody est utilisée pour mapper l’objet de réponse dans le corps de la réponse. Une fois que l’objet de réponse est renvoyé par la méthode du gestionnaire, MappingJackson2HttpMessageConverter entre en jeu et le convertit en réponse JSON. L’annotation @PathVariable est le moyen facile d’extraire les données de l’URI REST et de les mapper à l’argument de la méthode. L’annotation @RequestBody est utilisée pour mapper les données JSON du corps de la requête en objet Employee, encore une fois ceci est fait par le mappage MappingJackson2HttpMessageConverter. Le reste du code est simple et facile à comprendre, notre application est prête pour le déploiement et les tests. Il suffit d’exporter en tant que fichier WAR et de le copier dans le répertoire d’application web du conteneur de servlets. Si vous avez configuré le serveur dans STS, vous pouvez simplement le lancer sur le serveur pour le déployer. J’utilise WizTools RestClient pour invoquer les appels REST mais vous pouvez également utiliser l’extension Chrome Postman. Les captures d’écran ci-dessous montrent les différentes invocations des API REST exposées par notre application et leur sortie. Vérification de l’état de santé – Appel REST de l’employé fictif Créer un appel REST POST d’employé: Assurez-vous que le type de contenu de la requête est défini sur « application/json » sinon vous obtiendrez le code d’erreur HTTP 415.
Appel REST pour obtenir un employé
Appel REST pour supprimer un employé
Appel REST pour obtenir tous les employés
Programme client Rest Spring
Les clients REST sont utiles pour tester notre service web REST, mais la plupart du temps, nous devons invoquer les services REST via notre programme. Nous pouvons utiliser Spring RestTemplate
pour invoquer facilement ces méthodes. Ci-dessous se trouve un programme simple invoquant les méthodes REST de notre application à l’aide de l’API RestTemplate.
package com.journaldev.spring;
import java.util.LinkedHashMap;
import java.util.List;
import org.springframework.web.client.RestTemplate;
import com.journaldev.spring.controller.EmpRestURIConstants;
import com.journaldev.spring.model.Employee;
public class TestSpringRestExample {
public static final String SERVER_URI = "https://localhost:9090/SpringRestExample";
public static void main(String args[]){
testGetDummyEmployee();
System.out.println("*****");
testCreateEmployee();
System.out.println("*****");
testGetEmployee();
System.out.println("*****");
testGetAllEmployee();
}
private static void testGetAllEmployee() {
RestTemplate restTemplate = new RestTemplate();
// Nous ne pouvons pas obtenir List car le convertisseur JSON ne connaît pas le type de
// l'objet dans la liste et le convertit donc en un type d'objet JSON par défaut LinkedHashMap
List emps = restTemplate.getForObject(SERVER_URI+EmpRestURIConstants.GET_ALL_EMP, List.class);
System.out.println(emps.size());
for(LinkedHashMap map : emps){
System.out.println("ID="+map.get("id")+",Name="+map.get("name")+",CreatedDate="+map.get("createdDate"));;
}
}
private static void testCreateEmployee() {
RestTemplate restTemplate = new RestTemplate();
Employee emp = new Employee();
emp.setId(1);emp.setName("Pankaj Kumar");
Employee response = restTemplate.postForObject(SERVER_URI+EmpRestURIConstants.CREATE_EMP, emp, Employee.class);
printEmpData(response);
}
private static void testGetEmployee() {
RestTemplate restTemplate = new RestTemplate();
Employee emp = restTemplate.getForObject(SERVER_URI+"/rest/emp/1", Employee.class);
printEmpData(emp);
}
private static void testGetDummyEmployee() {
RestTemplate restTemplate = new RestTemplate();
Employee emp = restTemplate.getForObject(SERVER_URI+EmpRestURIConstants.DUMMY_EMP, Employee.class);
printEmpData(emp);
}
public static void printEmpData(Employee emp){
System.out.println("ID="+emp.getId()+",Name="+emp.getName()+",CreatedDate="+emp.getCreatedDate());
}
}
La plupart du programme est simple à comprendre, cependant lors de l’invocation d’une méthode REST renvoyant une collection, nous devons utiliser LinkedHashMap
car la conversion de JSON en objet ne connaît pas l’objet Employee et le convertit en collection de LinkedHashMap
. Nous pouvons écrire une méthode utilitaire pour convertir de LinkedHashMap
à notre objet Java Bean. Lorsque nous exécutons le programme ci-dessus, nous obtenons la sortie suivante dans la console.
ID=9999,Name=Dummy,CreatedDate=Tue Mar 04 21:02:41 PST 2014
*****
ID=1,Name=Pankaj Kumar,CreatedDate=Tue Mar 04 21:02:41 PST 2014
*****
ID=1,Name=Pankaj Kumar,CreatedDate=Tue Mar 04 21:02:41 PST 2014
*****
2
ID=1,Name=Pankaj Kumar,CreatedDate=1393995761654
ID=9999,Name=Dummy,CreatedDate=1393995761381
Un autre point est que les méthodes put de RestTemplate
n’ont pas d’option pour définir l’objet de réponse car la méthode PUT devrait être utilisée pour stocker quelque chose sur le serveur et un simple code d’état HTTP 200 devrait être suffisant.
Télécharger le projet Spring Restful Webservice
Voilà tout pour le tutoriel de l’application Web Spring Restful. Téléchargez le projet d’exemple à partir du lien ci-dessus et jouez avec pour en apprendre davantage. MISE À JOUR: En raison de nombreuses demandes pour fournir un exemple similaire avec XML ainsi que pour prendre en charge à la fois XML et JSON, j’ai étendu cette application dans Spring REST XML JSON Example pour prendre en charge à la fois les requêtes et les réponses XML et JSON. Je vous recommande vivement de passer en revue cela pour voir la beauté du framework Spring et à quel point il est facile de réaliser cela.
Vous pouvez télécharger le projet complet depuis notre Dépôt GitHub.