Spring es uno de los frameworks Java EE más ampliamente utilizados. Anteriormente hemos visto cómo utilizar Spring MVC para crear aplicaciones web basadas en Java. Hoy aprenderemos a crear servicios web Spring Restful utilizando Spring MVC y luego probarlo con el cliente Rest. Al final, también veremos cómo invocar el servicio web Spring Restful utilizando la API Spring RestTemplate.
REST de Spring
Utilizaremos la última versión de Spring 4.0.0.RELEASE y aprovecharemos la integración de Spring Jackson JSON para enviar respuestas JSON en la respuesta de la llamada rest. El tutorial está desarrollado en el entorno de desarrollo integrado (IDE) de Spring STS para crear fácilmente código esqueleto de Spring MVC y luego ampliarlo para implementar arquitectura Restful. Cree un nuevo proyecto de Spring MVC en el STS, nuestro proyecto final se verá como en la imagen de abajo. Examinaremos cada uno de los componentes uno por uno.
Archivos XML de configuración de Spring REST
Nuestro archivo pom.xml se ve así:
<?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>
La herramienta STS genera el archivo pom.xml para nosotros. Sin embargo, he actualizado el marco de Spring, AspectJ, SLF4J y Jackson a la última versión hasta la fecha de hoy. La mayor parte es común y se genera automáticamente, el punto importante a tener en cuenta es que he agregado las bibliotecas Jackson JSON en la dependencia porque las usaremos para convertir objetos a JSON y viceversa.
<?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>
Este archivo se genera automáticamente y no he cambiado nada en él. Sin embargo, si deseas cambiar los archivos de configuración de contexto y su ubicación, puedes hacerlo en el archivo 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>
Este archivo contiene los recursos compartidos que serán visibles para todos los componentes web. Estamos desarrollando un servicio REST simple, por eso no he cambiado nada aquí.
<?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 mayor parte está generada automáticamente y contiene configuraciones básicas. Sin embargo, puntos importantes a tener en cuenta son el elemento annotation-driven para admitir configuraciones basadas en anotaciones y la conexión de MappingJackson2HttpMessageConverter
al RequestMappingHandlerAdapter
messageConverters
para que se active la API de Jackson y convierta JSON a objetos Java y viceversa. Con esta configuración, utilizaremos JSON en el cuerpo de la solicitud y recibiremos datos JSON en la respuesta.
Clases de modelo Spring REST
Escribamos una clase POJO simple que servirá como entrada y salida para nuestros métodos de servicio 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;
}
}
El único punto importante a tener en cuenta es el uso de la anotación @JsonSerialize
para utilizar la clase DateSerializer
en la conversión de fechas del tipo Java al formato JSON y viceversa.
Endpoints del servicio web Restful de Primavera
Tendremos los siguientes puntos finales de servicios web rest.
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 |
Tenemos una clase que define todas estas URI como constantes de cadena.
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}";
}
Clase controladora del servicio web Restful de Primavera
Nuestra clase EmployeeController publicará todos los puntos finales del servicio web mencionados anteriormente. Veamos el código de la clase y luego aprenderemos sobre cada uno de los métodos en detalle.
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);
//Mapa para almacenar empleados, idealmente deberíamos usar una base de datos
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;
}
}
Por simplicidad, estoy almacenando todos los datos de los empleados en el HashMap empData. La anotación @RequestMapping se utiliza para mapear la URI de la solicitud al método del controlador. También podemos especificar el método HTTP que debería ser utilizado por la aplicación cliente para invocar el método REST. La anotación @ResponseBody se utiliza para mapear el objeto de respuesta en el cuerpo de la respuesta. Una vez que el objeto de respuesta es devuelto por el método del controlador, MappingJackson2HttpMessageConverter entra en acción y lo convierte en una respuesta JSON. La anotación @PathVariable es la forma sencilla de extraer los datos de la URI REST y mapearlos al argumento del método. La anotación @RequestBody se utiliza para mapear los datos JSON del cuerpo de la solicitud al objeto Employee, nuevamente esto se realiza mediante la conversión de MappingJackson2HttpMessageConverter. El resto del código es simple y autoexplicativo, nuestra aplicación está lista para ser implementada y probada. Simplemente exporta como un archivo WAR y cópialo en el directorio de la aplicación web del contenedor de servlets. Si tienes el servidor configurado en STS, simplemente puedes ejecutarlo en el servidor para implementarlo. Estoy utilizando WizTools RestClient para invocar las llamadas REST, pero también puedes usar la extensión para Chrome, Postman. Las capturas de pantalla a continuación muestran las diferentes invocaciones de las APIs REST expuestas por nuestra aplicación y su salida. Verificación de Salud – Llamada de Rest Dummy Employee Llamada POST para Crear Empleado: Asegúrate de que el tipo de contenido de la solicitud esté configurado como “application/json”, de lo contrario obtendrás un código de error HTTP 415.
Llamada de Rest para Obtener Empleado
Llamada de Rest para Eliminar Empleado
Llamada de Rest para Obtener Todos los Empleados
Programa Cliente Rest de Spring
Los Clientes Rest son buenos para probar nuestro servicio web rest, pero la mayoría de las veces, necesitamos invocar servicios rest a través de nuestro programa. Podemos usar Spring RestTemplate
para invocar estos métodos fácilmente. A continuación, se muestra un programa simple que invoca nuestros métodos rest de aplicación utilizando la 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();
//no podemos obtener List porque el convertidor JSON no sabe el tipo de
//objeto en la lista y, por lo tanto, lo convierte al tipo de objeto JSON predeterminado 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 mayor parte del programa es fácil de entender, sin embargo, al invocar un método rest que devuelve una Colección, necesitamos usar LinkedHashMap
porque la conversión de JSON a objeto no sabe sobre el objeto Empleado y lo convierte a la colección de LinkedHashMap
. Podemos escribir un método de utilidad para convertir de LinkedHashMap
a nuestro objeto Java Bean. Cuando ejecutamos el programa anterior, obtenemos la siguiente salida en la consola.
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
Otro punto es que los métodos put de RestTemplate
no tienen opción de establecer el objeto de respuesta porque el método PUT debe usarse para almacenar algo en el servidor y un simple código de estado HTTP 200 debería ser suficiente.
Descargar Proyecto de Servicio Web Restful de Spring
Eso es todo para el tutorial de aplicación web Spring Restful. Descarga el proyecto de ejemplo desde el enlace de arriba y juega con él para aprender más. ACTUALIZACIÓN: Debido a tantas solicitudes para proporcionar un ejemplo similar con XML, así como para admitir tanto XML como JSON, he ampliado esta aplicación en Spring REST XML JSON Ejemplo para admitir solicitudes y respuestas tanto en XML como en JSON. Te sugiero encarecidamente que lo revises para ver la belleza del framework de Spring y lo fácil que es lograr esto.
Puedes descargar el proyecto completo desde nuestro Repositorio de GitHub.