Ajax Con Spring Rest
Ajax Con Spring Rest
Spring MVC
Índice
1 AJAX con Spring......................................................................................................... 2
1.1 Caso de uso 1: respuesta del servidor como texto/fragmento de HTML.................2
1.2 Caso de uso 2: respuesta del servidor como objeto serializado............................... 3
1.3 Caso de uso 3: enviar objetos desde el cliente.........................................................5
2 Servicios web REST.....................................................................................................6
2.1 URIs......................................................................................................................... 7
2.2 Obtener recursos (GET)...........................................................................................8
2.3 Crear o modificar recursos (POST/PUT).................................................................9
2.4 Eliminar recursos (DELETE)................................................................................ 10
2.5 Parte del cliente......................................................................................................10
3 Tratamiento de errores en aplicaciones AJAX y REST............................................. 11
En una aplicación AJAX lo que necesitamos por parte del servidor es que nos permita
intercambiar información hacia/desde el cliente fácilmente. En su variante más sencilla
esa información sería simplemente texto o pequeños fragmentos de HTML. En casos más
complejos serían objetos Java serializados a través de HTTP en formato JSON o XML.
Vamos a ver qué funcionalidades nos ofrece Spring para implementar esto, planteando
varios casos de uso típicos en AJAX.
En este caso, el cliente hace una petición AJAX y el servidor responde con un fragmento
de texto plano o de HTML que el cliente mostrará en la posición adecuada de la página
actual. Por ejemplo, el típico caso del formulario de registro en que cuando llenamos el
campo de login queremos ver si está disponible, antes de rellenar los siguientes campos.
Cuando el foco de teclado sale del campo de login, se hace una petición AJAX al
servidor, que nos devolverá simplemente un mensaje indicando si está disponible o no.
La página HTML del cliente con el javascript que hace la petición AJAX y recibe la
respuesta podría ser algo como:
AJAX y Javascript
Mostraremos aquí el código javascript del cliente para tener el ejemplo completo, aunque no
podemos ver con detalle cómo funciona al no ser materia directa del curso. Usamos la librería
jQuery en los ejemplos para simplificar al máximo el código.
2
Copyright © 2012-2013 Depto. Ciencia de la computación e IA All rights reserved.
Aplicaciones AJAX y REST con Spring MVC
<span id="mensaje"></span><br>
Password: <input type="password" name="password"> <br>
Nombre y apellidos: <input type="text" name="nombre"> <br>
<input type="submit" value="registrar">
</form>
<script type="text/javascript">
$('#campo_login').blur(
function() {
$('#mensaje').load('loginDisponible.do',
"login="+$('#campo_login').val())
}
)
</script>
Del código jQuery anterior baste decir que cuando el foco de teclado se va (evento 'blur')
del campo con id "campo_login" es cuando queremos disparar la petición AJAX. El
método load() de jQuery lanza una petición AJAX a una determinada url con
determinados parámetros y coloca la respuesta en la etiqueta HTML especificada (en este
caso la de id "mensaje", un span que tenemos vacío y preparado para mostrar el mensaje).
El código Spring en el servidor, que respondería a la petición AJAX, sería el siguiente:
@Controller
public class UsuarioController {
@Autowired
private IUsuarioBO ubo;
@RequestMapping("/loginDisponible.do")
public @ResponseBody String loginDisponible(@RequestParam("login")
String login) {
if (ubo.getUsuario(login)==null)
return "login disponible";
else
return "login <strong>no</strong> disponible";
}
}
La única diferencia con lo visto en la sesión anterior es que el valor de retorno del método
no debe ser interpretado por Spring como el nombre lógico de una vista. Debe ser el
contenido de la respuesta que se envía al cliente. Esto lo conseguimos anotando el valor
de retorno del método con @ResponseBody. Cuando el valor de retorno es un String,
como en este caso, simplemente se envía el texto correspondiente en la respuesta HTTP.
Como veremos, si es un objeto Java cualquiera se serializará automáticamente.
Evidentemente no es una muy buena práctica tener "empotrados" en el código Java
directamente los mensajes que queremos mostrar al usuario, pero este trata de ser un
ejemplo sencillo. En un caso más realista usaríamos el soporte de internacionalización de
Spring para externalizar e internacionalizar los mensajes. O quizá sería el propio
javascript el que mostraría el mensaje adecuado.
3
Copyright © 2012-2013 Depto. Ciencia de la computación e IA All rights reserved.
Aplicaciones AJAX y REST con Spring MVC
queremos, además de saberlo, obtener como sugerencia algunos logins parecidos que sí
estén disponibles, como se hace en muchos sitios web.
En lugar de enviarle al cliente simplemente un mensaje, le enviaremos un objeto con un
campo booleano que indique si el login está disponible o no, y una lista de Strings con las
sugerencias. En caso de que esté disponible, no habría sugerencias. La clase Java que
encapsularía esta información desde el lado del servidor sería algo como lo siguiente (no
se muestran constructores, getters o setters, solo las propiedades)
@Controller
public class UsuarioController {
@Autowired
private IUsuarioBO ubo;
@RequestMapping("/loginDisponible.do")
public @ResponseBody InfoLogin loginDisponible(
@RequestParam("login") String
login) {
if (ubo.getUsuario(login)==null)
//Si está disponible, no hacen falta sugerencias
return new InfoLogin(true, null);
else
//si no lo está, generamos las sugerencias con la
ayuda del IUsuarioBO
return new InfoLogin(false,
ubo.generarSugerencias(login));
}
}
Por lo demás, como se ve, a nivel de API de Spring no habría cambios. Automáticamente
se serializará el objeto al formato adecuado. Por defecto, lo más sencillo en Spring es
generar JSON. Si usamos Maven, basta con incluir en el proyecto la dependencia de la
librería Jackson, una librería Java para convertir a/desde JSON que no es propia de
Spring, pero con la que el framework está preparado para integrarse:
<dependency>
<groupId>org.codehaus.jackson</groupId>
<artifactId>jackson-mapper-asl</artifactId>
<version>1.9.11</version>
</dependency>
En el lado del cliente, el Javascript debería obtener el objeto enviado desde Spring
(sencillo si lo que se envía es JSON) y mostrar las sugerencias en el HTML.
4
Copyright © 2012-2013 Depto. Ciencia de la computación e IA All rights reserved.
Aplicaciones AJAX y REST con Spring MVC
@XmlRootElement
public class InfoLogin {
private boolean disponible;
private List<String> sugerencias;
@XmlElement
public boolean isDisponible() {
return disponible;
}
@XmlElementWrapper(name="sugerencias")
@XmlElement(name="sugerencia")
public List<String> getSugerencias() {
return sugerencias;
}
...
}
5
Copyright © 2012-2013 Depto. Ciencia de la computación e IA All rights reserved.
Aplicaciones AJAX y REST con Spring MVC
En AJAX lo más habitual es que el cliente envíe los datos a través de un formulario
HTML. Ya vimos en la primera sesión de Spring MVC cómo tratar ese caso de uso,
recordemos que los datos se podían "empaquetar" automáticamente en un objeto Java y,
como ya veremos, validar declarativamente con JSR303. Pero también podríamos hacer
que el cliente envíe al servidor un objeto serializado en JSON o XML. Este objeto se
envía entonces en el cuerpo de la petición HTTP del cliente y el trabajo de Spring es
deserializarlo y "transformarlo" a objeto Java. En el método del controller que responda a
la petición simplemente anotamos el parámetro que queremos "vincular" al objeto con
@RequestBody.
Continuando con el ejemplo del registro de usuarios, supongamos que queremos enviar
desde el cliente el nuevo usuario en formato JSON (por ejemplo, porque usamos un
cliente de escritorio). Desde el lado del servidor bastaría con usar @RequestBody:
@RequestMapping("/altaUsuario.do")
public void altaUsuario(@RequestBody Usuario usuario) {
...
}
Para completar el ejemplo, mostraremos el código correspondiente del lado del cliente.
Este código envía los datos del formulario pero en formato JSON. Como en los ejemplos
anteriores, usamos el API de jQuery para la implementación, consultar su documentación
para más información de cómo funciona el código.
6
Copyright © 2012-2013 Depto. Ciencia de la computación e IA All rights reserved.
Aplicaciones AJAX y REST con Spring MVC
Desde Spring 3.0, el módulo MVC ofrece soporte para aplicaciones web RESTful, siendo
precisamente esta una de las principales novedades de esta versión. Actualmente Spring
ofrece funcionalidades muy similares a las del estándar JAX-RS, pero perfectamente
integradas con el resto del framework. Nos limitaremos a explicar aquí el API de Spring
para REST obviando los conceptos básicos de esta filosofía, que ya se vieron en el
módulo de componentes web.
2.1. URIs
Como ya se vio en el módulo de servicios web, en aplicaciones REST cada recurso tiene
una URI que lo identifica, organizada normalmente de modo jerárquico. En un sistema en
el que tenemos ofertas de alojamiento de distintos hoteles, distintas ofertas podrían venir
identificadas por URLs como:
/hoteles/excelsiorMad/ofertas/15
/hoteles/ambassador03/ofertas/15 (el id de la oferta es único solo dentro
del hotel)
/hoteles/ambassador03/ofertas/ (todas las del hotel)
Las ofertas aparecen determinadas primero por el nombre del hotel y luego por su
identificador. Como puede verse, parte de la URL es la misma para cualquier oferta,
mientras que la parte que identifica al hotel y a la propia oferta es variable. En Spring
podemos expresar una URL de este tipo poniendo las partes variables entre llaves:
/hoteles/{idHotel}/ofertas/{idOferta}. Podemos asociar automáticamente estas
partes variables a parámetros del método java que procesará la petición HTTP. Por
ejemplo:
@Controller
public class OfertaRESTController {
@Autowired
IOfertasBO obo;
@RequestMapping(value="hoteles/{idHotel}/ofertas/{idOferta}",
method=RequestMethod.GET)
@ResponseBody
public Oferta mostrar(@PathVariable String idHotel, @PathVariable int
idOferta) {
Oferta oferta = obo.getOferta(idHotel, idOferta);
return oferta;
}
}
Las partes variables de la URL se asocian con los parámetros Java del mismo nombre.
Para que esta asociación funcione automáticamente, hay que compilar el código con la
información de debug habilitada. En caso contrario podemos asociarlo explícitamente:
@PathVariable("idHotel") String idHotel. Spring puede convertir las
@PathVariable a los tipos más típicos: String, numéricos o Date.
7
Copyright © 2012-2013 Depto. Ciencia de la computación e IA All rights reserved.
Aplicaciones AJAX y REST con Spring MVC
no existe. Lo que tiene su lógica, ya que esta URL significa que queremos hacer alguna
operación con todas las ofertas del hotel, y esto es mejor que sea tarea de otro método
Java distinto a mostrar.
Nótese además que el método java mostrar viene asociado solo a las peticiones de tipo
GET. En una aplicación REST, típicamente el método Java encargado de editar una oferta
se asociaría a la misma URL pero con PUT, y lo mismo pasaría con insertar/POST, y
borrar/DELETE
Por otro lado, como ya hemos visto en la sección de AJAX, la anotación @ResponseBody
hace que lo que devuelve el método Java se serialice en el cuerpo de la respuesta HTTP
que se envía al cliente. La serialización en JSON o XML se hace exactamente de la
misma forma que vimos en AJAX.
Nótese que el ejemplo anterior se podría modificar de modo sencillo para una aplicación
web convencional con JSP, como veníamos haciendo en las sesiones de MVC.
Únicamente habría que poner un parámetro de tipo Model, añadirle el objeto oferta
encontrado y devolver un String con el nombre lógico de la vista, donde se mostrarían
los datos.
@Controller
@RequestMapping("/hoteles/{idHotel}/ofertas")
public class OfertasController {
@Autowired
IOfertasBO obo;
@RequestMapping(method=RequestMethod.GET,
value="{idOferta}",
produces=MediaType.APPLICATION_JSON_VALUE)
public ResponseEntity<Oferta> obtenerOferta(@PathVariable String
idHotel,
@PathVariable int
idOferta)
throws
OfertaInexistenteException {
Oferta oferta = obo.getOferta(idHotel, idOferta);
return new ResponseEntity<Oferta>(oferta, HttpStatus.OK);
}
}
8
Copyright © 2012-2013 Depto. Ciencia de la computación e IA All rights reserved.
Aplicaciones AJAX y REST con Spring MVC
• Para simplificar las URIs de cada método se hace que el controller en general
responda a la parte "fija" y común a todas las URIs de las que se encargará, y en cada
método se pone solo la parte que va a continuación. Nótese que los parámetros del
método pueden referenciar cualquier @PathVariable de la URI, aunque aparezca en
la anotación de la clase (como el id. del hotel).
• Usamos el atributo produces de @RequestMapping para fijar el valor de la cabecera
HTTP "content-type", al igual que hacíamos en AJAX.
• Como ya hemos dicho, la clase ResponseEntity representa la respuesta HTTP.
Cuando en el cuerpo de la respuesta queremos serializar un objeto usamos su clase
para parametrizar ResponseEntity. Hay varios constructores de esta clase. El más
simple admite únicamente un código de estado HTTP (aquí 200 OK). El que usamos
en el ejemplo tiene además otro parámetro en el que pasamos el objeto a serializar.
• Nótese que en caso de solicitar una oferta inexistente se generaría una excepción, lo
que en la mayoría de contenedores web nos lleva a una página HTML de error con la
excepción, algo no muy apropiado para un cliente REST, normalmente no preparado
para recibir HTML. Veremos luego cómo arreglar esto, convirtiendo automáticamente
las excepciones en códigos de estado HTTP.
• Ya no hace falta la anotación @ResponseBody ya que al devolver un
ResponseEntity<Oferta>, ya estamos indicando que queremos serializar un objeto
en la respuesta HTTP.
En este caso, el cliente envía los datos necesarios para crear el recurso y el servidor le
debería responder, según la ortodoxia REST, con un código de estado 201 (Created) y en
la cabecera "Location" la URI del recurso creado. Veamos cómo se implementaría esto en
Spring:
@RequestMapping("/hoteles/{idHotel}/ofertas")
public class OfertasController {
@Autowired
IOfertasBO obo;
//Ya no se muestra el código de obtenerOferta
//...
@RequestMapping(method=RequestMethod.POST, value="")
public ResponseEntity<Void> insertarOferta(@PathVariable idHotel,
@RequestBody Oferta
oferta,
HttpServletRequest
peticion) {
int idOFerta = obo.crearOferta(idHotel, oferta);
HttpHeaders cabeceras = new HttpHeaders();
try {
cabeceras.setLocation(new
URI(peticion.getRequestURL()+
Integer.toString(idOFerta)));
} catch (URISyntaxException e) {
e.printStackTrace();
}
9
Copyright © 2012-2013 Depto. Ciencia de la computación e IA All rights reserved.
Aplicaciones AJAX y REST con Spring MVC
@RequestMapping(method=RequestMethod.PUT, value="{idOferta}")
public ResponseEntity<Void> insertarOferta(@PathVariable idHotel,
@RequestBody Oferta oferta) {
int idOFerta = obo.modificarOferta(idHotel, oferta);
return new ResponseEntity<Void>(HttpStatus.CREATED);
}
Este caso de uso suele ser sencillo, al menos en lo que respecta a la interfaz. Simplemente
debemos llamar a la URI del recurso a eliminar, y devolver 200 OK si todo ha ido bien:
@RequestMapping(method=RequestMethod.DELETE, value="{idOferta}")
public ResponseEntity<Void> borrarOferta(@PathVariable
idHotel,@PathVariable idOferta) {
obo.eliminarOferta(idHotel, idOferta);
return new ResponseEntity<Void>(HttpStatus.OK);
}
El código del cliente REST podría escribirse usando directamente el API de JavaSE, o
librerías auxiliares como Jakarta Commons HttpClient, que permite abrir conexiones
HTTP de manera sencilla. No obstante, que sea sencillo no implica que no sea tedioso y
necesite de bastantes líneas de código. Para facilitarnos la tarea, Spring 3 ofrece la clase
RestTemplate, que permite realizar las peticiones REST en una sola línea de código
Java.
10
Copyright © 2012-2013 Depto. Ciencia de la computación e IA All rights reserved.
Aplicaciones AJAX y REST con Spring MVC
Como se ve, en el cliente se usa la misma notación que en el servidor para las URLs con
partes variables. En los parámetros del método Java getForObject se coloca el tipo
esperado (la clase Oferta) y, por orden, las partes variables de la URL. Igual que en el
servidor, si tenemos las librerías de Jackson en el classpath se procesará automáticamente
el JSON. Así, Jackson se encarga de transformar el JSON de nuevo en un objeto Java que
podemos manipular de modo convencional.
Si hubiéramos implementado en el servidor un método en el controller para dar de alta
ofertas, podríamos llamarlo desde el cliente así:
11
Copyright © 2012-2013 Depto. Ciencia de la computación e IA All rights reserved.
Aplicaciones AJAX y REST con Spring MVC
@ExceptionHandler(OfertaInexistenteException.class)
public ResponseEntity<String>
gestionarNoExistentes(OfertaInexistenteException oie) {
return new ResponseEntity<String>(oie.getMessage(),
HttpStatus.NOT_FOUND);
}
@ExceptionHandler({OfertaInexistenteException.class,
HotelInexistenteException.class})
public ResponseEntity<String> gestionarNoExistentes(Exception e) {
return new ResponseEntity<String>(e.getMessage(),
HttpStatus.NOT_FOUND);
}
Spring MVC genera unas cuantas excepciones propias, caso por ejemplo de que se
produzca un error de validación en un objeto que hemos querido chequear con JSR303,
ya veremos cómo (BindException),o que el cliente no acepte ninguno de los formatos
que podemos enviarle (HttpMediaTypeNotAcceptableException), o que intentemos
llamar con POST a un método que solo acepta GET
(HttpRequestMethodNotSupportedException), entre otros. En esos casos actúa un
exception handler definido por defecto que lo único que hace es capturar las excepciones
12
Copyright © 2012-2013 Depto. Ciencia de la computación e IA All rights reserved.
Aplicaciones AJAX y REST con Spring MVC
y generar códigos de estado HTTP (400 en el primer caso, 406 en el segundo y 405 en el
tercero). Si queremos que se haga algo más, como enviar un mensaje con más
información en el cuerpo de la respuesta, tendremos que definir nuestros propios handlers
para esas excepciones.
13
Copyright © 2012-2013 Depto. Ciencia de la computación e IA All rights reserved.
Aplicaciones AJAX y REST con Spring MVC
14
Copyright © 2012-2013 Depto. Ciencia de la computación e IA All rights reserved.