Spring es uno de los frameworks Java EE más utilizados. Ya hemos visto cómo usar Spring MVC para crear aplicaciones web basadas en Java. Hoy aprenderemos a crear servicios web Spring Restful usando Spring MVC y luego lo probaremos con el cliente Rest. Por último, también veremos cómo invocar el servicio web Spring Restful usando Spring RestTemplate API .
DESCANSO DE PRIMAVERA
Usaremos la última versión de Spring 4.0.0.RELEASE y utilizaremos la integración de Spring Jackson JSON para enviar una respuesta JSON en la respuesta de la llamada Rest. El tutorial está desarrollado en Spring STS IDE para crear código esqueleto de Spring MVC fácilmente y luego se extiende para implementar la arquitectura Restful. Cree un nuevo proyecto Spring MVC en STS, nuestro proyecto final se verá como la imagen a continuación. Analizaremos cada uno de los componentes uno por uno.
Archivos XML de configuración REST de Spring
Nuestro archivo pom.xml se ve como se muestra a continuación.
?xml version="1.0" encoding="UTF-8"?project 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"modelVersion4.0.0/modelVersiongroupIdcom.journaldev/groupIdartifactIdSpringRestExample/artifactIdnameSpringRestExample/namepackagingwar/packagingversion1.0.0-BUILD-SNAPSHOT/versionpropertiesjava-version1.6/java-versionorg.springframework-version4.0.0.RELEASE/org.springframework-versionorg.aspectj-version1.7.4/org.aspectj-versionorg.slf4j-version1.7.5/org.slf4j-versionjackson.databind-version2.2.3/jackson.databind-version/propertiesdependencies!-- Jackson --dependencygroupIdcom.fasterxml.jackson.core/groupIdartifactIdjackson-databind/artifactIdversion${jackson.databind-version}/version/dependency!-- Spring --dependencygroupIdorg.springframework/groupIdartifactIdspring-context/artifactIdversion${org.springframework-version}/versionexclusions!-- Exclude Commons Logging in favor of SLF4j --exclusiongroupIdcommons-logging/groupIdartifactIdcommons-logging/artifactId/exclusion/exclusions/dependencydependencygroupIdorg.springframework/groupIdartifactIdspring-webmvc/artifactIdversion${org.springframework-version}/version/dependency!-- AspectJ --dependencygroupIdorg.aspectj/groupIdartifactIdaspectjrt/artifactIdversion${org.aspectj-version}/version/dependency!-- Logging --dependencygroupIdorg.slf4j/groupIdartifactIdslf4j-api/artifactIdversion${org.slf4j-version}/version/dependencydependencygroupIdorg.slf4j/groupIdartifactIdjcl-over-slf4j/artifactIdversion${org.slf4j-version}/versionscoperuntime/scope/dependencydependencygroupIdorg.slf4j/groupIdartifactIdslf4j-log4j12/artifactIdversion${org.slf4j-version}/versionscoperuntime/scope/dependencydependencygroupIdlog4j/groupIdartifactIdlog4j/artifactIdversion1.2.15/versionexclusionsexclusiongroupIdjavax.mail/groupIdartifactIdmail/artifactId/exclusionexclusiongroupIdjavax.jms/groupIdartifactIdjms/artifactId/exclusionexclusiongroupIdcom.sun.jdmk/groupIdartifactIdjmxtools/artifactId/exclusionexclusiongroupIdcom.sun.jmx/groupIdartifactIdjmxri/artifactId/exclusion/exclusionsscoperuntime/scope/dependency!-- @Inject --dependencygroupIdjavax.inject/groupIdartifactIdjavax.inject/artifactIdversion1/version/dependency!-- Servlet --dependencygroupIdjavax.servlet/groupIdartifactIdservlet-api/artifactIdversion2.5/versionscopeprovided/scope/dependencydependencygroupIdjavax.servlet.jsp/groupIdartifactIdjsp-api/artifactIdversion2.1/versionscopeprovided/scope/dependencydependencygroupIdjavax.servlet/groupIdartifactIdjstl/artifactIdversion1.2/version/dependency!-- Test --dependencygroupIdjunit/groupIdartifactIdjunit/artifactIdversion4.7/versionscopetest/scope/dependency/dependenciesbuildpluginspluginartifactIdmaven-eclipse-plugin/artifactIdversion2.9/versionconfigurationadditionalProjectnaturesprojectnatureorg.springframework.ide.eclipse.core.springnature/projectnature/additionalProjectnaturesadditionalBuildcommandsbuildcommandorg.springframework.ide.eclipse.core.springbuilder/buildcommand/additionalBuildcommandsdownloadSourcestrue/downloadSourcesdownloadJavadocstrue/downloadJavadocs/configuration/pluginplugingroupIdorg.apache.maven.plugins/groupIdartifactIdmaven-compiler-plugin/artifactIdversion2.5.1/versionconfigurationsource1.6/sourcetarget1.6/targetcompilerArgument-Xlint:all/compilerArgumentshowWarningstrue/showWarningsshowDeprecationtrue/showDeprecation/configuration/pluginplugingroupIdorg.codehaus.mojo/groupIdartifactIdexec-maven-plugin/artifactIdversion1.2.1/versionconfigurationmainClassorg.test.int1.Main/mainClass/configuration/plugin/plugins/build/project
La herramienta STS genera el archivo pom.xml para nosotros. Sin embargo, he actualizado la versión de Spring Framework, AspectJ, SLF4J y Jackson a la más reciente a partir de hoy. La mayor parte de la parte es común y se genera automáticamente. El punto importante a tener en cuenta es que he agregado las bibliotecas JSON de Jackson 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_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-paramparam-namecontextConfigLocation/param-nameparam-value/WEB-INF/spring/root-context.xml/param-value/context-param!-- Creates the Spring Container shared by all Servlets and Filters --listenerlistener-classorg.springframework.web.context.ContextLoaderListener/listener-class/listener!-- Processes application requests --servletservlet-nameappServlet/servlet-nameservlet-classorg.springframework.web.servlet.DispatcherServlet/servlet-classinit-paramparam-namecontextConfigLocation/param-nameparam-value/WEB-INF/spring/appServlet/servlet-context.xml/param-value/init-paramload-on-startup1/load-on-startup/servletservlet-mappingservlet-nameappServlet/servlet-nameurl-pattern//url-pattern/servlet-mapping/web-app
Este archivo se genera automáticamente y no he cambiado nada en él. Sin embargo, si desea cambiar los archivos de configuración de contexto y su ubicación, puede hacerlo en el archivo web.xml.
?xml version="1.0" encoding="UTF-8"?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, estaremos desarrollando un servicio de descanso simple y es por eso que no he cambiado nada aquí.
?xml version="1.0" encoding="UTF-8"?beans:beans 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.xsdhttps://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsdhttps://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:beanbeans: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:beanbeans:property name="messageConverters"beans:listbeans:ref bean="jsonMessageConverter"//beans:list/beans:property/beans:bean!-- Configure bean to convert JSON to POJO and vice versa --beans:bean/beans:beancontext:component-scan base-package="com.journaldev.spring.controller" //beans:beans
La mayor parte de la parte se genera automáticamente y contiene configuraciones estándar. Sin embargo, los puntos importantes a tener en cuenta son el elemento impulsado por anotaciones para admitir la configuración basada en anotaciones y la conexión MappingJackson2HttpMessageConverterpara RequestMappingHandlerAdapter messageConvertersque la API de Jackson se active y convierta JSON en Java Beans y viceversa. Al tener esta configuración, usaremos JSON en el cuerpo de la solicitud y recibiremos datos JSON en la respuesta.
Clases del modelo REST de Spring
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 @JsonSerializela anotación para utilizar DateSerializerla clase para la conversión de fecha del tipo Java al formato JSON y viceversa.
Puntos finales del servicio web Restful de Spring
Tendremos los siguientes puntos finales de servicios web de descanso.
| No. de sl. | Dirección URL | Método HTTP | Detalles |
|---|---|---|---|
| 1 | /rest/emp/ficticio | CONSEGUIR | Servicio de control de salud, para insertar un dato ficticio en el almacenamiento de datos de los empleados |
| 2 | /resto/emp/{id} | CONSEGUIR | Para obtener el objeto Empleado en función del id |
| 3 | /descanso/emps | CONSEGUIR | Para obtener la lista de todos los empleados en el almacén de datos |
| 4 | /descanso/emp/crear | CORREO | Para crear el objeto Empleado y almacenarlo |
| 5 | /rest/emp/eliminar/{id} | PONER | Para eliminar el objeto Empleado del almacenamiento de datos en función de la identificación |
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 Spring
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. */@Controllerpublic class EmployeeController {private static final Logger logger = LoggerFactory.getLogger(EmployeeController.class);//Map to store employees, ideally we should use databaseMapInteger, Employee empData = new HashMapInteger, Employee();@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.Start getAllEmployees.");ListEmployee emps = new ArrayListEmployee();SetInteger 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;}}
Para simplificar, estoy almacenando todos los datos del empleado 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 debe utilizar 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 método del controlador devuelve el objeto de respuesta, 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 en el objeto Employee, nuevamente esto se hace mediante el mapeo MappingJackson2HttpMessageConverter. El resto del código es simple y autocomprensible, nuestra aplicación está lista para la implementación y la prueba. Simplemente exporte como archivo WAR y cópielo en el directorio de la aplicación web del contenedor de servlets. Si tiene el servidor configurado en el STS, puede simplemente ejecutarlo en el servidor para implementarlo. Estoy usando WizTools RestClient para invocar las llamadas de descanso, pero también puede usar la extensión de Chrome Postman. Las siguientes capturas de pantalla muestran las diferentes invocaciones de las API de descanso expuestas por nuestra aplicación y su salida. Comprobación de estado: obtener una llamada de descanso de empleado ficticia Crear una llamada de descanso POST de empleado : asegúrese de que el tipo de contenido de la solicitud esté configurado en “application/json”; de lo contrario, obtendrá el código de error HTTP 415. Obtener una llamada de descanso de empleado Eliminar una llamada de descanso de empleado Obtener una llamada de descanso de todos los empleados
Programa de descanso para clientes de Spring Rest
Los clientes Rest son útiles 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 RestTemplatepara invocar estos métodos fácilmente. A continuación, se muestra un programa simple que invoca los métodos Rest de nuestra aplicación mediante 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();//we can't get ListEmployee because JSON convertor doesn't know the type of//object in the list and hence convert it to default JSON object type LinkedHashMapListLinkedHashMap 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, cuando invocamos el método rest que devuelve una colección, debemos usarlo LinkedHashMapporque la conversión de JSON a objeto no conoce el objeto Employee y lo convierte en la colección de LinkedHashMap. Podemos escribir un método de utilidad para convertir de LinkedHashMapa nuestro objeto Java Bean. Cuando ejecutamos el programa anterior, obtenemos el siguiente resultado 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*****2ID=1,Name=Pankaj Kumar,CreatedDate=1393995761654ID=9999,Name=Dummy,CreatedDate=1393995761381
Otro punto es que RestTemplatelos métodos put no tienen la opción de establecer un 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 por el tutorial de la aplicación web Spring Restful. Descargue el proyecto de muestra desde el enlace anterior y juegue con él para obtener más información. ACTUALIZACIÓN : Debido a tantas solicitudes para proporcionar un ejemplo similar con XML y que admita tanto XML como JSON, he ampliado esta aplicación en Spring REST XML JSON Example para admitir solicitudes y respuestas tanto en XML como en JSON. Le sugiero enfáticamente que lo revise para ver la belleza del marco Spring y lo fácil que es lograrlo.
Puedes descargar el proyecto completo desde nuestro repositorio de GitHub .