Creación: 25-09-2010
Índice de contenidos
1. Introducción
2. Entorno
3. Configuración
4. El controlador
5. La vista
6. Conclusiones
7. Sobre el autor
1. Introducción
En este tutorial vamos a ver un ejemplo de como hacer con Spring servicios REST donde los parámetros de entrada y salida serán objetos JSON.
REST o RESTful (REpresentational State Transfer) es un tipo de servicios web que no se basan en el estándar SOAP. Una de sus principales ventajas es que son mucho más ligeros ya que requieren muchas menos cabeceras y capas (normalmente un simple GET o POST).
JSON (JavaScript Object Notation) es un formato ligero de intercambio de información (mucho más ligero que el XML) que ese basa en pares de clave valor.
La idea es utilizar estas tecnologías para construir lo que denominaremos SOAUI, es decir una capa de servicios (SOA = Service Oriented Architecture) sobre la que montaremos la interfaz de usuario (UI = User Interface). Con esta arquitectura conseguimos desacoplar completamente la presentación del negocio, de forma que la interfaz la podríamos desarrollar en HTML+JavaScript, o en Flex, o en iPhone, o en Android, o para comunicarme con otro proceso, o …
Las ventajas de esta arquitectura son claras, y en este tutorial veremos como con Spring va a resultar tremendamente sencillo montar esta arquitectura.
Aquí podéis encontrar todo el código fuente con Maven (compilar con mvn install
desde el proyecto “tutorial-parent”), y configurado con HSQLDB y Jetty (ejecutar con mvn jetty:run
desde el proyecto “tutorial-rest”).
Una vez levantado el Jetty podéis entrar en la aplicación con un navegador y la URL:
http://localhost:8080/autentia-rest-tutorial/rest/provider/
Otras URL que podéis probar:
- http://localhost:8080/autentia-rest-tutorial/rest/provider/1: El último numero es el id del provider que queréis mostrar.
- http://localhost:8080/autentia-rest-tutorial/rest/provider/names: Devuelve una lista con los nombres de los provider.
2. Entorno
El tutorial está escrito usando el siguiente entorno:
- Hardware: Portátil MacBook Pro 17′ (2.93 GHz Intel Core 2 Duo, 4GB DDR3 SDRAM, 128GB Solid State Drive).
- NVIDIA GeForce 9400M + 9600M GT with 512MB
- Sistema Operativo: Mac OS X Snow Leopard 10.6.4
- JDK 1.6.0_20
- Maven 2.2.1
- Spring 3.0.4.RELEASE
3. Configuración
En los pom hemos puesto las dependencias necesarias para usar los jar de Spring (core, web, …).
En el web.xml podemos destacar lo siguiente:
... <context-param> <param-name>contextConfigLocation</param-name> <param-value>classpath*:applicationContext.xml</param-value> </context-param> <listener> <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class> </listener> ... <servlet> <servlet-name>tutorial</servlet-name> <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> <load-on-startup>1</load-on-startup> </servlet> <servlet-mapping> <servlet-name>tutorial</servlet-name> <url-pattern>/rest/*</url-pattern> </servlet-mapping> ...
Sería la configuración típica de Spring Web, donde vemos como se declara el listener que carga el fichero applicationContext.xml
y como se declara el Dispatch Servlet de Spring que atenderá todas las peticiones que empiecen por rest
. A este servlet le
hemos dado el nombre tutorial, así que Spring buscará un fichero de configuración tutorial-servlet.xml donde pondremos la configuración de la capa web.
En el tutorial-servlet.xml podemos encontrar:
... <mvc:annotation-driven /> <context:component-scan base-package="com.autentia.tutorial.web" /> <bean class="org.springframework.web.servlet.view.ContentNegotiatingViewResolver"> <property name="mediaTypes"> <map> <entry key="html" value="text/html" /> <entry key="json" value="application/json" /> </map> </property> <property name="viewResolvers"> <list> <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver"> <property name="prefix" value="/WEB-INF/views/" /> <property name="suffix" value=".jsp" /> </bean> </list> </property> <property name="defaultViews"> <list> <bean class="org.springframework.web.servlet.view.json.MappingJacksonJsonView" /> </list> </property> </bean> ...
Vemos como se indica que usaremos la configuración por anotaciones y que debe buscar estas anotaciones a partir del paquete com.autentia.tutorial.web.
Luego se define un bean de tipo ContentNegotiationgViewResolver. Este es un resolver de Spring que lo que hace es, en función de las cabeceras de la request o de la extensión que pongamos en la URL determina que tipo de vista es la que tiene que “pintar” el resultado. En este punto es importante destacar como en el defaultViews estamos indicando que la vistas por defecto es el MappingJacksonJsonView. Esta clase se va a encargar de transformar a JSON las respuestas de nuestros métodos.
En el applicationContext.xml no hay nada especial, simplemente la definición del datasource, el transactionalmanager, y los beans de negocio.
4. El controlador
Aquí es donde está toda la chicha, si es que se le puede llamar “chicha” porque veréis como es extremadamente sencillo.
@Controller @RequestMapping("/provider") public class ProviderController { private final ProviderStore providerStore; @Inject public ProviderController(ProviderStore providerStore) { this.providerStore = providerStore; } @RequestMapping(method=RequestMethod.GET) public String getCreateForm(Model model) { model.addAttribute(new Provider()); return "provider/createForm"; } @RequestMapping(value="/names", method=RequestMethod.GET) public @ResponseBody List<String> getProvidersNames() { return providerStore.getProvidersNames(); } @RequestMapping(value="/store", method=RequestMethod.POST) public @ResponseBody Long addProvider(@RequestBody Provider provider) { final Provider storedProvider = providerStore.sotreProvider(provider); return storedProvider.getId(); } @RequestMapping(value="/{id}", method=RequestMethod.GET) public @ResponseBody Provider getProvider(@PathVariable Long id) { final Provider provider = providerStore.loadProvider(id); if (provider == null) { throw new ResourceNotFoundException(id); } return provider; } }
Podemos ver como la clase se declara como un @Controller
de Spring. Y con la anotación @RequestMapping
de la línea 02 le estamos diciendo que todas las URL que empiecen por el patrón /provider
los atenderá esta clase (para ver algunos ejemplos mirar las URL que aparecen en la introducción de este tutorial).
Luego tenemos una serie de métodos donde vamos afinando el comportamiento del controlador.
En la línea 18 vemos como usamos de nuevo la anotación @RequestMapping
, pero en esta ocasión le indicamos mediante el uso de values
el patrón de URL que serán atendidos por este método (/provider/names
). Y con method
le indicamos que este método sólo atenderá las peticiones hechas por GET.
En la línea siguiente usamos @ResponseBody
para indicar que el resultado del método lo vuelque en el cuerpo de la respuesta. Como hemos configurado que la vista por defecto es la transformación de JSON, lo que va a pasar es que Spring va a a coger nuestro resultado, lo va a representar en JSON y lo va a escribir en el cuerpo de la respuesta.
En la línea 24 aparece la anotación @RequestBody
. Con esto estamos indicando a Spring que el cuerpo de la request nos lo pase como parámetro del método. Spring se encargará de convertir el objeto JSON en una instancia de Provider
(la clase Provider es un POJO normal y corriente, Spring hará la conversión por introspección).
En la línea 29 y 30 podemos destacar el uso de {id}
y la anotación @PathVariable
. Con esto estamos indicando que una parte de la URL se tiene que interpretar como una
variable, que Spring nos inyectará como un parámetro de nuestro método.
En la línea 33 estamos lanzando una excepción:
@ResponseStatus(value = HttpStatus.NOT_FOUND) public class ResourceNotFoundException extends RuntimeException { private static final long serialVersionUID = 4553464302467525828L; private Long resourceId; public ResourceNotFoundException(Long resourceId) { this.resourceId = resourceId; } public Long getResourceId() { return resourceId; } }
En el código de esta excepción, en la línea 1 vemos como gracias a la anotación @ResponseStatus
estamos indicando a Spring que cuando se lance esta excepción se mande al cliente un código 404 de recurso no encontrado.
5. La vista
La vista es HTML puro + JavaScript. Se han usado JSPs con taglibs de Spring, pero simplemente por comodidad a la hora de generar el formulario.
En la JSP createForm.jsp se puede ver como se usa JavaScript (jQuery) para hacer la
llamada por AJAX y enviar un objeto JSON en la petición.
<%@ page session="false" %> <%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %> <%@ taglib uri="http://java.sun.com/jsp/jstl/fmt" prefix="fmt" %> <%@ taglib prefix="form" uri="http://www.springframework.org/tags/form" %> <html> <head> <title>Create Provider</title> <script type="text/javascript" src="<c:url value="/scripts/jquery-1.4.min.js" /> "></script> <script type="text/javascript" src="<c:url value="/scripts/json.min.js" /> "></script> </head> <body> <div class="container"> <h1> Create Account </h1> <div class="span-12 last"> <form:form modelAttribute="provider" action="provider/store" method="post"> <fieldset> <legend>Provider Fields</legend> <p> <form:label for="name" path="name" cssErrorClass="error">Name</form:label><br/> <form:input path="name" /><form:errors path="name" /> </p> <p> <input id="create" type="submit" value="Create" /> </p> </fieldset> </form:form> </div> </div> <div id="mask" style="display: none;"></div> <div id="popup" style="display: none;"> <div class="span-8 last"> <h3>Account Created Successfully</h3> <form> <fieldset> <p> <label for="assignedId">Assigned Id</label><br/> <input id="assignedId" type="text" readonly="readonly" /> </p> <p> <label for="confirmedName">Name</label><br/> <input id="confirmedName" type="text" readonly="readonly" /> </p> </fieldset> </form> <a href="#" onclick="closePopup();">Close</a> </div> </div> </body> <script type="text/javascript"> $(document).ready(function() { $("#provider").submit(function() { var account = $(this).serializeObject(); $.postJSON("provider/store", account, function(data) { $("#assignedId").val(data); showPopup(); }); return false; }); }); function showPopup() { $.getJSON("provider/" + $("#assignedId").val(), function(provider) { $("#confirmedName").val(provider.name); }); $('#popup').fadeIn('fast'); $('#mask').fadeIn('fast'); } function closePopup() { $('#popup').fadeOut('fast'); $('#mask').fadeOut('fast'); resetForm(); } function resetForm() { $('#provider')[0].reset(); } </script> </html>
6. Conclusiones
Esta arquitectura donde la vista está totalmente desacoplada del negocio presenta varias ventajas muy interesante, la primera de ellas es que desde el principio tenemos toda nuestra aplicación orientada a servicios, por lo que resulta muy sencillo usar cualquier tipo de cliente para comunicarnos con el negocio.
Además, gracias a Spring, la implementación es muy sencilla. Por lo que puede ser una tecnología a tener en cuenta para hacer nuestros desarrollos.
7. Sobre el autor
Alejandro Pérez García, Ingeniero en Informática (especialidad de Ingeniería del Software) y Certified ScrumMaster
Socio fundador de Autentia (Formación, Consultoría, Desarrollo de sistemas transaccionales)
mailto:alejandropg@autentia.com
Autentia Real Business Solutions S.L. – «Soporte a Desarrollo»
Buenas Alejandro, muy buen tutorial. Tendría una duda. Yo estoy utilizando Spring para crear portlets 2.0 en liferay 6. El caso es que necesito ajax para algunas cosas y quería montarme algún servicio RESTful. En este caso, mi dispatcher servlet está declarado en portlet.xml, que es del tipo org.springframework.web.portlet.DispatcherPortlet. Entonces, ¿debería declararme otro dispatcher en web.xml para atender peticiones restful? No se, no lo tengo muy claro.
Hola Alejandro, puedo implemntar esto en un Portlet?
Gracias
Gracias por el tutorial Alejandro. Solo decir que en Firefox 3.6 no funciona, lanza la excepción \\\»javax.servlet.ServletException: Could not resolve view with name \\\’provider/createForm\\\’ in servlet with name \\\’tutorial\\\’ \\\», sin embargo con Chrome 11 ha funcionado correctamente.
saludos por favor Puedes ayudarme con esta consulta, busque información referente a API o Frameworks Java que ayuden a la construcción de clases Java que implemente las características de REST. Mencione por lo menos 2 APIs o Frameworks.
gracias por su ayuda