100% encontró este documento útil (1 voto)
4K vistas624 páginas

Cuarta Edición: Craig Walls

Derechos de autor
© © All Rights Reserved
Nos tomamos en serio los derechos de los contenidos. Si sospechas que se trata de tu contenido, reclámalo aquí.
Formatos disponibles
Descarga como PDF, TXT o lee en línea desde Scribd
100% encontró este documento útil (1 voto)
4K vistas624 páginas

Cuarta Edición: Craig Walls

Derechos de autor
© © All Rights Reserved
Nos tomamos en serio los derechos de los contenidos. Si sospechas que se trata de tu contenido, reclámalo aquí.
Formatos disponibles
Descarga como PDF, TXT o lee en línea desde Scribd
Está en la página 1/ 624

Incluye Spring 4

Spri
CUARTA EDICIÓN

Craig Walls

A N A Y A
■ M U L T IM E D IA *
Spring
Cuarta Edición
Spring
Cuarta Edición

Craig Walls

ANAYA
■ M U L T IM E D IA * «
PR O G R A M A C IÓ N

T ít u l o d e l a o b r a o r ig in a l :

Spring in Action Fourth Edition

Tr a d u c t o r a :
Laura Ayuso Castrillo

Todos los nombres propios de programas, sistemas operativos, equipos


hardware, etc. que aparecen en este libro son marcas registradas de sus
respectivas compañías u organizaciones.

Reservados todos los derechos. El contenido de esta


obra está protegido por la Ley, que establece penas de
prisión y/o multas, además de las correspondientes
indemnizaciones por daños y perjuicios, para quienes
reprodujeren, plagiaren, distribuyeren o comunicaren
públicamente, en todo o en parte, una obra literaria,
artística o científica, o su transformación, interpretación
o ejecución artística fijada en cualquier tipo de soporte o
comunicada a través de cualquier medio, sin la preceptiva
autorización.

Authorized translation from English language edition published by Manning Publications


Copyright © 2015 by Manning Publications Co.
All rights reserved.

Edición española:
© EDICIONES ANAYA MULTIMEDIA (GRUPO ANAYA, S.A.), 2015
Juan Ignacio Lúea de Tena, 15. 28027 Madrid
Depósito legal: M.4426-2015
ISBN: 978-84-415-3682-1
Printed in Spain
5

Agradecimientos
Antes de que este libro llegara a sus manos, pasó por las de otras muchas personas.
Manos que se encargaron de editarlo, revisarlo, corregirlo y de gestionar todo el proceso
de publicación. Si no fuera por todas ellas, ahora no estaría leyendo este libro.
En primer lugar, me gustaría dar las gracias a la editorial por trabajar duro, por presio­
narme para que acabara este proyecto y por hacer su parte del trabajo para que la calidad
de este libro fuera la mejor posible. Por ello, quiero dar las gracias a: Marjan Bace, Michael
Stephens, Cynthia Kane, Andy Carroll, Benjamín Berg, Alyson Brener, Dottie Marisco, Mary
Piergies, Janet Vail y a otros muchos en la sombra.
A lo largo del proceso, ha habido una serie de personas a las que se les ha dado la opor­
tunidad de leer este manuscrito cuando solo era un borrador y que han aportado valiosos
comentarios para darle forma al producto final. Quiero expresar mi agradecimiento a todos
ellos: Bob Casazza, Chaoho Hsieh, Christophe Martini, Gregor Zurowski, James Wright,
Jeelani Basha, Jens Richter, Jonathan Thoms, Josh Hart, Karen Christenson, Mario Arias,
Michael Roberts, Paul Balogh y Ricardo da Silva Lima. De forma especial, me gustaría dar
las gracias a John Ryan, por asumir el trabajo de revisor técnico del manuscrito.
Evidentemente, quiero dar las gracias a mi esposa por soportar otro proyecto editorial
y por su apoyo. Te quiero más de lo que te imaginas.
A Maisy y Madi, mis pequeñas princesas, quiero darles las gracias por sus abrazos, por
sus risas y por sus consejos sobre lo que debería incluir en el libro.
También quiero dar las gracias a mis colegas del equipo Spring. ¿Qué puedo decir? Sois
increíbles. Soy muy afortunado por formar parte de una organización que impulsa Spring
hacia el futuro. Nunca dejareis de sorprenderme.
Gracias también a todas las personas que he conocido en mis conferencias en grupos
de usuarios y No Fluff/Just Stuff.
Por último, mi más sincero agradecimiento a los P hoenicians. Ya sabéis (como los segui­
dores de Epcot) lo que habéis hecho.

Sobre el autor
Craig Walls es ingeniero de la empresa Pivotal en los proyectos Spring Social y Spring
Sync y el autor de las ediciones anteriores de este manual. Es un gran defensor del marco
de trabajo Spring y suele dar charlas en grupos de usuarios locales y conferencias, además
de escribir sobre Spring. Cuando no está jugando con código, Craig pasa tanto tiempo como
puede con su mujer, sus dos hijas, sus dos pájaros y sus dos perros.
índice de
contenidos

Agradecimientos.....................................................................................................................................5
Sobre el autor........................................................................................................................................... 5

Introducción.................................................................................................................................. 20
Sobre este libro.......................................................................................................................................22
Hoja de ruta......................................................................................................................................22
Convenciones......................................................................................................................................... 24
Código fuente........................................................................................................................................ 25
Sobre la ilustración de la cubierta.....................................................................................................25

Parte I. El núcleo de Spring........................................................................................................27

1. Pasar a la acción........................................................................................................................28
Simplificar el desarrollo en Ja v a ....................................................................................................... 29
Liberar el potencial de los PO JO .................................................................................................30
Inyectar dependencias...................................................................................................................31
Funcionamiento de la D I........................................................................................................ 31
Inyectar una hazaña en un caballero...................................................................................33
La aplicación en funcionamiento.......................................................................................... 35
Aplicar aspectos.............................................................................................................................. 36
AOP en funcionamiento......................................................................................................... 38
Eliminar código reutilizable con plantillas.............................................................................. 41
Indice de contenidos

Contenedores para bean......................................................................................................................43


Trabajar con un contexto de aplicación.....................................................................................44
La vida de un bean.........................................................................................................................45
Componentes de Spring......................................................................................................................47
Módulos de Spring.........................................................................................................................47
Contenedor del núcleo de Spring......................................................................................... 49
Modulo AOP de Spring.................................................................................... 49
Acceso de datos e integración................................................................................................49
Web y acceso rem oto................................................................................................................50
Instrumentación........................................................................................................................ 50
Pruebas........................................................................................................................................50
El catálogo de Spring.....................................................................................................................51
Spring Web Flow....................................................................................................................... 51
Spring Web Services.................................................................................................................51
Spring Security.......................................................................................................................... 51
Spring Integration.....................................................................................................................51
Spring Batch............................................................................................................................... 52
Spring D ata................................................................................................................................ 52
Spring Social.............................................................................................................................. 52
Spring M obile............................................................................................................................ 53
Spring para Android................................................................................................................53
Spring Boot................................................................................................................................ 53
Novedades en Spring.......................................................................................................................... 53
Novedades de 3 .1 ........................................................................................................................... 53
Novedades de 3 .2 ........................................................................................................................... 54
Novedades de Spring 4.0...............................................................................................................56
Resumen................................................................................................................................................. 57

2. Conexión de bean.................................................................................................................. 58
Opciones de configuración de Spring..............................................................................................60
Conexión automática de bean........................................................................................................... 60
Crear bean detectables...................................................................................................................61
Asignar nombres a bean de análisis de componentes...........................................................64
Definir un paquete base para el análisis de componentes................................................... 64
Anotar bean para su conexión automática...............................................................................66
Verificar la configuración automática........................................................................................ 67
Conectar bean con Ja v a ....................................................................................................................... 69
Crear una clase de configuración................................................................................................69
Declarar un bean sencillo............................................................................................................. 70
Inyecciones con JavaConfig......................................................................................................... 71
8 índice de contenidos

Conectar bean con XM L...................................................................................................................... 72


Crear una especificación de configuración XM L.................................................................... 73
Declarar un sencillo elemento <bean >...................................................................................... 74
Inicializar un bean con inyecciones de constructor................................................................75
Inyectar constructores con referencias de bean................................................................. 75
Inyectar valores literales a constructores........................................................................... 77
Conectar colecciones................................................................................................................79
Configurar propiedades............................................................................................................... 81
Inyectar propiedades con valores literales......................................................................... 83
Importar y combinar configuraciones............................................................................................. 86
Hacer referencia a configuración XML en JavaConfig...........................................................86
Hacer referencia a JavaConfig en configuración XML.......................................................... 88
Resumen................................................................................................................................................. 89

3. Técnicas avanzadas de conexión..............................................................................................90


Entornos y perfiles............................................................................................................................... 91
Configurar bean de perfiles......................................................................................................... 92
Configurar perfiles en X M L...................................................................................................94
Activar perfiles................................................................................................................................ 96
Realizar pruebas con perfiles.................................................................................................98
Bean condicionales............................................................................................................................... 98
Solucionar ambigüedades en conexiones automáticas..............................................................101
Designar un bean principal........................................................................................................ 102
Calificar bean conectados automáticamente..........................................................................103
Crear calificadores personalizados.................................................................................... 104
Definir anotaciones de calificador personalizadas.........................................................105
Ámbitos de bean..................................................................................................................................107
Trabajar con el ámbito de sesión y de solicitud.................................................................... 108
Declarar proxy con ámbito en XML..........................................................................................110
Inyectar valores en tiempo de ejecución........................................................................................111
Inyectar valores externos.............................................................................................................112
Dentro del entorno de Spring..............................................................................................112
Resolver marcadores de posición de propiedades..........................................................114
Conexiones con SpEL................................................................................................................... 115
Ejemplos de SpEL................................................................................................................... 116
Expresar valores literales...................................................................................................... 117
Referencias a bean, propiedades y métodos.................................................................... 118
Trabajar con tipos en expresiones....................................................................................... 118
Operadores de SpEL...............................................................................................................119
Evaluar expresiones regulares.............................................................................................120
Evaluar colecciones................................................................................................................121
Resumen............................................................................................................................................... 122
índice de contenidos 9

4. Spring orientado a aspectos....................................................................................................... 124


Qué es la programación orientada a aspectos..............................................................................126
Terminología de AOP...................................................................................................................126
Consejo...................................................................................................................................... 127
Puntos de cruce....................................................................................................................... 127
Puntos de corte........................................................................................................................ 128
Aspectos.................................................................................................................................... 128
Introducciones......................................................................................................................... 128
Entrelazado...............................................................................................................................128
Compatibilidad de Spring con AOP............................................................. 129
Los consejos de Spring se escriben en Java...................................................................... 130
Spring aconseja los objetos en tiempo de ejecución....................................................... 130
Spring solo admite puntos de cruce de método..............................................................131
Seleccionar puntos de cruce con puntos de corte....................................................................... 131
Escribir puntos de corte...............................................................................................................132
Seleccionar bean en puntos de corte........................................................................................ 134
Crear aspectos anotados....................................................................................................................134
Definir un aspecto..................................................................................................>....................134
Anotaciones alrededor del consejo...........................................................................................139
Procesar parámetros en consejos...............................................................................................140
Anotar introducciones.................................................................................................................143
Declarar aspectos en X M L ................................................................................................................145
Declarar antes y después de un consejo.................................................................................. 147
Declarar alrededor del consejo..................................................................................................149
Pasar parámetros a consejos...................................................................................................... 151
Incluir nuevas funcionalidades mediante aspectos..............................................................152
Inyectar aspectos de A spectJ........................................................................................................... 153
Resumen................................................................................................................................................156

Parte II. Spring en la Web................................................................................................................157

5. Crear aplicaciones Web de Spring............................................................................................158

Introducción a Spring M V C .............................................................................................................159


Seguir el ciclo de vida de una solicitud................................................................................... 159
Configurar Spring M VC..............................................................................................................161
Configurar DispatcherServlet..............................................................................................161
Historia de dos contextos de aplicación............................................................................163
Habilitar Spring M VC........................................................................................................... 163
La aplicación Spittr....................................................................................................................... 166
Crear un sencillo controlador.......................................................................................................... 166
Probar el controlador...................................................................................................................168
10 índice de contenidos

Definir procesamiento de solicitudes en el nivel de la clase.............................................169


Pasar datos del modelo a la vista..............................................................................................170
Aceptar entradas de solicitud.......................................................................................................... 175
Aceptar parámetros de consulta...............................................................................................176
Aceptar entradas a través de parámetros de ruta................................................................. 178
Procesar formularios.......................................................................................................................... 181
Crear un controlador de procesamiento de formularios.................................................... 182
Validar formularios...................................................................................................................... 186
Resumen................................................................................................................................................189

6. Representar vistas W eb.............................................................................. 190


Resolución de vistas........................................................................................................................... 191
Crear vistas JSP....................................................................................................................................193
Configurar un solucionador de vistas compatible con JS P ................................................ 194
Resolver vistas JSTL................................................................................................................195
Utilizar las bibliotecas JSP de Spring.............................................................. 195
Vincular formularios al modelo...........................................................................................196
Mostrar errores........................................................................................................................ 198
La biblioteca de etiquetas generales de Spring...............................................................203
Mostrar mensajes internacionalizados..............................................................................204
Crear U R L................................................................................................................................ 206
Escapar contenido...................................................................................................................207
Definir un diseño con vistas Apache Tiles....................................................................................208
Configurar un solucionador de vistas Tiles........................................................................... 209
Definir m osaicos.....................................................................................................................210
Trabajar con Thymeleaf.....................................................................................................................214
Configurar un solucionador de vistas Thymeleaf................................................................214
Definir plantillas Thymeleaf...................................................................................................... 216
Vincular formularios con Thymeleaf.................................................................................218
Resumen............................................................................................................................................... 220

7. Operaciones avanzadas con Spring M V C ..............................................................................222


Configuración alternativa de Spring M V C ..................................................................................223
Personalizar la configuración de DispatcherServlet........................................................... 223
Añadir servlet y filtros adicionales.......................................................................................... 224
Declarar DispatcherServlet en web.xml..................................................................................226
Procesar datos de formularios multiparte.................................................................................... 228
Configurar un solucionador multiparte..................................................................................230
Resolver solicitudes multiparte con Servlet 3.0...............................................................230
Configurar un solucionador multiparte Jakarta Commons Fileupload................... 232
Procesar solicitudes multiparte.................................................................................................233
índice de contenidos 11

Recibir un archivo multiparte............................................................................................. 234


Guardar archivos en Amazon S3........................................................................................ 235
Recibir el archivo transferido como parte........................................................................ 236
Controlar excepciones.......................................................................................................................237
Asignar excepciones a códigos de estado H TTP..................................................................237
Crear métodos de control de excepciones..............................................................................239
Aconsejar controladores....................................................................................................................241
Transferir datos entre solicitudes de redirección........................................................................ 242
Redirecciones con plantillas de U RL....................................................................................... 243
Trabajar con atributos flash........................................................................................................ 244
Resumen............................................................................................................................................... 246

8. Trabajar con Spring Web F lo w ...............................................................................................248


Configurar Web Flow en Spring.....................................................................................................249
Conectar un ejecutor de flujo..................................................................................................... 250
Configurar un registro de flujo..................................................................................................250
Procesar solicitudes de flujo.................................................................................................251
Componentes de un flujo..................................................................................................................252
Estados............................................................................................................................................ 252
Estados de vista....................................................................................................................... 253
Estados de acción....................................................................................................................253
Estados de decisión................................................................................................................254
Estados de subflujo.................................................................................................................254
Estados finales......................................................................................................................... 255
Transiciones....................................................................................................................................255
Transiciones globales............................................................................................................. 256
Datos del flujo................................................................................................................................257
Declaración de variables....................................................................................................... 257
Ámbitos de datos de flujo.....................................................................................................258
Combinar todas las piezas: el flujo p izza.....................................................................................258
Definición del flujo base............................................................................................................. 259
Obtener información del cliente............................................................................................... 262
Solicitar un número de teléfono......................................................................................... 264
Buscar el cliente.......................................................................................................................265
Registrar un nuevo cliente....................................................................................................265
Comprobar el área de reparto.............................................................................................. 266
Almacenar los datos del cliente.......................................................................................... 267
Finalizar el flu jo ......................................................................................................................267
Crear un pedido............................................................................................................................ 268
Gestión de los pagos....................................................................................................................270
Proteger flujos Web............................................................................................................................ 272
Resumen............................................................................................................................................... 273
12 índice de contenidos

9. Seguridad en Spring.....................................................................................................................274
Introducción a Spring Security........................................................................................................ 275
Módulos de Spring Security...................................................................................................... 276
Filtrar solicitudes W eb.................................................................................................................277
Crear una sencilla configuración de seguridad....................................................................278
Seleccionar servicios de detalles de usuario...............................................................................280
Trabajar con un repositorio de usuarios en memoria...............................................................281
Autenticar sobre tablas de base de datos...............................................................................283
Reemplazar las consultas de usuario predeterminadas...............................................283
Trabajar con contraseñas codificadas.................................................................................284
Aplicar autenticación basada en LDAP........................................................................................ 285
Configurar la comparación de contraseñas....................................................................286
Hacer referencia a un servidor LDAP remoto................................................................287
Configurar un servidor LDAP integrado......................................................................... 287
Configurar un servicio de usuario personalizado...............................................................289
Interceptar solicitudes.......................................................................................................................290
Seguridad con expresiones de Spring....................................................................................293
Reforzar la seguridad del canal.................................................................................................294
Evitar falsificaciones de petición en sitios cruzados............................................................295
Autenticar usuarios............................................................................................................................ 297
Añadir una página de inicio de sesión personalizada....................................................... 297
Habilitar autenticación HTTP básica....................................................................................... 299
Activar la característica para recordar información.............................................................300
Cerrar sesión..................................................................................................................................301
Proteger la vista...................................................................................................................................302
Utilizar la biblioteca de etiquetas JSP de Spring Security.................................................302
Acceder a la información de autenticación...................................................................... 302
Representación condicional....................................................................................................... 304
Trabajar con el dialecto Spring Security de Thymeleaf....................................................... 305
Resumen............................................................................................................................................... 307
Parte III. Spring en el servidor...................................................................................................... 309

10. Acceso a bases de datos con Spring y JD B C ...................................................................... 310


Filosofía de acceso a datos de Spring.............................................................................................311
Jerarquía de excepciones de Spring......................................................................................... 312
Excepciones de persistencia en Spring independientes de la plataforma...............314
Ausencia de bloques catch ...................................................................................................315
Acceso a datos mediante plantillas.......................................................................................... 315
Configurar un origen de datos........................................................................................................ 318
Utilizar orígenes de datos JN D I................................................................................................318
Utilizar un origen de datos agrupado..................................................................................... 319
Orígenes de datos basados en controladores JDBC............................................................ 321
Utilizar un origen de datos incrustado..................................................................................322
Utilizar perfiles para seleccionar un origen de datos......................................................... 323
Utilizar JDBC con Spring..................................................................................................................325
Gestionar código JDBC descontrolado.................................................................................... 326
Trabajar con plantillas JD BC ...................................................................................................... 329
Añadir datos con JdbcTemplate.......................................................................................... 329
Leer datos con JdbcTemplate...............................................................................................331
Usar expresiones lambda de Java 8 con JdbcTemplate................................................332
Usar parámetros con nombre...............................................................................................333
Resumen............................................................................................................................................... 334

11. Persistencia de datos con asignación relacional de objetos...........................................336


Integración de Hibemate con Spring............................................................................................ 338
Declarar una sesión de fábrica de Hibemate......................................................................... 338
Hibemate sin código de Spring.................................................................................................340
Spring y el API Java Persistence.....................................................................................................342
Configurar una fábrica de administradores de entidades.................................................. 342
Configurar JPA gestionado por una aplicación...............................................................343
Configurar JPA gestionado por un contenedor...............................................................345
Obtener EntityManagerFactory desde JN D I................................................................... 347
Escribir un repositorio basado en JPA..................................................................................... 347
Repositorios JPA automáticos con Spring D ata.......................................................................... 350
Definir métodos de consulta...................................................................................................... 352
Declarar consultas personalizadas........................................................................................... 355
Combinar funcionalidades personalizadas........................................................................... 356
Resumen............................................................................................................................................... 358

12. Trabajar con bases de datos N oSQ L...................................................................................... 360


Persistencia de documentos con MongoDB.................................................................................361
Habilitar MongoDB......................................................................................................................362
Anotar tipos de modelo para persistencia en M ongoDB................................................... 366
Acceder a MongoDB con MongoTemplate.............................................................................369
Crear un repositorio MongoDB.................................................................................................370
Añadir métodos de consulta personalizados..................................................................372
Especificar consultas............................................................................................................. 373
Combinar comportamientos personalizados de repositorios...................................... 373
Trabajar con datos gráficos en Neo4j..............................................................................................375
Configurar Spring Data Neo4j...................................................................................................375
Anotar entidades gráficas.......................................................................................................... 378
Trabajar con Neo4jTemplate....................................................................................................... 381
14 índice de contenidos

Crear repositorios Neo4j automáticos.....................................................................................382


Añadir métodos de consulta................................................................................................384
Especificar consultas personalizadas.................................................................................385
Combinar comportamientos personalizados de repositorios..................................... 385
Trabajar con datos de clave-valor en Redis.................................................................................387
Conectarse a R edis....................................................................................................................... 387
Trabajar con RedisTemplate....................................................................................................... 389
Trabajar con valores sencillos.............................................................................................. 391
Trabajar con listas...................................................................................................................391
Realizar operaciones en conjuntos..................................................................................... 392
Vincular a una clave.............................................................................................................. 392
Establecer señalizadores de clave y valor..............................................................................393
Resumen............................................................................................................................................... 394

13. Almacenamiento de datos en caché...................................................................................... 396


Habilitar la compatibilidad con caché.......................................................................................... 397
Configurar un administrador de caché...................................................................................399
Almacenamiento en caché con Ehcache........................................................................... 399
Utilizar Redis para almacenamiento en caché................................................................401
Trabajar con varios administradores de caché.................................................................402
Anotar métodos para almacenamiento en caché..................................................................403
Completar la caché.......................................................................................................................403
Añadir valores a la caché......................................................................................................405
Personalizar la clave de caché............................................................................................. 405
Almacenamiento condicional en caché............................................................................ 406
Eliminar entradas de la caché....................................................................................................408
Declarar almacenamiento en caché en X M L................................................................................409
Resumen............................................................................................................................................... 413

14. Proteger métodos......................................................................................................................... 414


Proteger métodos con anotaciones.................................................................................................415
Restringir el acceso a métodos con @Secured....................................................................... 416
Utilizar @RolesAllowed de JSR-250 con Spring Security................................................... 417
Utilizar expresiones para proteger métodos................................................................................418
Expresar reglas de acceso a métodos....................................................................................... 419
Autorización previa de acceso a métodos........................................................................ 419
Autorización posterior de acceso a métodos...................................................................420
Filtrar entradas y salidas de métodos......................................................................................421
Postfiltrado de valores devueltos por métodos...............................................................421
Filtrado previo de parámetros de m étodo....................................................................... 422
Definir un evaluador de perm isos.....................................................................................423
Resumen............................................................................................................................................... 425
índice de contenidos K m

Parte IV. Integración de Sp rin g.................................................................................................... 427

15. Trabajar con servicios rem otos...............................................................................................428


Acceso remoto en Spring..................................................................................................................429
Trabajar con RM I................................................................................................................................ 432
Exportar un servicio RM I........................................................................................................... 432
Configurar un servicio RMI en Spring..............................................................................433
Conectar un servicio RMI........................................................................................................... 435
Exponer servicios remotos con Hessian y Burlap..................................................................... 437
Exponer funcionalidad de bean con Hessian o Burlap...................................................... 438
Exportar un servicio Hessian...............................................................................................438
Configurar el controlador Hessian..................................................................................... 439
Exportar un servicio de Burlap........................................................................................... 440
Acceder a servicios H essian/Burlap....................................................................................... 441
Utilizar Httplnvoker de Spring.......................................................................................................442
Exponer bean como servicios HTTP........................................................................................ 443
Acceder a servidos mediante HTTP........................................................................................ 444
Publicar y consumir servicios Web.................................................................................................445
Crear puntos finales JAX-WS habilitados para Spring...................................................... 446
Conexión automática de puntos finales JAX-WS en Spring....................................... 446
Exportar puntos finales JAX-WS independientes.......................................................... 447
Proxy de servicios JAX-WS en el lado cliente...................................................................... 449
Resumen............................................................................................................................................... 450

16. Crear API REST con Spring M V C ......................................................................................... 452


Acerca de R E ST ...................................................................................................................................453
Aspectos básicos de REST.......................................................................................................... 453
Compatibilidad de Spring con REST....................................................................................... 454
Crear el primer punto final REST...................................................................................................455
Negociar la representación de recursos..................................................................................457
Determinar el tipo de contenido solicitado...................................................................... 458
Influir en la forma de seleccionar tipos de contenido.................................................. 459
Ventajas y limitaciones de ContentNegotiatingViewResolver.................................... 462
Trabajar con conversores de mensajes H TTP........................................................................ 463
Devolver el estado de un recurso en el cuerpo de la respuesta................................. 465
Recibir el estado de un recurso en el cuerpo de la solicitud...................................... 466
Controladores predeterminados para la conversión de mensajes..............................467
Entregar más que recursos................................................................................................................469
Comunicar errores al cliente...................................................................................................... 469
Trabajar con ResponseEntity................................................................................................470
Controlar errores.....................................................................................................................471
Establecer encabezados en la respuesta..................................................................................473
16 índice de contenidos

Consumir recursos R EST..................................................................................................................475


Operaciones de RestTemplate...................................................................................................476
Obtener recursos con G E T ......................................................................................................... 478
Recuperar recursos.......................................................................................................................478
Extraer metadatos de la respuesta........................................................................................... 479
Añadir recursos con P U T........................................................................................................... 481
Eliminar recursos con DELETE.................................................................................................482
Publicar datos de recursos con POST......................................................................................483
Recibir respuestas de objeto desde solicitudes PO ST......................................................... 483
Recibir la ubicación de un recurso tras una solicitud POST........................................ 484
Intercambiar recursos..................................................................................................................485
Resumen.............................................. 487

17. Mensajería en Spring....................................................................................................... 488


Breve introducción a la mensajería asincrona............................................................................. 489
Enviar mensajes............................................................................................................................ 490
Mensajería punto a punto.....................................................................................................491
Mensajería de publicación-suscripción.............................................................................492
Evaluar las ventajas de la mensajería asincrona...................................................................493
Sin esperas............................................................................................................................... 493
Orientación del mensaje y desacoplamiento...................................................................493
Independencia de la ubicación........................................................................................... 494
Recepción garantizada.......................................................................................................... 494
Enviar mensajes con JM S ..................................................................................................................494
Configurar un agente de mensajes en Spring....................................................................... 495
Crear una fábrica de conexiones......................................................................................... 495
Declarar un destino de mensajes ActiveMQ.................................................................... 496
Utilizar la plantilla JMS de Spring........................................................................................... 497
Gestionar código JMS descontrolado.................................................................................497
Trabajar con plantillas JM S...................................................................................................499
Enviar m ensajes......................................................................................................................500
Configurar un destino predeterminado........................................................................... 502
Convertir mensajes al enviarlos.......................................................................................... 503
Consumir m ensajes................................................................................................................504
Crear POJO basados en m ensajes............................................................................................ 506
Crear un escuchador de mensajes...................................................................................... 506
Configurar escuchadores de m ensajes....................................................................................507
Utilizar RPC basados en mensajes........................................................................................... 508
Exportar servicios basados en JMS..................................................................................... 509
Consumir servicios basados en JM S ..................................................................................510
Mensajería con AMQP....................................................................................................................... 511
Breve introducción a AMQP...................................................................................................... 511
índice de contenidos 17

Configurar Spring para mensajería AM QP.......................................................................... 513


Declarar colas, intercambios y vinculaciones................................................................. 514
Enviar mensajes con RabbitTemplate...................................................................................... 516
Recibir mensajes AM QP............................................................................................................. 518
Recibir mensajes con RabbitTemplate............................................................................... 518
Definir POJO de AMQP basados en m ensajes...............................................................519
Resumen............................................................................................................................................... 520
18. M ensajería con WebSocket y ST O M P .................................................................................522
Trabajar con el API WebSocket de nivel inferior de Spring.................................................... 523
Afrontar la falta de compatibilidad con WebSocket.................................................................528
Trabajar con mensajes STOMP........................................................................................................ 531
Habilitar mensajería STOMP.....................................................................................................532
Habilitar un enlace de agente de STOMP....................................................................... 534
Procesar mensajes STOMP desde el cliente.......................................................................... 535
Procesar suscripciones.......................................................................................................... 537
Crear el cliente JavaScript.....................................................................................................538
Enviar mensajes al cliente.......................................................................................................... 539
Enviar un mensaje tras procesarlo..................................................................................... 539
Enviar un mensaje desde cualquier punto de una aplicación.................................... 540
Trabajar con mensajes para usuarios específicos........................................................................ 542
Trabajar con mensajes de usuario en un controlador......................................................... 543
Enviar mensajes a un usuario concreto..................................................................................545
Controlar excepciones de mensajes................................................................................................546
Resumen............................................................................................................................................... 547

19. Enviar correo con Sp rin g.......................................................................................................... 548


Configurar Spring para enviar correo electrónico...................................................................... 549
Configurar un emisor de correo electrónico.......................................................................... 549
Conectar y utilizar el remitente de correo.............................................................................551
Crear mensajes de correo electrónico avanzados...................................................................... 552
Añadir archivos adjuntos........................................................................................................... 552
Enviar mensajes de correo con contenido enriquecido...................................................... 554
Generar correo electrónico mediante plantillas.......................................................................... 555
Crear mensajes de correo electrónico con Velocity...............................................................555
Utilizar Thymeleaf para crear mensajes de correo...............................................................557
Resumen............................................................................................................................................... 559

20. Gestionar bean de Spring con JM X ....................................................................................... 560


Exportar bean de Spring como M Bean......................................................................................... 561
Exponer métodos por nombre...................................................................................................564
Usar interfaces para definir operaciones y atributos de MBean....................................... 566
18 índice de contenidos

Trabajar con MBean basados en anotaciones........................................................................ 567


Controlar colisiones de MBean..................................................................................................569
Acceso remoto a MBean....................................................................................................................570
Exponer MBean remotos............................................................................................................ 570
Acceder a MBean remotos.......................................................................................................... 571
Derivar MBean mediante proxy................................................................................................573
Controlar notificaciones....................................................................................................................574
Escuchar notificaciones................................................................................................................576
Resumen................................................................................................................................................576

21. Sim plificar el desarrollo de Spring con Spring Boot.....................................................578


Introducción a Spring Boot.............................................................................................................. 579
Añadir dependencias de inicio..................................................................................................580
Configuración automática.......................................................................................................... 583
La ILC de Spring Boot..................................................................................................................584
El Agente......................................................................................................................................... 585
Crear una aplicación con Spring Boot........................................................................................... 585
Procesar solicitudes...................................................................................................................... 587
Crear la v ista ..................................................................................................................................590
Añadir elementos estáticos........................................................................................................ 591
Persistencia de datos....................................................................................................................592
Probar la aplicación......................................................................................................................595
Groovy y la ILC de Spring Boot...................................................................................................... 597
Crear un controlador de Groovy...............................................................................................598
Persistencia con un repositorio de Groovy.............................................................................600
Ejecutar la ILC de Spring B o o t..................................................................................................601
Instalar la ILC .......................................................................................................................... 601
Ejecutar la aplicación Contacts con la IL C ....................................................................... 602
Adentrarse en una aplicación con Actuator.................................................................................603
Resumen................................................................................................................................................606

índice alfabético.................................................................................................................................608
In trodu cción
Lo mejor sigue mejorando. Hace más de 12 años, Spring irrumpió en el sector de la
programación con Java con el ambicioso objetivo de simplificar el desarrollo empresarial
con este lenguaje. Desafió a los pesados modelos de programación del momento con un
modelo más sencillo y ligero basado en simpes objetos de Java.
Ahora, varios años y versiones después, comprobamos que Spring ha tenido un gran
impacto en el desarrollo de aplicaciones empresariales. Se ha convertido en el estándar de
facto para innumerables proyectos de Java y ha influido en la evolución de algunas de las
especificaciones y estructuras que originalmente pretendía reemplazar. Se podría afirmar
que la actual especificación EJB (E nterprise JavaB ean s) hubiera sido muy diferente si Spring
no hubiera desafiado sus primeras versiones.
Pero Spring continua evolucionando con el objetivo constante de facilitar las compli­
cadas tareas de desarrollo y de proporcionar innovadoras herramientas a los progra­
madores de Java. Mientras que en sus inicios pretendía desafiar el statu s qu o, ahora
Spring ha dado un paso adelante y prepara el camino del desarrollo de aplicaciones
Java.
Por ello, es el momento de que una edición actualizada de este libro ilustre el estado
actual de Spring. En los últimos años han sucedido muchas cosas, tantas que no se podrían
abordar en una única edición. No obstante, he intentado abarcar la mayor cantidad de
información posible.
En esta edición encontrará las siguientes novedades:

• Un énfasis en la configuración de Spring basada en Java con opciones de configura­


ción de Java para la gran mayoría de aspectos del desarrollo con Spring.
• Configuración y perfiles condicionales para decidir en tiempo de ejecución qué confi­
guración de Spring usar o descartar.
• Mejoras en Spring MVC, en especial para la creación de servicios REST.
• Uso de Thymeleaf con aplicaciones Web Spring como alternativa a JSP.
• Habilitación de Spring Security mediante configuración basada en Java.
• Uso de Spring Data para generar automáticamente implementaciones de repositorios
en tiempo de ejecución para JPA, MongoDB y Neo4j.
• Nueva compatibilidad de almacenamiento en caché declarativo de Spring.
• Mensajería Web asincrona con WebSocket y STOMP.
• Spring Boot, un nuevo enfoque revolucionario para trabajar con Spring.

Si es un usuario avanzado de Spring, comprobará que estas novedades serán herra­


mientas muy útiles. Si es un recién llegado al mundo de Spring, ha elegido el momento
perfecto para aprender y este libro le ayudará a dar los primeros pasos.
Es sin duda un momento apasionante para trabajar con Spring. Programar y escribir
con Spring ha sido toda una experiencia en los últimos 12 años. ¡No puedo esperar a ver
qué es lo siguiente!
22 Introducción

Sobre este libro


El marco de trabajo Spring se creó con un objetivo muy específico en mente: facilitar el
desarrollo de aplicaciones Java EE. En la misma línea, este libro se ha escrito para facilitar
el aprendizaje de Spring. Mi objetivo no es presentarle una lista exhaustiva de las API de
Spring. En su lugar, espero presentar el marco de trabajo Spring de la forma más interesante
para un desarrollador Java EE, incluyendo ejemplos prácticos de código procedentes de
experiencias del mundo real. Como Spring es un marco de trabajo modular, este libro se
ha redactado siguiendo la misma idea. Soy consciente de que no todos los desarrolladores
tienen las mismas necesidades. Algunos preferirán comenzar con el marco de trabajo Spring
desde cero, mientras que otros querrán seleccionar diferentes temas y progresar a su propio
ritmo. En este sentido, el libro puede servir como una herramienta para aprender Spring
desde el principio, al mismo tiempo que sirve de guía y manual de referencia para aquellos
que quieran profundizar en características específicas.
Este libro está dirigido a todos los desarrolladores de Java, si bien los desarrolladores
para el ámbito empresarial lo encontrarán especialmente útil. Aunque voy a guiar a los
lectores mediante ejemplos de código cuya complejidad se incrementará capítulo a capí­
tulo, el verdadero potencial de Spring radica en su capacidad para facilitar el desarrollo de
aplicaciones empresariales, por lo que este tipo de desarrolladores podrán sacar el máximo
partido a los ejemplos incluidos en este libro. Como gran parte de Spring está orientado
a proporcionar servicios empresariales, es posible establecer muchas similitudes entre
Spring y EJB.

Hoja de ruta
El libro se divide en cuatro partes. La primera es una introducción a los aspectos básicos
del marco de trabajo Spring. La segunda amplía dichos aspectos para mostrarle cómo crear
aplicaciones Web con Spring. La tercera parte trata sobre los elementos comunes de una
aplicación Spring. La cuarta y última muestra cómo utilizar Spring para integrarse con otras
aplicaciones y servicios. En la primera vamos a hablar sobre el contenedor de Spring, la
inyección de dependencias (DI) y la programación orientada a aspectos (AOP), elementos
esenciales del marco de trabajo Spring. Esto le proporcionará una buena base sobre los
aspectos básicos de Spring que vamos a utilizar a lo largo del libro:
• En el capítulo 1 se presenta Spring, junto con ejemplos básicos de DI y AOP. También
se incluye un breve repaso del ecosistema de Spring.
• El capítulo 2 describe con mayor detalle DI y las distintas formas de combinar los
componentes (bean) de una aplicación, por ejemplo mediante XML, Java o de forma
automática.
• Una vez presentados los fundamentos de los bean, en el capítulo 3 veremos técnicas
avanzadas de conexión. No las utilizará con demasiada frecuencia pero las necesi­
tará en este capítulo para aprovechar al máximo las prestaciones del contenedor de
Spring.
Introducción 23

• En el capítulo 4 se describe la forma de utilizar AOP de Spring para desacoplar las


preocupaciones transversales de los objetos a los que prestan servicio. Este capítulo
también sienta las bases para los siguientes, donde va a utilizar AOP para propor­
cionar servicios declarativos como transacciones, seguridad y almacenamiento en
caché.

La parte II versa sobre el uso de Spring para crear aplicaciones Web:

• El capítulo 5 es una introducción al marco de trabajo Web Spring MVC. Aprenderá


a crear controladores para procesar solicitudes Web y responder con datos de
modelo.
• Cuando un controlador concluye su labor, es necesario representar los datos del
modelo con rma vista. En el capítulo 6 veremos las distintas tecnologías de vista que
puede usar con Spring, como JSP, Apache Tiles y Thymeleaf.
• El capítulo 7 amplía los fundamentos de Spring MVC. Aprenderá a personalizar su
configuración, a procesar transferencias de archivos multiparte, a solucionar excep­
ciones y a intercambiar datos entre solicitudes mediante atributos flash.
• El capítulo 8 trata sobre Spring Web Flow, una extensión de Spring MVC que permite
el desarrollo de aplicaciones Web conversacionales. En este capítulo aprenderá a crear
aplicaciones Web que guiarán al usuario a través de un flujo específico.
• En el capítulo 9 aprenderemos a aplicar seguridad al nivel Web de una aplicación
utilizando Spring Security.

En la parte III nos adentramos en la interfaz de una aplicación para ver cómo se procesan
y conservan los datos:

• En el capítulo 10 se aborda la persistencia de datos mediante la abstracción de Spring


sobre JDBC para trabajar con datos almacenados en una base de datos relacional.
• El capítulo 11 aborda la persistencia de datos desde un ángulo diferente, a través de
JAP (Java P ersisten ce A P I, API de persistencia de Java) para almacenar datos en una
base de datos relacional.
• El capítulo 12 analiza el funcionamiento de Spring con bases de datos no relació­
nales como MongoDB y Neo4j. Independientemente de dónde se guarden los datos,
el almacenamiento en caché puede mejorar el rendimiento si se accede a la base de
datos solo cuando sea necesario.
• El capítulo 13 presenta la compatibilidad de Spring con el almacenamiento declara­
tivo en caché.
• El capítulo 14 revisa Spring Security y le muestra cómo usar AOP para aplicar segu­
ridad en el nivel de los métodos.

La cuarta y última parte del libro describe cómo integrar Spring con otras aplicaciones
o servicios:
24 Introducción

• En el capítulo 15 aprenderá a crear y consumir servicios remotos, como RMI, Hessian,


Burlap y los basados en SOAP.
• El capítulo 16 vuelve a Spring MVC y le muestra cómo utilizarlo para crear servicios
RESÍful por medio del mismo modelo de programación.
• El capítulo 17 trata sobre cómo utilizar Spring para enviar y recibir mensajes asin­
cronos con JMS (Java M essage Service, Servicio de mensajes Java) y AMQP (A dvan ced
M essage Q ueuing Protocol, Protocolo avanzado de cola de mensajes).

• La mensajería asincrona adopta un giro diferente en el capítulo 18, donde veremos


cómo usar Spring con WebSocket y STOMP para comunicaciones asincronas entre
el servidor y el cliente.
• El capítulo 19 le enseñará a utilizar Spring para enviar mensajes de correo electró­
nico.
• En el capítulo 20 se destaca la compatibilidad de Spring con JMX (Java M anagem ent
E xtensions, Extensiones de gestión de Java) para monitorizar y modificar ajustes de
tiempo de ejecución para una aplicación de Spring.
• Por último, en el capítulo 21 se presenta una nueva forma de trabajar con Spring:
Spring Boot. Comprobará que le permite ignorar gran parte de la configuración
predefinida necesaria en las aplicaciones Spring para así poder centrarse en la funcio­
nalidad empresarial.

Convenciones
Para ayudarle a sacar el mayor partido al texto y saber dónde se encuentra en cada
momento, a lo largo del libro utilizamos distintas convenciones:

• Las combinaciones de teclas se muestran en negrita, como por ejemplo Control-A.


Los botones de las distintas aplicaciones también aparecen así.
• Los nombres de archivo, URL y código incluido en texto se muestran en un tipo de
letra m o n o e sp a cia l.
• Los menús, submenús, opciones, cuadros de diálogo y demás elementos de la interfaz
de las aplicaciones se muestran en un tipo de letra Arial.

En estos cuadros se incluye información importante directam ente relacionada


con el texto adjunto. Los trucos, sugerencias y comentarios afines relacionados
con el tema analizado se reproducen en este formato.
Introducción 25

Código fuente
Para desarrollar los ejemplos, puede optar por introducir de forma manual el código o
utilizar los archivos de código fuente que acompañan al libro. Todos los ejemplos se encuen­
tran disponibles en la página web de Anaya Multimedia (http:/ / www.anayamultimedia,
es) en la ficha correspondiente a este libro. Puede realizar la búsqueda de la ficha a través
de la búsqueda sencilla o avanzada y sus diferentes opciones.

Sobre la ilustración de la cubierta


La figura de la cubierta del libro es un "caraco" o habitante de la provincia de Karak,
situada en el suroeste de Jordania. Su capital es la ciudad de Al-Karak, coronada por un
castillo situado sobre una colina y desde el que se pueden disfrutar de excelentes vistas
del mar Muerto y de las llanuras situadas alrededor.
La ilustración procede de un libro francés sobre viajes, la E n cyclopédie des Voyages de J.G.
St. Sauveur, publicado en 1796. En aquella época, los viajes de placer eran un fenómeno
relativamente nuevo y las guías de viajes eran muy populares, introduciendo al turista en
el conocimiento de los habitantes de otras regiones de Francia y del extranjero.
La diversidad de los dibujos de la E n cyclopédie des V oyages nos muestra claramente
el carácter único e individual de las ciudades y provincias del mundo hace tan solo 200
años. Por aquel entonces, los códigos de vestimenta de dos regiones separadas por unas
decenas de kilómetros permitían identificar a las personas de forma única. Esta guía de
viajes recupera el sentido de aislamiento y de la distancia que existía en ese momento y en
el resto de periodos históricos, exceptuando nuestro presente hiperactivo.
Los códigos de vestimenta han cambiado desde entonces y la diversidad de las regiones,
tan abundante en esa época, se ha desvanecido. A menudo, resulta difícil distinguir a un
habitante de un continente de otra persona que viva en otro. Quizá, y si lo vemos desde
un punto de vista optimista, hemos cambiado la diversidad cultural y visual por una vida
personal más variada. O quizás, una vida intelectual y técnica más variada e interesante.
En la editorial nos gusta fomentar la creatividad, la iniciativa y la diversión de la infor­
mática con portadas basadas en la rica diversidad de la vida regional de hace solo dos
siglos, que hemos recuperado gracias a las imágenes de esta guía de viajes.
Parte I.
El núcleo
de Spring

Con Spring podemos llevar a cabo una gran cantidad de tareas. Sin embargo, sus princi­
pales características serían la Inyección de dependencias (DI) y la Programación orientada
a aspectos (AOP).
En el capítulo 1 veremos una breve introducción de Spring con la presentación de DI y
AOP en Spring y cómo pueden ayudarnos a desacoplar objetos de aplicación.
En el capítulo 2 profundizaremos en la conexión de los componentes de una aplicación.
Veremos la configuración automática, la basada en Java y las opciones de XML.
En el capítulo 3 aprenderemos diversos trucos y técnicas para aprovechar al máximo las
prestaciones de Spring, como la configuración condicional, la solución de ambigüedades
durante la conexión automática, los ámbitos y el lenguaje de expresiones de Spring.
El capítulo 4 trata el uso de las características de AOP de Spring para desacoplar servicios
generales del sistema (como la seguridad o las auditorías) de los objetos a los que prestan
servicio. Este capítulo sienta las bases para capítulos posteriores donde aprenderá a utilizar
AOP de Spring para proporcionar transacciones declarativas y de seguridad.
Capítulo

1 P asar a la a cció n

CONCEPTOS FUNDAMENTALES:

• El contenedor de bean de Spring.


• Los módulos del núcleo de Spring.
• El ecosistema de Spring.
• Novedades de Spring.
Es una época perfecta para ser programador de Java. En sus casi 20 años de historia,
Java ha visto momentos buenos y menos buenos. A pesar de algunos aspectos como los
applet, Enterprise JavaBeans (EJB), JDO (Java D ata O bjects, Objetos de datos de Java) e
innumerables sistemas de inicio de sesión, Java ha disfrutado de una rica y diversa historia
como plataforma sobre la que se ha creado gran parte del software empresarial. Y Spring
ha desarrollado un papel esencial en dicha historia.
En sus inicios, Spring se creó como alternativa a tecnologías Java empresariales más
pesadas, en especial a EJB. Spring ofrecía un modelo de programación más ligero que el de
EJB. Dotaba a los objetos de Java (POJO) de prestaciones que antes solo estaban disponibles
a través de EJB y otras especificaciones empresariales de Java.
Con el tiempo, EJB y J2EE evolucionaron. EJB comenzó a ofrecer un sencillo modelo
de programación propio orientado a POJO. Ahora utiliza conceptos como la Inyección de
dependencias (DI) y la Programación orientada a aspectos (AOP), seguramente inspirado
por el éxito de Spring.
Aunque J2EE (denominado JEE actualmente) pudo seguir el ritmo de Spring, Spring
nunca dejó de evolucionar. Ha continuado su avance en áreas en las que, incluso ahora,
JEE está empezando a explorar o en las que directamente no ofrece nada nuevo. El desa­
rrollo para móviles, la integración de API sociales, las bases de datos NoSQL o la nube
son algunas de las áreas en las que Spring sigue innovando, y su futuro parece muy
prometedor.
Como he dicho antes, es el momento perfecto para ser programador de Java. Este libro es
un análisis de Spring. En este primer capítulo abordaremos Spring desde un nivel superior,
para que se haga una idea de lo que ofrece. Conocerá los tipos de problemas que le permite
resolver y se preparará para emprender su viaje por el resto del libro.

Simplificar el desarrollo en Java


Spring es un marco de trabajo de código abierto creado por Rod Johnson, quien describió
su funcionamiento en el libro E xpert O ne-on-O ne: J2E E D esign an d D evelopm ent. Spring
se creó para hacer frente a la complejidad del desarrollo de aplicaciones empresariales,
haciendo posible el uso de JavaBean sencillos para conseguir objetivos que antes solo
eran posibles con EJB. Sin embargo, la utilidad de Spring no se limita al desarrollo en el
lado del servidor. Cualquier aplicación de Java puede beneficiarse de Spring, gracias a su
simplicidad, capacidad de prueba y acoplamiento débil.
Aunque Spring utiliza las palabras bean y JavaBean de manera indistinta para referirse
a los componentes de aplicaciones, no quiere decir que un componente de Spring deba
seguir la especificación JavaBean de forma estricta. Un componente de Spring puede ser
cualquier tipo de POJO. En este libro vamos a asumir la definición amplia de JavaBean,
que sería sinónimo de POJO.
Como podrá comprobar a lo largo de este libro, Spring le permite llevar a cabo gran
cantidad de tareas. Sin embargo, en la raíz de todas las funciones que ofrece, se encuentran
integradas una serie de ideas fundamentales, todas ellas centradas en el objetivo principal
de Spring: simplificar el desarrollo de Java.
30 Capítulo 1

Sin duda, se trata de una afirmación atrevida. Muchos marcos de trabajo afirman simpli­
ficar esto o aquello. Sin embargo, Spring está dirigido a simplificar el desarrollo de Java en
general. Esto requiere una explicación más amplia.
Para poder reducir la complejidad de Java, Spring utiliza cuatro estrategias principales:

• Desarrollo ligero y mínimamente invasivo con objetos POJO.


• Acoplamiento débil m ediante la inyección de dependencias y la orientación de inter­
faces.
• Programación declarativa mediante aspectos y convenciones comunes.
• Reducción del código reutilizable mediante aspectos y plantillas.

Prácticamente todo lo que Spring hace puede reducirse a una o varias de estas cuatro
estrategias. A lo largo del capítulo voy a ampliar cada una de estas ideas, mostrando
ejemplos concretos de cómo Spring consigue simplificar el desarrollo en Java. Vamos a
comenzar comprobando cómo Spring es muy poco invasivo al promover el desarrollo
orientado a POJO.

Liberar el potencial de los POJO


Si ha desarrollado en Java durante un tiempo, probablemente haya visto e incluso
trabajado con marcos que le obligan a ampliar una de sus clases o implementar una de sus
interfaces. El ejemplo clásico sería un bean de sesión sin estado de la especificación EJB 2.
Pero EJB no era el único; las primeras versiones de Struts, WebWork, Tapestry y muchas
otras especificaciones y estructuras de Java resultaban igual de invasivas.
Spring evita, en la medida de lo posible, la inclusión de código innecesario gracias a
su API. Casi nunca le obliga a implementar una interfaz o ampliar una clase específica
de Spring. En su lugar, las clases de una aplicación basada en Spring no suelen incluir
indicaciones de que se estén utilizando por parte de éste. En el peor de los casos, una clase
puede incluir una anotación con uno de los comentarios de Spring pero, en el resto de los
casos, va a ser un POJO.
Para demostrarlo, fíjese en la clase H ello W o rld B ean del siguiente código.

Listado 1.1. Spring no le obliga a incluir código innecesario en HelloWorldBean.


package com. habuma. spring;
public c la s s HelloWorldBean {
public Strin g sayHelloO { / / Esto es todo lo que se n ecesita ,
return "Helio World";
i
i

Como puede apreciar, es una sencilla clase de Java, un POJO. No tiene nada especial
que indique que se trata de un componente de Spring. El modelo de programación no
invasivo de Spring permite que esta clase funcione tanto en aplicaciones de Spring como
en las que no lo sean. A pesar de su aspecto sencillo, los POJO pueden ser muy potentes.
31

Una de las formas en las que Spring los utiliza es combinándolos mediante la inyección de
dependencias. Veamos cómo la inyección de dependencias puede contribuir a mantener
los objetos de aplicación desacoplados entre sí.

Inyectar dependencias
Quizás el término inyección de dependencias le intimide, ya que puede parecer una
técnica de programación compleja o un patrón de diseño. Sin embargo, en realidad no es
tan complicada como parece. Al aplicarla en sus proyectos, podrá comprobar que el código
se vuelve más sencillo, fácil de comprender y de probar.

Funcionamiento de la DI
Cualquier aplicación que no sea de prueba (es decir, algo ligeramente más complejo que
un ejemplo H elloW o rld ) está formada por dos o más clases, que colaboran entre sí para
lev ar a cabo algún tipo de lógica de negocio. Tradicionalmente, cada objeto es responsable
de obtener sus propias referencias a los objetos con los que colabora (sus dependencias).
Esto puede generar código muy acoplado y difícil de probar.
Examinemos la clase K n ig h t que aparece en el listado 1.2.

Listado 1.2. DamseIRescuingKnight solo puede interactuar con RescueDamselQuest.


package com .springinaction. knights;

public c la s s DamseIRescuingKnight implements Knight {

private RescueDamselQuest quest;

public DamseIRescuingKnight() { / / Acoplamiento estrecho con RescueDamselQuest.


th is.q u e st = new RescueDamselQuest( );
}
public void embarkOnQuest()
qu est. embark( );
}
}
Como puede comprobar, D a m se IR e scu in g K n ig h t (El valiente caballero) crea su propio
reto, R e s c u e D a m s e lQ u e s t (Rescatar a la damisela), dentro del constructor. Esto hace que
D a m s e IR e s c u in g K n ig h t esté acoplado de forma estrecha con R e s c u e D a m s e lQ u e s t,
limitando el repertorio de hazañas en las que puede participar el caballero. Si hay que
rescatar a la damisela, el caballero está ahí. Sin embargo, si hay que matar a un dragón o
acudir a la Mesa Redonda, este caballero se va a quedar sentado en su sitio. Además de
todo esto, sería muy difícil redactar una unidad de prueba para D a m s e IR e sc u in g K n ig h t.
En este prueba, querría comprobar que el método de la hazaña em b ark () se invoque al
invocar el método em barkOnQuest () del caballero. Sin embargo, no hay una forma clara de
conseguirlo. Así que, desafortunadamente, D a m s e IR e s c u in g K n ig h t no podrá probarse.
32 Capítulo 1

El acoplamiento es una bestia de dos cabezas. Por un lado, el código con acoplamiento
estrecho es difícil de probar, de reutilizar, de comprobar y, al solucionar fallos, lo más
probable es que cuando eliminemos uno, surjan dos o más. Por otro lado, es necesario
contar con cierto acoplamiento (un código sin acoplamiento no hace absolutamente nada).
Para conseguir algo útil, las clases tienen que conocerse. Es decir, el acoplamiento es nece­
sario, pero debe utilizarse con cuidado. Con la DI, una tercera parte asigna a los objetos
sus dependencias en el momento de la creación, la cual coordina cada objeto del sistema.
En este caso, no se espera que los objetos creen u obtengan sus dependencias, ya que éstas
se inyectan en los objetos que las necesitan (véase la figura 1.1).

Figura 1.1. La inyección de dependencias implica otorgar a un objeto sus dependencias,


en lugar de que éste tenga que obtenerlas por sí solo.

Para ilustrar esta idea, veamos como en el listado 1.3, BraveKnight no solo es un
caballero valiente, sino que, además, es capaz de participar en cualquier hazaña que surja
en su camino.

Listado 1.3. BraveKnight puede aceptar cualquier Quest que se le asigne.


package com. sp ringinaction.knights;

public cla ss BraveKnight implements Knight {

p riv ate Quest quest;

public BraveKnight(Quest quest) { //In y e c ta r Quest,


th is.q u e st = quest;
}
public void embarkOnQuest()
qu est. embark();
}

Como puede comprobar, a diferencia de DamselRescuingKnight, BraveKnight


no crea su propia hazaña. En su lugar, se le asigna una durante la construcción con un
argumento constructor. Este tipo de inyección de dependencias se denomina inyección
de constructor.
Pasar a la acción 33

Asimismo, la hazaña que realiza el caballero se designa como Quest, una interfaz que
implementan todas las h a z a ñ a . De esta forma, BraveKnight (nuestro valiente caballero)
podría embarcarse en RescueDamselQuest (Rescatar a una damisela), SlayDragonQuest
(Matar a un dragón), MakeRoundtableRounderQuest (Ir a la Mesa Redonda) o cualquier
implementación de Quest que se le asignase.
La clave en este ejemplo es que B ra v e K n ig h t no está acoplado a ninguna implemen­
tación específica de Q u est. No importa en qué tipo de hazaña se le pida que participe,
siempre y cuando implemente la interfaz Q u est. Es la ventaja esencial que ofrece la DI: el
acoplamiento débil. Si un objeto solo obtiene sus dependencias a través de la interfaz (y
no a través de su implementación o por la forma en la que se instancian), la dependencia
puede cambiarse por otra sin que el objeto dependiente detecte la diferencia.
Una de las formas más habituales de cambiar una dependencia es utilizando una imple-
mentación de prueba durante las comprobaciones. En el caso de DamselRescuingKnight,
no pudimos realizar pruebas debido al acoplamiento estrecho. Sin embargo, en el caso de
BraveKnight, podemos hacerlo con facilidad incluyendo una implementación de prueba
de Quest, tal y como se muestra en el listado 1.4.

Listado 1.4. Probamos BraveKnight inyectando un Quest de prueba.


package com .springinaction.knights;
import s t a t ic o rg .m o ck ito .M o ck ito ;
iinport o rg .ju n it.T e s t;

public cla ss BraveKnightTest {

@Test
public void knightShouldEmbarkOnQuest()
Quest mockQuest = mock(Quest. c l a s s ) ; / / Crear Quest de prueba.
BraveKnight knight = new BraveKnight(mockQuest) ; / / Inyectar Quest de prueba,
knight. embarkOnQuest();
verify(mockQuest, times (1)) . embarkO ;
}
i
En este caso, utilizamos un objeto de marco de trabajo de prueba denominado Mockito
para crear una implementación de prueba de la interfaz Quest. Con el objeto de prueba,
podemos crear una nueva instancia de BraveKnight inyectando el Quest de prueba
mediante el constructor. Después de invocar el método embarkOnQuest ( ) , solicitamos
a Mockito que verifique que el método embark () de Quest solo se invoca una vez.

Inyectar una hazaña en un caballero


Ahora que contamos con una clase BraveKnight escrita de forma que podemos
proporcionarle cualquier hazaña que queramos, ¿cómo podemos especificar el Quest
que queremos asignarle? Imagine que quiere que BraveKnight se embarque en una
misión para derrotar a un dragón. Podría usar SlayDragonQuest, como se muestra en
el siguiente listado.
34 Capítulo 1

Listado 1.5. SlayDragonQuest es un elemento Quest que se inyecta en BraveKnight.


package com .springinactio n .knights;

import ja v a .i o .PrintStream;

public cla ss SlayDragonQuest implements Quest {

private PrintStream stream;

public SlayDragonQuest(PrintStream stream) {


t h i s . stream = stream;
}
public void embark() {
stream .p rin tIn ("Embarking on quest to slay the dragon!");
}
}

Como puede apreciar, SlayDragonQuest implementa la interfaz Quest, perfecta para


BraveKnight. Comprobará también que en lugar de depender de System.out.print In ()
como en muchos de los ejemplos de Java para principiantes, SlayDragonQuest solicita
PrintStream a través de su constructor. La duda es cómo proporcionar SlayDragon
Quest a BraveKnight y PrintStream a SlayDragonQuest. La acción de creación de
asociaciones entre componentes de aplicación se denomina conexión. Hay muchas formas de
conectar componentes en Spring, aunque el enfoque más utilizado suele ser mediante XML.
En el listado 1.6, podemos ver un sencillo archivo de configuración de Spring, knights .
xml, que conecta los elementos BraveKnight, SlayDragonQuest y PrintStream.

Listado 1.6. Inyección de SlayDragonQuest en BraveKnight con Spring.


<?xml v e rsio n = "l.0" encoding="UTF-8"?>
cbeans xmlns="h ttp : / /www.springframework.org/schema/beans"
xm lns:xsi="h ttp : / / w w w . w3.o rg/2001/XMLSchema-in stan ce"
x s i : schemaLocation="h ttp : //www.springframework.org/schema/beans
h ttp : //www.springframework.org/schema/beans/spring-beans.xsd" >

<bean id ="knight" c la s s = "com.springinactio n .knig hts.BraveKnight">


<constructor-arg ref="qu est" /> / / Inyectar bean con la hazaña.
</bean>

cbean id ="quest" class="com .springinaction.k nights. SlayDragonQuest" />


<constructor-arg value="#{T(System ).o u t}" /> / / Crear SlayDragonQuest.
</bean>

</beans>

Aquí, B r a v e K n ig h t y S la y D ra g o n Q u e s t se declaran como bean en Spring. En el caso


del bean B r a v e K n ig h t, se construye y se pasa una referencia al bean S la y D r a g o n Q u e s t
como argumento de constructor. Mientras tanto, la declaración del bean Sla y D ra g o n Q u e st
usa el lenguaje de expresiones de Spring para pasar S y s te m , o u t (un P r in t S t r e a m ) al
constructor de S la y D r a g o n Q u e s t.
Pasar a la acción 35

Si la configuración XML no se ajusta a sus necesidades, sepa que Spring también le


permite expresar configuración con Java. El siguiente ejemplo es el equivalente basado en
Java del listado 1.6.

Listado 1.7. Spring ofrece configuración basada en Java como alternativa a XML.
package com.springinaction.knights.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import com.springinaction.knights.BraveKnight;
import com.springinaction.knights.Knight;
import com.springinaction.knights.Quest;
import com.springinaction.knights.SlayDragonQuest;

@Conf igurat ion


public class KnightConfig {

@Bean
public Knight knight() {
return new BraveKnight(quest{));
}
@Bean
public Quest q u est0 {
return new SlayDragonQuest(System.out);
}
1
Independientemente de que use configuración basada en XML o en Java, las ventajas
de la DI son las mismas. Aunque BraveKnight depende de Quest, no conoce qué tipo
de Quest recibe ni de dónde proviene. Del mismo modo, SlayDragonQuest depende
de PrintStream, pero desconoce de dónde se origina. Solamente Spring, a través de su
configuración, sabe cómo combinar todas las piezas, lo que permite cambiar estas depen­
dencias sin modificar las clases dependientes.
Es un enfoque sencillo para conectar bean en Spring. No se preocupe demasiado ahora
mismo por los detalles. En el siguiente capítulo trataremos con mayor detalle la configu­
ración de Spring. Asimismo, aprenderemos otras formas de conectar los bean en Spring.
Ahora que ya ha declarado la relación entre BraveKnight y Quest, tendrá que cargar
el archivo de configuración XML y poner en marcha la aplicación.

La aplicación en funcionamiento
En una aplicación de Spring, un contexto de aplicación carga las definiciones de bean
y las conecta entre sí. El contexto de aplicación de Spring es el responsable de la creación
v conexión de los objetos que forman la aplicación. Spring incluye varias implementa-
ciones de su contexto de aplicación y cada una se diferencia por la forma de cargar su
configuración. Puesto que los bean en knights .xml se declaran en un archivo XML,
un posible contexto de aplicación sería ClassPathXmlApplicationContext. Esta
36 Capítulo 1

implementación de contexto de Spring carga el contexto de Spring en uno o más archivos


XML, situados en la ruta de clases de la aplicación. El método main ( ) , en el listado 1.8,
utiliza ClassPathXmlApplicationContext para cargar knights .xml y obtener una
referencia al objeto Knight.

Listado 1.8. KnightMain.java carga el contexto Spring incluyendo un caballero.


package com.springinaction.k nig hts;

import org. springframework. co n tex t. support. ClassPathXmlApplicationContext;

public cla ss KnightMain {

public s t a t ic void m ain{String[] args) throws Exception {


ClassPathXmlApplicationContext context = / / Cargar contexto de Spring.
new ClassPathXmlApplicationContext("META-INF/spring/knights.xml") ;
Knight knight = context.getBean(Knight. c l a s s ) ; / / Obtener e l bean knight,
knight.embarkOnQuest() ; / / U tiliz a r knight,
co n tex t. c lo s e ();
}
}

En este caso, el método main () crea el contexto de aplicación de Spring en función del
archivo knights .xml. A continuación, utiliza el contexto de aplicación como una fábrica
para obtener el bean con el ID knight. Con una referencia al objeto Knight, invoca el
método embarkOnQuest () para que el caballero se embarque en la hazaña asignada. Tenga
en cuenta que esta clase no tiene ninguna información sobre el tipo de Quest en la que se
encuentra nuestro héroe. En este aspecto, desconoce que está tratando con BraveKnight.
Solo el archivo knights .xml conoce cuáles son las implementaciones. Con todo esto, ya
cuenta con una rápida introducción a la inyección de dependencias. Aprenderá mucho más
sobre la D I a lo largo del libro. Ahora, podemos pasar a otra de las estrategias de simplifi­
cación en Java con Spring: la programación declarativa orientada a aspectos.

Aplicar aspectos ___________ _______ ________


Aunque la D I permite vincular componentes de software de forma débil, la programa­
ción orientada a aspectos le permite capturar la funcionalidad utilizada en su aplicación y
aplicarla a componentes reutilizables.
La programación orientada a aspectos suele definirse como una técnica que promueve
la separación de problemas dentro de un sistema de software. Los sistemas están formados
por varios componentes, cada uno de los cuales es responsable de una funcionalidad
específica. A menudo, estos componentes cuentan con responsabilidades adicionales
más allá de sus funciones básicas. Los servicios de sistema, como el inicio de sesión, la
administración de transacciones y la seguridad, suelen encontrarse en componentes cuya
responsabilidad principal corresponde a otros elementos. A estos servicios de sistema se
les suele denominar preocupaciones transversales, ya que tienden a incluirse en diferentes
componentes del sistema.
Pasar a la acción 37

Al repartir estas preocupaciones transversales entre diferentes componentes, introdu­


cimos dos niveles de complejidad en el código:

• El código que implementa las preocupaciones a nivel del sistema se duplica entre
diferentes componentes. Es decir, si quiere modificar la forma en que funcionan esas
preocupaciones, tendrá que acceder a varios componentes. Incluso aunque haya
abstraído la preocupación a un módulo independiente, para que el impacto sobre
sus componentes sea una única invocación, la invocación de ese método se duplica
en varias ubicaciones.
• Sus componentes van a incluir código innecesario que no va a estar alineado con su
funcionalidad básica. Un método para añadir una entrada en una libreta de direc­
ciones solo debería encargarse de cómo añadir la dirección, y no de si es seguro o
transaccional.

La figura 1.2 muestra esta complejidad. Los objetos de negocio de la izquierda están
demasiado implicados con los servicios del sistema. No solo cada objeto sabe si se ha iniciado
sesión, se ha asegurado o se ha visto implicado en un contexto transaccional, sino que cada
uno de ellos es responsable de llevar a cabo estos servicios por sí mismo.

Figura 1.2. La invocación de preocupaciones a nivel del sistema, como el inicio de sesión
o ia seguridad, suele repartirse entre módulos en los que éstas no son la preocupación principal.

La AOP hace todo lo posible para incluir en módulos estos servicios y, a continuación,
aplicarlos de forma declarativa a los componentes a los que deben afectar. Esto permite
que los componentes tengan una mayor cohesión entre sí y se centren en sus objetivos
concretos, ignorando el resto de servicios del sistema que puedan verse implicados. En
resumen, los aspectos garantizan que los POJO sigan siendo sencillos.
Quizá sea más fácil de entender si piensa en los aspectos como mantas que cubren
varios componentes de una aplicación, como se muestra en la figura 1.3. En su núcleo,
una aplicación está formada por módulos que implementan funcionalidades de negocio.
Con la AOP puede cubrir su aplicación principal con capas de funcionalidad. Estas capas
pueden aplicarse de forma declarativa a lo largo de su aplicación de forma flexible, sin
38 Capítulo 1

que el núcleo de la aplicación ni siquiera sepa que existen. Se trata de un concepto potente,
ya que permite que las preocupaciones de seguridad, transacciones e inicio de sesión no
cubran de código innecesario la lógica de negocio del núcleo de la aplicación.

Administrador de transacciones

Servicio de
estudiantes

Servicio de Servicio de
cursos profesores

Servicio de Servicio de
facturación contenidos

Figura 1.3. Al utilizar AOP, los componentes a nivel del sistema cubren aquellos componentes
a los que afectan. De esta forma, los componentes de aplicación pueden centrarse
en sus funcionalidades de negocio específicas.

Para demostrar la forma en que pueden aplicarse los aspectos en Spring, volvamos al
ejemplo del caballero, añadiendo un aspecto básico de Spring.

AOP en funcionamiento
Cualquier persona que sepa algo sobre caballeros conoce sus hazañas porque los
juglares se encargaron de cantarlas. Supongamos que quiera registrar las idas y venidas
de BraveKnight (El valiente caballero) utilizando los servicios de un juglar. En el listado
1.9, podemos ver la clase Minstrel (Juglar) que podríamos utilizar para ello.

Listado 1.9. Uso de Minstrel para registrar las hazañas.


package com. sp ringinaction.k nights;

import ja v a . i o . PrintStream;

public cla ss M instrel {

private PrintStream stream;

public M instrel(PrintStream stream) {


th is.stream = stream;
}
public void singBeforeQuest() { / / Se invoca antes de la hazaña,
stream .p rin tln ("Fa la la , the knight is so b ra v e !");
}
Pasar a la acción 39

public void singAfterQuest() { / / S e invoca después de la hazaña.


streara.println{"Tee hee hee, the brave knight " + "did embark on a quest!");
}
}

Cómo puede comprobar, Minstrel es una clase sencilla con dos métodos. El método
singBef oreQuest () seva a invocar antes de que un caballero se embarque en una hazaña,
mientras que el método singAf terQuest () se invocará después de que el caballero la
haya completado. En ambos casos, Minstrel canta las hazañas del caballero a través de
un PrintStream inyectado a través de su constructor.
No debería ser difícil incluirla en el código anterior, por lo que vamos a realizar los
cambios necesarios para que B r a v e K n ig h t utilice M i n s t r e l . El listado 1.10 m uestra un
prim er intento.

Listado 1.10. BraveKnight que invoque los métodos Minstrel.


package com.springinaction.knights;

public class BraveKnight implements Knight {

private Quest quest;


private Minstrel minstrel;

public BraveKnight(Quest quest, Minstrel minstrel) {


this.quest = quest;
this.minstrel = minstrel;
}
public void embarkOnQuestO throws QuestException {
m in stre l. singBeforeQuest0 ; / / ¿Debería gestionar e l cab allero a su propio ju g lar?
qu est. embark();
m in stre l. singAfterQuest();
}
}
Debería funcionar. Sin embargo, hay algo que no parece correcto. ¿Realmente debería
ser responsabilidad del caballero gestionar a su propio juglar? Lo normal sería que el juglar
hiciera su trabajo sin que el caballero tuviera que pedírselo. Después de todo, es el trabajo
de los juglares. Así que, ¿por qué tendría que recordárselo el caballero?
Además, puesto que el caballero tiene que estar informado sobre el juglar, nos vemos
obligados a inyectar Minstrel en BraveKnight. Esto no solo hace más complejo el
código de Brave Knight, sino que también nos plantea qué sucedería si un caballero no
tuviera juglar. ¿Y si el valor de Minstrel es nuil? ¿No deberíamos introducir lógica de
comprobación nuil para ese caso?
Su clase BraveKnight está comenzando a volverse más compleja, y lo haría aún más de
tener que gestionar la situación nul lMinstrel. Sin embargo, con AOP podemos declarar
que el juglar debería cantar sobre las hazañas del caballero, liberando a éste de tener que
gestionar los métodos Minstrel directamente. Para convertir Minstrel en un aspecto,
solo tiene que declararlo como tal en el archivo de configuración de Spring. En el listado 1.11
podemos ver el archivo knights .xml actualizado para declarar Minstrel como aspecto.
40 Capítulo 1

Listado 1.11. Declaración de Minstrel como aspecto.


<?xml v e rsio n = "l.0" encoding="UTF-8"?>
cbeans xmlns="http://www.springframework. org/schema/beans"
xmlns:x s i= "h ttp : //www.w3. org/200l/XMLSchema-in stan ce"
xmlns: aop="h ttp : //www.springframework.org/schema/aop"
x s i : schemaLocation="http://www.springframework.org/schema/beans/aop"
h ttp : //www. springframework. org/schema/aop/spring-aop-3. 2 . xsd
h ttp : //www. springframework. org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">

<bean id ="knight" class="com .springinaction.knig hts. BraveKnight">


<constructor-arg r e f= "quest" />
</bean>

<bean id ="quest" c la s s = "com.springinaction.k nights. SlayDragonQuest" />


<constructor-arg value="#{T(System ). out}" />
</bean>

cbean id ="m instrel" class="com .springinaction.knights.M in strel">


/ / D eclarar bean M instrel.
<constructor-arg value="#{T(System ).o u t}" />
</bean>

<aop: config>
<aop:aspect r e f = "m instrel">
<aop:pointcut id ="embark"
expression="execution(* * . embarkOnQuest( . . ) ) " / > / / D efin ir punto de co rte.

<aop:before p o in tcu t-re f= "embark" / / D eclarar antes del aviso.


method="singBeforeQuest"/>

<aop:a fte r p o in tcu t-re f= "embark" / / Declarar después del aviso.


method="singAfterQuest"/>
</aop: aspect>
</aop: config>
</beans>

Estamos utilizando el espacio de nombres de configuración aop de Spring para declarar


el bean M i n s t r e l como aspecto. En primer lugar, debe declarar M i n s t r e l como bean.
A continuación, haga referencia a este bean en el elemento < a o p : a s p e e t > . Para seguir
definiendo el aspecto, declaramos (con < a o p : a f t e r > ) que, antes de invocar el método
em barkO n Q uest ( ) , debe invocarse s i n g B e f o r e Q u e s t () de M i n s t r e l . A esto se le
denomina aviso previo. A continuación (con < a o p : a f t e r > ) , se declara que el método
s i n g A f t e r Q u e s t () se ejecute después de invocar em b ark O n Q u est ( ) . A esto se le
denomina aviso posterior.
En ambos casos, el atributo p o in te u t - r e f hace referencia a un punto de corte llamado
em bark. Este punto se define en el elemento < p o i n t c u t > anterior con un atributo
e x p r e s s io n configurado para seleccionar dónde debe aplicarse el aviso. La sintaxis de
la expresión es el lenguaje de expresiones de puntos de corte AspectJ. No se preocupe si
no conoce AspectJ o los detalles sobre cómo escribir expresiones de punto de corte de este
tipo. Trataremos con mayor detalle la AOP de Spring en el capítulo 4. Por el momento, es
Pasar a la acción 41

suficiente con que sepa que ha solicitado a Spring que invoque los métodos de M i n s t r e l
s i n g B e f o r e Q u e s t () y s in g A f t e r Q u e s t ( ) , antes y después de que B r a v e K n ig h t se
embarque en una hazaña.
Y eso es todo. Con un poco de XML, acaba de convertir a M i n s t r e l en un aspecto de
Spring. No se preocupe si no acaba de entender todo esto. En el capítulo 4 verá muchos
más ejemplos de AOP que le ayudarán a comprenderlo mejor. Por el momento, hay dos
puntos importantes que debe considerar a partir de este ejemplo.
En primer lugar, M i n s t r e l sigue siendo un POJO. Nada indica que se vaya a utilizar
como aspecto. En su lugar, se convierte en uno cuando lo declaramos como tal en el contexto
de Spring.
En segundo lugar (y más im portante), M i n s t r e l puede aplicarse a B r a v e K n ig h t sin
que éste tenga que invocarlo de form a explícita. De hecho, B r a v e K n ig h t desconoce por
completo la existencia de M i n s t r e l .
También debemos dejar claro que, aunque hemos utilizado algo de la magia de Spring
para convertir M i n s t r e l en un aspecto, antes se declaró como <b ean > de Spring. Lo que
debe tener en cuenta en este punto es que, con los aspectos de Spring, puede hacer cualquier
cosa que ya pudiera hacer con los bean, como por ejemplo inyectarlos como dependencias.
Utilizar aspectos para que los juglares canten sobre los caballeros es divertido. Sin
embargo, la AOP de Spring puede utilizarse para cosas más prácticas. Como podrá
comprobar más adelante, la AOP de Spring puede proporcionar transacciones declarativas
y de seguridad.

Eliminar código reutilizable con plantillas


¿Ha escrito un fragmento de código en alguna ocasión y ha sentido que ya lo había
escrito antes? No, no se trata de un déjà vu, sino de código reutilizable: aquel que tenemos
que escribir una y otra vez para llevar a cabo tareas habituales y sencillas.
Lamentablemente, hay bastantes aplicaciones en las que el uso de las API de Java requiere
utilizar una gran cantidad de este tipo de código. Un ejemplo sería cuando trabajamos con
JDBC para realizar una consulta en una base de datos. Por ejemplo, si ha trabajado antes
con JDBC, puede que haya escrito algo similar al código del listado 1.12.

Listado 1.12. Muchas API de Java, como JDBC, requieren el uso de código reutilizable.
public Employee getEmployeeByld(long id)
Connection conn = null;
PreparedStatement stmt = null;
ResultSet rs = null;
Try {
conn = dataSource.getConnection();
stmt = conn.prepareStatement(
"select id, firstname, lastname, salary from 11 +
"employee where id=?"); // Seleccionar empleado,
stmt.setLong(1, id);
rs = stmt.executeQuery();
Employee employee = null;
if (rs.next()) {
42 Capítulo 1

employee = new Employee(); // Crear objeto a partir de los datos,


employee.setId(rs.getLong("id"));
employee.setFirstName(rs.getString("firstname"));
employee.setLastName(rs.getString("lastname"));
employee.setSalary(rs.getBigDecimal("salary"));
}
return employee;
} catch (SQLException e) { // ¿Qué deberíamos hacer aquí?

} finally {
if(rs != null) { // Limpiar el desorden,
try {
rs.close();
} catch(SQLException e) {}

if(stmt != null){
try {
stmt.close ();
} catch(SQLException e) {}
}
if(co n n != nuil)
{ tr y {
co n n .ció se();
} catch(SQLException e) {}
}
}
return n u il;
}
Como puede ver, este código JDBC ejecuta una consulta sobre la base de datos para
obtener el nombre de un empleado y su salario. Sin embargo, estoy seguro de que le costó
darse cuenta. Se debe a que el pequeño fragmento de código específico para ejecutar el
código de un empleado se encuentra oculto entre una gran cantidad de código JDBC. En
primer lugar, tiene que crear una conexión, a continuación, una instrucción y, por último,
tiene que ejecutar la consulta para obtener los resultados. Asimismo, para cumplir los requi­
sitos de JDBC, se necesita obtener S Q L E x c e p tio n , una excepción comprobada, aunque
no hay mucho que pueda hacer si ésta aparece.
Por último, después de todo lo dicho, tiene que limpiar el desorden, cerrando la cone­
xión, la instrucción y el conjunto de resultados. Esto podría provocar la ira de JDBC, por
lo que aquí también tiene que obtener la excepción S Q L E x c e p tio n .
Lo más destacado del listado 1.12 es que gran parte del código es exactamente el mismo
que escribiría para cualquier otra operación JDBC. En realidad, solo una mínima parte está
relacionada con la consulta del empleado, y la mayor parte es código JDBC reutilizable.
JDBC no está solo en el negocio del código reutilizable. Muchas actividades suelen
requerir código similar. Por ejemplo, JMS, JNDI y el consumo de servicios REST.
Spring busca eliminar el código reutilizable encapsulándolo en plantillas. Jd b cT e m p la te
permite realizar operaciones de base de datos sin todos los requisitos asociados a JDBC
tradicional. Por ejemplo, si utilizamos S im p le Jd b c T e m p la t e (una especialización de
Jd b c T e m p la te , que aprovecha características de Java 5), el método g e tE m p lo y e e B y ld ()
Pasar a la acción 43

puede reescribirse para que se centre en la tarea de recuperar información del empleado, en
lugar de tener que cumplir los requisitos del API JDBC. El listado 1.13 muestra el aspecto
de un método g etE m p lo y eeB y ld () actualizado.

Listado 1.13. Las plantillas le permiten centrar el código en una tarea concreta.
public Employee getEmployeeByld(long id) {
return j dbcTemplate. queryForObj e c t (
"s e le c t id, firstnam e, la s tríame, salary " + / / Consulta SQL.
"from employee where id =?",
new RowMapper<Employee>() {
public Employee mapRow(ResultSet rs,
in t rowNum) throws SQLException { / / Asignar resultados a l o b jeto .
Employee employee = new Employee();
em ployee.setId(rs.getLong("id ”) ) ;
employee. setFirstN am e(rs. g e tS trin g ("firstnam e") ) ;
employee. setLastName(r s . g e tS trin g ("lastname" ) ) ;
employee. setSalary (rs.g etB ig D ecim a l("sa la ry ")) ;
return employee;
}
}
id ); / / E sp e cificar parámetro de consulta.
}
Como puede ver, esta nueva versión de g e tE m p lo y e e B y ld () es mucho más sencilla y
precisa, al centrarse en seleccionar un empleado de la base de datos. Al método q u e r y F o r
Ob j e c t () se le proporciona la consulta SQL, un RowMapper (para asignar el conjunto de
datos resultantes a un objeto de dominio) y ninguno o más parámetros de consulta. Lo que
no verá en g e tE m p lo y e e B y ld () es código reutilizable JDBC como en el ejemplo anterior.
Todo se lleva a cabo de forma interna en la plantilla.
He mostrado cómo Spring reduce la complejidad en el desarrollo de Java mediante el
uso de desarrollo orientado a POJO, inyección de dependencias, AOP y plantillas. Al mismo
tiempo, he explicado cómo configurar bean y aspectos en archivos de configuración basados
en XML. Sin embargo, ¿cómo se cargan estos archivos? ¿Y dónde? Echemos un vistazo a
los contenedores de Spring, la ubicación en la que se encuentran los bean de la aplicación.

Contenedores para bean_________ ______________


En una aplicación basada en Spring, sus objetos de aplicación van a residir dentro del
contenedor de Spring. Como se muestra en la figura 1.4, el contenedor va a crear los objetos,
los va a conectar, a configurar y a administrar su ciclo de vida completo (desde new hasta
f i n a l i z e ()).
En el siguiente capítulo, aprenderá a configurar Spring para que sepa qué objetos debe
crear, configurar y conectar entre sí. Sin embargo, antes es importante familiarizarse con
el contenedor en sí mismo. Si conoce el contenedor, comprenderá mejor la forma en que se
van a gestionar sus objetos. El contenedor se encuentra en el núcleo del marco de trabajo de
Spring. El contenedor de Spring utiliza DI para administrar los componentes que forman
44 Capítulo 1

una aplicación. Esto incluye la creación de asociaciones entre componentes que colaboran
entre sí. De esta forma, los objetos están más limpios y son más fáciles de comprender,
permiten su reutilización y son más fáciles de probar.

Figura 1.4. En una aplicación Spring, los objetos se crean, se conectan y residen dentro
de un contenedor de Spring.

Hay varios tipos de contenedores. Spring incluye varias implementaciones de contenedor


que pueden clasificarse en dos tipos. Por un lado, las fábricas de bean (definidas por la
interfaz o r g . s p r i n g f ra m ew o rk . b e a n s . f a c t o r y . B e a n F a c t o r y ) , son los contene­
dores más sencillos y proporcionan un soporte básico para la DI. Por otro lado, tenemos
los contextos de aplicación (definidos por la interfaz o r g . s p r i n g f ram ew o rk . c o n t e x t .
A p p l ic a t io n C o n te x t), que se crean sobre la base de una fábrica de bean que proporciona
servicios de marco de trabajo de aplicación, como por ejemplo la capacidad de resolver
mensajes de texto a partir de un archivo de propiedades o la de publicar eventos de apli­
cación a oyentes de eventos interesados. Aunque es posible trabajar con Spring utilizando
fábricas de bean o contextos de aplicación, las primeras suelen ofrecer una funcionalidad
demasiado limitada para la mayoría de las aplicaciones. Por ese motivo, los contextos de
aplicación suelen ser la opción preferida. Vamos a centramos en trabajar con contextos de
aplicación, y no emplearemos demasiado tiempo en comentar las fábricas de bean.

T rab ajar co n un co n te x to d e a p lica ció n *•


Spring cuenta con diferentes tipos de contextos de aplicación. Los más habituales son:

• A n n o ta tio n C o n f i g A p p l i c a t i o n C o n t e x t : Carga un contexto de aplicación de


Spring desde una o varias clases de configuración basadas en Java.
• A n n o ta tio n C o n f ig W e b A p p lic a t io n C o n t e x t : Carga un contexto de aplicación
Web de Spring desde una o varias clases de configuración basadas en Java.
• C la s s P a t h X m lA p p lic a t io n C o n t e x t : Carga una definición de contexto a partir
de un archivo XML situado en la ruta de clases, y trata los archivos de definición de
contexto como recursos de ruta de clases.
Pasar a la acción 45

• F il e S y s t e m X m lA p p lic a t io n C o n t e x t : Carga una definición de contexto desde


un archivo XML en el sistema de archivos.
• X m lW e b A p p lic a t io n C o n t e x t : Carga definiciones de contexto a partir de un
archivo XML almacenado en una aplicación Web.

Hablaremos con mayor detalle sobre A n n o ta tio n C o n f ig W e b A p p lic a tio n C o n te x t


y X m lW e b A p p lic a tio n C o n te x t en el capítulo 8, cuando veamos las aplicaciones de
Spring basadas en la Web. Por el momento, vamos a limitarnos a cargar el contexto de
aplicación desde el sistema de archivos con F il e S y s t e m X m lA p p lic a t io n C o n t e x t o,
desde la ruta de clases, con C la s s P a t h X m lA p p lic a t io n C o n t e x t .
El procedim iento para cargar un contexto de aplicación desde el sistema de archivos o
desde la ruta de clases es similar a la forma en que se cargan los bean en una fábrica de bean.
Por ejemplo, esta sería la form a de cargar un F il e S y s t e m X m lA p p lic a t io n C o n t e x t :

ApplicationContext context = new


FileSystemXmlApplicationContext(" c : /knight.xm l") ;

De form a similar, podem os cargar un contexto de aplicación desde dentro de la ruta de


clases de la aplicación utilizando C la s s P a t h X m lA p p lic a t io n C o n t e x t :

ApplicationContext context = new


ClassPathXmlApplicationContext("knight.xm l") ;

La diferencia entre ambos ejemplos es que F il e S y s t e m X m lA p p lic a t io n C o n t e x t


va a buscar k n i g h t . xm l en una ubicación específica del sistema de archivos, mientras
que C la s s P a t h X m lA p p lic a t io n C o n t e x t lo buscará en cualquier ubicación de la ruta
de clases (incluyendo los archivos JAR).
Por otra parte, si prefiere cargar su contexto de aplicación desde una configuración de
Java, puede usar A n n o ta tio n C o n f i g A p p l i c a t i o n C o n t e x t :
ApplicationContext context = new AnnotationConfigApplicationContext(
com .springinaction.knights. con fig .K n ig h tC on fig .class);

En lugar de especificar un archivo XML desde el que cargar el contexto de aplicación


de Spring, A n n o ta tio n C o n f i g A p p l i c a t i o n C o n t e x t recibe una clase de configuración
desde la que carga los bean.
Si dispone de un contexto de aplicación, puede recuperar bean del contenedor Spring
invocando el método g e t B e a n () del contexto.
Ahora que ya conoce los aspectos básicos implicados en la creación de un contenedor
Spring, vamos a examinar con mayor atención el ciclo de vida de un bean en el contenedor
bean.

La vida de un bean
En una aplicación Java tradicional, el ciclo de vida de un bean es sencillo. La palabra
clave new de Java se utiliza para distanciar el bean. A continuación, el bean está listo para
utilizarlo. Una vez deje de utilizarse, puede eliminarse.
46 Capítulo 1

Frente a esto, el ciclo de vida de un bean en un contenedor Spring es más complejo. Es


importante comprender el ciclo de vida de un bean de Spring, ya que pueda que quiera
sacar partido a las oportunidades que Spring ofrece para personalizar la forma en que se
crea un bean. La figura 1.5 muestra el ciclo de vida de inicio de un bean típico a medida
que se carga en un contexto de aplicación de Spring.

Crear Relllenar setBeanName() de setBeanFactoryO d e \ \ setAppíicationContextO da


instancias propiedades BeanNameAware BeanFactoryAware /~ / ApplicationContextAware

Bean Postprocessor afterPropertiesSetO Invocar método Bean Postprocessor


de preinicialización de InitializingBean init personalizado de postinícialización

Ei bean está
listo para su uso

El contenedor
está cerrado

destroy() de Invocar método


destroy
DisposableBean personalizado

Figura 1.5. Un bean pasa por varios pasos entre su creación y su eliminación
en el contenedor de Spring. Cada paso es una oportunidad para personalizar la forma
en que el bean se administra en Spring.

Como puede ver, una fábrica de bean lleva a cabo varios pasos de configuración antes
de que el bean esté listo para su uso. Los pasos que tienen lugar en la figura 1.5 son:

1. Spring instancia el bean.


2. Spring inyecta valores y referencias de bean en las propiedades de éste.
3. Si el bean implementa BeanNameAware, Spring proporciona el ID del bean al método
se tB e a n N a m e ( ) .
4. Si el bean implementa B e a n F a c to ry A w a re , Spring invoca el método s e t B e a n
F a c t o r y ( ) , proporcionando él mismo la fábrica de bean.
5. Si el bean implementa A p p lic a t io n C o n t e x t A w a r e , Spring invoca el método
s e t A p p l i c a t i o n C o n t e x t ( ) , proporcionándolo en una referencia al contexto de
aplicación contenedor.
6. Si el bean implementa la interfaz B e a n P o s t P r o c e s s o r , Spring invoca su método
p o s t P r o c e s s B e o r e l n i t i a l i z a t i o n ().
7. Si el bean implementa la interfaz I n i t i a l i z i n g B e a n , Spring invoca su método
a f t e r P r o p e r t i e s S e t ( ). De forma similar, si el bean se ha declarado con un
método i n i t , se invoca el método de inicialización especificado.
Pasar a la acción 47

8. Si el bean implementa B e a n P o s t P r o c e s s o r , Spring invoca su método p o s t


P r o c e s s A f t e r l n i t i a l i z a t i o n ().
9. Llegados a este punto, el bean estará listo para que la aplicación lo utilice, y va a
permanecer en el contexto de la aplicación hasta que se elimine.
10. Si el bean implementa la interfaz D is p o s a b le B e a n , Spring invoca sus métodos
de s t r o y (). Del mismo modo, si se ha declarado con un método de s t ro y , se invoca
el método especificado.

Ahora ya sabe cómo crear y cargar un contenedor de Spring. Sin embargo, un contenedor
vacío no es de mucha utilidad, ya que no incluye nada a menos que usted lo ponga en su
interior. Para obtener los beneficios de la DI de Spring, debemos conectar nuestros objetos
de aplicación al contenedor de Spring. Hablaremos con mayor detalle sobre la conexión
de bean en el siguiente capítulo, pero antes veamos los componentes que forman el marco
de trabajo Spring y lo que ofrecen sus últimas versiones.

Componentes de Spring
Como ya ha visto, el marco de trabajo Spring está orientado a simplificar el desarrollo
de Java para aplicaciones empresariales mediante la inyección de dependencias, la progra­
mación orientada a aspectos y la reducción de código reutilizable. Si esto fuera lo único que
hiciera Spring, merecería la pena utilizarlo. Sin embargo, hay mucho más.
Dentro del marco de trabajo Spring, encontrará varias vías para facilitar el desarrollo
con Java. Además, más allá del marco Spring, hay un gran ecosistema de proyectos que se
basan en el núcleo del marco del trabajo y que permiten ampliar Spring en áreas como los
servicios Web, REST o NoSQL.
Veamos cuáles son los elementos que forman parte del núcleo del marco de trabajo
Spring. A continuación, ampliaremos nuestra visión para examinar el resto de miembros
del gran catálogo de Spring.

Módulos de Spring
El marco de trabajo Spring está formado por varios módulos diferenciados. Al descargar
y descomprimir la distribución Spring 4.0, encontrará 20 módulos diferentes, con tres
archivos JAR por cada módulo (véase la figura 1.6). Estos módulos se pueden agrupar en
seis categorías de funcionalidad (véase la figura 1.7).
En conjunto, estos módulos le proporcionan todo lo que necesita para desarrollar apli­
caciones empresariales. Sin embargo, no tiene por qué basar su aplicación por completo en
el marco de trabajo Spring. Puede elegir aquellos módulos que se ajusten a su aplicación
y buscar otras opciones cuando Spring no le proporcione lo que necesite. Spring ofrece
incluso puntos de integración con otros marcos de trabajo y bibliotecas, por lo que ni
siquiera tendrá que escribirlas.
48 Capítulo 1

mm
Inicio Compartir Vista v m

© v->: - t ¿ < spring-framework-4.1.3.RELEASE ► libs v 0 Buscar en libs p I


Nombre Fecha de modifica,.. Tipo Tamaño
m’ Favoritos
Descargas i 1 spring-aop-4.1.3.RELEASE.jar 09/12/2014 2:00 Archivo JAR 351 KB 1
¡£ f Escritorio | "l spring-aspects-4,1.3.RELEASE.jar 09/12/2014 2:03 Archivo JAR 56 KB I
Sitios recientes j_] spring-beans-4.1,3.RELEASE.jar 09/12/20141:59 Archivo JAR 692 KB i
f l spring-context-4.1.3.RELEASE.jar 09/12/2014 2:00 Archivo JAR 1.003 KB |
Bibliotecas □ spring-context-support-4.1.3,RELEASE.jar 09/12/20142:00 Archivo JAR 174 KB |
¡Üi Documentos j j spring-core-4.1.3.RELEASE.jar 09/12/20141:59 Archivo JAR 984 KB 1
Ü Imágenes □ spr¡ng-express¡on-4,1.3.RELEASE,jar 09/12/2014 2:00 Archivo JAR 2541 B
£ Música j _ j spring-instrument-4.1.3.RELEASE.jar 09/12/2014 2:00 Archivo JAR 8 KB
j§ | Vídeos I 1 spring-instrument-tomcat-4.1.3.RELEASE.jar 09/12/2014 2:03 Archivo JAR 11 KB |
j 1 spring-jdbc-4.1.3.RELEASE.jar 09/12/2014 2:00 Archivo JAR 417 KB
9 $ Grupo en el hogar j_ j spring-jms-4.1.3.RELEASE.jar 09/12/2014 2:03 Archivo JAR 263 KB |
B Paola Sánchez j 1 spring-messaging-4.1.3.RELEASE,jar 09/12/2014 2:02 Archivo JAR 282 KB
Q spring-orm-4,1.3.RELEASE jar 09/12/2014 2:01 Archivo JAR 368 KB I
;** Equipo i 1 spring-oxm-4.1.3.RELEASEjar 09/12/2014 2:01 Archivo JAR 81 KB
j _ j spring-test-4.1.3.RELEASE.jar 09/12/2014 2:03 Archivo JAR 491 KB
| % Red j ) spring-t<-4.1.3.RELEASE jar 09/12/2014 2:00 Archivo JAR 247KB 1
m PC.PABLO L J spring-web-4.1.3.RELEASE.jar 09/12/2014 2:01 Archivo JAR 697 KB i.
» vmware-host j _ j spring-webmvc-4.1.3.RELEASEjar 09/12/2014 2:02 Archivo JAR 764 KB '
| ] spring-webmvc-portlet-4.1.3.RELEASE.jar 09/12/20142:02 Archivo JAR 173 KB 0
j _ j spring-websocket-4.1.3.RELEASEjar 09/12/2014 2:03 Archivo JAR 379 KB I

20 elementos
b n I

Figura 1.6. La distribución Spring 4.0 está formada por 20 módulos.

Pruebas

Prueba

Figura 1.7. El marco de trabajo Spring está formado por seis categorías de módulos.

Echemos un vistazo a cada uno de los módulos de Spring de forma individual para así
ver cómo encaja cada uno de ellos en el panorama general de Spring.
Pasar a la acción 49

Contenedor del núcleo de Spring


El elemento central del marco de trabajo Spring es un contenedor que gestiona la forma
en que los bean de una aplicación de Spring se crean, configuran y administran. Dentro
de este módulo encontrará la fábrica de bean de Spring, que es la parte de Spring que
proporciona la inyección de dependencias. Si construye sobre la fábrica de bean, encontrará
varias implementaciones del contexto de aplicaciones de Spring, cada una de los cuales
proporciona una forma diferente de configurar Spring. Además de la fábrica de bean y del
contexto de aplicación, este módulo también proporciona servicios empresariales como
correo electrónico, acceso JNDI, integración EJB y programación.
Todos los módulos de Spring se basan en el contenedor de núcleo. Al configurar su
aplicación, va a utilizar estas clases de forma implícita. Vamos a hablar sobre el módulo
del núcleo a lo largo de este libro, comenzando en el capítulo siguiente, donde hablaremos
más sobre la inyección de dependencias.

Modulo AOP ele Spring


Spring proporciona una amplia compatibilidad para la programación orientada a
aspectos mediante su módulo AOP. Sirve como base para desarrollar los aspectos propios
de su aplicación Spring. Al igual que la DI, la AOP admite el acoplamiento débil de objetos
de la aplicación. Sin embargo, con AOP, las preocupaciones de aplicación (como las tran­
sacciones y la seguridad) se desacoplan de los objetos a los que se aplican.
Hablaremos más sobre el soporte de Spring para AOP en el capítulo 4.

Acceso de datos e integración


Cuando se trabaja con JDBC, a menudo, el resultado es una gran cantidad de código reuti-
lizable que establece una conexión, crea una instrucción, procesa un conjunto de resultados
y, a continuación, cierra la conexión. Los módulos JDBC y Objetos de acceso a datos (DAO)
de Spring permiten abstraer el código repetitivo, de forma que pueda mantener el código
de su base de datos limpio y sencillo, además de evitar problemas que se produzcan por el
cierre de los recursos de la base de datos. Este módulo también crea una capa con una serie
de excepciones comprensibles sobre los mensajes de error generados por varios servidores
de base de datos. De esta forma, no tendrá que descifrar crípticos mensajes de error SQL.
Para aquellos que prefieran utilizar una herramienta de Asignación relacional de objetos
(ORM) sobre JDBC, Spring cuenta con un módulo ORM. El soporte de Spring para ORM
se basa en el soporte para DAO, lo que proporciona un método adecuado de crear varios
DAO para diferentes soluciones ORM. Spring no intenta implementar su propia solución
ORM, sino que proporciona conexiones a diferentes marcos de trabajo ORM populares,
como Hibemate, Java Persistence API, Java Data Objects e iBATIS SQL Maps. La adminis­
tración de transacciones de Spring es compatible con cada uno de estos marcos de trabajo
ORM, así como con JDBC.
Veremos, en el capítulo 10, cómo la abstracción JDBC basada en plantillas de Spring
puede simplificar, de manera considerable, el código JDBC cuando examinemos el acceso
a datos con Spring.
50 Capítulo 1

Este módulo también incluye una abstracción Spring sobre JMS (Java M essage Service,
Servicio de mensajes Java) para la integración asincrona con otras aplicaciones mediante
mensajería. Asimismo, desde la versión 3.0 de Spring, este módulo incluye las caracte­
rísticas de asignación de objetos a XML que formaban parte, en su origen, del proyecto
Spring Web Services.
Por último, este módulo utiliza el módulo AOP de Spring para proporcionar servicios
de administración de transacciones para objetos en una aplicación de Spring.

Web y acceso remoto


El paradigma Modelo-Vista-Controlador (MVC) es un enfoque aceptado, de forma
general, para la creación de aplicaciones Web, en el que la interfaz de usuario se separa
de la lógica de la aplicación. Java cuenta con un gran número de marcos de trabajo MVC,
como por ejemplo Apache Struts, JSF, Web Work y Tapestry entre los más populares.
Aunque Spring integra varios marcos de trabajo MVC conocidos, su módulo Web y de
acceso remoto incluye un marco de trabajo MVC que promueve las técnicas de acoplamiento
débil de Spring en el nivel Web de una aplicación, como veremos en capítulos posteriores.
Además de para aplicaciones Web de cara al usuario, este módulo también incluye varias
opciones de acceso remoto para la creación de aplicaciones que Ínter actúan con otras. Las
capacidades de acceso remoto de Spring incluyen RMI (R em óte M ethod Invocation, Invocación
de métodos remotos), Hessian, Burlap, JAX-WS y el propio invocador HTTP de Spring.
Spring también ofrece una elevada compatibilidad para exponer y consumir API REST.
En el capítulo 15 hablaremos sobre el acceso remoto con Spring y en el 16 aprenderemos
a crear y consumir API REST.

Instrumentación
El módulo de instrumentación de Spring incluye compatibilidad para añadir agentes a
la JVM. En concreto, proporciona un agente para Tomcat que transforma archivos de clases
al cargarse desde el cargador de clases.
Si le parece complicado de entender, no se preocupe. La instrumentación proporcionada
por este módulo apenas tiene aplicación práctica y no lo veremos en el libro.

Pruebas
Spring tiene en cuenta la importancia de las pruebas escritas por los desarrolladores y,
por ello, incluye un módulo para probar las aplicaciones.
Dentro de este módulo, va a encontrar una serie de implementaciones de objetos de
prueba para escribir unidades de prueba para el código, que funcionan con JNDI, servlets
y portlets. Para realizar pruebas a nivel de integración, este módulo permite la carga de un
conjunto de bean en un contexto de aplicación de Spring, así como trabajar con estos en
ese contexto.
A lo largo del libro veremos muchos ejemplos controlados mediante pruebas, gracias a
las funciones de prueba que ofrece Spring.
Pasar a la acción 51

ES catálogo ele Spring


Si hablamos de Spring, hay mucho más de lo que vemos a simple vista. De hecho, hay
mucho más que lo incluido en la descarga del marco de trabajo. Si solo hablásemos del
núcleo de Spring, estaríamos omitiendo el enorme potencial que ofrece el amplio catálogo
de Spring. Este incluye varios marcos de trabajo y bibliotecas que se basan tanto en el
núcleo de Spring como entre sí. De forma conjunta, el catálogo permite utilizar el modelo
de programación de Spring para prácticamente cualquier faceta del desarrollo en Java.
Necesitaríamos varios libros para hablar sobre todo lo que ofrece el catálogo completo
de Spring y, de hecho, este libro omite gran parte de este contenido. En cualquier caso,
vamos a echar un vistazo a algunos de los elementos del catálogo. Por el momento, este es
un resumen de lo que se esconde debajo del núcleo de Spring.

Spring Web Flow


Spring Web Flow se basa en el marco de trabajo MVC del núcleo Spring para permitir
el desarrollo de aplicaciones Web conversacionales basadas en flujos, que guían al usuario
hacia un objetivo (por ejemplo, un asistente o una cesta de la compra). En el capítulo 8
hablaremos en detalle sobre Spring Web Flow. Asimismo, puede obtener más información
en h t t p : / /www. s p r in g s o u r c e . o rg /W eb flo w .

Spring Web Services


Aunque el núcleo del marco de trabajo Spring permite publicar bean de Spring de forma
declarativa como servicios Web, estos se basan en un modelo arquitectónico inferior de
último contrato. El contrato para el servicio viene determinado por la interfaz del bean.
Spring Web Services ofrece un modelo de servicios Web de contrato en primer lugar, donde
las implementaciones de los servicios se escriben para satisfacer el contrato de servicios.
En este libro no vamos a hablar sobre Spring Web Services. Si está interesado, puede
encontrar más información en h t t p : / /d o c s . s p r i n g . i o / s p r i n g - w s / s i t e s / .

Spring Security
La seguridad es un aspecto esencial de muchas aplicaciones. Spring Security se imple-
menta utilizando AOP y ofrece un mecanismo de seguridad declarativo para aplicaciones
Spring. En capítulos posteriores aprenderemos a añadir Spring Security a nuestras aplica­
ciones. Puede encontrar más información sobre Spring Security en h t t p : / / p r o j e c t s .
s p r in g .io /s p r in g -s e c u r ity /.

Spring Integration
Un gran número de aplicaciones empresariales interactúa con otras aplicaciones del
mismo tipo. Spring Integration ofrece implementaciones con varios patrones de integración
comunes en el estilo declarativo de Spring.
52 Capítulo 1

No vamos a hablar sobre Spring Integration en este libro. Sin embargo, si desea
obtener más información, puede acceder al sitio Web h t t p : / / p r o j e c t s . s p r i n g . i o /
s p r in g -in te g r a tio n /.

Spring Batch
Cuando es necesario realizar operaciones con grandes cantidades de datos, nada
supera al procesamiento por lotes. Si va a desarrollar una aplicación de lotes, puede sacar
partido al modelo de desarrollo robusto y orientado a POJO de Spring utilizando Spring
Batch.
No vamos a hablar sobre Spring Batch en este libro aunque, si está interesado,
podrá encontrar más información al respecto en h t t p : / / p r o j e c t s . s p r i n g . i o /
s p r in g -b a tc h /.

Spring Data
Spring Data facilita el uso de todo tipo de base de datos en Spring. Aunque las bases de
datos relaciónales han sido omnipresentes durante años en las aplicaciones empresariales,
las aplicaciones modernas empiezan a reconocer que no siempre la mejor forma de servir
datos es por medio de las filas y columnas de una tabla. Una nueva generación de bases de
datos, denominadas NoSQL, ofrece nuevas formas de trabajar con datos que resulta más
adecuada que un modelo relacional.
Independientemente de que utilice una base de datos documental como MongoDB,
gráfica como Neo4j o incluso relacional, Spring Data ofrece un modelo de programación
simplificado para persistencia. Incluye, para muchos tipos de base de datos, un mecanismo
automático de repositorios que crea una implementación de repositorios.
En el capítulo 11 veremos el uso de Spring Data para simplificar el desarrollo de API
de persistencia de Java QPA) y ampliaremos el tema en el 12 para incluir varias bases de
datos NoSQL.

Spring Social
Las redes sociales son una tendencia en auge en Internet. De hecho, cada vez más apli­
caciones se integran con redes sociales como Facebook y Twitter. Si está interesado en este
tipo de programación, le recomendamos que eche un vistazo a Spring Social, la extensión
para redes sociales de Spring.
Pero Spring Social es mucho más que tuits y amigos. A pesar de su nombre, Spring Social
se centra menos en el concepto social que en el concepto conexión. Le permite conectar su
aplicaciones Spring a API REST, incluidas muchas que no tienen un propósito esencial­
mente social.
Por limitaciones de espacio no hemos podido incluir Spring Social en el libro, pero si le
interesa conectar Spring a Facebook o Twitter, visite las guías que encontrará en h t t p s : / /
s p r in g . io /g u id e s /g s /a c c e s s in g -fa c e b o o k / y h ttp s / / s p r in g . io /g u id e s /
g s /a c c e s s in g -tw itte r /.
Pasar a la acción 53

Spring Mobife
Las aplicaciones móviles son otra de las áreas de crecimiento considerable en el desarrollo
de software. Los teléfonos inteligentes y las tablets se están convirtiendo en los dispositivos
preferidos de muchos usuarios. Para ello, Spring Mobile ofrece una nueva extensión a
Spring que permite el desarrollo de aplicaciones Web móviles.

Spring para Android


Spring Mobile está relacionado con Spring Android. Este proyecto está dirigido a llevar
la simplicidad que ofrece el marco de trabajo Spring al desarrollo de aplicaciones nativas
para dispositivos que utilicen Android. Inicialmente, este proyecto ofrece una versión
de RestTemplate de Spring que puede utilizarse dentro de una aplicación de Android.
También funciona con Spring Social para permitir que aplicaciones nativas de Android se
conecten a API REST. No vamos a hablar sobre Spring Android en el libro aunque, si está
interesado, podrá encontrar más información al respecto en h t t p : / /p r o j e c t s . s p r i n g .
io /s p r in g -a n d r ó id /.

Spring Boot
Spring simplifica considerablemente muchas tareas de programación e incluso elimina
gran parte del código reutilizable que necesitaría en condiciones normales. Spring Boot es
un nuevo y apasionante proyecto que adopta un enfoque de desarrollo con Spring para
simplificar Spring. Spring Boot recurre a técnicas de configuración automática que permitan
eliminar gran parte (y en muchos casos la totalidad) de la configuración de Spring. También
ofrece varios proyectos iniciales para que consiga reducir el tamaño de los archivos de su
proyecto de Spring, independientemente de que utilice Maven o Gradle.
Abordaremos Spring Boot en la parte final del libro.

Novedades en Spring
Han pasado casi tres años desde que escribí la tercera edición de este libro y han pasado
muchas cosas en este tiempo. Se han publicado tres importantes versiones del marco de
trabajo Spring (3.1, 3.2 y ahora la 4.0), cada una de las cuales ha aportado nuevas carac­
terísticas y mejoras para facilitar el desarrollo de aplicaciones. Asimismo, algunos de los
componentes del catálogo de Spring han experimentado cambios de envergadura.
Hemos actualizado esta edición del libro para abarcar la mayoría de las novedades de
estas versiones, que le presentamos brevemente a continuación.

N ovedades de 3 . 1
Spring 3.1 incluía diversas novedades y mejoras, principalmente orientadas a simpli­
ficar y mejorar la configuración. Además, Spring 3.1 proporcionaba compatibilidad con el
almacenamiento declarativo en caché y numerosas mejoras de Spring MVC. Veamos una
lista de las principales características de Spring 3.1:
54 Capítulo 1

• Para solucionar el problema habitual de selección de diferentes configuraciones para


distintos entornos (como los de desarrollo, prueba y producción), Spring 3.1 presentó
los perfiles de entorno. Permiten, por ejemplo, seleccionar un bean de origen de datos
distinto en función del entorno en el que se implemente la aplicación.
• Partiendo de la configuración basada en Java de Spring 3.0, Spring 3.1 añadió varias
anotaciones de activación para habilitar determinadas funciones de Spring con una
misma anotación.
• Se añadió a Spring compatibilidad con almacenamiento declarativo en caché, lo que
permite declarar límites y reglas de almacenamiento en caché con sencillas anota­
ciones, similar a lo que ya se hacía con la declaración de límites de transacciones.
• Un nuevo espacio de nombres c otorgó a la inyección de constructores el mismo estilo
orientado a atributos que el espacio de nombres p de Spring 2.0 había otorgado a la
inyección de propiedades.
• Spring empezó a admitir Servlet 3.0, incluida la posibilidad de declarar servlets y
filtros en configuraciones basadas en Java en lugar de w eb. xm l.
• Se mejoró la compatibilidad de Spring con JPA para poder configurar JPA en Spring
sin necesidad de un archivo p e r s i s t e n c e .xm l.

Spring 3.1 también incluyó varias mejoras en el MVC de Spring:


• Vinculación automática de variables de ruta a atributos de modelos.
• Los atributos @ R e q u e stM a p p in g p ro d u ce s y co n su m es, para comparaciones con
los encabezados A c c e p t y C o n te n tT y p e de una solicitud.
• Una anotación @ R e q u e s t P a r t que permite vincular partes de una solicitud multi-
parte a parámetros de métodos de controlador.
• Compatibilidad con atributos flash (los que sobreviven a una redirección) y un tipo
R e d i r e c t A t t r i b u t e s para transferir atributos flash entre solicitudes.

Igual de importantes que las novedades de Spring 3.1 son los elementos excluidos, en
especial las clases Jp a T e m p la te y Jp a D a o S u p p o rt de Spring, sustituidas por el uso nativo
de E n tity M a n a g e r . Aunque se quedaron obsoletas, se conservaron en Spring 3.2, pero
no debe utilizarlas ya que no se han actualizado para admitir JPA 2.0 y se han eliminado
en Spring 4. Veamos ahora las novedades de Spring 3.2.

Novedades de 3.2*•
Mientras que Spring 3.1 se centraba principalmente en mejoras de configuración,
incluidas las del MVC de Spring, Spring 3.2 fue una versión orientada al MVC, con las
siguientes novedades:
• Los controladores Spring 3.2 pueden aprovechar las solicitudes asincronas de Servlet
3 para distribuir el procesamiento de solicitudes en diferentes subprocesos, liberando
al subproceso de servlet para que procese más solicitudes.
Pasar a la acción 55

• Aunque los controladores MVC de Spring se habían probado como POJO desde
Spring 2.5, Spring 3.2 incluía una estructura de pruebas MVC de Spring para crear
pruebas más completas sobre controladores, comprobando su comportamiento como
controladores pero sin un contenedor de servlet.
• Además de las pruebas de controlador mejoradas, Spring 3.2 incluía soporte para
probar clientes basados en R e s tT e m p la t e sin enviar solicitudes al punto final REST
real.
• Una anotación @ C o n t r o l l e r A d v i c e permite recopilar métodos © E x c e p tio n
H a n d le r, @ I n i t B i n d e r y @ M o d e lA t tr ib u t e s comunes en una única clase para
aplicarlos a todos los controladores.
• Antes de Spring 3.2, la compatibilidad con la negociación de contenidos solo estaba
disponible a través de C o n t e n t N e g o t ia t in g V ie w R e s o lv e r , pero en Spring 3.2
se ofreció a través del MVC de Spring, incluso en métodos de controlador depen­
dientes de conversores de mensajes para el consumo y la producción de contenidos.
• Spring MVC 3.2 incluía una nueva anotación @ M a t r ix V a r i a b l e para vincular las
variables de matriz de una solicitud a parámetros de métodos de control.
• La clase base abstracta A b s t r a c t D i s p a t c h e r S e r v l e t l n i t i a l i z e r se puede
usar para configurar D i s p a t c h e r S e r v l e t sin w e b .x m l. Del mismo modo, la
subclase A b s t r a c t A n n o t a t i o n C o n f i g D i s p a t c h e r S e r v l e t l n i t i a l i z e r se
puede usar ahora para configurar Spring con configuración basada en Java.
• Se añadió la clase R e s p o n s e E n t i t y E x c e p t i o n H a n d l e r para usarla como
alternativa a D e f a u l t H a n d l e r E x c e p t i o n R e s o l v e r . Los métodos R e s p o n s e
E n t i t y E x c e p t io n H a n d le r devuelven R e s p o n s e E n t it y c O b j e t o > en lugar de
ModelAndView.
• R e s tT e m p la t e y los argumentos @ R e q u e stB o d y admiten tipos genéricos.
• Los métodos R e s tT e m p la t e y © R eq u estM ap p in g admiten el método PATCH de
HTTP.
• Los interceptores asignados admiten la exclusión de patrones URL del procesamiento
de los interceptores.
Aunque el MVC de Spring fue el foco principal de Spring 3.2, también se incluyeron
otras mejoras. A continuación destacamos las más interesantes:•
• Las anotaciones @ A u to w ir e d , @ V a lu e y @ B e a n se pueden usar como meta-
anotaciones para crear anotaciones de inyección y de declaración de bean persona­
lizadas.
• La anotación @Dat eT im eF o rm a t ya no tiene una dependencia en JodaTime. Se usa
si aparece JodaTime. En caso contrario se utiliza S im p le D a te F o rm a t.
• La compatibilidad con el almacenamiento declarativo en caché de Spring ofrece
soporte inicial con JCache 0.5.
56 Capítulo 1

• Se pueden definir formatos globales para analizar y representar fechas y horas.


• Se pueden configurar y cargar pruebas de integración en WebAppl i c a t io n C o n te x t.
• Se pueden realizar pruebas de integración sobre bean con ámbito de solicitud y de
sesión.
Veremos muchas de las funciones de Spring 3.2 en distintos capítulos del libro, en espe­
cial en los dedicados a la Web y a REST.

Novedades de Spring 4.0•


Spring 4.0 es la última versión disponible de Spring. Incluye numerosas y apasionantes
novedades, entre las que destacan los siguientes:

• Spring incluye ahora compatibilidad con la programación WebSocket, incluido


soporte para JSR-356: el API de Java para WebSocket.
• Al reconocer que WebSocket ofrece un API de nivel inferior, que reclama una abstrac­
ción de nivel superior, Spring 4.0 incluye un modelo de programación de nivel supe­
rior orientado a mensajes sobre WebSocket, basado en SockJS y que admite subpro­
tocolos STOMP.
• Un nuevo módulo de mensajería con numerosos tipos del proyecto Spring Integration.
Admite la compatibilidad con SockJS/STOMP de Spring e incluye soporte basado
en plantillas para publicar mensajes.
• Spring 4.0 es una de las primeras estructuras de Java (si no la primera) en admitir
funciones de Java 8, como las expresiones lambda. De esta forma se puede trabajar
con determinadas interfaces (como RowMapper con Jd b c T e m p la te ) de forma más
limpia y legible.
• Además de la compatibilidad con Java 8 se admite JSR-310, la API Data and Time,
lo que permite a los desarrolladores trabajar con fechas y horas en una API más
completa que la ofrecida por j a v a . ú t i l . D ate o j a v a . ú t i l . C a le n d a r .
• Se ha mejorado el proceso de programación de aplicaciones desarrolladas en Groovy,
lo que permite desarrollar aplicaciones de Spring totalmente en Groovy. Aparece por
tanto BeanBuilder de Grails para configurar aplicaciones de Spring con Groovy.
• Se incluye compatibilidad generalizada con la creación de bean condicionales para
poder declarar bean que solo se creen si se cumple una condición impuesta por el
programador.
• Spring 4.0 también incluye una nueva implementación asincrona de Re s tTemp l a t e
de Spring que devuelve inmediatamente un resultado pero que permite retrollamadas
una vez completada la operación.
• Se admiten muchas especificaciones JEE, como JMS 2.0, JTA 1.2, JPA 2.1 y Bean Vali-
dation 1.1.
Pasar a la acción 57

Como puede apreciar, muchas y completas novedades se han incluido en las últimas
versiones de Spring. Alo largo del libro veremos muchas de ellas, además de las que siempre
han existido en el marco de trabajo.

Resumen
Después de leer este capítulo, debería tener una buena idea de qué aporta Spring. Su
objetivo es simplificar el desarrollo de aplicaciones empresariales en Java y promover
el uso de código con acoplamiento débil. Para esto, es esencial el uso de la inyección de
dependencias y de la AOP.
En este capítulo hemos apreciado el potencial de la inyección de dependencias. Es una
forma de asociar objetos de aplicación, de forma que no tengan por qué saber de dónde
proceden sus dependencias o la forma en que se implementan. En lugar de adquirir las
dependencias por ellos mismos, se proporciona a los objetos dependientes aquellos de los
que dependen. Como los objetos dependientes solo suelen conocer los objetos inyectados
a través de las interfaces, el acoplamiento se mantiene débil.
Además de la inyección de dependencias, también hemos visto la compatibilidad de
Spring con AOP. Permite centralizar la lógica que, de otro modo, se vería dispersa por
toda la aplicación, concentrándola en un solo lugar: un aspecto. Cuando Spring conecta
los bean, estos aspectos se combinan en tiempo de ejecución, lo que permite otorgarles
nuevos comportamientos.
La inyección de dependencias y la AOP son básicas para el funcionamiento de Spring.
Por ese motivo, debe comprender su funcionamiento para poder utilizar el resto del marco
de trabajo. En este capítulo, solo hemos arañado la superficie de estas características. En
los siguientes, entraremos con mayor detalle en la DI y la AOP. Por ello, vamos a pasar al
capítulo 2, donde aprenderemos a conectar objetos entre sí en Spring utilizando la inyec­
ción de dependencias.
de bean

CONCEPTOS FUNDAMENTALES:

• Declaración de bean.
• Inyección de constructores y métodos de
establecimiento.
• Conexión de bean.
• Control de la creación y destrucción de bean.
¿Ha visto alguna vez una película hasta el final de los créditos? Resulta increíble
comprobar la cantidad de personas que participan en su producción. Además de los parti­
cipantes obvios (actores, guionistas, directores y productores), hay otros que no resultan
tan conocidos (músicos, personal de efectos especiales, directores artísticos, etc.).
Y eso sin hablar del encargado de iluminación, los cámaras, el mezclador, los sastres,
los maquilladores, los coordinadores de extras, los publicistas, los asistentes de cámara,
los decoradores, el electricista y (quizás, los más importantes) los responsables del
catering.
Ahora imagine qué sería de su película favorita si ninguna de estas personas se hubiera
comunicado entre sí. Digamos que, todos ellos, se hubieran presentado en el estudio y
comenzado a trabajar por sí mismos sin ningún tipo de coordinación. Si el director no dice
"Acción", el cámara no comenzará a grabar. Después de todo, quizás no habría importado
mucho, porque la actriz seguiría en su caravana y la iluminación no funcionaría porque
no se habría contratado al electricista. Quizá ya ha visto una película en la que parece
que esto es lo que ha sucedido. Sin embargo, las mayoría de las películas (o, al menos, las
buenas) son el resultado del trabajo de miles de personas colaborando entre sí con el fin
de conseguir un taquillazo.
En este sentido, un gran proyecto de software no es diferente. Cualquier aplicación
está formada por varios objetos que deben trabajar de forma conjunta para conseguir un
objetivo de negocio.
Estos objetos deben ser conscientes de la existencia de los otros y comunicarse entre sí
para llevar a cabo el trabajo. Por ejemplo, en una aplicación para realizar compras en línea,
un componente de administración de pedidos tiene que trabajar con uno de administración
de productos y otro de autorización de tarjetas de crédito. Todos, probablemente, tendrán
que trabajar con un componente de acceso de datos para leer y escribir en una base de
datos.
Sin embargo, como ya vimos en el capítulo anterior, el enfoque tradicional al crear
asociaciones entre objetos de aplicaciones (mediante construcción o búsqueda) genera
código complicado difícil de reutilizar y de probar como unidades. En el mejor de lo casos,
estos objetos hacen más de lo que deben. En el peor, están altamente acoplados entre sí, lo
que dificulta su reutilización y llevar a cabo pruebas.
En Spring, los objetos no son responsables de encontrar o crear el resto de objetos que
necesitan para llevar a cabo su trabajo. En su lugar, el contenedor les asigna referencias a
los objetos con los que tienen que colaborar. Un componente de administración de pedidos,
por ejemplo, puede necesitar un autorizador de tarjetas de crédito, pero no tiene que
crearlo. Solo tiene que mostrarse y recibir el autorizador que necesita para llevar a cabo su
trabajo.
La acción de crear estas asociaciones entre objetos de aplicación es la esencia de la inyec­
ción de dependencias (DI) y se denomina, de manera habitual, conexión. En este capítulo
vamos a aprender los aspectos básicos de la conexión de bean utilizando Spring. Puesto
que la DI es la tarea más elemental que Spring lleva a cabo, va a utilizar estas técnicas cada
vez que desarrolle aplicaciones basadas en Spring.
Existen varias formas de conectar bean en Spring. Para empezar, nos centraremos en
los tres enfoques de configuración del contenedor de Spring más habituales.
60 Capítulo 2

Opciones de configtsraeión de Sprin


Como mencionamos en el capítulo anterior, el contenedor de Spring se encarga de crear
los bean en su aplicación y de coordinar la relaciones entre estos objetos a través de la DI,
pero como programador tendrá que indicarle a Spring qué bean crear y cómo conectarlos.
A la hora de expresar una especificación de conexión de bean, Spring es muy flexible y le
ofrece tres mecanismos principales:

• Configuración explícita en XML.


• Configuración explícita en Java.
• Detección implícita y conexión automática de bean.

A primera vista, puede parecerle que tener tres opciones complica el uso de Spring.
Las técnicas se solapan ligeramente y puede resultar abrumador decidir cuál es la más
indicada en cada caso. Pero no se preocupe, en muchos casos es cuestión de gustos perso­
nales y puede elegir el enfoque que mejor se adecúe a cada situación. Está muy bien poder
disponer de varias opciones para conectar bean en Spring, pero llegará un momento en el
que tenga que elegir una.
¿Cuál? La más adecuada para su proyecto. ¿Y quién le obliga a tomar una decisión?
Los estilos de configuración de Spring se pueden combinar, por lo que podría elegir XML
para conectar una serie de bean, la configuración basada en Java de Spring (JavaConfig)
para otros e incluso dejar que Spring los detecte de forma automática.
Incluso así, le recomiendo que siempre que pueda recurra a la configuración automá­
tica. Cuanta menos configuración explícita tenga que hacer, mejor. Si tiene que configurar
bean de forma explícita (por ejemplo si no se encarga de mantener el código fuente), le
aconsejaría JavaConfig sobre XML. Por último, recurra a XML solo cuando un nombre de
espacios XML no tenga equivalente en JavaConfig.
En este capítulo analizaremos detalladamente las tres opciones y las aplicaremos a lo
largo del libro. Para empezar, nos detendremos en la configuración automática de Spring.

Conexión automática de bean*•


En un apartado posterior veremos cómo expresar las conexiones de Spring tanto en
Java como en XML. Aunque esas técnicas de conexión explícita le serán muy útiles, nada
supera a la facilidad de uso de la configuración automática de Spring. ¿Por qué molestarse
en conectar bean de forma explícita si se puede configurar Spring para que lo haga auto­
máticamente? Spring aborda la conexión automática desde dos frentes:

• Análisis de componentes: Spring detecta automáticamente los bean que debe crear
en el contexto de la aplicación.
• Conexión automática: Spring satisface automáticamente las dependencias de bean.
Conexión de bean 61

Al combinar estas dos técnicas se consigue reducir al mínimo la configuración explícita.


Para ilustrarlas, crearemos varios bean que representen algunos de los componentes de un
equipo estéreo. Primero crearemos una clase C o m p a ctD isc que Spring detectará y creará
como bean. Después, crearemos la clase C D P la y e r para que Spring la detecte y le inyecte
el bean C o m p a ctD isc.

Crear bean detectables


En la era de los archivos MP3 y la música por Internet, los discos compactos pueden
parecer un tanto arcaicos, no tanto como las cintas o los vinilos, pero cada vez son más
escasos, el último vestigio de la distribución física de la música.
A pesar de ello, un CD ilustra a la perfección el funcionamiento de la DI. Los repro­
ductores de CD no sirven de mucho a menos que introduzcamos (o inyectemos) un CD.
Podríamos afirmar que un reproductor de CD depende de un CD para hacer su trabajo.
Para ilustrar este ejemplo en Spring, estableceremos el concepto de CD en Java. El
siguiente listado muestra C o m p a c tD isc, una interfaz que define un CD.

Listado 2.1. La interfaz CompactDisc define el concepto de CD en Java.


package soundsystem;

public in terfa ce CompactDisc {


void p la y ();
}
Los detalles concretos de la interfaz C o m p a ctD isc no son importantes, pero sí que la
hayamos definido como tal. Como interfaz, define el contrato mediante el que un repro­
ductor de CD puede operar con un CD, y reduce al mínimo la relación entre cualquier
implementación de un reproductor de CD y el propio CD.
No obstante, seguimos necesitando una im plem entación de C o m p a ctD isc. De hecho,
podríam os tener varias. En este caso, com enzarem os con una, la clase S g t P e p p e r s ,
mostrada en el siguiente listado.

Listado 2.2. La clase SgtPeppers, anotada con @CompactDisc, implementa CompactDisc.


package soundsystem;
import org. springframework. stereotyp e. Component;

©Component
public cla ss SgtPeppers implements CompactDisc {

private String t i t l e = "Sgt. Pepper's Lonely Hearts Club Band";


private String a r t i s t = "The B e a tle s";

public void playO {


System .o u t.p rin tln {"Playing " + t i t l e + " by " + a r t i s t ) ;
}
62 Capítulo 2

Como sucede con la interfaz C o m p a c tD isc, los detalles de S g tP e p p e r s son irrele­


vantes. Lo importante es que S g tP e p p e r s se anote con @Component. Esta sencilla anota­
ción identifica a esta clase como clase de componente y sirve como pista para que Spring
sepa que debe crear un bean para la clase. No es necesario configurar explícitamente el bean
S g tP e p p e r s ; Spring se encargará de ello porque esta clase está anotada con @Component.
Sin embargo, el análisis de componentes no está activado de forma predeterminada.
Debe escribir una configuración explícita para indicarle a Spring que busque clases anotadas
con @Component y que cree bean para ellas. La clase de configuración del siguiente listado
muestra la configuración mínima necesaria para conseguirlo.

Listado 2.3. @ComponentScan habilita el análisis de componentes.


package soundsystem;
import org. springframework. co n tex t. annotation. ComponentScan;
import org.springframework. co n tex t. annotation.Configuration;

@Configuration
@ComponentScan
public cla ss CDPlayerConfig {
}
La clase C D P la y e rC o n f i g define una especificación de conexión de Spring, expresada
en Java. En un apartado posterior veremos con mayor detalle la configuración de Spring
basada en Java. Por el momento, fíjese en que C D P la y e rC o n f i g no define explícitamente
ningún bean, sino que se anota con @ C om p on en tScan para habilitar el análisis de compo­
nentes en Spring.
Sin configuración adicional, @ C om pon en tScan analiza de forma predeterminada el
mismo paquete de la clase de configuración. Por ello, como C D P la y e rC o n f i g pertenece
al paquete soundsystem , Spring examina dicho paquete y todos sus paquetes secundarios
en busca de clases anotadas con @Com ponent. Debería encontrar la clase C o m p a ctD isc
y crear automáticamente un bean para la misma en Spring.
Si prefiere habilitar el análisis de componentes mediante configuración XML, puede
usar el elemento < c o n t e x t : co m p o n en t-sca n > del espacio de nombres c o n t e x t de
Spring. Veamos un ejemplo de configuración XML mínima para ello.

Listado 2.4. Habilitación del análisis de componentes en XML.


<?xml v e rsio n = "l.0" encoding="UTF-8"?>
cbeans xmlns="h ttp : //www.springframework.org/schema/beans"
xmlns:x s i= "h ttp : / /www.w3. org/2O01/XMLSchema-in stan ce"
xmlns: context= "h ttp : //www.springframework.org/schema/context"
x s i : schemaLocation="h ttp : / /www. springframework. org/schema/beans
h ttp : //www.springframework.org/schema/beans/spring-beans.xsd
h ttp : //www.springframework. org/schema/context
h ttp : / /www.springframework. org/schem a/context/spring-context.xsd">

«¡context: component-scan base-package="soundsystem" />

</beans
Conexión de bean 63

Aunque XML sea una opción válida para habilitar el análisis de componentes/ en el resto
de este apartado nos centraremos en la configuración basada en Java. Si prefiere el estilo
XML, le alegrará saber que el elemento c c o n t e x t : com ponent s c a n > dispone de atributos
y subelementos similares a los atributos utilizados al trabajar con @ C om ponentScan.
Lo crea o no, con solo dos clases ya tiene algo que se puede probar. Para probar que el
análisis de componentes funciona, crearemos una sencilla prueba de JUnit que genera un
contexto de aplicación de Spring y se asegura de la creación del bean C o m p a ctD ise , como
hace C D P la y e r T e s t en el siguiente listado.

Listado 2.5. Prueba de la detección de CompactDisc mediante el análisis de componentes.


package soundsystem;

import s t a t ic o r g .ju n i t . A s s e r t ;

import o r g .ju n it. T e st;


import org. ju n i t . runner.RunWith;
import org.springframework.beans. fa c to ry . annotation.Autowired;
import org.springframework. t e s t . co n tex t. ContextConfiguration;
import org. springframework. t e s t . co n tex t. ju n it4 . SpringJUnit4ClassRunner;

@RunWith(SpringJUnit4ClassRunner. class)
@CcntextConfiguration(classes=CDPlayerConfig. class)
public cla ss CDPlayerTest {

@Autowired
private CompactDisc cd;

@Test
public void cdShouldNotBeNull() {
assertN otN ull(cd);
}

C D P la y e r T e s t recurre a S p r in g J U n it 4 C l a s s R u n n e r de Spring para que se cree


automáticamente un contexto de aplicación de Spring al iniciarse la prueba, y la anota­
ción @ C o n te x tC o n f i g u r a t i o n le indica que cargue su configuración desde la clase
C D P lay erC on f ig . Como esa clase de configuración incluye @Component S ean , el contexto
de aplicación resultante debe incluir el bean C o m p a ctD isc.
Para demostrarlo, la prueba tiene una propiedad de tipo C o m p a ctD isc anotada con
@ A u tow ired para inyectar el bean C om p actD isc en la prueba (describiremos @ A u tow ired
en breve). Por último, un sencillo método de prueba se asegura de que la propiedad cd no
sea n u i l . Si no es n u i l significa que Spring ha podido detectar la clase C o m p a ctD isc,
automáticamente la crea como bean en el contexto de aplicación de Spring y la inyecta en
la prueba.
La prueba se superará sin problemas. Su primer sencillo ejercicio de análisis de compo­
nentes ha sido todo un éxito. Aunque solo lo hemos usado para crear un bean, la misma
cantidad mínima de configuración le servirá para detectar y crear todos los que quiera.
64 Capítulo 2

Todas las clases del o bajo el paquete so u n d s y s te m anotadas con @Component también
se crearán como bean. Merece la pena usar una línea con @ C om pon en tScan a cambio de
innum erables bean creados automáticamente.
A continuación nos adentrarem os en @ C om pon en tScan y @Component para ver qué
más podem os hacer con el análisis de componentes.

Asignar nombres a bean de análisis de componentes


En un contexto de aplicación de Spring se asigna un ID a todos los bean. Puede que en
el ejemplo anterior no fuera evidente pero aunque no asignamos implícitamente un ID al
bean S g tP e p p e rs , se le asignó uno derivado de su nombre de clase; en concreto, el ID
s g tP e p p e r s , con la primera letra del nombre de la clase en minúscula.
Si prefiere asignar un ID diferente al bean, basta con pasar el ID deseado como valor a la
anotación ©Component. Por ejemplo, para identificar al bean como l o n e l y H e a r t sC lu b ,
tendría que anotar la clase S g t P e p p e r s con ©Component de esta forma:
©Component{ " lonelyHeartsClub")
public cla ss SgtPeppers implements CompactDisc {

}
Otra forma de asignar nombres a un bean implica descartar la anotación ©Component y,
en su lugar, usar la anotación @Named de la especificación de dependencias de Java JSR-330
para proporcionar un ID de bean:
package soundsystem;
import javax.inject.Nam ed;

@Named( "lonelyHeartsClub")
public cla ss SgtPeppers implements CompactDisc {

}
Spring admite la anotación @Named como alternativa a @Component. Hay sutiles dife­
rencias pero en la mayoría de los casos son intercambiables.
Dicho esto, en mi caso prefiero la anotación @Component ya que básicamente @Named
resulta un tanto pobre. No describe sus acciones tan bien como @Component. Por ello, no
usaremos @Named en el resto de los ejemplos.

Definir un paquete base para e! análisis


de componentes
Hasta ahora hemos usado ©Component S e a n sin atributos, lo que significa que de forma
predeterminada asumirá el paquete de la clase de configuración como paquete básico para
el análisis de componentes. ¿Y si desea examinar un paquete distinto o examinar varios?
Conexión de bean 65

Un motivo habitual por el que definir explícitamente el paquete base es para mantener
todo el código de configuración en un paquete propio, independiente al resto del código
de la aplicación. En ese caso, no servirá el paquete base predeterminado. Sin problema.
Para especificar un paquete base distinto, basta con especificarlo en el atributo v a lu é de
@ C om ponentScan:

@Configuration
@ComponentScan("soundsystem")
public cla ss CDPlayerConfig {}

Si quiere indicar claram ente que se está definiendo el paquete base, puede hacerlo con
el atributo b a s e P a c k a g e s :
@Configuration
@ComponentScan(basePackages="soundsystem")
public cla ss CDPlayerConfig {}

Habrá comprobado que b a s e P a c k a g e s es plural, lo que significa que puede especi­


ficar varios paquetes base. Para ello, basta con establecer b a s e P a c k a g e s en una matriz
de paquetes para analizar:
@Configuration
@ComponentScan(basePackages={ "soundsystem", "video"})
public cla ss CDPlayerConfig {}

Al establecer los paquetes base, se expresan como valores S t r i n g . Imagino que es


correcto, pero la seguridad de tipos no es demasiado alta. Si tuviera que refactorizar los
nombres de los paquetes, los paquetes base especificados serían incorrectos.
En lugar de especificar los paquetes como sencillos valores S t r i n g , @Component S e a n
le ofrece la opción de especificarlos a través de clases o interfaces de los paquetes:

@Configuration
@ComponentScan(basePackageClasses={CDPlayer. c la s s , DVDPlayer. c la s s })
public cla ss CDPlayerConfig {}

Como puede apreciar, el atributo b a s e P a c k a g e s se ha sustituido por b a s e P a c k a g e


C l a s s e s , y en lugar de identificar los paquetes con nombres S t r i n g , la matriz propor­
cionada a b a s e P a c k a g e C la s s e s incluye clases. Los paquetes en los que se encuentren
dichas clases se utilizarán como paquete base para el análisis de componentes.
Aunque hemos especificado clases de componente para b a s e P a c k a g e C la s s e s , podría
crear una interfaz de marcador vacía en los paquetes que examinar. Con una interfaz de
marcador, seguiría contando con una referencia que poder refactorizar a una interfaz, pero
sin referencias a código de la aplicación (que posteriormente podría refactorizar fuera del
paquete en el que se van a analizar los componentes).
Si todos los objetos de sus aplicaciones fueran independientes y no tuvieran dependen­
cias, como el bean S g tP e p p e r s , solamente necesitaría el análisis de componentes, pero
muchos objetos dependen de otros para realizar su labor. Necesita una forma de conectar
sus bean de análisis de componentes a las dependencias que tengan. Para ello, contamos
con la conexión automática, el otro lado de la configuración automática de Spring.
66 Capítulo 2

Anotar bean para su conexión automática


La conexión automática es una forma de permitir que Spring satisfaga automáticamente
las dependencias de un bean buscando a otros bean de la aplicación que coincidan con sus
necesidades. Para indicar que debe realizarse la conexión automática, se utiliza la anotación
@ A u to w ired de Spring.
Por ejemplo, fíjese en la clase C D P lay er del siguiente listado. Su constructor se anota con
@ A u to w ired , lo que indica que cuando Spring cree el bean C D P la y e r, debe instanciarlo
a través de ese constructor y pasar un bean que se asigne a Compac t D i s e .

Listado 2.6. Inyección de CompactDisc en el bean CDPlayer mediante conexión automática.


package soundsystem;
import org. springframework.beans. fa c to ry . annotation.Autowired;
import org. springframework. stereotyp e. Component;

@Component
public cla ss CDPlayer implements MediaPlayer {
prívate CompactDisc cd;

@Autowired
public CDPlayer(CompactDisc cd) {
th is .c d = cd;
}
public void play() {
cd .p lay ( );
}

}
El uso de la anotación @ A u to w ired no se limita a constructores. También se puede usar
en el método de establecimiento de una propiedad. Por ejemplo, si C D P la y e r tuviera un
método s e t C o m p a c tD isc ( ) , se podría anotar para conexión automática de esta forma:
@Autowired
public void setCompactDisc(CompactDisc cd) {
th is .c d = cd;
}
Después de que Spring haya instanciado el bean, intentará satisfacer las dependen­
cias expresadas a través de métodos como s e tC o m p a c tD is c () que están anotados con
@ A u tow ired .
En realidad, no hay nada especial con los métodos de establecimiento. @ A u to w ired
tam bién se puede aplicar a cualquier m étodo de la clase. Si C D P la y e r tuviera un método
i n s e r t D i s c ( ) , @ A u to w ired funcionaría igual de bien que en s e tC o m p a c tD is c ( ) :

@Autowired
public void insertDisc(CompactDisc cd) {
th is .c d = cd;
}
Conexión de bean 67

Independientemente de que se trate de un constructor, un método de establecimiento


o de cualquier otro método, Spring intentará satisfacer la dependencia expresada en los
parámetros del método. Si solo hay un bean que coincida, será el que se conecte. Si no hay
bean que coincidan, Spring generará una excepción cuando se cree el contexto de aplicación.
Para evitar la excepción, puede establecer el atributo r e q u i r e d de @ A u tow ired en f a l s e :
@Autowired(required=false)
public CDPlayer(CompactDisc cd) {
th is.c d = cd;
}
Cuando r e q u ir e d es f a l s e , Spring intenta realizar la conexión automática; pero
si no hay bean que coincidan, dejará el bean sin conectar. Sin embargo, debe establecer
r e q u ir e d en f a l s e . Si deja la propiedad sin conectar se podrían generar excepciones de
puntero nulo si no realiza comprobaciones de n u i l en su código.
En caso de que varios bean puedan satisfacer la dependencia, Spring generará una
excepción para indicar ambigüedades en la selección de un bean para la conexión auto­
mática. En el siguiente capítulo encontrará más información al respecto. @ A u to w ired es
una anotación específica de Spring. Si le preocupa usar anotaciones específicas de Spring
en su código para conexiones automáticas, podría usar la anotación @ ln j e c t :
package soundsystem;
import jav ax . i n je c t . I n je c t ;
import javax.inject.Nam ed;

@ N am ed
public cla ss CDPlayer {

© Inject
public CDPlayer(CompactDisc cd) {
th is .c d = cd;
}

}
@ In j e c t proviene de la especificación Java D ependency Injection (Inyección de depen­
dencias de Java), la misma que proporciona @Named. Spring admite la anotación @ In j e c t
para conexiones automáticas junto a @ A u to w ired . Aunque sean ligeramente distintas, en
la mayoría de los casos son intercambiables. No tengo una preferencia concreta; de hecho,
las utilizo indistintamente en mis proyectos. Sin embargo para los objetivos del libro,
usaremos @ A u to w ired . En su caso, puede usar la que desee.

Verificar la configuración automática


Después de anotar el constructor de C D P la y e r con @ A u to w ired , puede estar seguro
de que Spring le inyectará automáticamente un bean asignable a C o m p a ctD is c. Para
comprobarlo, cambiaremos C D P la y e r T e s t para reproducir el compact disc a través del
bean C D P la y er:
68 Capítulo 2

package soundsystem;
import s t a t ic org. ju n it.A s s e r t.* ;
import o rg .ju n it.R u le ;
import org. ju n i t . T e st;
import o r g .ju n it. c o n trib .ja v a . lang. system.StandardOutputStreamLog;
import org. ju n i t . runner.RunWith;
import org. springframework.beans. fa c to ry . annotation.Autowired;
import org. springframework. t e s t . co n tex t. ContextConfiguration;
import org. springframework. t e s t . co n tex t. ju n it4 . SpringJUnit4ClassRunner;

@RunWith(SpringJUnit4ClassRunner. c la s s )
©ContextConfiguration(classes=CDPlayerConfig. cla ss)
public cla ss CDPlayerTest {

@Rule
public fin a l StandardOutputStreamLog log =
new StandardOutputStreamLog();

@Autowired
private MediaPlayer player;

@Autowired
private CompactDisc cd;

@Test
public void cdShouldNotBeNull() {
assertN otN ull(cd);
}
©Test
public void play() {
p la y e r.p la y ();
assertE qu als(
"Playing Sgt. Pepper's Lonely Hearts Club Band" +
" by The Beatles\n",
lo g . getLog( ) ) ;
}

}
Además de inyectar C o m p actD isc, se inyecta el bean C D P lay er en la variable miembro
p l a y e r de la prueba (como tipo M e d ia P la y e r , más genérico). En el método de prueba
p l a y ( ) , se invoca el método p l a y () en C D P la y e r y se comprueba que realiza la tarea
esperada.
Las pruebas de código en el que se use S y s te m , o u t . p r i n t l n () son complicadas.
Por ello, en este ejemplo se usa S ta n d a r d O u tp u tS tr e a m L o g , una regla de JUnit de la
biblioteca de reglas del sistema ( h t t p : / / s t e f a n b i r k n e r . g i t h u b . i o / s y s t e m - r u l e s /
in d e x .h t m l) que nos permite realizar afirmaciones sobre lo que se haya escrito en la
consola. En este caso, se comprueba que el mensaje del método S g tP e p p e r s .p l a y () se
ha enviado a la consola.
Ya conoce, por tanto, los fundamentos del análisis de componentes y la conexión auto­
mática. En el siguiente capítulo repasaremos el análisis de componentes cuando veamos
formas de solucionar ambigüedades de dicho proceso.
Conexión de bean 69

A continuación veremos cómo conectar bean de forma explícita en Spring. Comenzaremos


con la capacidad de Spring para expresar configuraciones en Java.

Conectar bean con Java


Aunque en muchos casos se prefiera la configuración automática de Spring con análisis
de componentes y conexión automática, en otros tendremos que configurar Spring de forma
explícita. Por ejemplo, imagine que quiere conectar componentes de una biblioteca de
terceros a su aplicación. Como no dispone del código fuente de dicha biblioteca, no puede
anotar sus clases con @Component y @ A u tow ired . Por tanto, la configuración automática
no es una opción. En ese caso, tendrá que recurrir a la configuración explícita. Tiene dos
opciones: Java y XML. En este apartado veremos cómo usar JavaConfig y, en el siguiente,
la configuración XML de Spring.
Como mencionamos antes, JavaConfig es la opción preferida para la configuración
explícita ya que es más potente, ofrece seguridad de tipos y permite la refactorización. El
motivo: es código de Java, similar a cualquier otro código Java de su aplicación.
Pero conviene reconocer que el código JavaConfig no es como cualquier otro código de
Java. Conceptualmente se separa de la lógica empresarial y del código de dominio de su
aplicación. Aunque se exprese en el mismo lenguaje que esos componentes, JavaConfig
es código de configuración. Esto significa que no debe contener lógica empresarial, ni
invadir otro código en el que se encuentre dicha lógica. De hecho, aunque no sea obliga­
torio, JavaConfig suele configurarse en un paquete independiente al resto de la lógica de
la aplicación para evitar confusiones a este respecto.
A continuación veremos cómo configurar explícitamente Spring con JavaConfig.

Crear una clase de configuración


En el listado 2.3 anterior tuvimos el primer contacto con JavaConfig. Veamos de nuevo
el componente C D P la y e rC o n f i g de ese ejemplo:
package soundsystem;
import org.springframework. context.annotation . Configuration;

@Conf igurat ion


public cla ss CDPlayerConfig {
}
La clave para crear una clase de JavaConfig consiste en anotarla con @Conf i g u r a t io n ,
que la identifica como clase de configuración y que se espera que contenga detalles sobre
los bean que se van a crear en el contexto de aplicación de Spring.
Hasta el momento hem os recurrido al análisis de componentes para detectar los bean
que debe crear Spring. Aunque pueda usar conjuntamente el análisis de componentes y
la configuración explícita, en este apartado nos centraremos en la configuración explícita,
por lo que elim inam os la anotación @ C om p on en tScan de C D P la y e rC o n f ig .
70 Capítulo 2

Al hacerlo, la clase C D P la y e r C o n f i g deja de ser operativa. Si ahora ejecutara


C D P la y e r T e s t, la prueba fallaría con B e a n C r e a t io n E x c e p t io n . La prueba espera que
se inyecten C D P la y e r y C o m p a c tD isc, pero estos bean no se han creado ya que el análisis
de componentes no los ha detectado. Para que vuelva a funcionar la prueba, podemos recu­
perar @ C om ponentScan. No obstante, sin alejarnos de la configuración explícita, veamos
cómo conectar los bean C D P la y e r y C o m p a c tD isc en JavaConfig.

Declarar no bean sen cillo


Para declarar un bean en JavaConfig debe escribir un método que cree una instancia
del tipo deseado y anotarla con @Bean. Por ejemplo, el siguiente método declara el bean
C o m p a ctD isc:

@Bean
public CompactDisc sgtPeppersO {
return new SgtPeppersO;
}
La anotación @ B ean indica a Spring que este método devolverá un objeto que debe
registrarse como bean en el contexto de aplicación de Spring. El cuerpo del método contiene
lógica que provocará la creación de la instancia del bean. De forma predeterminada, al
bean se le asigna un ID que es el nombre del método anotado con @Bean. En este caso, el
nombre será s g t P e p p e r s . Si prefiere otro distinto, puede cambiar el nombre del método
o añadir un nombre diferente por medio del atributo ñame:
@Bean(name="lonelyHeartsClubBand")
public CompactDisc sgtPeppersO {
return new SgtPeppersO;
}
Independientemente de la técnica que utilice, esta declaración de bean es muy sencilla.
El cuerpo del método devuelve una nueva instancia de S g tP e p p e r s , pero al estar expre­
sado en Java, dispone de todas las prestaciones que ofrece este lenguaje para hacer todo lo
necesario para llegar al C o m p a c tD isc que se devuelve.
Si dejam os volar la im aginación, podría intentar cosas como seleccionar aleatoriamente
un elem ento C o m p a ctD isc entre varias opciones:

@Bean
public CompactDisc randomBeatlesCD() {
in t choice = (in t) Math.floor(Math.randomO * 4 );
i f (choice == 0) {
return new SgtPeppers();
} else i f (choice == 1) {
return new WhiteAlbumO ;
} else i f (choice == 2) {
return new HardDaysNight();
} else {
return new Revolver();
}
}
Conexión de bean 71

Le dejaré que piense en todas las formas posibles de aprovechar la potencia de Java
para crear un bean a partir de un método anotado con @Bean. Cuando termine, veremos
cómo inyectar el bean C o m p a ctD isc en C D P la y e r con JavaConfig.

In ye ccio n e s co n JavaConfig
El bean C o m p a c tD isc que hemos declarado es muy sencillo y carece de dependencias
propias, pero ahora debemos declarar el bean C D P la y e r, que depende de C o m p a ctD isc.
¿Cómo se podría conectar en JavaConfig?
La forma más sencilla de conectar bean en JavaConfig consiste en hacer referencia al
método del bean mencionado. Por ejemplo, podríamos declarar el bean C D P la y e r de esta
forma:
@Bean
public CDPlayer cdPlayerO {
return new CDPlayer(sgtPeppers( ) ) ;
}
El método c d P la y e r ( ) , como s g tP e p p e r s ( ) , se anota con @ Bean para indicar que
generará una instancia de un bean que registrar en el contexto de aplicación de Spring. El
ID del bean será c d P la y e r , igual que el nombre del método.
El cuerpo del método c d P la y e r () difiere ligeramente del del método s g t P e p p e r s ( ) .
En lugar de crear una instancia a través de su m étodo predeterm inado, la instancia
C D P la y e r se crea invocando su constructor, que acepta un componente C o m p a ctD isc.
Parece que C o m p a c tD isc se obtiene mediante la invocación de s g t P e p p e r s , pero no
es totalmente cierto. Como el método s g t P e p p e r s () está anotado con @Bean, Spring
interceptará todas las invocaciones y garantizará que el bean generado por ese método se
devuelva en lugar de permitir que se vuelva a invocar.
Por ejemplo, imagine que añade otro bean C D P la y e r igual que el primero:
©Bean
public CDPlayer cdPlayerO {
return new CDPlayer(sgtPeppersO);
}
©Bean
public CDPlayer anotherCDPlayer() {
return new CDPlayer(sgtPeppers( ) ) ;
}
Si la invocación de s g t P e p p e r s () se procesara como cualquier otra invocación de
un método de Java, cada C D P la y e r recibiría su propia instancia de S g t P e p p e r s , lo que
tendría sentido si estuviéramos hablando de verdaderos reproductores de CD y discos
compactos. Si tiene dos reproductores, no existe forma física de introducir simultáneamente
un mismo disco en ambos.
Sin embargo, en software, se podría inyectar la misma instancia de S g tP e p p e r s en
todos los bean que quisiéramos. De forma predeterminada, en Spring todos los bean son de
instancia única y no hay motivo para tener que crear una instancia duplicada del segundo
bean C D P la y e r. Por ello, Spring intercepta la invocación de s g t P e p p e r s 0 y se asegura
72 Capítulo 2

de que se devuelva en el bean de Spring creado cuando el propio Spring invocó s g t


P e p p e rs () para crear el bean C o m p a ctD isc. Por lo tanto, ambos bean C D P la y e r reciben
la misma instancia de S g tP e p p e r s . Entiendo que hacer referencia a un bean invocando
su método pueda parecerle confuso. Existe otra forma más fácil de digerir:
@Bean
public CDPlayer cdPlayer(CompactDisc compactDisc) {
return new CDPlayer(compactDisc);
}

Aquí, el método c d P l a y e r () solicita un elemento C o m p a ctD isc como parámetro.


Cuando Spring invoca c d P l a y e r () para crear el bean C D P la y e r, conecta automática­
mente un C o m p a ctD isc al método de configuración. Tras ello, el cuerpo del método puede
usarlo como considere oportuno. Con esta técnica, eí método c d P l a y e r () puede seguir
inyectando C o m p a c tD isc en el constructor de C D P la y e r sin hacer referencia explícita­
mente al método @Bean de C o m p a ctD isc.
Este enfoque para hacer referencia a otros bean suele ser el más indicado ya que no
depende de que el bean C o m p a c tD isc se declare en la misma clase de configuración.
De hecho, no hay nada que afirme que el bean C o m p a ctD isc tenga que declararse en
JavaConfig; se podría haber detectado mediante análisis de componentes o declarado en
XML. Podría dividir su configuración en una mezcla de clases de configuración, archivos
XML y bean automáticamente analizados y conectados. Independientemente de cómo se
haya creado C o m p a c tD isc, Spring lo transferirá a su método de configuración para crear
el bean C D P la y e r.
En cualquier caso, conviene reconocer que aunque ejecutemos la DI a través del cons­
tructor de C D P la y e r, no hay motivos para no aplicar otros estilos de DI. Por ejemplo, si
quisiera inyectar un C o m p a c tD isc a través de un método de establecimiento, podría usar
lo siguiente:
@Bean
public CDPlayer cdPlayer(CompactDisc compactDisc) {
CDPlayer cdPlayer = new CDPlayer(compactDisc);
cdPlayer. setCompactDisc(compactDisc);
return cdPlayer;
}
De nuevo, recordar que el cuerpo de un método @Bean puede utilizar los componentes
de Java necesarios para generar la instancia. La inyección de constructores y métodos de
establecimiento son dos sencillos ejemplos de lo que se puede hacer en un método anotado
con @Bean. Las posibilidades se limitan únicamente a las prestaciones del lenguaje Java.

Conectar bean con XML


Hasta el momento hemos visto cómo dejar que Spring detecte y conecte bean de forma
automática y también cómo conectar bean de forma explícita con JavaConfig. Pero existe
otra opción que, aunque sea menos deseable, tiene un extenso historial en Spring.
Conexión de bean 73

Desde los inicios de Spring, XMLha sido la principal forma de expresar configuraciones.
Se han creado innumerables líneas de XML en el nombre de Spring y, para muchos, Spring
se ha convertido en un sinónimo de configuración XML.
Aunque sea cierta esta prolongada asociación de Spring con XML, hay que decir que
XML no es la única opción para configurar Spring, y ahora que Spring cuenta con una sólida
compatibilidad con la configuración automática y la basada en Java, XML no debería ser
su primera opción.
No obstante, como ya existen muchas configuraciones de Spring basadas en XML,
es importante saber cómo usar XML con Spring. Espero que este apartado le sirva para
ayudarle a trabajar con configuraciones XML existentes y que recurra a la configuración
automática y a JavaConfig para sus nuevos proyectos de Spring.

Crear una especificación cíe configuración XM L


Antes de empezar a usar XML para conectar bean en Spring, tendrá que crear la espe­
cificación de configuración vacía. Con JavaConfig esto significa crear una clase anotada
con © C o n fig u ra tio n . Para la configuración XML significa crear un archivo XML cuya
raíz sea un elemento < b ean s>.
La configuración XML para Spring más sencilla posible sería la siguiente:
<?xml v e rsio n = "l.0" encoding="UTF-8"?>
cbeans xmlns="http://www.springframework.org/schema/beans1'
xmlns: x s i= "h ttp : //www.w3. org/2001/XML,Schema-in stan ce"
x s i : schemaLocation="h ttp : / /www.springframework. org/schema/beans
h ttp : / /www. springframework. org/schema/beans/spring-beans.xsd
h ttp : / /www. springframework. o rg /schema/context">

< !- - configuration d e ta ils go here -->

</beans>

Es evidente que esta configuración XML básica es mucho más compleja que la clase de
JavaConfig equivalente. Mientras que la anotación @Conf i g u r a t i o n de JavaConfig es
lo único que necesitábamos para empezar, los elementos XML necesarios para configurar
Spring se definen en varios archivos de esquema XML (XSD) que hay que declarar en el
preámbulo de un archivo de configuración XML.

Crear configuraciones XML con Spring Tool Suite


__ *________
Una forma sencilla de crear y gestionar archivos de configuración XML de Spring
consiste en usar Spring Tool Suite ( h t t p s : / / s p r i n g . i o / t o o l s / s t s ) . Seleccione
la opción de archivo de configuración de bean de Spring en el menú de Spring
Tool Suite para crear un archivo de configuración XML de Spring y e lija uno de
los espacios de nombres de configuración disponibles.
74 Capítulo 2

Los elementos XMLmás básicos para conectar bean se incluyen en el esquema s p r in g -


b e a n s, que se declara como espacio de nombres raíz de este archivo XML. El elemento
cb e a n s >, la raíz de cualquier archivo de configuración de Spring, es uno de los elementos
de este esquema. Existen otros esquemas para configurar Spring en XML. Aunque a lo largo
del libro nos centraremos en la configuración automática y la basada en Java, al menos
le informaré cuando entren en juego estos esquemas alternativos. Así pues, dispone de
una configuración XML de Spring totalmente válida, y totalmente inútil, ya que todavía
no declara ningún bean. Para darle vida, recrearemos el ejemplo del CD, en esta ocasión
utilizando configuración XML en lugar de la basada en Java o la automática.

Declarar un sencillo elemento <bean>


Para declarar un bean en una configuración de Spring basada en XML, usaremos otro
elemento del esquema s p r i n g - b e a n s : < b e a n > . El elemento < b e a n > es el equivalente
en XML de la anotación @ B ean de JavaConfig. Puede utilizarlo para declarar el bean
C o m p a c tD isc de esta forma:

<bean class="soundsystem.SgtPeppers" />

Aquí declaramos un bean muy sencillo. La clase empleada para crearlo se especifica en
el atributo c l a s s y se expresa como el nombre de clase totalmente cualificado.
Por falta de un ID asignado de forma explícita, el nombre del bean será el nombre de
clase totalmente cualificado. En este caso, será s o u n d s y s te m . S g t P e p p e r s # 0 . #0 es una
enumeración usada para diferenciar a este bean de otro del mismo tipo. Si declarara otro
bean S g tP e p p e r s sin identificarlo de forma explícita, recibiría automáticamente el ID
s o u n d s y s te m . S g t P e p p e r s # l .
Aunque es recomendable que los bean reciban nombres de forma automática, los
generados serán menos útiles si después tiene que hacer referencia a ellos. Por lo tanto, es
aconsejable asignar a cada bean un nombre concreto por medio del atributo id :
cbean id="compactDisc" class="soundsystem.SgtPeppers" />

Utilizaremos este nombre explícito en breve cuando conectemos este bean al bean
C D P la y er.

Nota . V- ......... : :

Para reducir la complejidad del código XML, asigne nombres explícitos a sus bean
solo cuando tenga que hacer referencia a los mismos por nombre (por ejem plo si
tiene que inyectar en otro bean una referencia a un bean concreto).

Pero antes de continuar, veamos alguna de las características de esta sencilla declaración
de bean. Lo primero que vemos que es no somos directamente responsables de la creación
de una instancia de S g tP e p p e r s como sucedía al usar JavaConfig. Cuando Spring ve este
Conexión de bean 75

elemento < b ea n > , crea unbean S g tP e p p e r s invocando su constructor predeterminado. La


creación de bean es mucho más pasiva con la configuración XML, pero también es mucho
menos completa que con JavaConfig, en la que se puede hacer todo lo posible para llevar
a la instancia del bean.
Otro aspecto destacado de esta declaración de <bean > es que se expresa el tipo del
bean como cadena establecida en el atributo c l a s s . ¿Quién puede afirmar que el valor
asignado a c l a s s hace referencia a una clase real? La configuración XML de Spring no
aprovecha las ventajas de la verificación de tiempo de compilación de los tipos de Java
a los que se hace referencia, y aunque haga referencia a un tipo real, ¿qué sucedería si se
cambia el nombre de la clase?

Si utiliza un IDE compatible con Spring como Spring Tool Suite le resultará más
sencillo garantizar la validez de su configuración XML para Spring.

Estos son algunos de los motivos por lo que es preferible usar JavaConfig a la configu­
ración XML. Le recomiendo que tenga en cuenta estos aspectos a la hora de elegir el estilo
de configuración para su aplicación. Continuemos con el análisis de la configuración XML
de Spring para ver cómo inyectar el S g tP e p p e r s en C D P la y er.

inieializar un bean co n inyecciones de constructor


Solo hay una forma de declarar un bean en la configuración XML de Spring: usar el
elemento <b ean > y especificar un atributo c l a s s . A partir aquí Spring toma el control.
Pero a la hora de declarar la DI en XML, existen varias opciones y estilos. En relación a
la inyección de constructores, puede elegir entre dos opciones básicas:

• El elemento < c o n s t r u c t o r - a r g > .


• El espacio de nombres c de Spring 3.0.

La diferencia entre ambas es puramente cuantitativa. Como veremos en breve, el


elemento < c o n s t r u c t o r - a r g > suele ser más detallado que usar el espacio de nombres
c y genera código XML más difícil de leer. Por otra parte, < c o n s t r u c t o r - a r g > puede
hacer varias cosas que el espacio de nombres no hace. Cuando analicemos la inyección de
constructores en XML de Spring, veremos ambas opciones. Primero nos centraremos en
cómo se comportan en la inyección de referencias de bean.

Inyectar constructores con referencias de bean


En su definición actual, el bean C D P la y e r tiene un constructor que acepta un elemento
C o m p a c tD isc, lo que le convierte en candidato perfecto para la inyección de una refe­
rencia de bean.
76 Capítulo 2

Como ya hemos declarado un bean S g t P e p p e r s y como la clase S g t P e p p e r s imple-


menta la interfaz C o m p a c tD isc, tenemos un bean para inyectar en un bean C D P la y er.
Basta con declarar el bean C D P la y e r en XM Ly hacer referencia al bean S g t P e p p e r s por
medio de su ID:
cbean id="cdPlayer" class="soundsystem.CDPlayer">
<constructor-arg ref="compactDisc" />
</bean>

Cuando Spring detecta este elemento < b e a n > , crea una instancia de C D P la y e r. El
elemento < c o n s t r u c t o r - a r g > le indica que pase al constructor de C D P la y e r una refe­
rencia al bean con el ID c o m p a c tD is c .
También se puede usar el espacio de nombres c de Spring. Apareció en Spring 3.0 como
una forma más sucinta de expresar argumentos de constructor en XML. Para usarlo, debe
declarar su esquema en el preámbulo del XML de esta forma:
<?xml v e rsio n = "l.0" encoding="UTF-8"?>
cbeans xmlns="h ttp : //www. springframework. org/schema/beans"
xmlns: c="h ttp : //www. springframework. org /schema/c "
xm lns:xsi="h ttp : //www.w3. org/2001/XMLSchema-in stan ce"
x s i : schemaLocation="http://www.springframework.org/schema/beans
h tt p :/ /www.springframework.org/schema/beans/spring-beans.xsd">

</beans>

Tras declarar el espacio de nombres c y el esquema, puede usarlo como argumento de


constructor de esta forma:
cbean id="cdPlayer" class="soundsystem.CDPlayer"
c : cd-ref="compactDisc" />

Aquí utilizamos el espacio de nombres c para declarar el argumento de constructor


como atributo del elemento <bean>. Es un nombre de atributo de aspecto antiguo. En la
figura 2.1 se ilustra la combinación de las partes del nombre del atributo. Comienza por
c :, el prefijo del espacio de nombres. Después aparece el nombre del argumento de cons­
tructor conectado. Tras ello, - r e f , una convención de nomenclatura que indica a Spring
que se está conectando una referencia al bean c omp a c t D i s c y n o a l valor literal de cadena
" c o m p a c tD is c ".

Nombre del argumento


de constructor El ID del bean que inyectar

^ r---------^
c : c d - r e f = "compactDisc"

Prefijo del espacio Inyección de una


de nombres C referencia de bean
Figura 2.1. Inyección de una referencia de bean a un argumento de constructor con el espacio
de nombres c de Spring.
Conexión de bean 77

Es evidente que el uso de atributos del espacio de nombres c es mucho más terso que
usar el elemento < c o n s t r u c t o r - a r g > . Es uno de los motivos por los que me gusta tanto.
Además de ser más fácil de leer, estos atributos son especialmente útiles cuando tengo que
escribir ejemplos de código para los márgenes de un libro.
Pero un aspecto que me preocupa del ejemplo anterior es que el espacio de nombres
c hace referencia directamente al nombre del argumento de constructor. Me resulta un
tanto extraño. Para hacer referencia a un parámetro por nombre es necesario compilar el
código con símbolos de depuración almacenados en el código de las clases. Si optimiza
sus proyectos para excluir símbolos de depuración, probablemente no funcione. Por el
contrario, podría hacer referencia a la posición del parámetro en la lista de parámetros:
<bean id="cdPlayer" class="soundsystem.CDPlayer"
c:_0-ref="CompactDisc" />

Este atributo parece incluso más extraño que el anterior. Hemos sustituido el nombre
del parámetro por 0, el índice del parámetro, pero como XML no permite usar dígitos
como primer carácter de un atributo, hemos tenido que añadir un guión bajo como prefijo.
El uso de un índice para identificar el argumento de constructor parece más acertado que
la referencia por su nombre. Aunque se excluyan los símbolos de depuración del proyecto,
los parámetros seguirán teniendo el mismo orden, y si hubiera varios argumentos de cons­
tructor, sería útil sin duda, pero como solo hay un argumento de constructor, dispone de
una opción más: no identificar el parámetro:
<bean id="cdPlayer" class="soundsystem.CDPlayer"
c:_-ref="CompactDisc" />

Es sin duda el atributo del espacio de nombres c más peculiar. No hay índice ni nombre
de parámetro; solo un guión bajo de marcador de posición seguido de - r e f para indicar
que estamos escribiendo una referencia. Después de intentar conectar una referencia a otros
bean, veamos cómo conectar valores literales a constructores.

Inyectar valores literales a constructores


Aunque la DI suele hacer referencia al tipo de conexión que hemos realizado hasta el
momento (referencias a objetos en otros objetos dependientes), en ocasiones solo necesitará
configurar un objeto con un valor literal. Para comprobarlo, imagine que tiene que crear
una nueva implementación de C o m p a ctD isc:
package soundsystem;

public cla ss BlankDisc implements CompactDisc {

prívate String t i t l e ;
prívate String a r t i s t ;

public BlankDisc(String t i t l e , String a r t is t ) {


t h is .t it le = t i t l e ;
t h i s .a r t i s t = a r t i s t ;
}
78 Capítulo 2

public void playO {


System .o u t.p rin tln ("Playing " + t i t l e + " by " + a r t i s t ) ;
}
}
Al contrario de lo que sucedía con S g t P e p p e r s , que contaba con un título y un artista,
esta implementación de C o m p a ctD isc es considerablemente más flexible. Similar a un
disco en blanco real, se puede establecer para incluir cualquier artista y título que quiera.
Ahora puede cambiar el bean S g tP e p p e r s existente para que utilice esta clase en su lugar:
<bean id="compactDisc"
c la s s = "soundsystem.BlankDisc">
<constructor-arg value="Sgt. Pepper's Lonely Hearts Club Band" />
<constructor-arg value="The B eatles" />
</bean>

De nuevo, el elemento < c o n s t r u c t o r - a r g > se usa para inyectarlo a argumentos


de constructor, pero en este caso, en lugar de usar el atributo r e f para hacer referencia a
otro bean, usamos el atributo v a lu é para indicar que el valor proporcionado se obtiene
literalmente y se inyecta en el constructor.
¿Qué aspecto tendría si utilizara atributos del espacio de nombres c? Una posibilidad
podría hacer referencia a los argumentos de constructor por nombre:
<bean id="compactDisc"
c la s s = "soundsystem. BlankDisc"
c :_ t i t l e = " S g t . Pepper's Lonely Hearts Club Band"
c:_ a rtist= "T h e B eatles" />

Como puede apreciar, la conexión de valores literales a través del espacio de nombres
c difiere de la conexión de referencias en que se excluye el sufijo - r e f del nombre del
atributo. Así pues, podríamos conectar los mismos valores literales mediante índices de
parámetro de esta forma:
<bean id="compactDisc"
class="soundsystem.BlankDisc"
c:_0="S g t. Pepper's Lonely Hearts Club Band"
c:_l="The B eatles" />

XML no permite que más de un atributo de un determinado elemento comparta el mismo


nombre. Por eso, no puede usar el guión bajo si tiene dos o más argumentos de constructor;
sí cuando haya únicamente uno. Para completar el ejemplo, imagine que B la n k D is c tiene
un solo argumento de constructor que acepta el título del álbum. En ese caso, en Spring
podría declararlo de esta forma:
cbean id="compactDisc" class="soundsystem.BlankDisc"
G :_s"§gt. F§pp§r'§ JdQnely H§§rt§ club Band" />

A la hora de conectar valores de referencia de bean y literales, tanto < c o n s t r u c t o r -


a r g > como los atributos del espacio de nombres c son igual de válidos, pero hay algo que
< c o n s t r u c t o r - a r g > puede hacer y el espacio de nombres c no. Veamos cómo conectar
colecciones a argumentos de constructor.
Conexión de bean 79

Conectar colecciones
Hasta el momento hemos asumido que C o m p a ctD isc se definía con un título y un
nombre de artista, pero si un CD real incluyera solo esos datos, nunca hubieran llegado
demasiado lejos. Lo que hace que compremos un CD es que contiene música en varias
pistas, cada una con una canción.
Si queremos que C o m p a c tD isc imite a un CD real, necesitamos el concepto de lista de
pistas. Fíjese en el siguiente ejemplo de B la n k D is c :
package soundsystem .collections;
import ja v a .u t i l .L i s t ;
import soundsystem. CompactDisc,-

public cla ss BlankDisc implements CompactDisc {

private String t i t l e ;
p rivate String a r t i s t ;
p rivate L ist<Strin g > track s;

public BlankDisc(String t i t l e , String a r t i s t , L ist<Strin g > tracks) {


th is .t it le = t i t l e ;
t h i s .a r t i s t = a r t i s t ;
th is .tr a c k s = track s;
}
public void play() {
System .o u t.p rin tln ( "Playing " + t i t l e + " by " + a r t i s t ) ;
for (String track : tracks) {
Sy stem .o u t.p rin tln ( " -Track: " + tra ck );

Este cambio afecta a la configuración del bean en Spring. Al declararlo, tendrá que
proporcionar una lista de pistas.
La solución más sencilla sería dejar la lista vacía (n u il). Al tratarse de un argumento
de constructor, debe especificarlo, pero puede pasar n u i l de esta forma:
<bean id="CompactDisc" class="soundsystem.BlankDisc">
<constructor-arg value="Sgt. Pepper's Lonely Hearts Club Band" />
<constructor-arg value="The B eatles" />
< co n stru c to r-a rg x n u ll/x /c o n stru cto r-a rg >
</bean>

El elemento < n u l l / > hace lo que esperamos: pasar n u i l al constructor. Es una solución
un tanto pobre, pero funciona durante la inyección. Al invocar el método p l a y () se genera
N u l l P o i n t e r E x c e p t i o n por lo que no resulta especialmente idónea.
Una solución más indicada sería proporcionar una lista de nombres de pista. Para
ello dispone de dos opciones. Por un lado, podría especificarlo como lista, por medio del
elemento < l i s t > :
80 Capítulo 2

cbean id="compactDisc" class="soundsystem.BlankDisc">


<constructor-arg value="Sgt. Pepper's Lonely Hearts Club Band" />
<constructor-arg value="The B eatles" />
<constructor-arg>
< lis t>
<value>Sgt. Pepper's Lonely Hearts Club Band</value>
<value>With a L it t le Help from My Friends</value>
<value>Lucy in the Sky with Diamonds</value>
<value>Getting Better</value>
<value>Fixing a Hole</value>
< !- - ...o t h e r tracks omitted for b r e v it y ... -->
< / l is t >
< /constructor-arg>
</bean>

El elemento < l i s t > es un secundario de < c o n s t r u c t o r - a r g > e indica que se va a


pasar una lista de valores al constructor. Se utiliza el elemento e v a l u é > para especificar
cada elemento de la lista. Del mismo modo podríamos conectar una lista de referencias
de bean con el elemento < r e f > en lugar de e v a lú e >. Por ejemplo, imagine que tiene una
lista D is c o g r a p h y con el siguiente constructor:
public Discography(String a r t i s t , ListcCompactDiso cds) { . . . }

Tras ello, puede configurar un bean D is c o g r a p h y de esta forma:


<bean id="beatlesDiscography"
class="soundsystem.Discography">
<constructor-arg value="The B eatles" />
<constructor-arg>
< lis t>
<ref bean="sgtPeppers" />
<ref bean="whiteAlbum" />
<ref bean="hardDaysNight" />
<ref bean="revolver" />

< / l is t >
< /constructor-arg>
</bean>

Tiene sentido usar e l i s t > para conectar un argumento de constructor de tipo j a v a .


u t i l . L i s t . Incluso así, también podría usar el elemento < s e t > de la misma forma:
cbean id="compactDisc" class="soundsystem.BlankDisc">
<constructor-arg value="Sgt. Pepper's Lonely Hearts Club Band" />
<constructor-arg value="The B eatles" />
<constructor-arg>
<set>
<value>Sgt. Pepper's Lonely Hearts Club Band</value>
<value>With a L it t l e Help from My Friends</value>
<value>Lucy in the Sky with Diamonds</value>
<value>Getting Better</value>
<value>Fixing a Hole</value>
< !- - ...o t h e r tracks omitted for b r e v it y ... -->
< /set>
</constructor-arg>
</bean>
Conexión de bean 8 1

Apenas hay diferencias entre < s e t > y < l i s t > . La principal es que cuando Spring crea
la colección que conectar, la puede crear como j a v a . ú t i l . S e t o como j a v a . ú t i l . L i s t .
Si es de tipo S e t , los valores duplicados se descartan y puede que el orden no se mantenga,
pero en cualquier caso, se puede conectar < s e t > o < l i s t > a L i s t , S e t o incluso a una
matriz. La conexión de colecciones es un proceso en el que < c o n s t r u c t o r - a r g > tiene
ventaja sobre los atributos del espacio de nombres c, ya que con atributos del espacio de
nombres no hay una forma evidente de hacerlo.
Hay otros muchos matices sobre el uso de < c o n s t r u c t o r - a r g > y el espacio de
nombres c para la inyección de constructores, pero le bastará con lo que hemos visto
hasta el momento, en especial si tiene en cuenta mi recomendación anterior sobre el uso
de configuración de Java antes que la de XML. Por lo tanto, en lugar de pormenorizar el
tema de la inyección de constructores en XML, nos centraremos en la forma de conectar
propiedades en XML.

Configurar propiedades
Hasta el momento, las clases C D P layer y B la n k D is c se han configurado mediante
inyección de constructores y carecen de métodos de establecimiento de propiedades. Veamos
cómo funciona la inyección de propiedades en XML de Spring. Imagine que la nueva clase
C D Player con propiedades inyectadas tiene este aspecto:
package soundsystem;
import org.springframework.beans. factory.annotation.Autow ired;
import soundsystem.CompactDisc;
import soundsystem.MediaPlayer;

public cla ss CDPlayer implements MediaPlayer {


prívate CompactDisc compactDisc;

@Autowired
public void setCompactDisc(CompactDisc compactDisc) {
t h i s . compactDisc = compactDisc;
}
public void playO {
compactDisc.play();
}
}

: •; . . : : : -■ ;
Elegir entre inyección de constructores e inyección de propiedades
Como regla general, prefiero la inyección de constructores para las dependencias
básicas y la de propiedades para las opcionales. Se podría argumentar que el
título, el artista y la lista de pistas son dependencias básicas de BlankDisc y que
la inyección de constructores es la opción correcta. Sin embargo, es discutible que
CompactDisc sea una dependencia básica u opcional de CDPlayer. Podría afirmar
que un CDPlayer seguiría teniendo cierta funcionalidad incluso sin introducir
un CompactDisc en su interior.
82 Capítulo 2

Ahora que C D P la y e r no tiene constructores (aparte del predeterminado), tampoco


tiene dependencias básicas y, por lo tanto, se podría declarar como un bean de Spring:
cbean id="cdPlayer"
class="soundsystem.CDPlayer" />

Spring no tendrá problemas para crear este bean. No obstante, su C D P lay erT es t fallaría
c o n N u l l P o i n t e r E x c e p t i o n , ya que no se ha inyectado la propiedad c o m p a c tD is c de
C D P la y e r, pero lo podemos corregir con el siguiente cambio:

cbean id="cdPlayer"
c la s s = "soundsystem. CDPlayer">
cproperty name="compactDisc" ref="compactDisc" />
</bean>

El elemento < p r o p e r t y > hace con los métodos de establecimiento de propiedades lo


que el elemento < c o n s t r u c t o r - a r g > hace con los constructores. En este caso hace refe­
rencia (con el atributo r e f ) al bean cuyo ID es c o m p a c tD is c y que se va a inyectar en la
propiedad c o m p a c tD is c (a través del método s e tC o m p a c tD is c ()). Si ahora ejecuta la
prueba, no se producen errores. Al igual que Spring ofrece el espacio de nombres c como
alternativa al elemento < c o n s t r u c t o r - a r g > , cuenta con un sucinto espacio de nombres
p como alternativa al elemento < p r o p e r t y > . Para habilitarlo, tendrá que declararlo junto
a los demás espacios de nombres del archivo XML:
<?xml v e rsio n = "l.0" encoding="UTF-8"?>
cbeans xmlns="h ttp : //www. springframework. org/schema/beans"
xmlns:p="h ttp : //www.springframework. org/schema/p"
xm lns:xsi="h ttp : / /www.w3.org/2001/XMLSchema-instance"
x s i : schemaLocation="h ttp : / /www. springframework. org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">

</bean>

Por medio del espacio de nombres p puede conectar la propiedad c o m p a c tD is c :


cbean id="cdPlayer"
class="soundsystem.CDPlayer"
p : compactDise-ref= "compactDisc" />

Los atributos del espacio de nombres p utilizan una convención de nombres similar a
los del espacio de nombres c, como se ilustra en la figura 2.2.

Nombre de la propiedad
El ID del bean que inyectar
X
c :compactDise-ref="compactDisc"

Prefijo del espacio inyección de una


de nombres de C referencia de bean

Figura 2.2. Inyección de una referencia de bean a una propiedad con el espacio de nombres p de Spring.
Conexión de bean 83

En primer lugar, se añade el prefijo p : al nombre del atributo para indicar que se va a
establecer una propiedad. Después aparece el nombre de la propiedad que inyectar y, por
último, el nombre termina con - r e f para indicar a Spring que estamos conectando una
referencia a un bean y no un valor literal.

inyectar propiedades con valores literales


Las propiedades se pueden inyectar con valores literales de la misma forma que los argu­
mentos de constructor. Para ilustrarlo, retomaremos el bean B la n k D is c pero en esta ocasión
lo configuraremos totalmente mediante inyección de propiedades, no de constructores:
package soundsystem;
import ja v a . ú t i l . L i s t ;
import soundsystem.CompactDisc;

public cla ss BlankDisc implements CompactDisc {

private String t i t l e ;
private String a r t i s t ;
private L ist<String > track s;

public void s e tT itle (S trin g t i t l e ) {


t h is .t it le = t i t l e ;
}
public void s e tA rtist(S trin g a r t is t ) {
th is .a r tis t = a r tis t;
}
public void setT racks(L ist<String> tracks) {
t h i s . tracks = track s;
}
public void play() {
System.o u t.p r in tln ("Playing " + t i t l e + " by " + a r t i s t ) ;
for (String track : tracks) {
System.o u t.p r in tln (" -Track: " + tra ck );
}
}
}

Ya no estamos obligados a conectar estas propiedades. Podríamos crear un bean


B la n k D is c prácticamente vacío de esta forma:
<bean id="reallyBlankD isc"
c la s s = "soundsystem. BlankDisc" />

Evidentemente, la conexión del bean sin establecer estas propiedades no funcionaría


correctamente en tiempo de ejecución. El método p l a y () indicaría que no se reproduce
nada y se generaría N u l l P o i n t e r E x c e p t i o n debido a la ausencia de pistas. Por lo
tanto, debería conectar las propiedades. Puede hacerlo con el atributo v a lu e del elemento
< p ro p e rty > :
84 Capítulo 2

<bean id="compactDisc"
class="soundsystem.BlankDisc">
<property name="t i t l e "
value="Sgt. Pepper's Lonely Hearts Club Band" />
<property nam e="artist" value="The B eatles" />
<prope rty name ="t racks">
< lis t>
<value>Sgt. Pepper's Lonely Hearts Club Band</value>
<value>With a L it t l e Help from My Friends</value>
<value>Lucy in the Sky with Diamonds</value>
<value>Getting B etter</value>
<value>Fixing a Hole</value>
< !- - ...o t h e r tracks omitted for b r e v it y ... -->
< / l is t >
</property>
</bean>

Además de usar el atributo v a lu e del elemento < p r o p e r ty > para establecer las
propiedades t i t l e y a r t i s t , comprobará que se establece la propiedad t r a c k s con un
elemento < 1 i s t > anidado, el mismo que usamos antes al conectar las pistas por medio de
< c o n s t r u c t o r - a r g > . Opcionalmente puede realizar la misma operación con atributos
del espacio de nombres p:
cbean id="compactDisc"
class="soundsystem.BlankDisc"
p : t i t l e = " S g t . Pepper's Lonely Hearts Club Band"
p :a rtist= "T h e B eatles">
<property name="tra ck s">
< lis t >
<value>Sgt. Pepper's Lonely Hearts Club Band</value>
<value>With a L it t l e Help from My Friends</value>
<value>Lucy in the Sky with Diamonds</value>
<value>Getting Better</value>
<value>Fixing a Hole</value>
< !- - ...o t h e r tracks omitted for b r e v it y ... -->
< / l is t >
</property>
</bean>

Como sucede con los atributos del espacio de nombres c, la única diferencia entre
conectar una referencia de bean y un valor literal es la presencia o ausencia de un sufijo
- r e f . Sin él, lo que conectamos con valores literales.
Sin embargo, no puede usar el espacio de nombres p para conectar una colección.
Desafortunadamente, no ofrece una forma de especificar una lista de valores (o referencias
de bean), pero puede recurrir al espacio de nombres ú t i l de Spring para simplificar el
bean B la n k D is c .
Primero tendrá que declarar el espacio de nombres u t i 1 y su esquema en el código XML:
<?xml v e rsio n = "l.0" encoding="UTF-8"?>
cbeans xmlns="http://www.springframework.org/schema/beans"
xm lns:xsi="h ttp : //www.w3. org/2001/XMLSchema-in stan ce"
xmlns:p="h ttp : //www.springframework.org/schema/p"
xmlns:u t il = " h ttp : //www.springframework.org/schem a/util"
Conexión de bean 85

x s i : schemaLocation= "h ttp : / /ww. springf ramework. org/schema/beans


h ttp : / / www. springframework.org/schema/beans/spring-beans.xsd
h ttp : / /www.springframework. org/schem a/util
h ttp -. / /www. springframework.org/schem a/u til/sp rin g-u til .xsd" >

</beans>

El espacio de nombres u t i l ofrece el elemento < u t i l : l i s t > / que crea un bean de


lista. Por medio de < u t i l : l i s t > , puede extraer la lista de pistas del bean B la n k D is c e
incluirla en su propio bean:
c u t i l : l i s t id ="track L ist">
<value>Sgt. Pepper's Lonely Hearts Club Band</value>
<value>With a L it t l e Help from My Friends</value>
<value>Lucy in the Sky with Diamonds</value>
<value>Getting B etter</value>
<value>Fixing a Hole</value>
< !- - ...o t h e r tracks omitted for b r e v it y ... -->
< / u t i l :1 i s t >

Ahora podrá conectar el bean de lista de pistas a la propiedad t r a c k s del bean


B la n k D is c como de costumbre:
<bean id="compactDisc"
class="soundsystem.BlankDisc"
p : t i t l e = " S g t . Pepper's Lonely Hearts Club Band"
p :a rtist= "T h e B eatles"
p :tra c k s-re f= "tra c k L ist" />

El elemento c u t i l : l i s t > es uno de los muchos que incluye el espacio de nombres


ú t i l . En la tabla 2.1 puede ver los que contiene.

Tabla 2.1. Elementos del espado de nombres útil de Spring.

cutil:constant> Hace referencia a un campo public static de un tipo y lo muestra


como bean.
cutil:list> Crea un bean que es una java.util.List de valores o referencias.

cutil:map> Crea un bean que es un j ava .util. Map de valores o referencias.

cutil:properties> Crea un bean que es java .util.Properties.


cutil:property-path> Hace referencia a una propiedad de bean (o anidada) y la muestra
como bean.
cutil:set> Crea un bean que es un ja v a . útil. Set de valores o referencias.

Podrá invocar los miembros del espacio de nombres ú t i l cuando los necesite. Por el
momento concluiremos el capítulo viendo formas de mezclar configuración automática,
de Java y de XML.
8 6 Capítulo 2

Importar y combinar configuraciones


En una aplicación típica de Spring es muy probable que tenga que usar tanto configura­
ción automática como explícita, y aunque se decante por JavaConfig para la configuración
explícita, habrá casos en los que XML sea la mejor opción.
Afortunadamente, ninguna de las opciones de configuración de Spring es excluyente.
Puede mezclar análisis de componentes y conexión automática con JavaConfig y /o confi­
guración XML. De hecho, como vimos en un apartado anterior, necesitará al menos cierta
configuración explícita para habilitar el análisis de componentes y la conexión automática.
Lo primero que debe saber sobre la combinación de estilos de configuración es que
en lo que respecta a la conexión automática, no importa de dónde provenga el bean que
conectar. La conexión automática tiene en cuenta todos los bean del contenedor de Spring,
independientemente de que se hayan declarado en JavaConfig o XML, o se haya detectado
mediante análisis de componentes. De este modo, tendrá que decidir cómo hace referencia
a estos beans: mediante configuración XML o Java. Comenzaremos viendo cómo hacer
referencia a bean configurados con XML desde JavaConfig.

Hacer referencia a configuración XML en JavaConfig


Imagine por un momento que C D P la y e rC o n f i g se ha vuelto incontrolable y decide
dividirlo en piezas. Es evidente que solo declara dos bean, lo que precisamente no es una
configuración compleja. Sin embargo, imagine que dos bean son demasiados bean.
Podría separar el bean B la n k D is c de C D P la y erC o n f i g en su propia clase CDConf ig :
package soundsystem;
import o rg . springframework.context. annotation. Bean;
import org.springframework. co n text. annotation. Configuration;

@Configuration
public cla ss CDConfig {
@Bean
public CompactDisc compactDisc() {
return new SgtPeppers();

Ahora que el método c o m p a c tD is c ( ) ha desaparecido de C D P lay erC o n f i g , necesita


combinar las dos clases. Una forma consiste en importar C D C on fig de C D P la y e rC o n f i g
por medio de la anotación @Im p o rt:
package soundsystem;
import org.springframework.context. annotation.Bean;
import org. springframework. co n tex t. annotation. Configuration;
import org. springframework. co n tex t. annotation. Import;

©Configuration
©Import(CDConfig.class)
public c la ss CDPlayerConfig {
Conexión de bean 87

@Bean
public CDPlayer cdPlayer(CompactDisc compactDisc) {
return new CDPlayer(compactDisc);
}
}
Mejor todavía, puede excluir @ Im p o rt de C D P la y e rC o n f i g y crear en su lugar un
S o u n d S y s te m C o n f i g de nivel superior que utilice @ Im p o r t para combinar ambas
configuraciones:
package soundsystem;
import org. springframework.context. annotation.Configuration;
import org. springframework. co n tex t. annotation. Import;

@Conf iguration
@Import( {CDPlayerConfig. c la s s , CDConfig. c l a s s } )
public c la ss SoundSystemConfig {
}
De cualquier forma, hemos separado la configuración de CDPlayer de la de B lan k D isc.
Imagine ahora que quiere configurar el bean B la n k D is c en XML de esta forma:
cbean id="compactDisc"
c la s s = "soundsystem. BlankDisc"
c:_0="Sg t. Pepper's Lonely Hearts Club Band"
c:_l="The B e a tle s">
<constructor-arg>
< lis t>
<value>Sgt. Pepper's Lonely Hearts Club Band</value>
<value>With a L it t l e Help from My Friends</value>
<value>Lucy in the Sky with Diamonds</value>
<value>Getting Better</value>
<value>Fixing a Hole</value>
< !- - ...o t h e r tracks omitted for b r e v it y ... -->
< / l is t >
</constructor-arg>
</bean>

Al declarar B la n k D is c en XML, ¿cómo puede cargarlo Spring junto al resto de la


configuración basada en Java?
La respuesta radica en la anotación @ I m p o r tR e s o u r c e . Si el bean B la n k D is c se
declara en el archivo c d - c o n f i g . xm l que se encuentra en la raíz de la ruta de clases,
puede cam biar S o u n d S y stem C o n f i g para que utilice @ I m p o r tR e s o u r c e de esta forma:

package soundsystem;
import org.springframework. co n tex t.annotation. Configuration;
import org. springframework. co n tex t. annotation. Import;
import org . springf ramework. co n tex t. annotation. ImportResource

©Configuration
@Import(CDPlayerConfig. class)
@ImportResource("cla ssp a th :cd-config.xm l")
public c la ss SoundSystemConfig {
}
8 8 Capítulo 2

Los dos bean, C D P la y e r configurado en JavaConfig y B la n k D is c configurado en


XML, se cargarán en el contenedor de Spring, y como el método @ Bean de C D P la y e r
acepta C o m p a c tD isc como parámetro, se conectará el bean B la n k D is c , aunque se haya
configurado en XML.
Volvamos a ver el ejercicio pero en esta ocasión haremos referencia desde XML a un
bean declarado en JavaConfig.

Hacer referencia a JavaConfig en configuración XML


Imagine que está trabajando con la configuración de Spring basada en XML y decide que
el código XML se ha descontrolado. Como antes, solo tiene dos bean, pero antes de verse
inundado por un aluvión de comillas angulosas, decide dividir el archivo de configuración
XML. Con JavaConfig vimos cómo usar @ Im port y @ Im p o rtR eso u rce para dividir las
clases. En XML puede usar el elemento c im p o rt > para dividir la configuración XML.
Por ejemplo, imagine que tiene que dividir el bean B la n k D is c en un archivo de confi­
guración propio con el nombre cd c o n f ig .x m l. Puede hacer referencia a este archivo
desde el archivo de configuración XML por medio de c im p o rt >:
<?xml v e r s io n = "l.0" encoding="UTF-8"?>
cbeans xmlns="h ttp : / /www.springframework. org/schema/beans"
xmlns: x s i= "h ttp : //www.w3.org/2001/XMLSchema-instance"
xmlns : c= ''h ttp : / /www. springf ramework. org/schema/c"
x s i : schemaLocation="h ttp : //www.springframework.org/schema/beans
h ttp : / /www. springframework. org/schema/beans/spring-beans.xsd" >

<import resource="cd-config.xm l" />

<bean id="cdPlayer"
class="soundsystem.CDPlayer"
c : cd -re f="compactDisc" />
</beans>

Ahora, imagine que en lugar de configurar B la n k D is c en XML, quiere hacerlo en


XML y dejar la configuración de C D P la y e r en JavaConfig. ¿Cómo puede la configuración
basada en XML hacer referencia a una clase de JavaConfig?
La respuesta no es intuitiva. El elemento c im p o r t> solo sirve para importar otros
archivos de configuración XML y no existe un elemento XML cuya función sea importar
clases de JavaConfig. Sin embargo, existe un elemento que ya sabe que puede usar para
transferir configuración de Java a XML: cbean >. Para importar una clase de JavaConfig a
una configuración XML debe declarar el bean de esta forma:
<?xml v e rsio n = "l.0" encoding="UTF-8"?>
cbeans xmlns="http://www. springframework. org/schema/beans"
xmlns:x s i= "h ttp : //www.w3. org/2001/XMLSchema-instance"
xmlns: c="h ttp : //www.springframework. org/schema/c"
x s i : schemaLocation="h ttp : //www.springframework.org/schema/beans
h ttp : //www.springframework.org/schema/beans/spring-beans.xsd" >

cbean class="soundsystem.CDConfig" />


Conexión de bean 89

<bean id="cdPlayer"
c la s s = "soundsystem.CDPlayer"
c : c d -re f= "compactDisc" />
</beans>

Y así se combinan las dos configuraciones, una expresada en XML y la otra en Java. Del
mismo modo, podría crear un archivo de configuración de nivel superior que no declare
bean pero que combine dos o más configuraciones. Por ejemplo, podría excluir el bean
CDConf i g de la configuración XML anterior y usar un tercer archivo de configuración
que las combine:
<?xml v e rsio n = "l.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework. org/schema/beans"
xmlns:x s i= "h ttp : //www.w3. org/2001/XMLSchema-in stan ce"
xmlns: c="h ttp : //www.springframework. org/schema/c"
x s i : schemaLocation="h ttp : / /www. springframework. org/schema/beans
h ttp : //www.springframework.org/schema/beans/spring-beans.xsd">

<bean class="soundsystem.CDConfig" />


cimport resource="cdplayer-config.xm l" />

</beans>

Independientemente de que utilice conexiones JavaConfig o XML, suelo crear una


configuración raíz, como la anterior, que combine dos o más clases de conexión y /o
archivos XML. En dicha configuración raíz suelo habilitar el análisis de componentes (con
< c o n t e x t : com ponent - sca n > o ©Com ponents can). Utilizaremos esta técnica en muchos
de los ejemplos del libro.

Resumen
En el núcleo del marco de trabajo Spring se encuentra el contenedor de Spring. Se
encarga de gestionar el ciclo vital de los componentes de una aplicación, de su creación y
de garantizar el cumplimiento de sus dependencias para que puedan realizar su corres­
pondiente labor.
En este capítulo hemos visto las tres formas principales de conectar bean en Spring:
configuración automática, configuración explícita basada en Java y configuración explícita
basada en XML. Independientemente de la que elija, estas técnicas describen los compo­
nentes de una aplicación de Spring y las relaciones entre los mismos.
También le he recomendado que use la configuración automática siempre que pueda
para evitar los costes de mantenimiento propios de la explícita. Pero si tiene que configurar
Spring de forma explícita, recurra a la configuración basada en Java, más potente, de tipos
más seguros y más flexible que la basada en XML. Esta preferencia se plasmará en las
técnicas de conexión de los ejemplos que veremos a lo largo del libro.
Como la inyección de dependencias es una parte fundamental de Spring, las técnicas
descritas en este capítulo aparecerán en prácticamente todos los rincones del libro. Partiendo
de esta base, en el siguiente capítulo presentaremos técnicas de conexión de bean más avan­
zadas que le permitirán aprovechar al máximo las prestaciones del contenedor de Spring.
Capítulo
Técnicas avan zadas
3 de conexión

CONCEPTOS FUNDAMENTALES:

• Perfiles de Spring.
• Declaración condicional de bean.
• Ámbitos de bean.
• El Lenguaje de expresiones de Spring.
En el capítulo anterior vimos diversas técnicas de conexión de bean que le resultarán
muy útiles en sus proyectos, pero existen otras muchas; Spring cuenta con varios ases en
la manga para la conexión avanzada de bean.
En este capítulo nos adentraremos en algunas de estas técnicas avanzadas que no utilizará
de manera cotidiana en sus proyectos pero no por eso puede considerarlas menos útiles.

Entornos y perfiles
Uno de los aspectos más complicados del desarrollo de software consiste en realizar la
transición de una aplicación entre entornos. Determinadas opciones de un entorno concreto
adoptadas para el desarrollo no resultan adecuadas ni funcionan cuando la aplicación
pasa de la fase de desarrollo a la de producción. La configuración de bases de datos, los
algoritmos de encriptación y la integración con sistemas externos son algunos ejemplos de
operaciones que varían entre entornos de implementación.
Piense en la configuración de bases de datos. En un entorno de desarrollo, es muy
probable que utilice una base de datos incrustada cargada con datos de prueba. Por ejemplo,
en una clase de configuración de Spring podría usar E m b e d d e d D a ta b a s e B u ild e r en un
método @ B ean de esta forma:
@Bean(destroyMethod="shutdown")
public DataSource dataSourceO {
return new EmbeddedDatabaseBuilder()
.addScript("classpath:schema.sql")
.addScript("classpath:test-data.sql" )
.build();
}

Se crea un bean de tipo j a v a x . s q l . D a ta S o u rce , pero lo más interesante es cómo se crea.


Al usar E m b e d d e d D a ta b a s e B u ild e r se configura una base de datos Hypersonic incrus­
tada cuyo esquema se define en sc h e m a . s q l y se carga con datos de prueba de t e s t d a t a .
s q l . Este elemento D a t a S o u r c e es muy útil en un entorno de desarrollo en el que se
ejecutan pruebas de integración o se inicia una aplicación para realizar pruebas manuales.
Se puede contar con la base de datos en cualquier estado siempre que se inicia.
Aunque esto hace que un origen de datos creado por E m b e d d e d D a ta b a s e B u ild e r
sea perfecto para la fase de desarrollo, no es la opción adecuada para la fase de producción.
En ella querrá recuperar un origen de datos de su contenedor por medio de JNDI. En ese
caso, resulta más indicado el siguiente método @Bean:
@Bean
public DataSource dataSourceO {
JndiObjectFactoryBean jndiObjectFactoryBean =
new JndiObjectFactoryBean();
jndiObjectFactoryBean.setJndiName("jdbc/myDS") ;
jndiObjectFactoryBean. setR esourceR ef(true);
jndiObj ectFactoryBean.setProxylnterface(javax.sql.DataSource.class);
return (DataSource) jndiObjectFactoryBean.getObject();
92 Capítulo 3

Al recuperar un origen de datos desde JNDI, su contenedor puede adoptar decisiones


sobre su creación, incluso la transferencia desde una agrupación de conexiones gestionada
por el contenedor. Incluso en ese caso, el uso de un origen de datos gestionado por JNDI
resulta más adecuado para producción y facilita las pruebas de integración o desarrollo.
Por otra parte, en entornos QA podría seleccionar una configuración totalmente diferente,
por ejemplo configurando una agrupación de conexiones Commons DBCP de esta forma:
©Bean(destroyMethod="c ió s e ")
public DataSource dataSourceO {
BasicDataSource dataSource = new BasicDataSource();
dataSource. s e tU rl("jd b c:h 2 : tc p : //d b s e r v e r /-/te s t" ) ;
dataSource. setDriverClassName("org .h2.D riv er") ;
dataSource. setUsername("sa " ) ;
dataSource. setPassword{"password") ;
dataSource. s e t ln i t i a lS i z e (20);
dataSource. setMaxActive(30);
return dataSource;

Evidentemente, estas tres versiones del método d a t a S o u r c e () son diferentes. Las tres
generan un bean de tipo j a v a x . s q l . D a t a S o u r c e , pero ahí acaban las semejanzas. Cada
una aplica una estrategia distinta para generar el bean D a ta S o u r c e . De nuevo, no se trata
de describir la configuración de un D a t a S o u r c e (que veremos en un capítulo posterior),
pero es evidente que el aparentemente sencillo D a t a S o u r c e no lo es tanto. Es un ejemplo
perfecto de bean que puede variar entre entornos. Necesitará una forma de configurar un
bean D a t a S o u r c e para que en cada entorno se seleccione la configuración más acertada.
Una forma de hacerlo consiste en configurar cada bean en una clase de configuración
independiente (o archivo XML) y después tomar una decisión en tiempo de generación
(puede que con perfiles Maven) sobre qué compilar en la aplicación. El problema de esta
solución es que requiere volver a generar la aplicación en cada entorno. Puede que no sea
complicado de desarrollar a QA, pero una regeneración de QA a producción puede generar
errores y provocar úlceras a los miembros de su equipo de QA.
Afortunadamente Spring cuenta con una solución que no requiere dicha regeneración.

La solución de Spring para bean de un entorno concreto no es muy diferente a las de


tiempo de generación. Evidentemente, se adopta una decisión específica del entorno sobre
qué bean crear y cuál no, pero en lugar de decidirlo en tiempo de generación, Spring espera
al tiempo de ejecución. Por ello, la misma unidad de implementación (un archivo WAR,
por ejemplo) funcionará en todos los entornos sin necesidad de una nueva generación.
En la versión 3.1, Spring añadió los perfiles de bean. Para usarlos, debe recopilar todas
las definiciones de bean en uno o varios perfiles y después asegurarse de activar el correcto
al implementar su aplicación en cada uno de los entornos.
En configuración de Java puede usar la anotación @ P r o f i l e para especificar a qué
perfil pertenece un bean. Por ejemplo, el bean D a t a S o u r c e de base de datos incrustada
se podría configurar en una clase de configuración de esta forma:
Técnicas avanzadas de conexión 93

package com.myapp;
import j avax. a c tiv a tio n . DataSource;
import org.springfram ework.context.annotation.Bean;
import o rg . springframework. co n tex t. annotation. Configuration;
import org.springframework.context. annotation. P r o file ;
import
o rg . springframework. j dbc. datasource. embedded. EmbeddedDatabaseBuilder;
import
org. springframework. j dbc. datasource. embedded. EmbeddedDatabaseType;

©Configuration
@ P ro file ("dev")
public cla ss DevelopmentProfileConfig {

©Bean(destroyMethod="shutdown")
public DataSource dataSource() {
return new EmbeddedDatabaseBuilder()
. setType(EmbeddedDatabaseType.H2)
. addScript("classpath:schem a. s q l")
. addScript("classp ath : te s t-d a ta . s q l")
.b u ild ();
}
}

Fíjese en la anotación @ P r o f i l e aplicada en el nivel de la clase. Indica a Spring que los


bean de esta clase de configuración solo deben crearse si el perfil dev está activo. En caso
contrario, los métodos @ B ean se ignoran.
Además, podría usar otra clase de configuración para la fase de producción:

package com.myapp;
import j avax. a c tiv a tio n .DataSource;
import org.springframework.co n tex t. annotation.Bean;
import org. springframework.context. annotation.Configuration;
import o rg . springframework. co n tex t. annotation. P r o file ;
import org.springframework. jn d i.JndiObjectFactoryBean;

©Configuration
@ P ro file ("prod")
public cla ss ProductionProfileConfig {

@Bean
public DataSource datasource() {
JndiObjectFactoryBean jndiObjectFactoryBean =
new JndiObjectFactoryBean();
jndiObjectFactoryBean. setJndiName("jdbc/myDS") ;
jndiO bjectFactoryBean.setResourceRef(true);
jndiO bjectFactoryBean.setProxylnterface(
javax.sql.D ataSou rce. c l a s s ) ;
return (DataSource) jndiO bjectFactoryBean.getO bject();
}

En este caso, el bean no se crea a menos que el perfil p ro d esté activo.


94 Capítulo 3

En Spring 3.1 solo se podía usar la anotación @ P r o f i l e en el nivel de las clases pero
desde Spring 3.2 se puede usar @ P r o f i l e en el nivel de los métodos, junto a la anotación
@Bean. De este modo puede combinar ambas declaraciones de bean en una misma clase
de configuración, como ilustra el siguiente ejemplo.

Listado 3.1. La anotación @Profile conecta bean en función de archivos activos.


package com.myapp;
import javax.activation.DataSource;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Profile;
import
org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseBuilder;
import
org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseType;
import org.springframework.jndi.JndiObjectFactoryBean;

(»Configuration
public class DataSourceConfig {

@Bean(destroyMethod="shutdown")
@Profile("dev") // Conectado para el perfil "dev",
public DataSource embeddedDataSource() {
return new EmbeddedDatabaseBuilder()
.setType(EmbeddedDatabaseType.H2)
.addScript("classpath:schema.sql")
.addScript("classpath:test-data.sql")
.build();
}
@Bean
(»Profile("prod") // Conectado para el perfil "prod",
public DataSource jndiDataSource() {
JndiObjectFactoryBean jndiObjectFactoryBean =
new JndiObjectFactoryBean();
jndiObjectFactoryBean.setJndiName("jdbc/myDS");
jndiObjectFactoryBean.setResourceRef(true);
jndiObj ectFactoryBean.setProxylnterface(javax.sql.Datasource.class);
return (DataSource) jndiObjectFactoryBean.getObject();
}
}
Lo que no resulta aparente en el ejemplo es que aunque cada uno de los bean D a ta S o u rc e
se encuentra en un perfil y se crea solo si éste se ha activado, es que probablemente haya
otros bean no definidos en el ámbito de un determinado perfil. Los bean sin un perfil se
crearán igualmente, independientemente del perfil activo.

Configurar perfiles en XML


También puede configurar bean en perfiles en XML si establece el atributo p r o f i l e
del elemento < b e a n s > . Por ejemplo, para definir el bean D a t a S o u r c e de base de datos
incrustada para desarrollo en XML, podría crear el siguiente archivo de configuración XML:
Técnicas avanzadas de conexión 95

<?xml version="1.0" encoding="UTF-8"?>


cbeans xmlns="http://wwrf.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:jdbc="http://www.springframework.org/schema/jdbc"
xsi:schemaLocation="
http://www.springframework.org/schema/j dbc
http://www.springframework.org/schema/jdbc/spring-jdbc.xsd
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd"
prof ile="dev">

<jdbc:embedded-database id="dataSource">
<jdbc:script location="classpath:schema.sql" />
<jdbc:script location="classpath:test-data.sql" />
</jdbc:embedded-database>

</beans>

Como en el caso anterior, podría crear otro archivo de configuración y establecer


p r o f i 1 e en p ro d para el bean D a ta S o u r c e obtenido desde JNDI y listo para producción,
y podría crear otro archivo XML para el bean D a t a S o u r c e definido por una agrupación
de conexiones y especificado por el perfil qa. Todos los archivos de configuración XML
se recopilan en la unidad de implementación (seguramente un archivo WAR) pero solo se
utilizarán los que tengan un nombre de atributo que coincida con el perfil activo.
En lugar de crear varios archivos XML para cada entorno, también puede definir
elementos cb e a n s > incrustados en el elemento c b e a n s > raíz. De este modo se recopilan
todas las definiciones de bean con perfiles en un mismo archivo XML, como se muestra a
continuación.

Listado 3.2. Los elementos <beans> se pueden repetir para especificar varios perfiles.
<?xml version="l.O" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns :jdbc= "http -.//www. springf ramework.org/schema/jdbc"
xmlns:jee="http://www.springframework.org/schema/jee"
xmlns:p="http://www.springframework.org/schema/p"
xsi:schemaLocation="
http://www.springframework.org/schema/jee
http://www.springframework.org/schema/jee/spring-jee.xsd
http://www.springframework.org/schema/jdbc
http://www.springframework.org/schema/jdbc/spring-jdbc.xsd
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">

cbeans profile="dev"> // Bean del perfil "dev".


<jdbc:embedded-database id="dataSource">
cjdbc:script location="classpath:schema.sql" />
<jdbc:script location="classpath:test-data.sql" />
</jdbc:embedded-database>
</beans>

cbeans profile ="qa"> // Bean del p e rfil "qa".


96 Capítulo 3

<bean id ="dataSource"
c la s s = "org. apache. commons. dbcp.BasicDataSource"
destroy-method="cióse"
p :u rl= "jd b c:h 2 : tc p : //d b s e rv e r/-/te s t"
p : driverClassName="org.h2.D river"
p: username="sa "
p:password="password"
p :in itia lS iz e = "2 0 "
p:maxActive="30" />
</beans>

<beans profile="prod"> / / Bean del p e r fil "prod".


<j e e : jndi-lookup id="dataSource"
j ndi-name="j dbc/myDatabase"
reso u rce-ref="tru e"
proxy-interface="javax.sql.D ataSou rce" />
</beans>
</beans>

Además de que todos estos bean se definen ahora en el mismo archivo XML, el efecto
es el mismo que si se definieran en archivos XML independientes. Hay tres bean, los tres
de tipo ja v a x . s q l .D a t a S o u r c e y los tres con el ID d a t a S o u r c e , pero en tiempo de
ejecución solo se crea uno, en función del perfil activo. Eso nos lleva a preguntamos cómo
se activa un perfil.

Activar perfiles*•
Spring dispone de dos propiedades para determinar qué perfiles se han activado:
s p r i n g . p r o f i l e s . a c t i v e y s p r i n g . p r o f i l e s . d e f a u l t . Si se establece s p r i n g .
p r o f i l e s . a c t i v e , su valor determina qué perfiles están activos. En caso contrario Spring
se fija en s p r i n g . p r o f i l e s . d e f a u l t . Si no se establece ninguna de las dos propiedades,
no habrá perfiles activos y solo se crean los bean que no tengan perfil.
Existen varias formas de establecer estas propiedades:
• Como parámetros de inicialización en D is p a t c h e r S e r v le t .
• Como parámetros de contexto de una aplicación Web.
• Como entradas JNDI.
• Como variables de entorno.
• Como propiedades del sistema JVM.
• Con la anotación @ A c t iv e P r o f i l e s en una clase de pruebas de integración.
Le dejaré que elija la mejor combinación de s p r in g . p r o f i l e s . a c t i v e y s p r i n g .
p r o f i l e s . d e f a u l t acorde a sus necesidades.
Personalmente, me gusta establecer s p r i n g . p r o f i l e s . d e f a u l t en el perfil de
desarrollo por medio de parámetros en D i s p a t c h e r S e r v l e t y el contexto de servlet
(por C o n t e x t L o a d e r L i s t e n e r ) . Por ejemplo, el archivo w eb .x m l de una aplicación
Web podría establecer s p r i n g . p r o f i l e s , d e f a u l t , como se muestra a continuación.
Técnicas avanzadas de conexión 97

Listado 3.3. Establecimiento de perfiles predeterminados en el archivo web.xm'i


de una aplicación Web.

<?xml version="l.0" encoding="UTF-8"?>


<web-app version="2.5"
xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee
http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd">

<context-param>
<param-name>contextConf igLocat ion</param-name >
<param-value>/WEB-INF/spring/root-context.xml</param-values
</context-param>

<context-param>
<param-name>spring.profiles.default</param-name> // Establecer perfil predeter-
// minado para el contexto.
<param-value>dev</param-values
</context-pararas

«listeners
«listener-classs
org.springframework.web.context.ContextLoaderListener
«/listener-classs
«/listeners

«servlets
«servlet-namesappServlet«/servlet-names
«servlet-classs
org.springframework.web.servlet.DispatcherServlet
</servlet-classs
<init-params
«param-namesspring.profiles.default«/param-names // Establecer perfil
// predeterminado para el
// servlet.
«param-value sdev</param-value s
«/init-params
<load-on-startupsi</load-on-startups
</servlets

«servlet-mappings
«servlet-namesappServlet«/servlet-names
<url-patterns/«/url-patterns
«/servlet-mappings

«/web-apps

Al establecer s p r in g . p ro f i l e s , de f a u l t de esta forma, cualquier desarrollador


puede recuperar el código de la aplicación desde el código fuente y ejecutarlo con ajustes
de desarrollo (como una base de datos incrustada) sin necesidad de configuración adicional.
Después, al implementar la aplicación en un entorno QA o de producción, el responsable
de hacerlo puede establecer s p r i n g . p r o f i l e s . a c t i v e con propiedades del sistema,
variables de entorno o JNDI. Si se establece s p r in g . p r o f i l e s . a c t i v e , es irrelevante
98 Capítulo 3

en lo que se haya establecido s p r i n g . p r o f i l e s . d e f a u l t ; los perfiles establecidos


en s p r in g . p r o f i l e s . a c t i v e tienen preferencia. Habrá comprobado que p r o f i l e s
(perfiles) aparece en plural tanto en s p r i n g . p r o f i l e s . a c t i v e como en s p r in g .
p ro f i l e s , d e f a u l t . Significa que puede activar varios perfiles al mismo tiempo si
enumera los nombres de los perfiles separados por comas. Evidentemente no tendría
mucho sentido habilitar tanto los perfiles dev y prod al mismo tiempo pero podría habilitar
simultáneamente varios perfiles ortogonales.

Realizar pruebas con perfiles


En una prueba de integración querrá usar la misma configuración (o un subconjunto
de la misma) que vaya a utilizar en producción, pero si su configuración hace referencia
a bean en perfiles, necesitará una forma de habilitar el perfil correcto al ejecutar dichas
pruebas.
Spring le ofrece la anotación @ A c t iv e P r o f i l e s para que especifique el perfil que
debe activarse al ejecutar una prueba. Suele ser el perfil de desarrollo que hay que activar
durante una prueba de integración. Por ejemplo, el siguiente fragmento de una clase de
prueba usa @ A c t iv e P r o f i l e s para activar el perfil dev:
ORunWith(SpringJUnit4ClassRunner. class)
@C ontextC onfiguration(classes={PersistenceTestC onfig.class))
@ A ctiveP rofiles("dev")
public cla ss PersistenceTest {

}
Los perfiles de Spring son perfectos para definir bean condicionalmente cuando la
condición se basa en el perfil activo, pero Spring 4 le ofrece un mecanismo más genérico
para definir bean condicionales en el que la condición es decisión suya. A continuación
veremos cómo definir bean condicionales con Spring 4 y la anotación @ C o n d it io n a l.

Sean condicionales
Imagine que quiere configurar uno o varios bean solo si hay una biblioteca disponible
en la ruta de clases de la aplicación, o que quiere crear un bean siempre que se declare otro
concreto o que se establezca una determinada variable de entorno.
Hasta la llegada de Spring 4 era complicado lograr este nivel de configuración condi­
cional pero ahora cuenta con la nueva anotación © C o n d it io n a l que puede aplicar a
métodos @Bean. Si la condición indicada evalúa a t r u e , se crea el bean. En caso contrario
se ignora.
Imagine por ejemplo que tiene la clase M a g icB e a n y quiere que Spring solo la instancie
si se ha establecido la propiedad de entorno m ag ic. Si el entorno carece de dicha propiedad,
se ignora M a g icB ea n . El siguiente código muestra una configuración que configura condi­
cionalmente M a g ic B e a n con @ C o n d it io n a l.
Técnicas avanzadas de conexión 99

Listado 3.4. Configuración condicional de un bean.


SBean
©Conditional(MagicExistsCondition. class) / / Crear condicionalmente e l bean,
public MagicBean magicBeanO {
return new MagicBean();
}
Como puede apreciar, © C o n d it io n a l recibe una clase que especifica la condición, en
este caso M a g i c E x i s t s C o n d it io n . © C o n d it io n a l cuenta con una interfaz C o n d itio n :

public in te rfa ce Condition {


boolean matches(ConditionContext c tx t,
AnnotatedTypeMetadata metadata);
}
La clase proporcionada a @ C o n d it io n a l puede ser de cualquier tipo que implemente
la interfaz C o n d it io n . Comprobará que es una interfaz muy sencilla de implementar y
solo necesita proporcionar una implementación para el método m a tc h e s (). Si m a tc h e s ()
devuelve t r u e , se crea el bean anotado con © C o n d it io n a l. Si m a tc h e s () devuelve
f a l se , los bean no se crean.
En este ejemplo tendrá que crear una im plem entación de C ondi t i o n que base su deci­
sión en la presencia de una propiedad m a g ic en el entorno. El siguiente código muestra
M a g i c E x i s t s C o n d i t i o n , una im plem entación de C o n d it io n que se encarga de ello.

Listado 3.5. Comprobación de la presencia de magic en Condition.


package com.habuma. restfu n ;
import org. springframework. co n tex t. annotation.Condition;
import org. springframework. co n tex t. annotation.ConditionContext;
import org. springframework. co re. type.AnnotatedTypeMetadata;
import org. springframework.ú t i l . C la ssU tils;

public cla ss MagicExistsCondition implements Condition {

public boolean matches(


ConditionContext context, AnnotatedTypeMetadata metadata) {
Environment env = context.getEnvironment();
return env.containsProperty("magic") ; / / Comprobar la propiedad "magic".
}
}
El método m a tc h e s () de este código es sencillo pero completo. Usa el entorno obte­
nido del objeto C o n d it io n C o n t e x t proporcionado para comprobar la presencia de la
propiedad de entorno m a g ic. Para este ejemplo, el valor de la propiedad es irrelevante;
basta con que exista. Como resultado, se devuelve t r u e de m a t c h e s ( ) . Por ello, la
condición se cumple y se crean los bean cuyo método @ C o n d it io n a l haga referencia a
M a g i c E x i s t s C o n d it io n .
Por otra parte, si la propiedad no existe, la condición falla, m a t c h e s () devuelve
f a l s e y no se crea ningún bean. M a g i c E x i s t s C o n d i t i o n solamente usa el entorno
de C o n d i t i o n C o n t e x t , pero una im plementación C o n d i t i o n puede tener en
100 Capítulo 3

cuenta otros m uchos factores. El m étodo m a t c h e s () recibe C o n d i t i o n C o n t e x t y


A n n o ta te d T y p e M e ta d a ta para tomar su decisión. C o n d it io n C o n t e x t es una interfaz
que tiene el siguiente aspecto:

public in te rfa ce ConditionContext {


BeanDefinitionRegistry g etR eg istry ( );
ConfigurableListableBeanFactory getBeanFactory();
Environment getEnvironment();
ResourceLoader getResourceLoader();
ClassLoader getClassLoader();
}
Desde C o n d it io n C o n t e x t puede hacer lo siguiente:

• Comprobar definiciones debean a través de B ean D ef i n i t i o n R e g i s t r y , devuelto


por g e t R e g i s t r y ( ) .
• Comprobar la presencia de uno o varios bean e incluso examinar sus propiedades a
través de C o n f i g u r a b l e L i s t a b l e B e a n F a c t o r y , devuelto por g e t B e a n
F a c t o r y ().

• Comprobar la presencia y los valores de variables de entorno a través del entorno


recuperado desde g e tE n v ir o n m e n t ( ) .
• Leer y examinar el contenido de recursos cargados a través de R e s o u r c e L o a d e r ,
devuelto por g e t R e s o u r c e L o a d e r ( ) .
• Cargar y comprobar la presencia de clases a través de C la s s L o a d e r , devuelto por
g e tC la s s L o a d e r ().

En cuanto a A n n o ta te d T y p e M e ta d a ta , le permite inspeccionar anotaciones que


también se pueden haber incluido en el método @ B ean. Como C o n d it io n C o n t e x t ,
A n n o ta te d T y p e M e ta d a ta es una interfaz, con el siguiente aspecto:
public in terface AnnotatedTypeMetadata {
boolean isAnnotated(String annotationType);
Map<String, Object> getA nnotationA ttributes(String annotationType) ,-
Map<String, Object> getA nnotationA ttributes(
String annotationType, boolean classV aluesA sString);
MultiValueMap<String, Object> getAHAnnotationAttributes (
String annotationType);
MultiValueMap<String, Object> getA llA nnotationA ttributes(
String annotationType, boolean classValuesAsString) ,-

Con el método i s A n n o t a t e d () puede comprobar si el método @ Bean está anotado


con un tipo de anotación concreto. Con los otros métodos, podrá comprobar los atributos
de la anotación aplicada al método @Bean.
Desde Spring 4, la anotación @ P ro f i l e se ha refactorizdo para basarla en @ C on dit i o n a l
y la interfaz C o n d it io n . Para ver otro ejemplo de cómo trabajar con @ C o n d it io n a l y
C ond i t io n , veamos cómo se implementa @ P r o f i l e en Spring 4. La anotación @ P ro f i l e
tiene este aspecto:
Técnicas avanzadas de conexión 101

@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType. TYPE, ElementType.METHOD})
@Documented
O Conditional(ProfileCondition.class)
public ©interface P ro file {
Strin g [] valu é();
}
La propia anotación @ P r o f i l e se anota con @ C o n d i t i o n a l y hace referencia a
P r o f i l e C o n d i t i o n como im plem entación de C o n d it io n . Como veremos a continua­
ción, P r o f i l e C o n d i t i o n implementa C o n d it io n y tiene en cuenta varios factores tanto
de C o n d it io n C o n t e x t como de A n n o ta te d T y p e M e ta d a ta para tomar su decisión.

Listado 3.6. ProfileCondition comprueba si un perfil de bean es aceptable.


cla ss ProfileCondition implements Condition {
public boolean matches(
ConditionContext context, AnnotatedTypeMetadata metadata) {
i f (context. getEnvironment() != nuil) {
MultiValueMap<String, Object> a ttr s =
metadata. g etA llA n n o tatio n A ttrib u tes(P ro file.class. getName( ) ) ;
i f (a ttrs != nuil) {
for (Object valué : a t t r s . g e t ( "valué")) {
i f (co n text.getEnvironment()
. a c c e p tsP ro file s( ( (S trin g []) v alué))) {
return true;
}
}
return fa ls e ;
}
}
return tru e;
}
}
Como puede apreciar, P r o f i l e C o n d i t i o n obtiene todos los atributos de anotación de
@ P r o f i l e de A n n o ta te d T y p e M e ta d a ta y con ellos comprueba explícitam ente el atri­
buto v a lu é , que contiene el nombre del perfil del bean. Después consulta con el entorno
recuperado de C on d i t i o n C o n t e x t si el perfil está activo (mediante la invocación del
m étodo a c c e p t s P r o f i l e s ( ) ) .

Solucionar ambigüedades
en conexiones automáticas
En el capítulo anterior vimos cómo usar la conexión automática para que Spring
realizara todo el trabajo de inyectar referencias de bean en argumentos o propiedades de
constructores. La conexión automática es de gran ayuda, ya que reduce la cantidad de
configuración explícita necesaria para ensamblar componentes de aplicación, pero solo
102 Capítulo 3

funciona cuando un bean coincide exactamente con el resultado deseado. Si hay más de
un bean, la ambigüedad impide que Spring conecte automáticamente la propiedad, argu­
mento de constructor o parámetro de método. Para ilustrarlo, imagine que ha anotado con
© A u to w ired el siguiente método s e t D e s s e r t ( ) :

SAutowired
public void setD essert(D essert dessert) {
th is .d e s s e rt = d essert;
}
En este ejemplo, D e s s e r t es una interfaz implementada por tres clases: C ake, C o o k ie s
e Ic e C r e a m :

©Component
public cla ss Cake implements Dessert { . . . }

@Component
public cla ss Cookies implements Dessert { . . . }

©Component
public cla ss IceCream implements Dessert { . . . }

Como las tres implementaciones se anotan con @Com ponent, se seleccionan durante
el análisis de componentes y se crean como bean en el contexto de aplicación de Spring.
Tras ello, cuando Spring intenta conectar automáticamente el parámetro D e s s e r t en s e t
D e s s e r t ( ) , no cuenta con una opción evidente.
Aunque muchos no tendríamos problema en tomar una decisión si nos ofrecen varios
postres para elegir, Spring no puede elegir uno. Solo puede fallar y generar una excepción,
para ser exactos N oU n iq u eB ean D ef i n i t i o n E x c e p t i o n :
nested exception is
org.springframework.beans. factory.NoUniqueBeanDefinitionException:
No qualifying bean of type [com .desserteater.D essert] is defined:
expected single matching bean but found 3: cake, cookies, iceCream

Evidentemente, este ejemplo se ha modificado para ilustrar el problema de las ambi­


güedades en la conexión automática. En la realidad, es un problema menos habitual de lo
que imagina. Aunque la ambigüedad fuera un problema serio, siempre suele haber una
única implementación de un tipo concreto, por lo que la conexión automática funciona a
la perfección.
No obstante, si se produce una ambigüedad, Spring le ofrece varias soluciones. Puede
declarar uno de los bean candidatos como opción principal o usar calificadores para que
Spring reduzca las opciones a un único candidato.

Designar un bean principal


Si es como yo, le gustarán todos los postres; galletas, pasteles, helado... Pero si tuviera
que elegir su preferido ¿cuál sería?
Técnicas avanzadas de conexión 103

A la hora de declarar bean, puede evitar las ambigüedades si designa a uno de los bean
candidatos como el principal. En caso de producirse una ambigüedad, Spring elegirá al
principal entre todos los candidatos. Básicamente lo que hacemos es declarar nuestro bean
"preferido".
Imagine que su postre favorito es el helado (Ice C re a m ). Para expresarlo en Spring,
utilice la anotación @ P rim a ry , que se puede usar junto a ©Component para bean de
análisis de componentes o junto a @ B ean para los declarados en configuración de Java.
Por ejemplo, a continuación declaramos el bean Ic e C re a m , anotado con ©Component,
como la opción principal:
©Component
@Primary
public cla ss IceCream implements Dessert { . . . }

Si declara explícitamente el bean Ic e C re a m en configuración de Java, el método @ Bean


tendría este aspecto:
@Bean
©Primary-
public Dessert iceCreamO {
return new IceCreamO;
}
Si configura sus bean en XML, el elemento <bean> cuenta con el atributo p rim a ry
para especificar un bean principal:
<bean id="iceCream"
c ía s s = "com.desserteater.IceCream "
primary="true" />

Independientemente de cómo designe el bean principal, el efecto será el mismo: indicar


a Spring que debe elegir ese bean en caso de ambigüedad. También funciona si designa dos
o más bean principales. Por ejemplo, fíjese en la siguiente clase Cake:
©Component
@Primary
public cla ss Cake implements Dessert { . . . }

Ahora hay dos bean D e s s e r t principales, C ake e Ic e C re a m , lo que genera un nuevo


problema de ambigüedad. Al igual que Spring no puede elegir entre varios bean candidatos,
no puede elegir entre varios principales. Evidentemente, cuando se designa más de un bean
como principal, no existen candidatos principales. Un mecanismo más potente contra las
ambigüedades son los calificadores, que veremos a continuación.

Calificar bean conectados automáticamente


La limitación de los bean principales es que @ P rim a ry no reduce las posibilidades a
una única opción. Solamente designa una opción preferida. Si hay más de un elemento
principal no podrá hacer mucho por limitar más las opciones.
104 Capítulo 3

Por el contrario, los calificadores de Spring aplican una operación de limitación a


todos los bean candidatos para llegar, en última instancia, al único bean que cumpla las
calificaciones indicadas. Si después de aplicar todos los calificadores sigue existiendo la
ambigüedad, puede aplicar nuevos calificadores para limitar todavía más las opciones.
La anotación @ Q u a lif i e r es la principal forma de trabajar con calificadores. Se puede
aplicar junto a @ A u to w ired o @ In j e c t en el punto de inyección para especificar el bean
que se va a inyectar. Imagine, por ejemplo, que desea asegurarse de que el bean Ic e C re a m
se ha inyectado en s e t D e s s e r t ():
@Autowired
@Q ualifier( "iceCream")
public void setDessert(Dessert dessert) {
th is .d e s s e rt = dessert;
}
Es uno de los calificadores más sencillos. El parámetro proporcionado a @ Q u a lif i e r
es el ID del bean que desea inyectar. Todas las clases anotadas con @ C om ponent se
crean como bean cuyo ID es el nombre de la clase sin la mayúscula inicial. Por tanto, @
Q u a l i f i e r ( " ic e C r e a m " ) hace referencia al bean generado cuando el análisis de compo­
nentes creó una instancia de la clase Ic e C re a m .
En realidad, el proceso es un tanto más complejo. Para ser más precisos, @ Q u a lif i e r
(" ic e C r e a m " ) hace referencia al bean que tiene la cadena " ic e C r e a m " como calificador.
Al no haber especificado ningún otro calificador, todos los bean reciben uno predeterminado
que coincide con su ID correspondiente. Por lo tanto, el método s e t D e s s e r t () se inyectará
con el bean que tenga " ic e C r e a m " como calificador. Solamente sucede en el bean que
tenga el ID ic e C re a m , creado durante el análisis de componentes de la clase Ice C re a m .
Basar la calificación en el calificador del ID de bean predeterminado es sencillo pero
puede provocar problemas. Imagine qué pasaría si refactorizamos la clase Ic e C re a m y
cambiamos su nombre por Ge l a t o . En ese caso, el ID del bean y el calificador predeter­
minado serían g e l a t o , que no coincide con el calificador de s e t D e s s e r t ( ) . La conexión
automática fallaría. El problema es que hemos especificado un calificador en s e t D e s s e r t ()
directamente vinculado al nombre de clase del bean inyectado. Cualquier cambio en el
nombre de la clase anularía la eficacia del calificador.

Crear calificadores personalizados


En lugar de depender el ID de bean como calificador, puede asignar su propio calificador
a un bean. Para ello, basta con añadir la anotación @ Q u a lif i e r a la declaración del bean.
Por ejemplo, se puede aplicar junto a @Com ponent de esta forma:
@Component
@Q ualifier( "coid")
public class IceCream implements Dessert { . . . }

En este caso, se asigna el calificador c o i d al bean Ice C re a m . Como no está vinculado


al nombre de la clase, puede cambiar el nombre de la clase Ic e C re a m sin que afecte a la
conexión automática. Seguirá funcionando mientras haga referencia al calificador c o i d
en el punto de inyección:
Técnicas avanzadas de conexión 105

©Autowired
©Qualifier("cold")
public void setDessert(Dessert dessert) {
th is.d e sse rt = dessert;
}
Conviene destacar que @ Q u a lif i e r también se puede usar junto a la anotación @Bean
al definir bean de forma explícita con configuración de Java:
@Bean
@Q ualifier( "coid ")
public Dessert iceCreamO {
return new IceCreamO;
}
Al definir valores @ Q u a lif i e r personalizados, es recomendable usar un término
descriptivo para el bean, en lugar de un nombre arbitrario. En este caso, hemos descrito el
bean IceC ream como c o id , para que en el punto de inyección se describa su significado.

Definir anotaciones de calificador personalizadas


Los calificadores orientados a rasgos son más indicados que los basados en ID de bean,
pero seguirán produciéndose problemas si tiene varios bean que compartan rasgos comunes.
Imagine por ejemplo qué sucedería si añade este nuevo bean D e s s e r t :
©Component
©Qualifier("cold ")
public class Popsicle implements Dessert { . . . }

¡Vaya! Ahora hay dos postres fríos (co ld ). De nuevo se enfrenta a una ambigüedad en
la conexión automática de los bean. Necesita más calificadores para limitar la selección a
un único bean.
Puede que la solución consista en añadir otro © Q u a l i f i e r tanto en el punto de inyec­
ción como en la definición del bean. La clase Ic e C re a m tendría este aspecto:
©Component
©Qualifier("cold ")
©Qualifier("creamy")
public class IceCream implements Dessert { . . . }

Puede que la clase P o p s i c l e utilizara otro © Q u a l i f i e r :


©Component
©Qualifier("cold")
©Qualifier(" f r u i t y " )
public c la s s Popsicle imp!emente Cvceeert ( . . . }

Y, en el punto de inyección, podría lim itarlo a Ic e C re a m de esta forma:


©Autowired
©Qualifier("cold")
©Qualifier("creamy")
106 Capítulo 3

public void setDessert(Dessert dessert) {


th is.d e sse rt = dessert;
}
Solo hay un pequeño problema: Java no permite la repetición de varias anotaciones del
mismo tipo en el mismo elemento.1El compilador generará errores si lo hace. No puede usar
@ Q u a lif i e r (al menos no directamente) para limitar la lista de candidatos de conexión
automática a una única opción.
Sin embargo, puede crear anotaciones de calificador personalizadas que representen
los rasgos con los que desee calificar sus bean. Para ello, basta con crear una anotación que
esté anotada con @ Q u a lif i e r . En lugar de usar @ Q u a lif i e r ( " c o i d " ), puede usar una
anotación @ C old personalizada definida de esta forma:
@Target({ElementType. CONSTRUCTOR, ElementType. FIELD,
ElementType.METHOD, ElementType. TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Qualifier
public @interface Coid { }

Del mismo modo, puede crear una nueva anotación @Creamy como sustitución de
@ Q u a l i f i e r ( " c r e a m y " ):

@Target({ElementType. CONSTRUCTOR, ElementType. FIELD,


ElementType.METHOD, ElementType. TYPE})
©Retention(RetentionPolicy.RUNTIME)
@Qualifier
public @interface Creamy { }

Y puede crear anotaciones @ S o f t , @ C r is p y y @ F r u i t y para usarlas en lugar de la


anotación @ Q u a lif i e r cuando sea necesario. Al anotarlas con @ Q u a lif i e r , adoptan sus
características; son anotaciones de calificador con entidad propia.
Ahora ya podemos retomar Ic e C re a m y anotarla con @ C old y @Creamy de esta forma:
@Component
@Cold
@Creamy
public class IceCream implements Dessert { . . . }

Del mismo modo, podemos anotar la clase P o p s i c l e con @ C old y @ F r u it y :


©Component
@Cold
@Fruity
public class Popsicle implements Dessert { . . . }

Por último, en el punto de inyección, puede usar cualquier combinación de anotaciones


de calificador necesaria para limitar la selección al bean que se ajuste a sus especificaciones.
Para llegar al bean Ic e C re a m , se puede anotar el método s e t D e s s e r t () de esta forma:

1. J a v a 8 p e rm ite a n o ta c io n e s rep e tid a s s ie m p re q u e se a n o te n con @ R e p e a ta b le . In clu so en ese caso, la


a n o ta c ió n @ Q u a iifie r de S p rin g no se a n o ta co n @ R e p e a ta b ie .
Técnicas avanzadas de conexión 107

©Autowired
@Cold
©Creamy-
public void setDessert(Dessert dessert) {
th is .d e s s e rt = dessert;
}

Al definir anotaciones de calificador personalizadas puede usar varios calificadores sin


limitaciones ni quejas del compilador de Java. Además, los tipos son más seguros que usar
la anotación @ Q u a lif i e r por sí sola y especificar el calificador como cadena.
Fíjese atentamente en el método s e t D e s s e r t () y en cómo se anota. No indicamos
de forma explícita que queremos conectarlo automáticamente al bean Ic e C re a m , sino
que identificamos el bean deseado a través de sus rasgos, @ C old y @Creamy. Por tanto,
s e t D e s s e r t () sigue desvinculado de cualquier implementación específica de D e s s e r t .
Servirá cualquier bean que coincida con esos rasgos, y en nuestra selección actual de imple-
mentaciones de D e s s e r t , el bean Ic e C re a m es el único que coincide.
En este apartado y en el anterior hemos visto dos formas de ampliar Spring mediante
anotaciones personalizadas. Para crear una anotación condicional personalizada se hace una
nueva anotación y se anota con @ C o n d it i o n a l , y para crear una anotación de calificador
personalizada se realiza la nueva anotación y se anota con @ Q u a lif i e r . Esta técnica se
puede aplicar por medio de muchas de las anotaciones de Spring, que acabarán convertidas
en anotaciones personalizadas de propósito especial.
A continuación veremos cómo declarar bean para crearlos en distintos ámbitos.

Ámbitos d e bean*•
De forma predeterminada, todos los bean se crean como instancias únicas en el contexto
de aplicación de Spring. Es decir, independientemente de cuántas veces se inyecte un bean
en otros, siempre se inyecta la misma instancia.
En la mayoría de los casos, basta con bean de instancia única. No se puede justificar el
coste de instanciar y destruir instancias de objetos que solo se utilizan para tareas menores
cuando un objeto carece de estado y se puede reutilizar repetidamente en una aplicación.
Pero en ocasiones tendrá que trabajar con una clase mutable que mantiene cierto
estado y, por tanto, no se puede reutilizar. En ese caso, no es aconsejable declararla como
instancia única ya que el objeto puede generar problemas inesperados al reutilizarlo
después.
Spring define varios ámbitos bajo los que se puede crear un bean, descritos a continuación:

• Instancia única: Se crea una instancia del bean para toda la aplicación.
• Prototipo: Se crea una instancia del bean cada vez que se inyecta o se recupera del
contexto de aplicación de Spring.
• Sesión: En una aplicación Web, se crea una instancia del bean por cada sesión.
• Solicitud: En una aplicación Web, se crea una instancia del bean por cada solicitud.
108 Capítulo 3

El ámbito de instancia única es el predeterminado, pero como hemos mencionado, no


es el más adecuado para tipos mutables. Para seleccionar un tipo alternativo puede usar
la anotación @ S co p e, junto a @Com ponent o @Bean.
Por ejemplo, si recurre al análisis de componentes para detectar y declarar un bean,
puede anotar la clase del bean con @Scope para convertirlo en bean de prototipo:
@Coraponent
©Scope(ConfigurableBeanFactory. SCOPE_PROTOTYPE)
public class Notepad { . . . }

Aquí, se especifica el ámbito de prototipo por medio de la constante SCOPE_PROTOTYPE


de la clase C onf ig u r a b le B e a n F a c t o r y . También podría usar @ Sco p e ( " p r o t o t y p e " ) ,
pero la constante es más segura y menos proclive a errores.
Por otra parte, si configura el bean N o te p a d como prototipo en Java, puede usar
@ S co p e junto a @ B ean para especificar el ámbito deseado:
©Bean
@Scope(Conf igurableBeanFactory.SCOPE_PROTOTYPE)
public Notepad notepad() {
return new Notepad();
}
Y en el caso de que configure el bean en XML, puede definir el ámbito por medio del
atributo s e ope del elemento <bean >:
ebean id="notepad"
class="com.myapp.Notepad"
scope="prototype" />

Independientemente de cómo especifique el ámbito de prototipo, se creará una instancia


del bean cada vez que se inyecte o se recupere del contexto de aplicación de Spring, por lo
que todo el mundo recibe su propia instancia de Notepad.

Trabajar con el ámbito de sesión y de solicitud


En una aplicación Web puede resultar muy útil instanciar un bean que se comparta en el
ámbito de una determinada solicitud o sesión. Por ejemplo, en una aplicación de comercio
electrónico puede tener un bean que represente el carro de la compra del usuario. Si es un
bean de instancia única, todos los usuarios añadirían productos al mismo carro, pero si el
carro de la compra tiene ámbito de prototipo, los productos añadidos desde una zona de la
aplicación pueden no estar disponibles en otra zona diferente en la que se haya inyectado
otro carro de la compra con ámbito de prototipo.
En el caso del bean de carro de la compra, tiene más sentido el ámbito de sesión por su
vinculación directa a cada usuario. Para aplicar un ámbito de sesión puede usar la anotación
@Scope de la misma forma que al especificar el ámbito de prototipo:
@Component
@Scope(
value=WebApplicationContext. SCOPE_SESSION,
proxyMode=ScopedProxyMode. INTERFACES)
public ShoppingCart c art() { . . . }
Técnicas avanzadas de conexión 109

Aquí establecem os al atributo v a l u é en la constante S C O P E _ S E S S IO N de


W e b A p p lic a t io n C o n te x t (que tiene el valor s e s s i o n ) . De esta forma indicamos a
Spring que cree una instancia del bean S h o p p in g C a r t para cada sesión de la aplicación
Web. Habrá varias instancias del bean S h o p p in g C a r t, pero solo se creará una por cada
sesión y básicamente será una instancia única en lo que a cada sesión se refiere.
@ S co p e también tiene un atributo proxyM ode establecido en S co p e d P ro x y M o d e .
INTERFACES. Este atributo soluciona un problema que aparece al inyectar un bean con
ámbito de sesión o de solicitud a un bean con ámbito de instancia única, pero antes de
explicarlo veremos un caso que presenta dicho problema.
Imagine que desea inyectar el bean S h o p p in g C a r t al siguiente método de estableci­
miento en S t o r e S e r v i c e , un bean de instancia única:
@Component
public class StoreService {

@Autowired
public void setShoppingCart(ShoppingCart ShoppingCart) {
t h i s . ShoppingCart = ShoppingCart;
}

Como S t o r e S e r v i c e es un bean de instancia única, se creará en el contexto de apli­


cación de Spring al cargarse. Mientras se crea, Spring intenta inyectar S h o p p in g C a r t
al método s e t S h o p p in g C a r t ( ) , pero como S h o p p in g C a r t es un bean de ámbito de
sesión, todavía no existe y no habrá una instancia hasta que un usuario cree una sesión.
Es más, habrá varias instancias de S h o p p in g C a r t: una por usuario. No queremos
que Spring inyecte una única instancia de S h o p p in g C a r t en S t o r e S e r v i c e , sino que
S t o r e S e r v i c e funcione con la instancia de S h o p p in g C a r t de la sesión activa cuando
S t o r e S e r v i c e tenga que trabajar con el carro de la compra.
En lugar de inyectar el bean S h o p p in g C a r t en S t o r e S e r v i c e , Spring debe inyectar
un proxy en S h o p p in g C a rt (véase el listado 3.2). Este proxy mostrará los mismos métodos
que S h o p p in g C a r t que, a efectos de S t o r e S e r v i c e , es el carro de la compra, pero
cuando S t o r e S e r v i c e invoca métodos en S h o p p in g C a r t, el proxy delega la invocación
al verdadero bean S h o p p in g C a r t con ámbito de sesión (véase la figura 3.1).
Ahora ya podem os describir el atributo p r o x y M o d e . Está establecido en
Sco p ed P ro x y M o d e. INTERFACES, lo que indica que el proxy debe implementar la interfaz
S h o p p in g C a r t y delegar en el bean de implementación.
Es correcto (y el modo de proxy idóneo) siempre que S h o p p in g C a r t sea una interfaz
y no una clase, pero si S h o p p in g C a r t es una clase concreta, Spring no podrá crear un
proxy basado en interfaz. En su lugar tendrá que usar CGLib para generar un proxy basado
en clases. Así pues, si el tipo del bean es una clase concreta, debe establecer proxyM ode
en S co p e d P ro x y M o d e . TARGET_CLASS para indicar que el proxy debe generarse como
extensión de la clase de destino. Aunque nos hayamos centrado en el ámbito de sesión,
recuerde que los bean con ámbito de solicitud sufren los mismos problemas de conexión.
Por ello, también deben inyectarse como proxy con ámbito.
110 Capítulo 3

t
03
: Bean con ámbito
0 de sesión/solicitud
c

Ni
't Bean con ámbito
;\»e9?. eh
0
de sesión/solicitud
f > y Oe\ )
Bean de N
Se inyecta en Proxy con
instancia «g
0) ámbito ,_ _ _ D e / e g a en N
única C Bean con ámbito
v J J'
-
0 de sesión/solicitud
C
r®/«
es?

N
•g Bean con ámbito
0 de sesión/solicitud
C
)
Figura 3.1. Los proxy de ámbito permiten la inyección diferida de beans con ámbito
de solicitud y de sesión.

Declarar proxy con ámbito en XML


Si declara sus bean con ámbito de sesión o de solicitud en XML, no podrá usar la anota­
ción @Scope ni su atributo proxyMode. El atributo sco p e del elemento <bean > le permite
establecer el ámbito del bean pero ¿cómo puede especificar el modo de proxy?
Para establecer el modo de proxy debe usar un nuevo elemento del espacio de nombres
aop de Spring:
cbean id ="cart"
c la s s = "com.myapp. ShoppingCart"
scope="session">
<aop: scoped-proxy />
</bean>

< a o p : s c o p e d -p r o x y > es el equivalente de la configuración XML de Spring del atri­


buto proxyMode de la anotación @Scope. Indica a Spring que cree un proxy con ámbito
para el bean. De forma predeterminada usa CGLib para crear un proxy de clase de destino,
pero puede pedirle que genere un proxy basado en interfaz si establece el atributo p r o x y -
t a r g e t - c l a s s en f a l s e :
<bean id ="cart"
class="com. myapp. ShoppingCart"
scope="session">
<aop: scoped-proxy pro x y-target-class="false" />
</bean>

Para usar el elemento < a o p : s co p e d -p ro x y > debe declarar el espacio de nombres aop
de Spring en su configuración XML:
Técnicas avanzadas de conexión 111

<?xml v e r sio n = "l.0" encoding="UTF-8"?>


<beans xmlns="h tt p : //www. springframework. org/schema/beans"
xmlns:xsi="h tt p : //www.w3. org/2001/XMLSchema-instance"
xmlns: aop="h tt p : / /www.springframework.org/schema/aop"
x s i : schemaLocation="
h tt p : //www.springframework.org/schema/aop
h ttp : //www.springframework.org/schema/aop/spring-aop.xsd
h t t p : / /www.springframework.org/schema/beans
h tt p : //www. springframework. org/schema/beans/spring-beans.xsd" >

</beans>

Abordaremos el espacio de nombres aop en el siguiente capítulo cuando trabajemos


con Spring y la programación orientada a aspectos. Por el momento, finalizaremos este
capítulo con otra de las opciones avanzadas de conexión de Spring: el lenguaje de expre­
siones de Spring.

Inyectar valores en tiempo de ejecución


Cuando hablamos de la inyección de dependencias y de la conexión, nos referimos a la
conexión de una referencia de bean a una propiedad o a un argumento de constructor de
otro bean. Suele tratarse de la asociación de un objeto con otro.
Pero otra faceta de la conexión de bean es cuando conectamos un valor como argumento
a una propiedad de bean o a su constructor, como hicimos en el capítulo anterior al conectar
el nombre de un álbum al constructor o a la propiedad t i t l e de un bean B la n k D is c . Por
ejemplo, podría haber conectado B la n k D is c de esta forma:
@Bean
public CompactDisc sgtPeppersO {
return new BlankDisc(
"Sgt. Pepper's Lonely Hearts Club Band",
"The B e a tle s");
}
Aunque hubiera servido a nuestros objetivos, definir el título y el artista del bean
B la n k D is c , lo haría con valores predefinidos en la clase de configuración. Del mismo
modo, si hubiéramos utilizado XML, los valores también estarían predefinidos:
cbean id="sgtPeppers"
class="soundsystem.BlankDisc"
c : _ t i t l e = " S g t . Pepper's Lonely Hearts Club Band"
c:_artist="T h e Beatles" />

En ocasiones estos valores nos sirven pero en otros casos conviene evitarlos y dejar que
se determinen en tiempo de ejecución. Para esos casos, Spring ofrece dos formas de evaluar
valores en tiempo de ejecución:

• Marcadores de posición de propiedades.


• SpEL (Spring E xpression Language, Lenguaje de expresiones de Spring).
112 Capítulo 3

Comprobará en breve que la aplicación de ambas técnicas es similar, aunque su función


y compartimiento difieran. Empezaremos con los marcadores de posición de propiedades,
la más sencilla de las dos, para después abordar SpEL.

inyectar valores externos


La forma más sencilla de resolver valores externos en Spring consiste en declarar un
origen de propiedades y recuperar las propiedades a través del entorno de Spring. Por
ejemplo, el siguiente código muestra una clase de configuración básica de Spring que utiliza
propiedades externas para conectar un bean BlankDisc.

Listado 3.7. Uso de la anotación @PropertySource y Environment.


package cotn. soundsystem;
import org. springframework.beans. fa c to ry . annotation.Autowired;
import org.springframework.context. annotation.Bean;
import org.springframework.context. annotation.Configuration;
import org.springframework.context. annotation. PropertySource;
import org.springframework. co re. env. Environment;

@Configuration
@PropertySource("classpath:/com/soundsystem/app.properties") / / Declarar un origen
/ / d e propiedades.
public cla ss ExpressiveConfig {

©Autowired
Environment env;
@Bean
public BlankDisc disc() {
return new BlankDisc(
e n v .g e tP r o p e r ty (" d is c .title " ) , / / Recuperar valores de la propiedad.
env.getProperty(" d is c . a r t i s t " ) ) ;
}
}
En este ejemplo, @ P ro p e rty S o u rc e hace referencia al archivo app .p r o p e r t i e s de
la ruta de clases, cuyo aspecto podría ser el siguiente:
d i s c . t i t l e = S g t . Peppers Lonely Hearts Club Band
d i s c . artist=The Beatles

Este archivo de propiedades se carga en el entorno de Spring, desde el que se puede


recuperar después. Mientras tanto, en el método d i s c () se crea un nuevo B la n k D is c y
sus argumentos de constructor se resuelven desde el archivo de propiedades mediante la
invocación de g e t P r o p e r t y ().

Dentro del e n to rn o de Spring


Hablando de E n v iro n m e n t, debe saber que el método g e t P r o p e r t y () del listado 3.7
no es el único que puede usar para obtener el valor de una propiedad. g e t P r o p e r t y ()
cuenta con cuatro variantes:
Técnicas avanzadas de conexión 113

• String getProperty (String clave)


• String getProperty (String clave, String valorPredeterminado)
• T getProperty (String clave, Class<T> tipo)
• T getProperty (String clave, Class<T> tipo, T valorPredeterminado)

Las dos primeras siempre devuelven un valor S t r in g . Ya vimos cómo usar la primera
en el listado 3.7, pero podemos modificar ligeramente el método @Bean para trabajar con
valores predeterminados si las propiedades especificadas no existen:
@Bean
public BlankDisc disc() {
return new BlankDisc(
env.getProperty(" d is c . t i t l e " , "Rattle and Hum"),
en v .g e tP ro p e rty ("d isc.a rtist", "U2"));
}

Las dos siguientes formas de getProperty () son similares a las dos primeras pero
reconocen que no todos los valores pueden ser cadenas. Imagine por ejemplo que tiene que
recuperar un valor que representa el número de conexiones que se deben mantener en una
agrupación. Si recibe un valor String del archivo de propiedades, tendrá que convertirlo
en Integer antes de poder usarlo. Uno de los métodos sobrecargados de getProperty ()
se encarga de realizar dicha conversión:
in t connectionCount =
env.getProperty( "db.connection.count", In teg er. c lass, 30);

E n viron m en t ofrece varios métodos relacionados con propiedades. Si usa cualquiera


de los métodos g e t P r o p e r t y () sin especificar un valor predeterminado, recibirá n u i l
si la propiedad no está definida. Si quiere exigir la definición de la propiedad, puede usar
g e tR e q u ir e d P r o p e r ty () de esta forma:
@Bean
public BlankDisc disc() {
return new BlankDisc(
env.getRequiredProperty(" d is c . t i t l e " ) ,
en v.getRequiredProperty("disc.artist"));
}
Aquí, si las propiedades di s e . title o disc. artist no están definidas, se genera
IllegalStateException. Si quiere comprobar la existencia de una propiedad, puede
invocar containsProperty () en Environment:
boolean t i t l e E x i s t s = env.containsProperty(" d is c . t i t l e " );

Por último, si tiene que resolver una propiedad en una clase, utilice el método
getPropertyAsClass():
Class<CompactDisc> cdClass =
env.getPropertyAsClass(" d is c . c l a s s " , CompactDisc. c l a s s ) ;
114 Capítulo 3

Alejándonos ligeramente del tema de las propiedades, Environment le ofrece varios


métodos para comprobar qué perfiles están activos:

• String [] getActiveProf iles () : Devuelve una matriz de nombres de perfiles


activos.
• String [] getDef aultProf iles () : Devuelve una matriz de nombres de perfil
predeterminados.
• booleanacceptsProf iles (String. ..perfiles): Devuelve trué si el entorno
admite los perfiles proporcionados.

En el listado 3.6 vimos cómo usar el método acceptsProf iles ( ) . En ese caso,
Environment se recuperó de ConditionContext y se utilizó el método accepts
Profiles () para garantizar que había un perfil de un bean concreto antes de crear el
bean. Por lo general no necesitará los métodos de perfil de Environment, pero conviene
saber que están disponibles.
La recuperación de propiedades directamente de Environment es muy útil, en especial
cuando hay que conectar bean en configuración de Java, pero Spring también le permite
conectar propiedades con valores de marcador de posición que se resuelven desde un
origen de propiedades.

Resolver marcadores de posición de propiedades


Spring siempre ha admitido la posibilidad de externalizar propiedades en un archivo de
propiedades y conectarlas a bean de Spring por medio de valores de marcador de posición.
En operaciones de conexión de Spring, los valores de marcador de posición son nombres
de propiedades envueltos en $ { . . . }. Como ejemplo, puede resolver los argumentos de
constructor de B la n k D is c en XML de esta forma:
<bean id="sgtPeppers"
c l a s s = "soundsystem.BlankDisc"
c :_ title = "$ {d is c .title }"
c : _ a r t i s t = " $ { d i s c . a r t i s t } " />

Como puede apreciar, el argumento de constructor t i t l e recibe un valor resuelto de


la propiedad d i s c . t i t l e , y el argumento a r t i s t se conecta al valor de la propiedad
d i s c . a r t i s t . De esta forma, la configuración XML no usa valores predefinidos, sino
valores que se resuelven desde un origen externo al archivo de configuración (más adelante
abordaremos la resolución de estas propiedades). Al recurrir al análisis de componentes y a
la conexión automática para crear e inicializar los componentes de su aplicación, no existe
un archivo de configuración o una clase en la que especificar los marcadores de posición.
En su lugar, puede usar la anotación @ V a lu e como hicimos con @ A u to w ired . En la clase
B la n k D is c , por ejemplo, podríamos escribir el constructor de esta forma:

public BlankDisc(
@ V a lu e ( " $ { d i s c .ti tl e } ") String t i t l e ,
OValue("$ ( d i s c . a r t i s t }") String a r t i s t ) {
Técnicas avanzadas de conexión 115

th is .title = title ;
th is .a r tis t = a rtis t;

Para poder usar valores de m arcador de posición, debe configurar un bean


P r o p e r t y P l a c e h o l d e r C o n f i g u r e r o un bean P r o p e r t y S o u r c e s P l a c e h o l d e r
C onf ig u r e r . Desde Spring 3.1, se prefiere P r o p e r ty S o u r c e s P la c e h o ld e r C o n f i g u r e r
ya que resuelve marcadores de posición sobre el entorno de Spring y su conjunto de orígenes
de propiedades.
El siguiente m étodo @ Bean configura P r o p e r t y S o u r c e s P la c e h o l d e r C o n f i g u r e r
en Java:

©Bean
public
s t a t i c PropertySourcesPlaceholderConfigurer placeholderConfigurer() {
return new PropertySourcesPlaceholderConfigurer();
}
Si prefiere usar configuración XML, el elemento c c o n t e x t : p r o p e r t y - p l a c e
h o l d e r > del espacio de nombres c o n t e x t de Spring le ofrece un bean P r o p e r t y
S o u r c e s P la c e h o ld e r C o n fig u r e r :
<?xml v e r sio n = "l.0" encoding="UTF-8"?>
■ cbeans xmlns="http://www. springframework. org/schema/beans"
xmlns :x si = "h ttp -. / / www.w3 . org/2001/XMLSchema-instance"
xmlns: context="h tt p : / /www. springframework. org/schema/context"
x s i : schemaLocation="
h tt p : //www. springframework. org/schema/beans
h tt p : / /www.springframework. org/schema/beans/spring-beans.xsd
h tt p : //www.springframework. org/schema/context
h tt p : / /www. springframework.org/schema/context/spring-con text.xsd" >

<context:property-placeholder />

</beans>

La resolución de propiedades externas es una forma de retrasar la resolución de valores


hasta el tiempo de ejecución pero se centra principalmente en E n v iro n m e n t de Spring y
en orígenes de propiedades. El lenguaje de expresiones de Spring, por su parte, ofrece una
forma más general de calcular valores para inyectarlos en tiempo de ejecución.

Conexiones con SpEL


Spring 3 introdujo el Lenguaje de expresiones de Spring (SpEL), una forma potente y
concisa de conectar valores con las propiedades de un bean o con los argumentos de un
constructor utilizando expresiones que se evalúan durante el tiempo de ejecución. Con
SpEL, puede disponer de funciones de conexión con bean que serían muy difíciles de llevar
a cabo (o, incluso, imposibles) utilizando el estilo de conexión tradicional de Spring. SpEL
cuenta con un gran número de funcionalidades, incluyendo:
116 Capítulo 3

• La habilidad de hacer referencia a los bean por su ID.


• La invocación de métodos y el acceso a propiedades de objetos.
• Operaciones lógicas, relaciónales y matemáticas en valores.
• Comparación de expresiones regulares.
• Manipulación de colecciones.

Como veremos más adelante, también se puede usar SpEL para otras operaciones
distintas a la inyección de dependencias. Spring Security, por ejemplo, admite la definición
de restricciones de seguridad con expresiones SpEL, y si usa plantillas Thymeleaf como
vistas de una aplicación MVC de Spring, dichas plantillas pueden usar expresiones SpEL
para hacer referencia a datos de modelos.
Para empezar, veremos varios ejemplos de expresiones SpEL y cómo conectarlas a bean.
Después nos adentraremos en algunas de las expresiones primitivas de SpEL que puede
combinar para lograr otras más potentes.

Ejemplos de SpEL
SpEL es un lenguaje de expresiones tan flexible que resultaría imposible mostrarle todas
sus aplicaciones en este libro. Lo que sí veremos son varios ejemplos básicos que pueden
servirle de inspiración para crear sus propias expresiones.
Lo primero que debe saber es que las expresiones SpEL se encierran entre # { . . . },
similar a la forma en que los marcadores de posición de propiedades se encierran entre
$ { . . . }. La siguiente es posiblemente una de las expresiones SpEL más sencillas que
puede escribir:
#{ 1 }
Si excluye los marcadores # { . . . } verá el cuerpo de la expresión, una constante numé­
rica. No le sorprenderá saber que esta expresión evalúa al valor numérico 1.
Evidentemente, es improbable que utilice una expresión tan simple en una aplicación
real. Creará expresiones más interesantes como la siguiente:
#{T(System). currentTimeMillis()}

En últim a instancia, esta expresión evalúa la hora actual en m ilisegundos desde el


mom ento en que se haya evaluado la expresión. El operador T () evalúa j a v a . l a n g .
S y s te m como tipo para poder invocar el m étodo s t a t i c c u r r e n t T i m e M i l l i s ( ) .
Las expresiones SpEL también pueden hacer referencia a otros bean o propiedades de
estos. Por ejemplo, la siguiente expresión evalúa al valor de la propiedad a r t i s t en un
bean con el ID s g t P e p p e r s :
# { sgtPeppers. a r t i s t }

También puede hacer referencia a propiedades del sistema a través del objeto
s y s te m P r o p e r tie s :
# { systemProperties[ 'd i s c . t i t l e ' ]}
Técnicas avanzadas de conexión 117

Son algunos ejemplos básicos de SpEL. Observaremos más al final del capítulo, pero
antes veremos cómo usar estas expresiones durante la conexión de bean.
Al inyectar propiedades y argumentos de constructor a bean creados mediante análisis
de componentes, puede usar la anotación @Value como hicimos antes con los marcadores
de posición de propiedades. En lugar de usar una expresión de marcador de posición, se
usa una expresión SpEL. Por ejemplo, el constructor B la n k D is c tendría este aspecto:
public BlankDisc(
@Value("#{systemProperties['disc.title']}") String title,
@Value("#{systemProperties['disc.artist']}") String artist) {
this.title = title;
this.artist = artist;

En configuración XML puede pasar la expresión SpEL al atributo v a lu é de < p ro p e rty >
o < c o n s t r u c t o r - a r g > , o como el valor asignado a una entrada del espacio de nombres
p o c. Por ejemplo, en la siguiente declaración XML del bean B la n k D is c se establecen sus
argumentos de constructor desde una expresión SpEL:
<bean id="sgtPeppers"
class="soundsystem.BlankDisc"
c:_title="#{systemProperties['dise.title']}"
c;_artist="#{systemProperties['disc.artist']}" />

Después de ver varios ejemplos básicos y cómo inyectar valores resueltos desde expre­
siones SpEL, repasemos algunas de las expresiones primitivas admitidas en SpEL.

Expresar valores literales


Ya hemos visto un ejemplo de SpEL para expresar un valor literal entero, pero también
se puede usar con números de coma flotante, valores de cadena y Booleanos. El siguiente
ejemplo es una expresión SpEL que es un valor de coma flotante:
#{3.14159}

Los números también se pueden expresar en notación científica. Por ejemplo, la siguiente
expresión evalúa a 98,700:
# { 9 . 87E4}

Una expresión SpEL también puede evaluar valores literales de cadena, como:
#{'Helio'}

Por último, los literales Booleanos t r u e y f a l s e se evalúan a su valor Booleano, como


por ejemplo
#{false}

Trabajar con valores literales en SpEL es poco productivo. Después de todo, no necesita
SpEL para establecer una propiedad entera en 1 o una propiedad Booleana en f a l s e .
Admito que no tiene mucho sentido usar expresiones SpEL que solo contengan valores
118 Capítulo 3

literales, pero recuerde que las expresiones SpELmás interesantes están formadas por otras
más sencillas, de modo que conviene saber cómo trabajar con valores literales en SpEL ya
que los acabará por necesitar para crear expresiones.

Referencias a bean, propiedades y métodos


Otra de las tareas básicas que podemos conseguir con una expresión SpEL es hacer
referencia a otro bean por su ID. Por ejemplo, podemos utilizar SpEL para conectar un
bean con la propiedad de otro bean utilizando su ID como expresión SpEL (en este caso,
un bean con el ID s g t P e p p e r s ) :
# { sgtPeppers}

Imagine que ahora quiere hacer referencia a la propiedad a r t i s t del bean s g t


P e p p e r s en una expresión:
# { sgtPeppers. a r t i s t }

La primera parte del cuerpo de la expresión hace referencia al bean con el ID s g t


P e p p e r s . Después aparece un delimitador de punto que es una referencia a la propiedad
a r tis t.
Además de hacer referencia a las propiedades de un bean, puede invocar métodos en
el mismo. Imagine, por ejemplo, que tiene otro bean con el ID a r t i s t S e l e c t o r . Puede
invocar el método s e l e c t A r t i s t () de ese bean en una expresión SpEL de esta forma:
# { a r t i s t S e l e c t o r . s e l e c t A r t i s t ()}

También puede invocar métodos en el valor devuelto por el método invocado. Por
ejemplo, si s e l e c t A r t i s t () devuelve S t r i n g , puede invocar to U p p e rC a s e () para
mostrar el nombre del artista en mayúsculas:
# { a r t i s t S e l e c t o r . s e l e c t A r t i s t ( ) . toUpperCase()}

Funcionará mientras s e l e c t A r t i s t () no devuelva n u i l . Par evitar N u l l P o i n t e r


E x c e p t io n , puede usar el operador de seguridad de tipos:

# { a r t i s t S e l e c t o r . s e l e c t A r t i s t ( ) ? . toUpperCase()}

En lugar de un solitario punto ( . ) para acceder al m étodo to U p p e rC a s e ( ) , ahora


usamos el operador ?, que comprueba que el elemento situado a su izquierda no sea n u i l
antes de acceder a la parte derecha. Así pues, si s e l e c t A r t i s t () devuelve n u i l , SpEL
ni siquiera intentará invocar to U p p e rC a s e () y la expresión evaluará a n u i l .

Trabajar con tipos en expresiones


La clave para trabajar con métodos y constantes de ámbito de clase en SpEL consiste
en usar el operador T ( ) . Por ejemplo, para expresar la clase M ath de Java en SpEL, tendrá
que usar el operador T () de esta forma:
T (j ava. lang.Math)
Técnicas avanzadas de conexión 119

El resultado del operador T () es un objeto C la s s que representa j a v a . l a n g . Math.


Si lo desea, puede incluso conectarlo a una propiedad de bean de tipo C la s s , pero el valor
real del operador T () es que le permite acceder a métodos y constantes estáticos del tipo
evaluado. Por ejemplo, imagine que tiene que conectar el valor de p i a una propiedad de
bean, como hace la siguiente expresión SpEL:
T (j ava. lang.Math) . PI

Del mismo modo, con el operador T () se pueden invocar métodos estáticos en el tipo
resuelto. Hemos visto un ejemplo de T () para invocar S y s t e m . c u r r e n t T i m e M i l l i s ( ) .
El siguiente ejemplo evalúa a un valor aleatorio comprendido entre 0 y 1:
T (ja v a . lang.Math). random()

Operadores de SpEL
SpEL le ofrece varios operadores que puede aplicar a valores de expresiones SpEL. Se
resumen en la tabla 3.1.

Tabla 3.1. Operadores SpEL para manipular valores de expresiones.

Tipo de operador Operadores

Aritmético
Comparación <, lt, >, gt, ==, eq, <=, le, >=, ge
Lógico and, or, not, |
Condicional ? : (ternario), ? : (Elvis)
Expresión regular matches

Como ejemplo de uso de uno de estos operadores, fíjese en la siguiente expresión SpEL:
#{2 * T (ja v a . lang.Math). PI * c i r c l e . radius}

No solo es un magnífico ejemplo del operador de multiplicación (*) de SpEL, sino que
también muestra cómo combinar expresiones sencillas en otras más complejas. Aquí, se
multiplica el valor de p i por 2, y ese resultado se multiplica por el valor de la propiedad
r a d iu s del bean con el ID c i r c l e . Básicamente evalúa a la circunferencia del círculo
definido en el bean c i r c l e .
Del mismo modo puede usar el acento circunflejo O en una expresión para calcular
el área de un círculo:
# {T (ja v a . lang.Math). PI * c i r c l e . radius A 2 }

Este símbolo es el operador de potencia. En este caso, se usa para calcular el cuadrado
del radio del círculo. Al trabajar con valores S t r i n g , el operador + se encarga de la conca­
tenación, como en Java:
# { d i s c . t i t l e + ' by ' + d i s c . a r t i s t )
120 Capítulo 3

SpEL también le ofrece operadores de comparación para comparar valores de una expre­
sión. En la tabla 3.1 hay dos formas de operadores de comparación: simbólicos y textuales.
En la mayoría de los casos, los simbólicos son equivalentes a los textuales, y puede usar el
que mejor se adecúe a sus necesidades.
Por ejemplo, para comparar la igualdad de dos números puede usar el signo igual
doble (==):
#{co u n ter.to tal == 100}

O el operador textual eq:


#{co u n ter.to tal eq 100}

En cualquier caso, el resultado es el mismo. La expresión evalúa a un valor Booleano:


t r u e si c o u n t e r . t o t a l es igual a 10 0 o f a l s e en caso contrario.
SpEL también le ofrece un operador ternario de funcionamiento similar al de Java. Por
ejemplo, la siguiente expresión evalúa a la cadena "W in n e r! " si s c o r e b o a r d . s c o r e >
1 0 0 0 o a " L o s e r " encaso contrario:
# { scoreboard. score > 1000 ? "Winner!" : "Loser"}

Un uso habitual del operador ternario es comparar un valor n u i l y ofrecer un valor


predeterminado en su lugar. Por ejemplo, la siguiente expresión evalúa al valor de d i s e .
t i t l e si no es n u i l . Si d i s c . t i t l e es n u i l , la expresión evalúa a " R a t t l e and Hum".

# { d i s c . t i t l e ?: 'R a ttle and Hum'}

Esta expresión suele denominarse operador Elvis. Este extraño nombre proviene del
uso del operador como emoticono, en el que el signo de interrogación se parece a la forma
del peinado de Elvis Presley.

Evaluar expresiones regulares


Cuando trabajamos con texto, a menudo es útil comprobar si un texto coincide con un
determinado patrón. SpEL admite la comparación de patrones en expresiones mediante su
operador m a tch es. Éste intenta aplicar una expresión regular (que se proporciona como
argumento de lado derecho) respecto a un valor S t r i n g (que se proporciona como argu­
mento de lado izquierdo). El resultado de una evaluación m atch es es un valor Booleano:
t r u e si el valor coincide con la expresión regular y f a l s e de no ser así.
A modo de ejemplo, supongamos que queremos comprobar si un S t r i n g contiene
una dirección de correo electrónico válida. En ese caso, podríamos aplicar el operador
m a tc h e s de esta manera:
#{admin.email matches ' [a-zA-ZO-9._%+-]+@[a-zA-ZO-9. - ] +\\. com'}

Detallar el funcionamiento de esta expresión se encuentra fuera del ámbito de este


libro. De hecho, esta expresión regular no es lo bastante completa como para cubrir todos
los escenarios, aunque nos permite demostrar el funcionamiento del operador m atch es.
Técnicas avanzadas de conexión 121

Evaluar colecciones
Algunos de los trucos más increíbles de SpEL se realizan con colecciones y matrices.
La operación más básica que puede realizar es hacer referencia a un elemento de una lista:
# { jukebox. songs[ 4 ] . t i t l e }

Evalúa la propiedad t i t l e del quinto elemento (con base cero) de la propiedad de


colección son g s en el bean con el ID j ukebox. Para animar las cosas, imagino que podría
seleccionar aleatoriamente una canción de j ukebox:
# { jukebox. songs[T(java. lang.Math).randomO *
jukebox.songs. s i z e ( ) ] . t i t l e }

El operador [ ] utilizado para obtener un elemento indexado de una colección o una


matriz también se puede usar para obtener un único carácter de una cadena. Por ejemplo:
# {'T h is is a t e s t ' [3]}

Hace referencia al cuarto carácter de la cadena, es decir, s.


SpEL también le ofrece un operador de selección (. ? [ ]) para filtrar una colección en un
subconjunto de la misma. Como ejemplo, imagine que desea una lista de todas las canciones
en las que la propiedad a r t i s t sea A ero sm ith . La siguiente expresión usa el operador
de selección para llegar a la lista de canciones disponibles de Aerosmith:
# { jukebox.songs. ? [ a r t i s t eq 'Aerosmith']}

Como puede apreciar, el operador de selección acepta otra expresión entre corchetes.
Cuando SpEL itera por la lista de canciones, evalúa esa expresión por cada entrada de la
colección de canciones. Si la expresión evalúa a t r u e , la entrada se transfiere a la nueva
colección; en caso contrario se excluye de la misma. En este caso, la expresión interna
comprueba que la propiedad a r t i s t de la canción sea igual a A ero sm ith .
SpEL le ofrece otras dos operaciones de selección: . ^ [ ] para seleccionar la primera
entrada que coincida y . $ [ ] para seleccionar la última que coincida. Para ilustrarlo, fíjese
en la siguiente expresión, que busca la primera canción de la lista cuya propiedad a r t i s t
sea A ero sm ith :
# { jukebox.songs. ^ [ a r t i s t eq 'Aerosmith']}

Por último, SpEL le ofrece un operador de proyección (. ! [ ]) para proyectar propiedades


desde los elementos de una colección a otra. Como ejemplo, imagine que no quiere una
colección de objetos de canción, sino una colección de los títulos de todas las canciones.
La siguiente expresión proyecta la propiedad t i t l e a una nueva colección de cadenas:
# { jukebox.songs.! [ t i t l e ] }

Evidentemente, el operador de proyección se puede combinar con los demás operadores


de SpEL, incluido el de selección. Por ejemplo, podría usar esta expresión para obtener una
lista de todas las canciones de Aerosmith:
# { jukebox.songs. ? [ a r t i s t eq 'Aerosmith'] . ! [ t i t l e ] }
122 Capítulo 3

Apenas hemos visto una mínima parte de las prestaciones de SpEL. Habrá otras oportu­
nidades de usar SpEL a lo largo del libro, en especial al definir restricciones de seguridad.
Sin embargo, acabaremos el análisis de SpEL con una advertencia. Las expresiones SpEL
resultan muy útiles y potentes para inyectar valores a bean de Spring de forma dinámica.
Puede resultar tentador crear expresiones muy elaboradas pero no debería excederse.
Cuando más complejas sean, más importante será que las pruebe. En última instancia, las
expresiones SpEL se proporcionan como valores S t r i n g y pueden ser difíciles de probar.
Por ello, le recomiendo que simplifique sus expresiones al máximo para que las pruebas
no se compliquen.

Resumen
En este capítulo hemos abarcado numerosos frentes para de esa forma ampliar las
técnicas básicas de conexión de bean descritas en el capítulo anterior con nuevos trucos
avanzados de conexión.
Empezamos con los perfiles de Spring para solucionar un problema habitual en el que los
bean de Spring deben variar entre entornos de implementación. Al resolver bean específicos
del entorno comparándolos con uno o varios perfiles activos, Spring permite implementar
la misma unidad de desarrollo entre varios entornos sin necesidad de regeneraciones.
Los bean con perfiles son una forma de crear bean en tiempo de ejecución condicional­
mente, pero Spring 4 ofrece una forma más genérica de declarar bean que se crean (o no)
en función del resultado de una determinada condición. La anotación @ C o n d it io n a l,
combinada con una implementación de la interfaz C o n d it io n de Spring, ofrece a los
desarrolladores un potente y flexible mecanismo para la creación condicional de bean.
También hemos visto dos técnicas para resolver ambigüedades en conexiones automá­
ticas: los bean principales y los calificadores. Aunque sea muy sencillo designar un bean
como principal, también resulta limitado, por lo que hemos visto el uso de calificadores
para limitar a un único bean los candidatos a la conexión automática. Además, hemos
aprendido a crear anotaciones de calificador personalizadas para describir un bean a través
de sus rasgos.
Aunque la mayoría de los bean de Spring se crean como instancias únicas, en ocasiones
resulta más apropiado recurrir a otras estrategias de creación. Spring permite crear bean
como instancias únicas, prototipos o con ámbito de solicitud o de sesión. Al declarar estos
últimos, vimos cómo controlar la creación de proxy con ámbitos, bien basados en clases
o en interfaces.
Por último, hemos descrito el lenguaje de expresiones de Spring, que le permite resolver
valores en tiempo de ejecución para su inyección en propiedades de bean.
Tras establecer los cimientos de la conexión de bean, nos centraremos en la progra­
mación orientada a aspectos (AOP). Al igual que la inyección de dependencias permite
desligar unos componentes de otros cuando colaboran entre ellos, AOP permite desligar
a los componentes de su aplicación de tareas que implican a varios componentes. En el
siguiente capítulo aprenderemos a crear y trabajar con aspectos en Spring.
Capítulo

4 S p rin g orientado
a a sp e c to s

CONCEPTOS FUNDAMENTALES:

• Fundamentos de la programación orientada a aspectos.


• Creación de aspectos a partir de POJO.
• Uso de anotaciones @AspectJ.
• Inyección de dependencias en aspectos AspectJ.
En el momento de escribir este capítulo es verano en el lugar en el que vivo (Texas, EE.
UU.) y se están experimentando temperaturas récord. Hace calor y, con un tiempo como
éste, el aire acondicionado es imprescindible. La desventaja es que utiliza electricidad y
cuesta dinero. Hay muy pocas cosas que podamos hacer para no tener que pagar por tener
una casa refrigerada y cómoda. Cada hogar cuenta con un contador que mide los kilovatios
gastados y, una vez al mes, alguien viene a mirar ese contador, para que así la compañía
eléctrica sepa cuánto tiene que facturarnos.
Ahora imagine qué sucedería si el contador dejase de funcionar y nadie viniese a medir
nuestro consumo eléctrico. Suponga que fuera decisión de cada persona ponerse en contacto
con la compañía eléctrica y notificar su consumo. Aunque es posible que algunas personas
concienciadas lo hicieran, la mayoría no se preocuparía. La mayoría realizaría una medición
estimada o ni siquiera se preocuparía de medir. Tener electricidad gratis sería genial para
los consumidores, aunque probablemente a las compañías eléctricas no les gustaría tanto.
La supervisión del consumo eléctrico es una función importante pero no es precisamente
la mayor prioridad del propietario de una casa. Cortar el césped, pasar la aspiradora y
limpiar el baño son el tipo de tareas en las que el propietario participa de forma activa,
mientras que medir el consumo eléctrico es un evento pasivo desde su punto de vista
(aunque sería genial si cortar el césped también fuera un evento pasivo, sobre todo en estos
días con tanto calor). Algunas de las funciones de los sistemas de software son como los
contadores eléctricos de nuestras casas. Las funciones tienen que actuar dentro de diferentes
puntos de la aplicación, aunque no sería deseable ejecutarlas en todos los puntos. El inicio
de sesión, la seguridad y la administración de transacciones son importantes pero ¿son
las actividades en las que sus objetos de aplicación deberían participar de forma activa o
quizá sería mejor que estos se centrasen en los problemas del dominio de negocio para el
que están diseñados y dejar que determinados aspectos los gestione otra parte?
En el desarrollo de software, las funciones que abarcan varios puntos dentro de una
aplicación se denominan preocupaciones transversales. Por lo general, estas preocupa­
ciones se encuentran conceptualmente separadas de la lógica de negocio de la aplicación
(aunque suelen encontrarse incluidas de forma directa en ésta). Separar estas preocupa­
ciones transversales de la lógica de negocio es donde la Programación orientada a aspectos
(AOP) entra en acción.
En el capítulo 2 ha aprendido a utilizar la inyección de dependencias (DI) para admi­
nistrar y configurar sus objetos de aplicación. Mientras que la DI le ayuda a desacoplar
los objetos de su aplicación entre sí, la AOP le ayuda a desacoplar las preocupaciones
transversales de los objetos a los que afectan.
Un buen ejemplo de la aplicación de los aspectos sería el inicio de sesión, pero no es la
única. A lo largo del libro va a poder ver diferentes aplicaciones prácticas de los aspectos,
incluyendo transacciones declarativas, seguridad y almacenamiento en caché.
Este capítulo se centra en la compatibilidad de Spring con los aspectos, incluyendo la
forma de declarar clases regulares para que sean aspectos, así como la forma de utilizar
anotaciones para crear aspectos. Además, aprenderá cómo AspectJ (otra implementación
popular de AOP) puede complementar el marco de trabajo AOP de Spring. Sin embargo,
antes de entrar en materia con las transacciones, la seguridad y el almacenamiento en caché,
vamos a ver cómo se implementan los aspectos en Spring, comenzando por una visión
general sobre los aspectos fundamentales de AOP.
126 Capítulo 4

Qyé es la programación orientada a aspectos


Como se ha indicado con anterioridad, los aspectos pueden ayudarle a modularizar las
preocupaciones transversales. Básicamente, una preocupación transversal puede definirse
como cualquier funcionalidad que afecta a varios puntos de una aplicación. Por ejemplo,
la seguridad es una preocupación transversal, ya que varios métodos de una aplicación
pueden contar con normas de seguridad aplicados a estos. En la figura 4.1 puede ver una
representación visual de las preocupaciones transversales.

Figura 4.1. Los aspectos modularizan las preocupaciones transversales, aplicando


una lógica que abarca varios objetos de aplicación.

La figura 4.1 representa una aplicación típica que se divide en varios módulos. El objetivo
principal de cada uno es proporcionar servicios para un dominio concreto. Sin embargo,
cada módulo también requiere funcionalidades similares, como seguridad o administración
de transacciones. Una técnica habitual orientada a objetos para reutilizar una funciona­
lidad común es aplicar la herencia o la delegación. Sin embargo, la herencia puede llevar
a una herencia de objetos precaria si se utiliza la misma clase básica en toda la aplicación.
Asimismo, la delegación puede ser compleja, ya que puede que sea necesario realizar
invocaciones complicadas del objeto delegado. Los aspectos ofrecen una alternativa para
la herencia y la delegación que puede ser mucho más sencilla en muchos casos. La AOP le
permite definir la funcionalidad común en una ubicación y cómo y dónde se va a aplicar,
de forma declarativa, sin que tenga que modificar la clase a la que va a aplicar esta nueva
característica. Las preocupaciones transversales pueden modularizarse en clases especiales
llamadas aspectos. Esto ofrece dos ventajas. En primer lugar, la lógica de cada preocupación
se encuentra ahora en una única ubicación, en lugar de encontrarse repartida por todo el
código. En segundo lugar, nuestros módulos de servicio son más claros, ya que ahora solo
contienen el código de su preocupación principal (o funcionalidad clave), mientras que las
preocupaciones secundarias se han transferido a los aspectos.

Terminología de AOP
Al igual que la mayoría de las tecnologías, AOP ha generado su propia jerga. Los aspectos
suelen definirse en términos de consejos, puntos de corte y puntos de cruce. La figura 4.2
muestra cómo se relacionan estos elementos entre sí.
Spring orientado a aspectos 127

Desafortunadamente, los términos que se utilizan para describir las características de


AOP no son intuitivos y para poder entender su funcionamiento debe conocerlos.

Figura 4.2. La funcionalidad de un aspecto (consejo) se incluye en la ejecución de un programa


en uno o más puntos de cruce.

Consejo
Cuando el empleado encargado de medir su contador aparece en su casa, su propósito es
informar a la compañía eléctrica sobre el número de kilovatios/hora consumidos. Tiene una
lista de hogares que visitar y la información que recopila es muy importante. Sin embargo,
el acto de medir el consumo eléctrico es el trabajo real del empleado.
Del mismo modo, los aspectos tienen un propósito, es decir, un trabajo para el que se
han creado. En términos de AOP, al propósito de un aspecto se le denomina consejo.
Los consejos definen tanto el qué como el cuándo de un aspecto. Además de describir
el trabajo que un aspecto debe llevar a cabo, los consejos deben responder a la pregunta
de cuándo deben llevarlo a cabo. ¿Debe aplicarse antes de que se invoque un método?
¿Después? ¿Tanto antes como después de la invocación del método? ¿O solo debería apli­
carse en el caso de que un método genere un error?
Los aspectos de Spring pueden trabajar con cinco tipos de consejos:
• Antes: La funcionalidad del consejo tiene lugar antes de que el método se invoque.
• Después: La funcionalidad del consejo tiene lugar después de que finalice el método,
con independencia del resultado.
• Después de la devolución: La funcionalidad del consejo tiene lugar después de que
el método se complete con éxito.
• Después de la generación: La funcionalidad del consejo tiene lugar después de que
el método genere un error.
• Alrededor: El consejo encapsula el método, lo que proporciona cierta funcionalidad
antes y después del método invocado.

Pontos de cruce
Una compañía eléctrica presta servicios a varias casas o incluso a toda una ciudad.
Cada hogar cuenta con un contador eléctrico que tiene que leerse y, por tanto, cada hogar
es un objetivo potencial para el empleado que mide los contadores. Este empleado podría
128 Capítulo 4

leer cualquier tipo de dispositivos pero, para realizar su trabajo, tiene que centrarse en los
contadores conectados a los hogares. Del mismo modo, su aplicación cuenta con miles de
oportunidades para que se aplique un consejo. Estas oportunidades se denominan puntos
de cruce. Un punto de cruce es aquel punto de la ejecución de la aplicación al que puede
conectarse un aspecto. Este punto puede ser un método que se está invocando, un error
que se está produciendo o incluso un campo que se está modificando. Son los puntos en
los que el código de sus aspectos puede insertarse en el flujo normal de la aplicación para
añadir un nuevo comportamiento.

Puntos de corte
Un solo empleado no puede visitar todos los hogares a los que presta servicio la compañía
eléctrica. En su lugar, a cada uno se le asigna un número de casas. Del mismo modo, un
aspecto no tiene que aconsejar a todos los puntos de cruce de una aplicación. Los puntos
de corte ayudan a reducir los puntos de cruce aconsejados por un aspecto.
Si el consejo define el qué y el cuándo de los aspectos, los puntos de corte definen el
dónde. Una definición de punto de corte compara uno o más puntos de cruce en los que
debe incluirse el consejo. A menudo, estos puntos de corte se especifican utilizando nombres
de métodos y clases explícitos o mediante expresiones regulares que definen clases coin­
cidentes y patrones de nombre de método. Algunos marcos de trabajo AOP le permiten
crear puntos de corte dinámicos que determinan si los consejos se aplican en función de
decisiones de tiempo de ejecución como, por ejemplo, el valor de parámetros de métodos.

Aspectos
Cuando un empleado inicia su jornada laboral, sabe lo que tiene que hacer (recopilar
la información sobre el consumo eléctrico) y qué hogares debe visitar para obtener esa
información. Por tanto, sabe todo lo que necesita para llevar a cabo su trabajo.
Un aspecto combina los consejos y los puntos de corte. De forma conjunta, definen todo
lo que debe de hacerse sobre un aspecto: qué hacer, dónde hacerlo y cuándo debe hacerse.

Introducciones
Una introducción le permite añadir nuevos métodos o atributos a las clases existentes.
Por ejemplo, puede crear una clase de consejo A udi t a b l e que mantenga el estado de
un objeto cuando se modificó por última vez. Podría ser tan sencillo como contar con un
método s ë t L a s t M o d if i e d (D a te ) y una variable de instancia para mantener este estado.
El nuevo método y la variable de instancia pueden introducirse a las clases existentes sin
tener que cambiarlas, asignándoles el nuevo comportamiento y el estado.

Entrelazado
El entrelazado es el proceso de aplicar aspectos a un objeto de destino para crear un
nuevo objeto proxy. Los aspectos se entrelazan en el objeto de destino en los puntos de
cruce especificados. El entrelazado puede tener lugar en diferentes puntos a lo largo de la
vida útil del objeto de destino:
Spring orientado a aspectos 129

• Tiempo de compilación: Los aspectos se entrelazan al compilar la clase de destino,


lo que requiere un compilador especial. El compilador de entrelazado de AspectJ
funciona de esta manera.
• Tiempo de carga de clases: Los aspectos se entrelazan cuando la clase de destino se
carga en la JVM. Esto requiere un C la s s L o a d e r especial que mejora el código de
bytes de la clase de destino antes de que la clase se introduzca en la aplicación. El
entrelazado de tiempo de carga (LTW) de AspectJ 5 admite este tipo de entrelazado.
• Tiempo de ej ecución: Los aspectos se entrelazan durante la ejecución de la aplicación.
Por lo general, un contenedor AOP va a generar de forma dinámica un objeto proxy
que se va a delegar al objeto de destino mientras se entrelaza con los aspectos. Es la
forma de entrelazar los aspectos AOP de Spring.

Sin duda, hay muchos nuevos términos con los que debe familiarizarse. Si examina de
nuevo la figura 4.1, verá cómo los consejos contienen el comportamiento de preocupaciones
transversales que tiene que aplicarse a los objetos de una aplicación. Los puntos de cruce
son todos los puntos dentro del flujo de ejecución de la aplicación en los que puede apli­
carse el consejo. El punto de corte define dónde (en qué puntos de cruce) se va a aplicar
el consejo. El concepto clave que debe tener en cuenta es que los puntos de corte definen
qué puntos de cruce se aconsejan.
Ahora que ya se ha familiarizado con la terminología básica de AOP, vamos a ver cómo
se implementan estos elementos clave de AOP en Spring.

Compatibilidad de Spring con AOP*•


No todos los marcos de trabajo AOP se crean por igual. Pueden diferenciarse en el nivel
de detalle de los modelos de punto de cruce. Unos le permiten aplicar consejos al nivel de
modificación de campos, mientras que otros solo exponen los puntos de cruce relacionados
con invocaciones de métodos. Estos también pueden diferenciarse en cómo y dónde entre­
lazan los aspectos. Sea cual sea el caso, la habilidad de crear puntos de corte que definen
los puntos de cruce en los que los aspectos deben entrelazarse es lo que define un marco de
trabajo AOP. Como este libro trata sobre Spring, vamos a centramos en el AOP de Spring.
Aún así, existe un gran número de sinergias entre los proyectos de Spring y de AspectJ, y
la compatibilidad de Spring con AOP cuenta con muchos elementos del proyecto AspectJ.
La compatibilidad de Spring con AOP cuenta con cuatro variantes:

• AOP basado en proxy de Spring clásico.


• Aspectos POJO puros.
• Aspectos basados en anotaciones @ A sp e ct J .
• Aspectos AspectJ inyectados (disponibles en todas las versiones de Spring).
Los tres primeros elementos son variaciones de la propia implementación de AOP
de Spring. Por tanto, la compatibilidad de Spring con AOP se limita a la intercepción de
métodos.
130 Capítulo 4

El término clásico suele tener connotaciones positivas. Los coches clásicos, los torneos
de golf clásicos y la Coca-Cola clásica son todos positivos, pero el modelo de programación
AOP clásico de Spring no lo es tanto. Bueno, lo fue en su día, pero ahora Spring admite
formas más sencillas de trabajar con aspectos. Si lo comparamos con el modelo declarativo
o el basado en anotaciones, el modelo AOP clásico de Spring parece tosco y demasiado
complejo, de modo que no lo analizaremos en el libro.
Con el espacio de nombres aop de Spring puede convertir los POJO puros en aspectos.
En realidad, dichos POJO solamente proporcionarán métodos que se invocan como reacción
a un punto de corte. Desafortunadamente esta técnica requiere configuración XML pero es
una forma sencilla de convertir cualquier objeto en aspecto.
Spring recurre a los aspectos de AspectJ para habilitar AOP controlado por anotaciones.
Entre bastidores, sigue siendo AOP basado en proxy de Spring, pero el modelo de progra­
mación es prácticamente idéntico al uso de aspectos anotados de AspectJ. La ventaja de
este estilo de AOP es que se puede emplear sin necesidad de configuración XML.
Si sus necesidades de AOP superan la intercepción de métodos sencillos (por ejemplo,
la de constructores o propiedades), querrá implementar aspectos de AspectJ. En ese caso,
el cuarto estilo le permitirá inyectar valores a aspectos controlados por AspectJ.
A lo largo de este capítulo, vamos a aprender más sobre las técnicas de AOP, pero antes
de empezar, es importante que se familiarice con una serie de puntos clave del marco de
trabajo AOP de Spring.

Los consejos de Spring se escriben en java


Todos los consejos que se crean en Spring se escriben en una clase estándar de Java.
De esta forma, puede desarrollar sus aspectos en el mismo entorno de desarrollo inte­
grado (IDE) que utilizará para el desarrollo normal en Java. Además, los puntos de corte
que definen dónde se aplica el consejo pueden especificarse con anotaciones o en XML
en el archivo de configuración de Spring, técnicas conocidas para los desarrolladores en
Java.
Esto contrasta con AspectJ. Aunque ahora admite aspectos basados en anotaciones,
también se incluye como una extensión de lenguaje para Java. Este enfoque tiene tanto
ventajas como inconvenientes. Al disponer de un lenguaje específico para AOP, puede
obtener mayor potencia y un control más preciso, así como un conjunto de herramientas
para AOP más amplio. Sin embargo, tendrá que aprender a manejar una nueva herramienta
y sintaxis para llevarlo a cabo.

Spring aconseja los objetos en tiempo de ejecución


En Spring los aspectos se entrelazan con bean administrados por Spring durante el
tiempo de ejecución, empaquetándolos con una clase proxy. Como puede ver en la figura 4.3,
la clase proxy actúa como bean de destino, interceptando las invocaciones de los métodos
aconsejados y reenviándola al bean de destino. El proxy lleva a cabo la lógica de aspectos
entre el momento en el que el proxy intercepta la invocación del método y el momento en
el que se invoca el método del bean de destino.
Spring orientado a aspectos 131

Figura 4.3. Los aspectos de Spring se ¡mplementan como proxy que empaquetan el objeto
de destino. El proxy gestiona las invocaciones de métodos, aplica lógica de aspectos adicional
y, después, invoca el método de destino.

Sjpibí a va. apernar i m .ohipíci de proxy hasta que la aplicación necesite ese bean proxy.
Si utiliza A p p l i c a t i o n C o n t e x t , los objetos proxy se crearán cuando éste cargue todos
los bean desde B e a n F a c t o r y . Como Spring crea los proxy en tiempo de ejecución, no va
a necesitar un compilador especial para entrelazar los aspectos en el AOP de Spring.

Spring solo admite puntos de cruce de método


Como ya se ha indicado con anterioridad, dispone de varios modelos de punto de cruce
y de corte a través de diferentes implementaciones de AOP. Al basarse en proxy dinámicos,
Spring solo admite puntos de cruce de método. Es una diferencia respecto a otros marcos
de trabajo AOP como AspectJ y JBoss, que proporcionan puntos de cruce de constructor y
de campo además de los de método. La falta de puntos de corte de campo en Spring impide
que pueda crear consejos muy específicos, como por ejemplo interceptar actualizaciones
del campo de un objeto y, al no contar con puntos de corte de constructor, no hay forma
de aplicar consejos al instanciar un bean. Sin embargo, la intercepción de métodos debería
ajustarse a la mayoría o a todas sus necesidades. En caso de que necesite algo más que la
intercepción de métodos, puede complementar AOP de Spring con AspectJ.
Ahora que ya tiene una idea general de lo que puede hacerse con AOP y cuál es su
compatibilidad con Spring, es el momento de crear aspectos. Para ello, vamos a comenzar
hablando del modelo declarativo AOP.

Seleccionar puntos de cruce con puntos de corte


Como se ha indicado con anterioridad, los puntos de corte se utilizan para indicar dónde
debe aplicarse el consejo de un aspecto. Junto a éste, los puntos de corte son los elementos
más importantes de un aspecto. Por tanto, es importante saber cómo escribirlos.
En AOP de Spring, los puntos de corte se definen utilizando el lenguaje de expresiones
de puntos de corte de AspectJ. Si ya lo conoce, definir puntos de corte en Spring será sencillo
pero, en caso contrario, este apartado le servirá a modo de lección rápida.
132 Capítulo 4

Lo más importante que debe saber sobre los puntos de corte de AspectJ en lo relacio­
nado con AOP de Spring es que Spring solo admite un subconjunto de los designadores de
punto de corte disponibles en AspectJ. Recuerde que AOP de Spring se basa en proxy y que
ciertas expresiones no se aplican a estos. En la tabla 4.1 puede ver la lista de designadores
que puede utilizar en AOP de Spring.

Tabla 4.1. Spring usa el lenguaje de expresiones de puntos de corte de AspectJ para definir
aspectos de Spring.
P " ......... .....................
Designador de AspectJ Descripción

args() Limita las coincidencias del punto de cruce a la ejecución de aquellos


métodos cuyos argumentos son instancias de los tipos dados.
@args() Limita las coincidencias de puntos de cruce a la ejecución de aquellos
métodos cuyos argumentos se anotan con los tipos de anotación
proporcionados.
execution() Hace coincidir los puntos de cruce que son ejecuciones de método.
this() Limita las coincidencias de puntos de cruce a aquellas en las que
la referencia de bean del proxy AOP es de un tipo determinado.
target() Limita las coincidencias de puntos de cruce a aquellas en las que
el objeto de destino es de un tipo determinado.
@target() Limita la coincidencia de los puntos de cruce cuando la clase de un
objeto en ejecución cuenta con una anotación del tipo dado.
within() Limita la coincidencia a los puntos de cruce de ciertos tipos.
@within() Limita la coincidencia a los puntos de cruce dentro de ciertos tipos que
cuenten con la anotación dada (la ejecución de métodos declarados
en tipos con la anotación dada al utilizar AOP de Spring).
©annotation Limita las coincidencias de puntos de cruce a aquellos en los que
el asunto del punto de cruce cuenta con la anotación dada.

Si intenta utilizar cualquiera del resto de designadores de AspectJ, se producirá un error


1 1 1 e g a lA rg u m en tE x cep t io n .
Si examina los designadores compatibles, se dará cuenta de que e x e c u t io n es el
único que realiza coincidencias. El resto se utiliza para limitarlas. Esto quiere decir que
e x e c u t io n va a ser el principal designador que va a utilizar en cada definición de punto
de cruce que escriba, mientras que utilizará el resto de designadores para limitar el alcance
del punto de cruce.

Escribir puntos de corte


Para ilustrar el uso de aspectos en Spring, necesita un elemento que sea el sujeto de los
puntos de corte del aspecto. Para ello, definiremos una interfaz P erfo rm a n ce:
Spring orientado a aspectos 133

package concert;

public in terface Performance {


public void perform();
}
P e r fo r m a n c e representa cualquier tipo de actuación en directo, ya sea una obra de
teatro, una película o un concierto. Imagine que quiere escribir un aspecto que desencadene
el método p e r f orm () de P e r fo r m a n c e . La expresión de punto de corte que se muestra
en la figura 4.4 puede utilizarse para aplicar un consejo siempre que se ejecute el método
p e r fo rm ().

Devolver El tipo al que □ método Aceptar cualquier


cualquier tipo pertence el método \ argumento

^------------ i r ^ — i rA
e x e c u t io n (* c o n c e r t . Performance.p erfo rm ( . . ) )

Se de scencad ena tra s Especificación del método


la ejecución del m étodo

Figura 4.4. Selección del método perform de Performance con una expresión de punto
de corte de AspectJ.

Hemos utilizado el designador e x e c u t io n () para seleccionar el método p e r fo rm () de


P e r fo r m a n c e . La especificación del método comienza por un asterisco, lo que indica que
no nos importa el tipo que devuelva el método. A continuación, especificamos el nombre
de clase calificado por completo y el nombre del método que queremos seleccionar. Para
la lista de parámetros del método, utilizamos un doble punto (. .), que indica que el punto
de cruce debe seleccionar cualquier método p e r fo r m ( ) , con independencia de cuál sea
la lista de argumentos. Ahora supongamos que quiere limitar el ámbito de ese punto de
corte al paquete c o n c e r t . En ese caso, podríamos limitar la coincidencia utilizando el
designador w i t h i n () (véase la figura 4.5).

Ejecución del método Instrument.playQ

e x e c u t io n (* c o n c e r t . Performance. perform ( . . ) )
&& w i t h i n ( c o n c e r t . * ) )

Operador de Cuando el método se invoca desde


combinación cualquier clase del paquete concert

Figura 4.5. Se limita el alcance de un punto de corte con el designador withinQ.

Fíjese en que hemos utilizado el operador && para combinar los designadores e x e c u ­
t i o n () y w it h in () en una relación AND (en la que ambos designadores deben coincidir
para que lo haga el punto de corte). De forma similar, podríamos utilizar el operador | |
para indicar una relación OR. Asimismo, el operador ! puede utilizarse para negar el efecto
de un designador.
134 Capítulo 4

Puesto que & tiene un significado especial en XML, puede utilizar and en lugar de &&,
al especificar puntos de corte en una configuración de Spring basada en XML. De forma
similar, o r y n o t pueden utilizarse en lugar de | | y !, respectivamente.

Seleccionar bean en puntos de corte


Además de los designadores de la tabla 4.1, Spring añade un designador b ean () que
le permite identificar bean por sus ID dentro de una expresión de punto de corte, b ean ()
acepta un ID o nombre de bean como argumento y limita el efecto del punto de corte a ese
bean específico. Por ejemplo, examine el siguiente punto de corte:
execution(* concert. Performance.perform())
and bean( 'woodstock')

En este caso, estamos indicando que queremos aplicar el consejo del aspecto a la ejecu­
ción del método p e rfo rm () de P e rfo rm a n c e , aunque limitándolo al bean con el ID
w ood stock. Reducir el punto de corte a un bean específico puede ser muy interesante
en algunos casos, aunque también podemos utilizar la negación para aplicar un aspecto a
todos los bean que no tengan un ID específico:
execution(* concert. Performance.perform())
and ¡bean( 'woodstock')

En este caso, el consejo del aspecto va a entrelazarse con todos los bean cuyo ID no sea
w ood stock. Ahora que ya hemos visto los aspectos básicos sobre cómo escribir consejos,
veamos cómo escribir un consejo y declarar los aspectos que van a utilizar esos puntos de
corte.

■ Crear aspectos anotados


Una de las características clave introducidas por AspectJ 5 es la posibilidad de utilizar
anotaciones para crear aspectos. Antes de esta versión, la creación de aspectos de AspectJ
implicaba aprender a utilizar una extensión de lenguaje de Java. Sin embargo, gracias al
modelo orientado a anotaciones de AspectJ, convertir una clase en un aspecto es tan sencillo
como utilizar un par de anotaciones.
Ya hemos definido la interfaz P e r fo r m a n c e como sujeto de los puntos de corte de un
aspecto. A continuación usaremos anotaciones de AspectJ para crear un aspecto.

Definir un aspecto
Una actuación no sería tal sin público. ¿O no? Si lo piensa desde el punto de vista de una
actuación, el público es importante pero no esencial para el funcionamiento de la propia
actuación; es una preocupación distinta. Por ello, tendría sentido definir el público como
aspecto que se aplica a la actuación. El siguiente código muestra la clase A u d ie n c e que
define el aspecto que necesitamos.
Spring orientado a aspectos 135

Listado 4.1. La clase Audience: un aspecto que presencia una actuación.


package concert ;
import o r g .a s p e c t j. lang. annotation.AfterReturning;
import o r g .a s p e c t j. lang. annotation.AfterThrowing;
import o r g .a s p e c t j. lang.annotâtion.Aspect;
import org. aspectj . lang. annotation. Before ;

@Aspect
public cla ss Audience {

@Before("execution(** co n cert. Performance.perform( . . ) ) " ) //Antes de la actuación,


public void silenceCellPhones() {
System.out.println( "Silencing c e l l phones");
}
@Before("execution(** concert. Performance.perform(. . ) ) " ) / / Antes de la actuación.
public void takeSeatsO {
System.out.println("Taking s e a t s " ) ;
}
@AfterReturning("execution(** concert. Performance.perform( . . ) ) " )
/ / Después de la actuación.
public void applause() {
System.out.println("CLAP CLAP CLAP!!!");
}
@AfterThrowing("execution(** co n cert. Performance.perform( . . ) ) " )
/ / Después de una mala
/ / actuación.
public void demandRefund() {
System.ou t. p r in t ln ( "Demanding a refund");

}
La nueva clase A u d ie n c e está anotada con @ A s p e c t. Esta anotación indica que
A udi e n c e no es un POJO, sino un aspecto, y a lo largo de la clase A u d ie n c e hay métodos
anotados para definir las funciones específicas del aspecto.
A u d ie n c e dispone de cuatro métodos que describen acciones que el público puede
realizar mientras asiste a una actuación. Antes de la actuación, el público debe sentarse
( t a k e S e a t s ( ) ) y apagar sus teléfonos móviles ( s i l e n c e C e l l P h o n e s ()). Si la actuación
les gusta, el público debe aplaudir (a p p la u s e ()), pero si no cumple sus expectativas,
el público puede solicitar que le devuelvan el dinero de la entrada (dem andRefund ()).
Como puede apreciar, estos métodos se anotan con anotaciones de consejo para indicar
cuándo deben invocarse los métodos. AspectJ le ofrece cinco anotaciones para definir
consejos, mostradas en la tabla 4.2.

Tabla 4.2. Spring usa anotaciones de AspectJ para declarar métodos de consejos.

@After El método de consejo se invoca después de que el método aconsejado


devuelva o genere una excepción.
136 Capítulo 4

Anotación Consejo

@AfterReturning El método de consejo se invoca después de que el método aconsejado


devuelva un resultado.
@AfterThrowing El método de consejo se invoca después de que el método aconsejado
genere una excepción.
@Around El método de consejo encapsula al método aconsejado.
@Before El método de consejo se invoca antes de invocar el método aconsejado.

La clase A u d ie n c e usa tres de las cinco anotaciones de consejo. Los métodos t a k e


S e a t s () y s i l e n c e C e l l P h o n e s () se anotan con @ B e f o r e para indicar que deben
invocarse antes de que empiece la actuación. El método a p p l a u s e () se anota con
@ A f t e r R e t u r n i n g para que se invoque después de que una actuación devuelva un
resultado satisfactorio y se añade la anotación @Af t e r T h r o w in g a d em an d R efu n d ()
para que se invoque si se generan excepciones durante una excepción.
Habrá comprobado que todas estas anotaciones reciben como valor una expresión de
punto de corte, la misma en los cuatro métodos. Se podría haber asignado una expresión
diferente a cada uno pero este punto de corte en particular se adecúa a las necesidades
de todos los métodos de consejo. Si se fija atentamente en la expresión de punto de corte
asignada a las anotaciones de consejo, verá que se desencadena al ejecutar el método
p e rfo rm O en P erfo rm a n ce.
Es una pena tener que repetir cuatro veces la misma expresión de punto de corte. Este
tipo de duplicación no es recomendable. Sería más adecuado poder definir el punto de corte
una vez y después hacer referencia al mismo siempre que lo necesitemos. Afortunadamente
existe una solución: la anotación @ P o in t c u t define un punto de corte reutilizable en un
aspecto @ A sp e c t J . El siguiente código muestra el aspecto A u d ie n c e actualizado para
usar @ P o in t c u t .

Listado 4.2. Declarar una expresión de punto de corte con @Pointcut.


package concert;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;

@Aspect
public d a s s Audience {

©Pointcut("execution(** concert.Performance.perform(..))") // Definir punto de corte


// con nombre.
public void performance() {}

@Before("performance()") // Antes de la actuación,


public void silenceCellPhones() {
System.out.println("Silencing cell phones");
Spring orientado a aspectos 137

}
@Befo r e ("performance()") / / Antes de la actuación,
public void takeSeatsO {
Sy stem .o u t.p rin tln ("Taking s e a t s " ) ;
}
©AfterReturning{ "performance()") / / Después de la actuación,
public void applause() {
System .o u t.p rin tln ("CLAP CLAP CLA P!!!");
}
@AfterThrowing("performance()") / / Después de una mala actuación,
public void demandRefund() {
System.o u t.p r in tln ( "Demanding a refund");
}

En A u d ie n c e , el método p e r fo r m a n c e () se anota con @ P o in t c u t . El valor asignado


a la anotación @ P o in t c u t es una expresión de punto de corte, como las que hemos usado
antes con las anotaciones de consejo. Al anotar p e r fo r m a n c e () con @ P o in t c u t de esta
forma, básicamente ampliamos el lenguaje de expresiones de punto de corte para poder
usar p e r fo r m a n c e () en cualquier punto de sus expresiones de punto de corte en el que
usaría la expresión más extensa. Como puede apreciar, se cambia la expresión extensa por
p e r fo r m a n c e () en todas las anotaciones de consejo.
El cuerpo del método p e r fo r m a n c e () es irrelevante y, de hecho, debe dejarse vacío.
El propio método es un simple marcador que proporciona a la anotación @ P o in t c u t algo
a lo que conectarse. Aparte de las anotaciones y del método p e r fo r m a n c e ( ) , la clase
A u d ie n c e es básicamente un POJO. Sus métodos se pueden invocar como los de cualquier
otra clase de Java y se pueden probar individualmente como en cualquier otra clase de Java.
A u d ie n c e es otra clase de Java pero que está anotada para poder utilizarse como aspecto.
Y como sucede con las clases de Java, se puede conectar como bean en Spring:
@Bean
public Audience audience() {
return new Audience();
}
Si se detuviera aquí, A u d ie n c e solo sería un bean en el contenedor de Spring. Aunque
esté anotada con anotaciones de AspectJ, no se procesaría como aspecto sin algo que inter­
pretara dichas anotaciones y que creara los proxy que la convirtieran en aspecto.
Si está usando JavaConfig, puede activar los proxy automáticos aplicando la anotación @
E n a b le A s p e c t JA u to P r o x y en el nivel de clase de la clase de configuración. La siguiente
clase de configuración muestra cómo habilitar los proxy automáticos en JavaConfig.

Listado 4.3. Habilitación de proxy automáticos de anotaciones de AspectJ en JavaConfig.


package concert

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
Capítulo 4

import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;

©Configuration
@EnableAspectJAutoProxy / / H a b ilita r proxy automáticos de AspectJ.
©ComponentScan
public cla ss ConcertConfig {

@Bean
public Audience audience() { / / Declarar e l bean Audience.
return new Audience();

Si utiliza XML para conectar sus bean en Spring, tendrá que usar el elemento < a o p :
a s p e c t j -a u to p ro x y > del espacio de nombres aop de Spring. La configuración XML del
siguiente código ilustra cómo se hace.

Listado 4.4. Habilitación de proxy automáticos de AspectJ en XML con el espacio de nombres aop.
<?xml v e rsio n = "l.0" encoding="UTF-8"?>
<beans xmlns="h ttp : //www.springframework.org/schema/beans"
xm lns:xsi="h ttp : //www.w3. org/2001/XMLSchema-instance"
xmlns: context="http://www.springframework. org/schema/context"
xmlns: aop="h ttp : //www.springframework.org/schema/aop" / / Declarar e l espacio de
/ / nombres aop de Spring.
x s i -. schemaLocation= "h ttp : //www. springf ramework. org/schema/aop
h ttp : //www. springframework. org/schema/aop/spring-aop.xsd
h ttp : / /www.springframework.org/schema/beans
h ttp : / /www.springframework. org/schema/beans/spring-beans.xsd
h ttp : //www.springframework.org/schema/context
h ttp : //www.springframework.org/schem a/context/spring-context.xsd">

«context: component-scan base-package="concert" />

<aop: aspectj-autoproxy / > / / H abilitar proxy automáticos de AspectJ.

cbean class="concert.A udience" /> / / Declarar e l bean Audience.


</beans>

Independientemente de que use JavaConfig o XML, los proxy automáticos de AspectJ


usan el bean anotado con @ A sp e c t para crear un proxy alrededor de los demás bean en
los que coinciden los puntos de corte del aspecto. En este caso, se crea un proxy para el
bean C o n c e r t , con los métodqs de consejo de A u d ie n c e aplicados antes y después del
método p e r f orm ().
Conviene recordar que los proxy automáticos de AspectJ de Spring solo usan anotaciones
@ A sp e ct J como guía para crear aspectos basados en proxy. Entre bastidores, siguen siendo
aspectos de Spring basados en proxy. Es un dato relevante ya que significa que aunque
utilice anotaciones @ A sp e c t J , seguirá estando limitado a invocar métodos en proxy. Si
quiere aprovechar al máximo las prestaciones de AspectJ, tendrá que usar el tiempo de
ejecución de AspectJ sin recurrir a Spring para crear aspectos basados en proxy.
Spring orientado a aspectos 139

Llegados a este punto, su aspecto se define por medio de métodos de consejo para antes
y después del consejo, pero la tabla 4.2 menciona otro tipo: el que se aplica alrededor del
consejo. Es lo suficientemente distinto a los demás como para dedicarle un apartado propio.

Anotaciones alrededor dei consejo


Las anotaciones alrededor del consejo son el tipo de consejo más potente. Le permiten
escribir lógica para envolver el método aconsejado en su totalidad. Básicamente es como
escribir consejos de antes y después en un único método de consejo.
Para ilustrarlo, modificaremos el aspecto A udi en ce. En esta ocasión usaremos un único
método en lugar de métodos distintos para antes y después del consejo.

Listado 4.5. Nueva implementaclón del aspecto Audience con anotaciones alrededor del consejo.
package concert
import org. a s p e c tj. lang. ProceedingJoinPoint;
import o rg .a s p e c tj. lang. annotation.Around;
import o rg .a s p e c tj. lang.annotation.Aspect;
import o rg .a s p e c tj. lang. annotation.Pointcut;

@Aspect
public d a s s Audience {

@Pointcut("execution(** co n cert. Performance.perform( . . ) ) " ) / / Declarar punto de corte


/ / con nombre.
public void performance() {}

©Around("periormance ()") / / Mètodo de consejo alrededor.


public void watchPerformance(ProceedingJoinPoint jp) {
try {
System.out.println("Silencing cell phones");
System.out.println("Taking seats");
jp.proceed();
System.out.p r in tln ("CLAP CLAP CLA P!!!");
} catch (Throwable e) {
System .ou t.p rintln( "Demanding a refund");
}
}
}
En este caso, la anotación @Around indica que el método w a tc h P e rf orm ance () va a
aplicarse como un consejo alrededor del punto de corte p e rfo rm a n ce () . En este consejo,
el público apagará sus teléfonos y se sentará antes de la actuación y aplaudirá después de
la misma, y como antes, si se genera una excepción durante la actuación, el público pedirá
que le devuelvan el dinero.
Como puede apreciar, el efecto de este consejo es idéntico al que vimos con el consejo de
antes y después, pero aquí hay un solo método de consejo, mientras que antes se distribuía
entre cuatro métodos de consejo distintos.
140 Capítulo 4

En lo p rim ero que debe fija rse es que este n uevo m étod o de con sejo recibe
P r o c e e d i n g J o i n P o i n t como parámetro. Es un objeto necesario ya que indica cómo
invocar el m étodo aconsejado desde el consejo. El método de consejo hará todo lo que
necesita hacer y cuando esté listo para pasar el control al método aconsejado, invocará el
método p r o c e e d ( ) de P r o c e e d i n g J o i n P o i n t .
Es fundamental que no olvide incluir una invocación al método p ro c e e d (). En caso
contrario, el consejo bloqueará el acceso al método aconsejado. Puede que sea lo que busca
pero es muy probable que en algún momento necesite ejecutar el método aconsejado.
Al igual que puede omitir la invocación del método p ro c e e d ( ) para impedir el acceso
al método aconsejado, también puede invocarlo varias veces desde el consejo. Podría
hacerlo para implementar lógica de reintento para realizar intentos reiterados en el método
aconsejado en caso de que falle.

Procesar parámetros en consejos


Hasta el momento, los aspectos han sido sencillos, sin parámetros. La única excepción
ha sido el método w a t c h P e r f o rm a n ce () del ejemplo de consejo alrededor, que aceptaba
un parámetro P r o c e e d i n g J o i n P o i n t . En los demás casos, los consejos creados no se
molestan en examinar los parámetros pasados a los métodos aconsejados. Es correcto, ya
que el método p e r f orm () aconsejado no acepta parámetros.
Pero si su aspecto tuviera que aconsejar a un método que acepte parámetros ¿podría
acceder a los mismos y utilizarlos? Para comprobarlo, modificaremos la clase B la n k D is c
de un capítulo anterior. Actualmente, el método p la y () itera por todas las pistas e invoca
p la y T ra c k () en cada una, pero podríamos invocar directamente el método p la y T ra c k ()
para reproducir una pista concreta.
Imagine que quiere contar cuántas veces se reproduce cada pista. Podría cambiar el
método p l a y T r a c k () para controlar directamente ese contador cada vez que se invoque,
pero la lógica de recuento de pistas es una preocupación independiente a la reproducción
de una pista, por lo que no pertenece al método p l a y T r a c k (). Parece una tarea ideal
para un aspecto.
Para mantener un contador de reproducción de cada pista, crearemos T r a c k C o u n te r ,
un aspecto que aconseja a p l a y T r a c k (). El siguiente código ilustra este aspecto.

Listado 4.6. Usar consejos con parámetros para contar cuántas veces se reproduce una pista.
package soundsystem,-
import java.útil.HashMap;
import ja v a .ú t i l . Map;
import o rg .a s p e c tj. lang. annotation.Aspect;
import o rg .a s p e c tj. lang.annotation.Before;
import o rg .asp ectj.lan g .an n o tatio n .P o in tcu t;

©Aspect
public cla ss TrackCounter {

private Mapdnteger, Integer> trackCounts =


Spring orientado a aspectos 141

new HashMapcInteger, In teg er> ();

@Pointcut(
"execution(* soundsystem. CompactDisc.playTrack(in t )) " +
/ / Aconsejar e l método playTrack().
"&& args(trackNumber)")
public void trackPlayed(in t trackNumber) {}

@Before("trackPlayed(trackNumber)")
/ / Contar una p ista antes de reproducirla,
public void countTrack(int trackNumber) {
in t currentCount = getPlayCount(trackNumber);
trackCounts.put(trackNumber, currentCount + 1 );
}
public in t getPlayCount(int trackNumber) {
return trackCounts. containsKey(trackNumber)
? trackCounts.g e t (trackNumber) : 0;

}
}
Como sucede con todos los aspectos creados hasta ahora, éste usa @ P o in tc u t para
definir un punto de corte con nombre y @Bef o re para declarar un método que proporcionar
antes del consejo. Aquí la diferencia radica en que el punto de corte también declara pará­
metros que proporcionar al método de consejo. En la figura 4.6 se desarrolla la expresión
de punto de corte para mostrar dónde se especifica el parámetro.

El método Acepta un
El tipo al que pertenece el método argumento int
Devuelve
cualquier tipo

execution(* soundsystem.CompactDisc.playTrack(int))
A
&& args(trackNumber)

Especificación de argumentos
Figura 4.6. Declaración de un parámetro en una expresión de punto de corte
que se va a pasar a un método de consejo.

En la imagen, fíjese en el calificador a r g s (tra ck N u m b e r) de la expresión de punto


de corte. Indica que cualquier argumento i n t que se pase a la ejecución de p l a y T r a c k ()
también debe pasarse al consejo. El nombre del parámetro, trackN u m ber, también coincide
con el parámetro de la firma del método de punto de corte.
Se transfiere al método de consejo en el que se define la anotación @ B e f o r e con el punto
de corte con nombre, t r a c k P l a y e d (tr a c k N u m b e r ) . El parámetro del punto de corte se
alinea con el parámetro de mismo nombre del método de punto de corte, completando la
ruta del parámetro desde el punto de corte con nombre al método de consejo.
Ahora podemos configurar B la n k D is c y T r a c k C o u n te r como bean en la configura­
ción de Spring y habilitar los proxy automáticos de AspectJ, como se ilustra a continuación.
142 Capítulo 4

Listado 4.7. Configuración de TrackCounter para contar el número de reproducciones de una pista.
package soundsystem;
import ja v a .ú til.A rra y L ist;
import ja v a .u t i l .L is t;
import org. springframework. co n tex t. annotation. Bean;
import org. springframework. co n tex t. annotation.Configuration;
import org.springframework. co n tex t. annotation.EnableAspectJAutoProxy;

©Configuration
©EnableAspectJAutoProxy / / H ab ilitar proxy automáticos de AspectJ.
public cla ss TrackCounterConfig {

@Bean
public CompactDisc sgtPeppersO { / / El bean CompactDisc.
BlankDisc cd = new BlankDiscO;
c d .s e t T it le ( "S g t. Pepper's Lonely Hearts Club Band");
c d .s e tA r tis t("The B e a tle s ");
L ist<String> tracks = new A rrayL ist<Strin g >();
tr a c k s . add( "S g t. Pepper's Lonely Hearts Club Band");
tr a c k s . add("With a L it t l e Help from My F rien d s");
tr a c k s . add( "Lucy in the Sky with Diamonds");
tr a c k s . add( "Getting B e tte r ");
tr a c k s . add( "Fixing a H ole");
/ / . . . s e omiten la s demás p i s t a s ...
cd .se tT ra ck s(tra ck s);
return cd;
}
@Bean
public TrackCounter trackCounter() { / / El bean TrackCounter.
return new TrackCounter();
}
}

Por último, para demostrar que funciona, puede escribir la siguiente prueba. Reproduce
varias pistas y después confirma la cantidad de reproducciones a través del bean
T r a c k C o u n te r .

Listado 4.8. Probar el aspecto TrackCounter.


package soundsystem;
import s t a t ic org. ju n it.A s s e r t.* ;
import o rg .ju n it.A s s e r t;
import o rg .ju n it.R u le ;
import o rg .ju n it.T e s t;
import org. j u n it. co n trib . j ava. lang. system. StandardOutputStreamLog;
import o r g .ju n it. runner. RunWith;
import org. springframework.beans. fa cto ry . annotation.Autowired;
import org. springframework. t e s t . co n tex t. ContextConfiguration;
import org. springframework. t e s t . co n tex t. ju n it4 . SpringJUnit4ClassRunner;

@RunWith(SpringJUnit4ClassRunner. class)
©ContextConfiguration(classes=TrackCounterConfig. c la s s )
Spring orientado a aspectos 143

public cla ss TrackCounterTest {

@Rule
public fin a l StandardOutputStreamLog log = new StandardOutputStreamLog{);

@Autowired
p rivate CompactDisc cd;

@Autowired
p rivate TrackCounter counter;

©Test
public void testTrackCounter() {
cd .p lay T rack (l); / / Reproducir p ista s .
cd.playTrack(2);
cd.playTrack(3);
cd.playTrack(3);
cd.playTrack(3);
cd. playTrack(3) ;
cd.playTrack(7);
cd.playTrack(7);

assertE qu als(1, counter.getPlayCount(1 )); / / Confirmar los recuentos esperados.


assertE q u als(1, counter.getPlayCount(2 ));
assertE qu als(4, counter.getPlayCount(3 ));
assertE qu als(0, counter.getPlayCount(4 ));
assertE qu als(0, counter. getPlayCount(5 ));
assertE qu als(0, counter.getPlayCount(6 ));
assertE qu als(2, counter.getPlayCount(7 ));
}
}
Los aspectos con los que hemos trabajado hasta el momento envuelven métodos exis­
tentes en el objeto aconsejado, pero es uno de los trucos que pueden realizar los aspectos.
A continuación veremos cómo escribir aspectos que añaden nuevas funcionalidades a un
objeto aconsejado.

Anotar introducciones
Algunos lenguajes, como Ruby y Groovy, cuentan con el concepto de clases abiertas.
Permiten añadir nuevos métodos a un objeto o clase sin tener que cambiar directamente
la definición de esos objetos o clases. Lamentablemente, Java no es tan dinámico y, una
vez que se ha compilado una clase, hay muy poco que puede hacer para añadirle nuevas
funcionalidades.
No obstante, si lo piensa, ¿no es lo que hemos estado haciendo con los aspectos en este
capítulo? Es cierto que no hemos añadido nuevos métodos a objetos pero sí hemos añadido
nuevas funcionalidades a los métodos con los que ya contaban los objetos. Si un aspecto
puede contener métodos existentes con funcionalidades añadidas ¿por qué no añadir nuevos
métodos al objeto? De hecho, si utilizamos un concepto de AOP denominado introducción,
los aspectos pueden añadir nuevos métodos a los bean de Spring.
144 Capítulo 4

Recuerde que, en Spring, los aspectos son solo proxy que implementan la misma interfaz
que los bean que contienen. ¿Y si, además de implementar esas interfaces, los proxy también
se expusieran a través de una nueva interfaz? En ese caso, cualquier bean aconsejado por
el aspecto parecería implementar la nueva interfaz, incluso aunque su clase de implemen-
tación subyacente no lo hiciese. Es la idea que se ilustra en la figura 4.7.

Figura 4.7. Con AOP de Spring podemos incluir nuevos métodos en un bean. Un proxy intercepta
las ejecuciones y las delega a un objeto diferente que ¡mplementa el método.

Al invocar un método de la interfaz introducida, el proxy delega la invocación a otro


objeto que proporciona la implementación de la nueva interfaz. Esto nos proporciona un
bean cuya implementación se divide entre varias clases.
Pongamos esta idea en funcionamiento. Digamos que queremos introducir la siguiente
interfaz E n c o r e a b le para todas las implementaciones de P e r fo r m a n c e :
package co n cert;

public in terfa ce Encoreable {


void performEncore();
}
Lo que necesitamos es una forma de aplicar esta interfaz a las implementaciones
de P e r fo r m a n c e . Podríamos repasar todas las implementaciones de P e r fo r m a n c e y
cambiarlas para que también implementen E n c o r e a b le . Sin embargo, desde el punto
de vista del diseño, no sería la acción más prudente (ya que los conceptos E n c o r e a b le
y P e r f o r m a n c e no son mutuamente inclusivos). Además, puede que no sea posible
cambiar todas las implementaciones de P e r fo r m a n c e , sobre todo si estamos trabajando
con implementaciones de terceros y no disponemos del código fuente.
Por fortuna, las introducciones de AOP nos pueden ayudar sin que esto afecte a las
decisiones sobre el diseño o sin que sea necesario realizar cambios importantes en las
implementaciones existentes. Para poder utilizarlas, debe crear un nuevo aspecto:
package co n cert;

import o rg .a s p e c tj. lang.annotation.Aspect;


import org. aspect j . lang. annotation. DeclareParents
Spring orientado a aspectos 145

@Aspect
public cla ss Encoreablelntroducer {

@D eclareParents(value="concert. Performance+",
defaultImpl=DefaultEncoreable. class)
public s t a t ic Encoreable encoreable;

Como puede apreciar, E n c o r e a b l e l n t r o d u c e r es un aspecto, pero al contrario


de lo que sucede con los creados hasta el momento, no proporciona anotaciones antes,
después, ni alrededor del consejo. En su lugar, introduce la interfaz E n c o r e a b l e a los
bean P e r fo r m a n c e por medio de la anotación @ D e c l a r e P a r e n t s , que está formada
por tres partes:

• El atributo v a lu é identifica los tipos de bean que deben introducirse con la interfaz.
En este caso, todo lo que implemente la interfaz P e r fo r m a n c e (el signo más de la
parte final especifica cualquier subtipo de P e r fo r m a n c e , no P e r fo r m a n c e propia­
mente dicha).
• El atributo de f au 1 1 Imp 1 identifica la clase que proporciona la implementación para
la introducción, en este caso D ef a u l t E n c o r e a b l e .
• La propiedad s t a t i c anotada por @ D e c l a r e P a r e n t s especifica la interfaz que
introducir, en este caso la interfaz E n c o r e a b le .
Como sucede con cualquier aspecto, tendrá que declarar E n c o r e a b l e l n t r o d u c e r
como bean en el contexto de la aplicación de Spring:
<bean cla ss= "c o n c e rt. Encoreablelntroducer" />

A partir de aquí es hora de los proxy automáticos de Spring. Cuando Spring detecta
un bean anotado con @ A sp e c t, crea automáticamente un proxy que delega invocaciones
al bean con proxy o a la implementación de la introducción, en función de si el método
pertenece al bean o a la interfaz.
Las anotaciones y los proxy automáticos proporcionan un modelo de programación
muy útil para crear aspectos en Spring. Es sencillo y apenas requiere configuración, pero
la declaración de aspectos orientados a anotaciones supone una clara desventaja: hay que
poder anotar la clase de consejo y para ello es necesario contar con el código fuente. En
caso contrario, o si prefiere no añadir anotaciones AspectJ a su código, Spring le ofrece
otra opción para los aspectos. Veamos cómo se pueden declarar aspectos en el archivo de
configuración XML de Spring.

Declarar aspectos en XML________________ __

En un apartado anterior indiqué mi preferencia por la configuración basada en anota­


ciones antes que la basada en Java, y de la basada en Java sobre la basada en XML, pero
si tiene que declarar aspectos sin anotar la clase de consejo, tendrá que recurrir a la confi­
guración XML.
Capítulo 4

El espacio de nombres aop de Spring le ofrece diversos elementos muy útiles para
declarar aspectos en XML y que se describen en la tabla 4.3.

Tabla 4.3. Los elementos de configuración de AOP en Spring simplifican la declaración


de aspectos.

Elemento de configuración de AOP Función

<aop:advisor> Define un asesor AOP.


<aop:after> Define AOP después de un consejo (con independencia
de que el método aconsejado se devuelva de forma
correcta).
<aop:after-returning> Define un consejo AOP después de devolver un resultado.
<aop:after-throwing> Define un consejo AOP después de generarse una
excepción.
<aop:around> Define un consejo AOP alrededor.
<aop:aspect> Define un aspecto.
<aop:aspectj-autoproxy> Habilita aspectos controlados por anotaciones por medio
de @AspectJ.
<aop:before> Define un consejo AOP antes.
<aop:config> El elemento AOP de nivel superior. La mayoría de
elementos \<aop:\*\> deben incluirse dentro de
\<aop:conf ig\>.
<aop:declare-parents> Introduce interfaces adicionales para los objetos
aconsejados que se implementan de forma transparente.
<aop:pointcut> Define un punto de corte.

Ya hemos visto el elemento < a o p : a s p e e t j -a u to p ro x y > y cómo habilita los proxy


automáticos de clases de consejo anotadas con AspectJ,. pero los demás elementos del
espacio de nombres aop le permiten declarar aspectos directamente en su configuración
de Spring sin usar anotaciones.
Por ejemplo, volvamos a la clase Audi en ce. En esta ocasión eliminaremos todas las
anotaciones AspectJ:
package concert;

public cla ss Audience {

public void silenceC ellPhones() {


System .o u t.p rin tln ( "Silencing c e ll phones");
}
public void takeSeatsO {
System .o u t.p rin tln ("Taking s e a t s " ) ;
}
Spring orientado a aspectos 147

public void applause() {


System .o u t.p rin tln ("CLAP CLAP CLAP!!!");
}
public void demandRefund() {
System .o u t.p rin tln ("Demanding a refu nd");
}
}
Como puede comprobar, sin las anotaciones AspectJ no hay nada que destacar sobre la
clase A u d ie n c e . Se trata de una clase básica de Java con una serie de métodos. Asimismo,
podemos registrarla como bean en el contexto de aplicaciones de Spring al igual que cual­
quier otra clase.
A pesar de su sencilla apariencia, lo destacable de A u d ie n c e es que cuenta con todos
los elementos de un aspecto. Solo necesita una pequeña ayuda para convertirse en aspecto.

Declarar antes y después de un consejo


Podríamos volver a añadir todas las anotaciones AspectJ, pero ese no es el objetivo
de este apartado. En su lugar, utilizaremos los elementos del espacio de nombres aop de
Spring para convertir A udi e n ce en un aspecto. El siguiente ejemplo reproduce el código
XML que necesita para ello.

Listado 4.9. Clase Audience sin anotaciones, declarada como aspecto en XML.
<aop: config>
<aop:aspect ref="audience">

<aop:before / / Antes de la actuación.


pointcu t="execu tion(** co n cert. Performance.perform( . . ) ) "
method="silenceC ellPhones"/>

<aop:before / / Antes de la actuación.


pointcu t="execu tion(** co n cert. Performance.perform( . . ) ) "
method="takeSeats"/>

<aop: after-retu rn in g / / Después de la actuación.


pointcu t="execu tion(** co n cert. Performance.perform( . . ) ) "
method="applause"/>

<aop: after-throw ing / / Después de una mala actuación.


pointcut="execution(** co n cert. Performance.perform( . . ) ) "
method="demandRefund"/>
</aop:aspect>
</aop: config>

Lo primero que debemos tener en cuenta sobre los elementos de configuración AOP de
Spring es que la mayoría debe utilizarse dentro del contexto del elemento < a o p : c o n f ig > .
Hay excepciones a esta regla pero, en lo que concierne a declarar bean como aspectos,
siempre vamos a comenzar por el elemento < a o p : c o n f ig > .
148 Capítulo 4

Dentro de < a o p :c o n f i g > puede declarar uno o más asesores, aspectos o puntos
de corte. En el listado 4.9 hemos declarado un único aspecto utilizando el elemento
< a o p : a s p e e t> . El atributo r e f hace referencia al bean POJO que se va a utilizar para
proporcionar la funcionalidad del aspecto, en este caso a u d ie n c e . El bean al que se hace
referencia con el atributo r e f va a proporcionar los métodos invocados por cualquier
consejo en el aspecto.
Conviene destacar que el bean de consejo al que se hace referencia puede ser de cualquier
tipo que proporcione métodos que invocar en los pimíos de corte indicados. Esto hace que
la configuración XML para AOP de Spring sea una forma muy útil de usar tipos definidos
como consejo en bibliotecas de terceros, aunque no se puedan anotar con aspectos AspectJ.
El aspecto cuenta con cuatro tipos diferentes de consejos. Los dos elem entos
< a o p .- b e f o r e > definen el método antes del consejo que va a invocar los métodos t a k e -
S e a t s () y s i l e n c e C e l l P h o n e s () (declarados por el atributo m eth o d ) del bean
A u d ie n c e , antes de que se invoque cualquier método que coincida con el punto de corte.
El elemento < a o p : a f t e r - r e t u r n i n g > define un consejo tras la invocación del método
a p p l a u s e () después del punto de corte. Mientras tanto, el elemento < a o p : a f t e r -
th r o w in g > define un consejo después de generarse una excepción para invocar el método
dem andR ef und ( ), en caso de que se produzca alguna. La figura 4.8 muestra cómo se
integra la lógica del consejo en la de negocio.

Lógica de negocio Aspecto del público Lógica del consejo

<aop:before try {
method="takeSeats" audience.takeSeats() ;
pointcut-ref="performance"/>

<aop:before
method="turnOffCellPhones" audience.turnOffCellPhones() ;
pointcut-ref="performance"/>

performance.perform();

<aop:after-returning
method="applause" audience.applause() ;
pointcut-ref="performance"/>

<aop:after-throwing } catch (Exception e) {


method="demandRefund" audience.demandRefund();
pointcut-ref="performance"/> }

Figura 4.8. El aspecto Audience incluye cuatro fragmentos de consejo que integran su lógica
con los métodos que coinciden con el punto de corte del aspecto.

En todos los elementos del consejo, el atributo p o in t e u t define el punto de corte en el


que se va a aplicar el consejo. El valor proporcionado a este atributo va a ser un punto de
corte definido mediante la sintaxis de expresiones de punto de corte de AspectJ.
Spring orientado a aspectos 149

Probablemente se haya dado cuenta de que el valor del atributo p o in t c u t es el mismo


para todos los elementos del consejo. Se debe a que todo el consejo se está aplicando al
mismo punto de corte.
Cuando encontramos el mismo tipo de duplicación en el consejo con anotaciones AspectJ,
la eliminamos con la anotación @ P o in tc u t. Sin embargo, en declaraciones de aspectos
basados en XML tendrá que usar el elemento < a o p : p o in t c u t >. El siguiente código XML
muestra cómo extraer la expresión común de punto de corte en una única declaración de
punto de corte que usar en todos los elementos de consejo.

Listado 4.10. Definición de un punto de corte con nombre con <aop:pointcut>.


<aop:config>
<aop:aspect ref="audience">
<aop¡pointcut // Definir punto de corte.
id="performance"
expression="execution(** concert.Performer.perform(..))"/>

<aop:before // Referencias al punto de corte,


pointcut-re f= "performance"
method="silenceCellPhones" />
<aop:before
pointcut-ref= "performance"
method="takeSeats"/>
<aop:after-returning
pointcut-ref="performance"
method="applause" />

<aop:after-throwing
pointcut-ref= "performance"
method="demandRefund" />
</aop:aspect>
</aop:config>

Ahora, el punto de corte está definido en una única ubicación y se hace referencia al
mismo a través de varios elementos de consejo. El elemento < a o p : p o i n t c u t > define el
punto de corte para que tenga el id p e rfo rm a n ce . Mientras tanto, todos los elementos
de consejo se han modificado para que hagan referencia al punto de corte con nombre con
el atributo p o i n t c u t - r e f .
Tal y como se utiliza en el listado 4.10, el elemento < a o p : p o i n t c u t > define un punto
de corte al que pueden hacer referencia todos los consejos dentro del mismo elemento
< a o p : a s p e c t > . Además, también puede definir puntos de corte que se utilicen entre
diferentes aspectos, incluyendo los elementos < a o p : p o i n t c u t > dentro del ámbito del
elemento < a o p : c o n f ig > .

Declarar alrededor de! consejo


La implementación actual de Audi e n ce funciona muy bien. Sin embargo, las tareas
que tienen lugar antes y después del consejo presentan algunas limitaciones. En concreto,
es complejo compartir información sin recurrir a almacenarla en variables miembro.
150 Capítulo 4

Supongamos que, por ejemplo, además de desconectar los teléfonos móviles y de


aplaudir al final, queremos que el público mire sus relojes e informe sobre la duración
de la actuación. La única forma de conseguirlo, antes y después del consejo, es anotar la
hora de inicio antes del consejo y notificar la duración después. Sin embargo, para esto,
tendríamos que almacenar la duración en una variable miembro. Como Audi e n ce es de
instancia única, no sería seguro para el subproceso almacenar un estado de esta forma.
La declaración alrededor del consejo tiene una ventaja sobre la que se hace antes y
después de éste. Con este tipo de declaración, podemos conseguir el mismo objetivo pero
utilizando un único método. Puesto que todo el conjunto de consejos tiene lugar en un
único método, no va a haber necesidad de almacenar un estado en una variable miembro.
Por ejemplo, fíjese en la nueva clase Audi e n ce sin anotaciones con un único método
w a tc h P e r fo r m a n c e ().

Listado 4.11. El método watchPerformance() incluye AOP alrededor del consejo.

package concert;
import o rg . aspectj . lang. ProceedingJoinPoint;

public c la ss Audience {

public void watchPerformance(ProceedingJoinPoint jp) {


try {
System .o u t.p rin tin ("Silencing c e ll phones"); / / Antes de la actuación.
System.o u t.p r in t in ("Taking s e a ts " ); / / Antes de la actuación.
jp .p ro ceed O ; / / Continuar al método aconsejado.
' System.out.println("CLAP CLAP CLA P!!!"); / / Después de la actuación.
} catch (Throwable e) {
System.o u t.p r in tln ( "Demanding a refund"); / / Después de una mala actuación.

En el caso del aspecto del público, el método w a tc h P e rf orm ance () contiene toda
la funcionalidad de los cuatro métodos de consejo previos, aunque incluidos en un único
método, responsable de su propia gestión de excepciones. Las declaraciones alrededor del
consejo no se diferencian mucho de las de los demás tipos de consejo. Todo lo que tiene
que hacer es utilizar el elemento <ao p _aro u n d >, mostrado a continuación.

Listado 4.12. Declaración alrededor del consejo en XML con el elemento <aop_around>.

<aop: config>
<aop:aspect r e f ="audience">
<aop:pointcut
id= "performance"
expression^1execution(** co n cert. Performance.perform( . . ) ) " />
<aop:around / / Declaración alrededor del consejo,
pointcu t- r e f ="performance"
method="watchPerformance"/>
</aop:aspect>
</aop: config>
Spring orientado a aspectos 151

Al igual que con el resto de elementos XML de consejo, a < a o p : around> se le propor­
ciona un punto de corte y el nombre de un método de consejo. En este caso, utilizamos el
mismo punto de corte que antes, aunque hemos configurado el atributo m ethod para que
haga referencia al nuevo método w a tc h P e rf orm ance ().

Pasar parámetros a consejos


En un apartado anterior usamos anotaciones AspectJ para crear un aspecto encargado
de un contador de reproducciones de pistas en C om pactD isc. Ahora que estamos confi­
gurando los aspectos en XML, veamos cómo hacer lo mismo.
En primer lugar, eliminamos todas las anotaciones @ A spect J de T ra ck C o u n ter.

Listado 4.13. TrackCounter sin anotaciones.

package soundsystem;
import java.útil.HashMap;
import java.útil.Map;

public class TrackCounter {

private Map<Integer, Integer> trackCounts =


new HashMapcInteger, Integer>();

public void countTrack(int trackNumber) { // Declaración de método antes del consejo,


int currentCount = getPlayCount(trackNumber);
trackCounts.put(trackNumber, currentCount + 1);
}
public int getPlayCount(int trackNumber) {
return trackCounts.containsKey(trackNumber)
? trackCounts.get(trackNumber) : 0;
}
}
Sin las anotaciones de AspectJ, T r a c k C o u n te r parece desnudo y en su estado actual
no contará ninguna pista a menos que invoquemos explícitamente el método c o u n t
T r a c k ( ) , pero con ayuda de la configuración XML de Spring podemos restaurar el estado
de T r a c k C o u n te r como aspecto.
El siguiente código muestra la configuración completa de Spring que declara tanto el bean
T r a c k C o u n te r como el bean B la n k D is c y que habilita T r a c k C o u n te r como aspecto.

Listado 4.14. Configuración de TrackCounter en XML como aspecto con parámetros.

<?xml v e rsio n = "l.0" encoding="UTF-8"?>


cbeans xmlns="h ttp : //www.springframework.org/schema/beans"
xmlns: x s i= "h ttp : / /www.w3. org/2001/XMLSchema-in stan ce"
xmlns: aop="h ttp : //www.springframework.org /schema/aop"
x s i : schemaLocation=
"h ttp : //www. springframework. org/schema/aop
h ttp : //www.springframework.org/schema/aop/spring-aop.xsd
h ttp : //www.springframework.org/schema/beans
152 Capítulo 4

h ttp : //www.springframework. org/schema/beans/spring-beans.xsd" >


<bean id="trackCounter"
class="soundsystem.TrackCounter" /> / / El bean TrackCounter.
«bean id="cd"
class="soundsystem.BlankDisc"> / / E l bean BlankDisc.
«property nam e="title"
value="Sgt. Pepper's Lonely Hearts Club Band" />
«property nam e="artist" value="The B eatles" />
«property name="tracks">
< lis t>
<value>Sgt. Pepper's Lonely Hearts Club Band«/value>
<value>With a L it t l e Help from My Friends«/value>
<value>Lucy in the Sky with Diamonds«/value>
<value>Getting Better«/value>
<value>Fixing a Hole«/value>
< !- - . . . s e omiten la s demás p i s t a s ... -->
< / l is t >
«/property>
</bean>
«aop: config>
<aop:aspect ref="trackCounter"> / / Declarar TrackCounter como bean.
<aop:pointcut id="trackPlayed" expression=
"execu tion(* soundsystem.CompactDisc.playTrack(int))
and args(trackNumber)" />
<aop:before
pointcut-ref="trackPlayed"
method="countTrack"/>
</aop:aspect>
</aop: config>
</beans>

Como puede apreciar, usamos los mismos elementos XML del espacio de nombres
aop que antes; declaran un POJO que se procesará como aspecto. La única diferencia
significativa es que ahora la expresión de punto de corte incluye un parámetro que se va
a pasar al método de consejo. Si la compara con la del listado 4.6, verá que prácticamente
son idénticas. La única diferencia es que aquí usamos la palabra clave and en lugar de &&
(ya que en XML estos signos se interpretan como inicio de una entidad).
Después de utilizar el espacio de nombres aop de Spring para declarar aspectos básicos
en XML, veamos cómo usarlo para declarar aspectos de introducción.

Incluir nuevas funcionalidades mediante aspectos


Con anterioridad hemos visto cómo utilizar la anotación @ D e c la re P a re n t s de AspectJ
para introducir un nuevo método en un bean aconsejado, pero las introducciones de AOP
no son exclusivas de AspectJ. Por medio del elemento <aop : d e c l a r e - p a r e n t s > del
espacio de nombres aop de Spring puede hacer lo mismo en XML. El siguiente fragmento
de XML es equivalente a la anotación basada en AspectJ que creamos antes:
«aop: aspect>
«aop:declare-parents
types-m atching="concert. Performance+"
Spring orientado a aspectos 153

im plem ent-interface="concert. Encoreable"


d efa u lt- impl="co n cert.DefaultEncoreable"
/>
</aop: aspect>

Como su nombre indica, < a o p : d e c l a r e - p a r e n t s > declara que los bean que aconseja
tendrán nuevos principales en su jerarquía de objetos. En este caso concreto, indicamos que
los bean cuyo tipo coincida con la interfaz P e r fo r m a n c e (de acuerdo al atributo t y p e s -
m a tc h in g ) deben tener E n c o r e a b l e en su parentesco (por el atributo im p le m e n t-
i n t e r f a c e ) . El último aspecto que aclarar es de dónde proviene la implementación de
los métodos de E n c o r e a b le .
Hay dos formas de identificar la implementación de la interfaz introducida. En este
caso, usamos el atributo d e f a u l t - im p l para identificar explícitamente la implementación
por su nombre de clase totalmente calificado. También se podría identificar por medio del
atributo d e l e g a t e - r e f :
<aop:aspect>
<aop: declare-parents
types-matching="con cert. Performance+"
implement-in te rfa c e = "co n cert. Encoreable"
d eleg a te -re f="encoreableDelegate"
/>
</aop: aspect>

El atributo d e l e g a t e - r e f hace referencia a un bean de Spring como delegado de intro­


ducción. Asume que en el contexto de Spring existe un bean con el ID e n c o r e a b le D e le g a te :
cbean id="encoreableDelegate"
class="concert.D efaultEncoreable" />

La diferencia entre identificar directamente el delegado con d e f a u l t - im pl e indirec­


tamente con d e l e g a t e - r e f es que la última opción puede ser un bean de Spring que
puede inyectarse automáticamente o configurarse a través de Spring.

Inyectar aspectos de Aspectj


Aunque AOP de Spring es suficiente para la mayor parte de las aplicaciones de los
aspectos, se trata de una solución AOP débil en comparación con AspectJ, que ofrece un
gran número de puntos de corte cuyo uso no está disponible en AOP de Spring. Por ejemplo,
los puntos de corte de constructor son adecuados cuando tiene que aplicar consejos sobre
la creación de un objeto. A diferencia de los constructores en otros lenguajes orientados a
objetos, los de Java se diferencian de los métodos normales. Esto hace que la AOP basada
en proxy de Spring no sea adecuada para aconsejar la creación de un objeto.
En su mayor parte, los aspectos de AspectJ son independientes de Spring. Aunque
pueden integrarse en cualquier aplicación de Java, incluyendo las de Spring, hay muy poca
implicación por parte de Spring en la aplicación de los aspectos de AspectJ.
154 Capítulo 4

Sin embargo, cualquier aspecto diseñado de manera adecuada y con significado va a


depender de otras clases para que le ayuden en su trabajo. Si un aspecto depende de una
o más clases al ejecutar su consejo, puede instanciar esos objetos con el aspecto. O incluso
mejor, puede utilizar la inyección de dependencias de Spring para inyectar bean en aspectos
de AspectJ.
Para ver un ejemplo, vamos a crear un nuevo aspecto para actuaciones, en concreto un
aspecto que desempeñe el papel de crítico que asiste a una actuación y después hace su
crónica. El nombre del aspecto será C r i t i c A s p e c t .

Listado 4.15. Implementación de un crítico de la actuación por medio de AspectJ.

package concert;
public aspect C riticA spect {
public C riticA sp ect() {}
pointcut performance() : execu tion{* perform( . . ) ) ;

afterR etu rning() : performance() {


System .ou t.println(criticism E ngine. g etC riticism ( ) ) ;
}
private CriticismEngine criticism Engine;
public void setCriticism Engine(Criticism Engine criticism Engine) {
/ / Inyectar CriticismEngine.
t h i s . criticism Engine = criticism Engine;
}

La principal responsabilidad de C r i t i c A s p e c t va a ser realizar comentarios sobre


una actuación después de que haya terminado. El punto de corte p e r f o r m a n c e ()
del listado 4.15 coincide con el método p e r f orm () . Cuando se combina con el consejo
a f t e r R e t u r n i n g ( ) , vamos a disponer de un aspecto que va a reaccionar al final de una
actuación. Lo que hace interesante al listado 4.15 es que el crítico no realiza comentarios por
sí solo. En su lugar, C r i t i c A s p e c t colabora con un objeto C r it ic is m E n g i n e , invocando
su método g e t C r i t i c i s m () para generar comentarios críticos después de una actuación.
Para evitar un acoplamiento innecesario entre C r i t i c A s p e c t y C r i t i c i s m E n g i n e , a
C r i t i c A s p e c t se le proporciona una referencia a C r i t i c i s m E n g i n e mediante la inyec­
ción de un establecedor. Esta relación se muestra en la figura 4.9.
CriticismEngine es una interfaz que declara un método getCriticism ( ) . En el
listado 4.16 se muestra la implementación de CriticismEngine.

Listado 4.16. Interfaz CriticismEngine para inyectar en CriticAspect.

package com .springinaction.springidol;

public cla ss CriticismEnginelmpl implements CriticismEngine {


public CriticismEnginelmpl() {}

public String g etC riticism () {


in t i = (int) (Math.random() * criticism P o o l. le n g th );
return c r itic is m P o o l[i];
Spring orientado a aspectos 155

/ / inyectado
private String[] criticism P o ol;
public void setCriticistnPool (String [] criticism P ool) {
t h i s . criticism P ool = criticism P o ol;

Figura 4.9. Los aspectos también necesitan la inyección. Spring puede inyectar aspectos AspectJ
con dependencias como si fueran cualquier otro bean.

C r i t ic is m E n g i n e lm p l implementa la interfaz C r i t i c i s m E n g i n e al seleccionar, de


forma aleatoria, un comentario crítico de un conjunto de críticas inyectadas. Esta clase se
puede declarar como <bean > de Spring utilizando el siguiente código XML:
<bean id="criticism Engine"
class="com .sp ringinaction.sp ring id ol. CriticismEnginelmpl">
<property nam e="criticism s">
< lis t>
<value>Worst performance ev er! </value>
<value>I laughed, I cried , then I realized I was at the
wrong show.</value>
<value>A must see show!</value>
< / l is t >
</property>
</bean>

Ahora contamos con una implementación C r i t i c i s m E n g i n e que podemos propor­


cionar a C r i t i c A s p e c t . Todo lo que queda es conectar C r i t i c i s m E n g i n e l m p l con
C r itic A s p e c t.
Antes de mostrar cómo llevar a cabo la inyección, debe saber que los aspectos de AspectJ
pueden integrarse en su aplicación sin que sea necesario recurrir a Spring. Sin embargo, si
quiere utilizar la inyección de dependencias para inyectar colaboradores en un aspecto de
AspectJ, tendrá que declarar el aspecto como un < b e a n > en la configuración de Spring.
La siguiente declaración < b e a n > inyecta el bean C r i t i c i s m E n g i n e en C r i t i c A s p e c t :
<bean c la s s = "com.sp rin g in actio n .sp rin g id o l. C riticA spect"
factory-method="aspectOf">
<propertyname="CriticismEngine"ref= "Criticism Engine"/>
</bean>
156 Capítulo 4

En su mayor parte, esta declaración < b ea n > no se diferencia de cualquier otra que pueda
encontrar en Spring. La gran diferencia radica en el uso del atributo f a c t o r y -m e t h o d .
Normalmente, el contenedor de Spring instancia los bean. Sin embargo, los aspectos de
AspectJ se crean en tiempo de ejecución. Cuando Spring tenga la oportunidad de inyectar
C r i t i c i s m E n g i n e en C r i t i c A s p e c t , éste ya se habrá instanciado.
Como Spring no es responsable de la creación de C r i t i c A s p e c t , no podemos limitarlos
a declarar Ju d g e A sp e c t como bean en Spring. En su lugar, necesitamos una forma de que
Spring pueda acceder a la instancia de C r i t i c A s p e c t ya creada por AspectJ para poder
inyectarle un elemento C r i t i c i s m E n g i n e . Por fortuna, todos los aspectos de AspectJ
cuentan con un método a s p e c t O f () que devuelve la instancia única del aspecto. Por
tanto, para obtener una instancia del aspecto, debe utilizar f a c t o r y -m e t h o d para ejecutar
el método a s p e c t O f ( ) , en lugar de intentar invocar el constructor de C r i t i c A s p e c t .
En resumen, Spring no utiliza la declaración <bean > de antes para crear una instancia
de C r i t i c A s p e c t (ya la ha creado el tiempo de ejecución de AspectJ). En su lugar, Spring
recupera una referencia al aspecto mediante el método de factoría a s p e c t O f O y, a conti­
nuación, ejecuta la inyección de dependencias sobre éste, tal y como indica el elemento
<bean>.

Resumen
AOP es un potente complemento a la programación orientada a objetos. Con los aspectos
puede agrupar comportamientos de aplicación que antes estaban distribuidos a lo largo de
ésta y convertirlos en módulos reutilizables. Puede declarar exactamente dónde y cuándo
se va a aplicar este comportamiento, lo que permite reducir la duplicación del código y
que sus clases se centren en su funcionalidad principal.
Spring cuenta con un marco de trabajo AOP que le permite insertar aspectos alrededor
de las ejecuciones de los métodos. Ha aprendido a integrar consejos antes, después y alre­
dedor de la invocación de un método, así como añadir comportamientos personalizados
para la gestión de excepciones.
Dispone de diferentes opciones para utilizar los aspectos en sus aplicaciones de Spring.
Conectar consejos y puntos de corte es más fácil en Spring gracias a la inclusión de la
compatibilidad con anotaciones @ A sp e c t J , así como de un esquema de configuración
simplificado.
Por último, hay ocasiones en las que AOP de Spring no es lo bastante potente y en las
que debe recurrir a AspectJ para contar con aspectos más potentes. En esas situaciones, le
hemos mostrado cómo utilizar Spring para inyectar dependencias en aspectos de AspectJ.
Llegados a este punto, hemos cubierto los aspectos básicos del marco de trabajo de
Spring. Ya ha visto cómo configurar el contenedor de Spring y cómo aplicar aspectos a
objetos gestionados por Spring. Como ha podido comprobar, estas técnicas principales le
permiten crear aplicaciones formadas por objetos acoplados de forma débil.
A continuación, nos centraremos en la creación de aplicaciones reales en Spring. En
concreto, en el siguiente capítulo veremos cómo crear aplicaciones Web con Spring.
Parte II.
Spring en la Web

Spring suele utilizarse para desarrollar aplicaciones Web, por lo que en esta segunda
parte del libro aprenderemos a usar el marco de trabajo MVC de Spring para añadir una
interfaz Web a sus aplicaciones.
En el capítulo 5 va a aprender los aspectos básicos del uso de Spring MVC, un marco
de trabajo Web basado en los principios del marco de trabajo Spring. Aprenderá a crear
controladores para gestionar solicitudes Web y a vincular parámetros de solicitud y de
carga de trabajo a sus objetos de negocio, al mismo tiempo que incluye características de
validación y de control de excepciones.
En el capítulo 6, es la continuación del anterior y muestra cómo utilizar datos de modelo
generados por controladores de Spring MVC y representarlos como HTML para los nave­
gadores de los usuarios. En el capítulo también analizaremos JSP (JavaServer P ages, Páginas
JavaServer), Apache Tiles y las plantillas Thymeleaf.
En el capítulo 7 veremos técnicas avanzadas para la creación de aplicaciones Web,
incluyendo opciones de configuración personalizadas de Spring MVC, el procesamiento
de transferencias de archivos multiparte y la transferencia de datos entre solicitudes por
medio de atributos flash.
En el capítulo 8 aprenderá a crear aplicaciones Web conversacionales basadas en flujos
mediante el marco de trabajo Spring Web Flow.
Como la seguridad es un aspecto muy importante para muchas de sus aplicaciones, en
el capítulo 9 aprenderá a utilizar la seguridad de Spring para proteger la información que
contiene su aplicación.
Crear ap lica cio n e s
W eb de S p rin g

CONCEPTOS FUNDAMENTALES:

Asignación de solicitudes a controladores de Spring.


Vinculación transparente de parámetros
de formularios.
Validación de envíos de formularios.
Como desarrollador profesional en Java, probablemente ya haya creado más de una
aplicación basada en la Web. Para muchos desarrolladores de Java, este tipo de aplica­
ciones son su principal campo de trabajo. Si cuenta con este tipo de experiencia, estará
familiarizado con los retos que plantean estos sistemas. En concreto, la gestión de estados,
los flujos de trabajo y la validación son características importantes a las que hay que hacer
frente. Ninguna de ellas es tarea sencilla, dada la ausencia de estado del protocolo HTTP.
El marco de trabajo Web de Spring está diseñado para ayudarle a hacer frente a estos
problemas. Basado en el patrón Modelo-Vista-Controlador (MVC), Spring MVC le ayuda
a crear aplicaciones basadas en la Web que son flexibles y cuentan con acoplamiento débil,
al igual que el propio marco de trabajo Spring.
En este capítulo, vamos a hablar sobre el marco de trabajo Spring MVC. Nos centraremos
en el uso de anotaciones para crear controladores que nos permitirán gestionar solicitudes
Web, parámetros y entrada de formularios. Antes de entrar en los aspectos específicos de
Spring MVC, vamos a comenzar por un repaso general y por configurar los elementos
básicos para que funcione.

introducción a Spring MVC


¿Ha jugado alguna vez a la ratonera? Es un juego infantil en el que hay que hacer pasar
una pequeña bola de acero a través de una serie de artilugios hasta conseguir hacer saltar una
ratonera. La bola pasa a través de curvas sinuosas, pequeños martillos, barcos en miniatura
e incluso una catapulta. Si la bola llega hasta el final, la ratonera cae sobre un pequeño ratón
de plástico. A primera vista, puede que piense que el marco de trabajo Spring MVC se parece
mucho a este juego. En lugar de mover una bola a través de diferentes artilugios, Spring
mueve las solicitudes entre un servlet distribuidor, asignaciones de gestores, controladores
y solucionadores de vistas. Sin embargo, la comparación acaba ahí, puesto que cada uno
de los componentes de Spring MVC lleva a cabo una función específica.
Veamos el ciclo de vida de una solicitud que, desde el cliente, recorre los componentes
de Spring MVC para generar, en última instancia, una solicitud que se devuelve al cliente.

Seguir el ciclo de vida de una solicitud


Cada vez que un usuario hace clic en un enlace o envía un formulario en su navegador
Web, se pone en marcha una solicitud. Al igual que un cartero o un trabajador de una
empresa de mensajería, una solicitud vive para llevar información de una parte a otra.
La solicitud siempre está ocupada. Desde que deja el navegador hasta que vuelve con
una respuesta realiza varias paradas, y en cada una deja información y recoge otra. En la
figura 5.1 podemos ver todas las paradas que realiza la solicitud.
Cuando la solicitud abandona el navegador O, lleva información sobre lo que el usuario
solicita. Como mínimo, la solicitud va a incluir la URL solicitada. Sin embargo, también
puede llevar consigo información adicional, como por ejemplo la enviada por el usuario
a través de un formulario.
160 Capítulo 5

Figura 5.1. Una solicitud transporta información durante varias etapas hasta llegar
a los resultados deseados.

La primera parada de la solicitud tiene lugar en D i s p a t c h e r S e r v l e t de Spring. Al


igual que la mayoría de marcos de trabajo Web basados en Java, Spring MVC canaliza las
solicitudes a través de un único servlet controlador. Un controlador frontal es un patrón
común de aplicación Web en el que un único servlet delega la responsabilidad de una
solicitud a otros componentes de una aplicación para llevar a cabo el procesamiento. En el
caso de Spring MVC, D i s p a t c h e r S e r v l e t sería el controlador frontal.
La tarea de D i s p a t c h e r S e r v l e t es enviar la solicitud a un controlador de Spring
MVC. Un controlador es un componente de Spring que procesa la solicitud. Sin embargo,
una aplicación típica puede tener varios controladores y D i s p a t c h e r S e r v l e t tiene que
decidir cuál elegir. Para ello, consulta con una o más asignaciones de controlador para
decidir cuál va a ser la siguiente parada de la solicitud ©. La asignación del controlador va a
prestar especial atención a la URL que transporta la solicitud a la hora de tomar la decisión.
Una vez se ha seleccionado un controlador adecuado, D i s p a t c h e r S e r v l e t envía
la solicitud en su camino hacia el controlador elegido ©. En éste, la solicitud va a soltar
su carga (la información enviada por el usuario) y a esperar con paciencia mientras el
controlador la procesa (en realidad, un controlador bien diseñado no lleva a cabo tareas
de procesamiento y, en su lugar, delega la responsabilidad de la lógica de negocio a uno o
más objetos de servicio).
A menudo, la lógica llevada a cabo por un controlador implica que parte de la informa­
ción tiene que enviarse de nuevo al usuario y mostrarse en el navegador. Esta información
recibe el nombre de modelo. Sin embargo, enviar información sin procesar al usuario no
es suficiente: necesita un formato para que éste pueda consultarla (por lo general, HTML).
Para ello, a la información se le tiene que asignar una vista, normalmente una JSP.
Una de las últimas tareas que lleva a cabo un controlador es empaquetar los datos
del modelo e identificar el nombre de la vista que debe generar el resultado. A conti­
nuación, envía la solicitud, junto con el modelo y el nombre de la vista, de vuelta a
D i s p a t c h e r S e r v l e t O.
Hasta el momento, el controlador no se acopla a ninguna vista y el nombre de la vista
devuelto a D i s p a t c h e r S e r v l e t no identifica una JSP específica. En concreto, ni siquiera
tiene que sugerir que la vista sea una JSP. En su lugar, solo cuenta con un nombre lógico que
Crear aplicaciones Web de Spring 161

se va a utilizar para examinar la vista que va a generar el resultado. D i s p a t c h e r S e r v l e t


va a consultar a un solucionador de vistas para asignar el nombre de la vista lógica a una
implementación de vista específica, que puede ser o no una JSP ©.
Ahora que D i s p a t c h e r S e r v l e t sabe qué vista va a procesar el resultado, el trabajo
de la solicitud está prácticamente terminado. Su última parada es la implementación de la
vista ©, por lo general una JSP, donde entrega los datos del modelo. La vista va a utilizarlos
para generar el resultado que el objeto de respuesta va a devolver al cliente O.
Como puede apreciar, una solicitud realiza varios pasos para generar una respuesta
para el cliente. La mayoría tiene lugar en Spring MVC, en los componentes ilustrados en la
figura 5.1. Aunque a lo largo del capítulo nos centraremos en la creación de controladores,
antes tenemos que configurar los componentes esenciales de Spring MVC.

Configurar Spring MVC


Según la figura 5.1 parece que hay muchos elementos que configurar. Afortunadamente,
gracias a las mejoras de las últimas versiones de Spring, es muy sencillo empezar a usar
Spring MVC. Por el momento, adoptaremos el enfoque más sencillo de configuración:
poder ejecutar los controladores que creemos. En un capítulo posterior veremos opciones
de configuración adicionales.

Configurar DispatcherServlet
En el corazón de Spring MVC se encuentra D i s p a t c h e r S e r v l e t , el primer contacto
de la solicitud con el marco de trabajo, y responsable de dirigir la solicitud a través de los
demás componentes.
Históricamente, servlets como D i s p a t c h e r S e r v l e t se han configurado en un archivo
w eb .x m l del archivo WAR de la aplicación Web. Es una posibilidad, pero gracias a los
nuevos avances de la especificación Servlet 3 y de Spring 3.1, no es la iónica, ni la que
usaremos en este capítulo.
En lugar de un archivo web. xm l, usaremos Java para configurar D i s p a t c h e r S e r v l e t
en el contenedor de servlet. En el siguiente listado se muestra la clase de Java necesaria.

Listado 5.1. Configuración de DispatcherServlet.

package s p i t t r . conf ig (-

import org.springframework.web. s e r v le t. support.


AbstractAnnotationConf ig D isp a tch e rS erv letln itia liz er

public cla ss SpittrW ebAppInitializer


extends A bstractA nnotationC onfigD ispatcherServletlnitializer {

@Override
protected Strin g [] getServletMappings() { / / Asignar D ispatcherServlet a / .
return new StringH { "/" };
162 Capítulo 5

©Override
protected Class<?>[] getRootConfigClasses() {
return new Class<?>[] { RootConfig. cla ss };
}
©Override
protected Class<?>[] getServletC onfigC lasses() {
/ / E sp e cificar clase de configuración.
return new Class<?>[] { WebConfig.class };
}

Antes de entrar en los detalles del código, seguramente se pregunte qué sentido tiene
la palabra spittr. La clase se llama S p i t t r W e b A p p I n i t i a l i z e r , del paquete s p i t t r .
c o n f ig . Lo explicaremos en breve, pero por el momento basta con recordar que el nombre
de la aplicación que crearemos es Spittr.
Para entender el funcionamiento del código anterior, debe saber que cualquier clase que
amplíe A b s t r a e t A n n o ta t io n C o n f i g D i s p a t c h e r S e r v l e t I n i t i a l i z e r se utilizará
automáticamente para configurar D i s p a t c h e r S e r v l e t y el contexto de aplicaciones de
Spring en el contexto de servlet de la aplicación.

Si necesita una explicación más detallada, aquí la tiene. En un entorno Servlet


3.0, el contenedor busca clases de la ruta de clases que implementen la interfaz
javax.servlet.ServletContainerlnitializer; si las encuentra, se usan para
configurar el contenedor de servlet. Spring proporciona una implementación de
dicha interfaz, SpringServletContainerinitializer, que a su vez busca clases
que implementen webApplicationinitializer y les delega la configuración.
Spring 3.2 añadió una im plementación base de webApplicationinitializer
denom inada AbstractAnnotationConfigDispatcherServletInitializer.
Como en nuestro caso SpittrWebAppInitializer amplía AbstractAnnotation
Conf igDispatcherServletlnit ializer (y por lo tanto implementa WebAppl icatión
initializer), se detectará automáticamente al implementarse en un contenedor
Servlet 3.0 y se usará para configurar el contexto de servlet.

Aunque su nombre sea realm ente extenso, A b s t r a c t A n n o t a t i o n C o n f i g


D i s p a t c h e r S e r v l e t l n i t i a l i z e r es muy fácil de usar. Si se fija en el listado 5.1, verá
que S p i t t r W e b A p p I n i t i a l i z e r reemplaza tres métodos. El primero, g e t S e r v l e t
M app in gs ( ) , identifica una o varias rutas al D i s p a t c h e r S e r v l e t al que se va a asignar.
En este caso se asigna a / para indicar que será el servlet predeterminado de la aplicación.
Procesará todas las solicitudes que reciba la aplicación.
Para entender los otros dos métodos, primero debe comprender la relación entre
D i s p a t c h e r S e r v l e t y un escuchador de servlet denominado C o n te x t L o a d e r L is t e n e r .
Crear aplicaciones Web de Spring 163

Historia de dos contextos de aplicación


Cuando se inicia D i s p a t c h e r S e r v l e t , crea un contexto de aplicaciones de Spring
y lo carga con los bean declarados en los archivos o clases de configuración que ha reci­
bido. Con el método g e t S e r v l e t C o n f i g C l a s s e s () del listado 5.1, le pedimos a ese
D i s p a t c h e r S e r v l e t que cargue su contexto de aplicación con los bean definidos en la
clase de configuración WebConf i g (mediante configuración de Java).
Pero en las aplicaciones Web de Spring suele haber otro contexto de aplicaciones, creado
por C o n t e x t L o a d e r L i s t e n e r .
Mientras que D i s p a t c h e r S e r v l e t debe cargar los bean que contengan compo­
nentes Web como controladores, solucionadores de vistas y asignaciones de controlador,
C o n t e x t L o a d e r L i s t e n e r debe cargar los demás bean de su aplicación. Estos bean
suelen ser componentes del nivel intermedio y del nivel de datos que controlan la parte
de servidor de la aplicación.
Entrebastidores, A b s t r a e tA n n o ta tio n C o n f ig D is p a t c h e r S e r v l e t l n i t i a l i z e r
crea tanto D i s p a t c h e r S e r v l e t como C o n t e x t L o a d e r L i s t e n e r . Las clases
@Conf i g u r a t i o n devueltas desde g e t S e r v l e t C o n f i g C l a s s e s () definirán bean para
el contexto de aplicación de D i s p a t c h e r S e r v l e t . Mientras tanto, se utiliza g e t R o o t
Conf i g C l a s s e s () devuelto por la clase @C onf i g u r a t i o n para configurar el contexto
de aplicación creado por C o n t e x t L o a d e r L i s t e n e r .
En este caso, nuestra configuración raíz se define en R o o tC o n f i g , mientras que la de
D i s p a t c h e r S e r v l e t se declara en W ebConf ig . Veremos estas dos clases de configura­
ción en breve.
Conviene destacar que la configuración de D i s p a t c h e r S e r v l e t a través de
A b s t r a c t A n n o t a t io n C o n f i g D i s p a t c h e r S e r v l e t l n i t i a l i z e r es una alternativa
al archivo w eb. xm l tradicional. Aunque si lo desea puede incluir un archivo w eb . xml junto
a una subclase de A b s t r a c t A n n o t a t i o n C o n f i g D i s p a t c h e r S e r v l e t l n i t i a l i z e r ,
no es necesario.
El único inconveniente de configurar D i s p a t c h e r S e r v l e t de esta forma y no en
un archivo w eb . xm l es que solo funcionará al implementar un servidor compatible con
Servlet 3.0, como Apache Tomcat 7 o superior. La especificación Servlet 3.0 es definitiva
desde diciembre de 2009 y es muy probable que tenga que implementar sus aplicaciones en
un contenedor de servlet compatible con Servlet 3.0. Si no trabaja con un servidor compa­
tible con Servlet 3.0, no le funcionará la configuración de D i s p a t c h e r S e r v l e t en una
subclase de A b s t r a c t A n n o t a t i o n C o n f i g D i s p a t c h e r S e r v l e t l n i t i a l i z e r . No
tendrá más remedio que configurar D i s p a t c h e r S e r v l e t en w eb . xm l. En un capítulo
posterior analizaremos w eb . xm l y otras opciones de configuración. Por el momento, nos
centraremos en W ebConf i g y R o o tC o n f ig , las dos clases de configuración mencionadas
en el listado 5.1, y en cómo habilitar Spring MVC.

Habilitar Spring MVC


Al igual que hay varias formas de configurar D i s p a t c h e r S e r v l e t , hay más de una
forma de habilitar componentes de Spring MVC. Históricamente, Spring se ha configu­
rado con XML y existe un elemento <m v c : a n n o t a t io n - d r iv e n > que puede usar para
164 Capítulo 5

habilitar Spring MVC controlado por anotaciones. En un capítulo posterior veremos


<m vc: a n n o t a t io n - d r iv e n > , entre otras opciones de configuración de Spring MVC,
pero por el momento, nos centraremos en la configuración basada en Java.
La configuración más sencilla de Spring MVC es una clase anotada con @EnableWebMvc:
package s p i t t r . config;
import org. springframework. co n tex t. annotation.Configuration;
import org. springframework. web. s e r v le t. config.annotation.EnableWebMvc;

@Configuration
©EnableWebMvc
public cla ss WebConfig {
}

Funcionará y habilitará Spring MVC, pero deja mucho que desear:

• No se configura un solucionador de vistas. Por ello Spring usará BeanN am e


V ie w R e s o lv e r de forma predeterminada, un solucionador de vistas que examina
los bean cuyos ID coincidan con el nombre de la vista y cuya clase implemente la
interfaz View.
• No se habilita el análisis de componentes. Por ello, la única forma de que Spring
detecte controladores es declararlos explícitamente en la configuración.
• D i s p a t c h e r S e r v le t se asigna como servlet predeterminado de la aplicación y
procesará todas las solicitudes, incluidas las de recursos estáticos como imágenes y
hojas de estilo.

Por lo tanto, necesitará más configuración en W eb C o n fig. En el siguiente código se


resuelven estas preocupaciones.

Listado 5.2. Configuración mínima pero muy útil para Spring MVC.

package s p i t t r . config;
import org. springframework. co n tex t. annotation. Bean;
import org. springframework. co n tex t. annotation.ComponentScan;
import org.springframework. co n tex t. annotation.Configuration;
import org. springframework.web. s e r v le t.ViewResolver;
import org. springframework.web. s e r v le t. co n fig . annotation.
DefaultServletHandlerConf ig u rer;
import org. springframework.web. s e r v le t. co n fig . annotation. EnableWebMvc;
import o rg . springframework.web. s e r v le t. co n fig . annotation.WebMvcConfigurerAdapter;
import org.springframework.web. servlet.view .InternalResourceView Resolver;

@Conf iguration
@EnableWebMvc / / H ab ilitar Spring MCV.
@ComponentScan(" s p i t t e r . web") / / H ab ilitar a n á lis is de componentes,
public cla ss WebConfig
extends WebMvcConfigurerAdapter {

@Bean
public ViewResolver ViewResolver() {
Crear aplicaciones Web de Spring

InternalResourceViewResolver resolver = / / Configurar un solucionador de v ista s JSP.


new InternalResourceViewResolver();
r e s o lv e r .s e tP r e fix ("/WEB-INF/views/" ) ;
re so lv e r. s e tS u ffix { " . j sp") ;
re so lv e r. setExposeContextBeansAsAttributes(true);
return resolv er;

©Override
public void configureDefaultServletH andling( / / Configurar procesamiento de
/ / contenido e s tá tic o .
DefaultServletHandlerConfigurer configurer)
con fig u rer. enable();

Lo primero que destacar en este código es que WebConf ig se anota ahora con @
Component Sean para que se examine el paquete spitter.web en busca de componentes.
Como veremos en breve, los controladores que escriba se anotarán con @Controller, lo
que les convierte en candidatos al análisis de componentes. Por lo tanto, no tendrá que
declarar explícitamente ningún controlador en la clase de configuración. Tras ello, añadimos
un bean ViewReso 1ver, en concreto InternaIResourceViewReso 1ver. En el siguiente
capítulo encontrará más información sobre los solucionadores de vista. Por el momento,
basta saber que se configuran para buscar archivos JSP envolviendo nombres de vista con
un prefijo y un sufijo concretos (por ejemplo, el nombre de vista home se resolverá como
/WEB-INF/views/home.j sp).
Por último, la nueva clase WebConf ig amplía WebMvcConf igurerAdapter y reem­
plaza su método conf igureDef aultServletHandling (). Al invocar enable () en
Def aultServletHandlerConf igurer, le pedimos a DispatcherServlet que dirija
las solicitudes de recursos estáticos al servlet predeterminado del contenedor de servlet y
que no las procese personalmente.
Tras configurar WebConf ig ¿qué pasa con RootConf ig? Como este capítulo se centra
en el desarrollo Web y como la configuración Web se realiza en el contexto de aplicación
creado por DispatcherServlet, RootConf ig será relativamente sencillo por ahora:
package s p i t t r . co n fig ;

import o rg . springframework.context. annotation.ComponentScan;


import org. springframework. co n tex t. annotation. ComponentScan. F i l t e r ;
import org. springframework. co n tex t. annotation. Configuration;
import org. springframework. co n tex t. annotation. F ilterT yp e;
import org.springframework.web. s e r v le t. co n fig . annotation.EnableWebMvc;

@Configuration
©ComponentScan(basePackages={ " s p i t t e r " },
ex clu d eF ilters={
©Filter(type=FilterType.ANNOTATION, value=EnableWebMvc. class)
})
public cla ss RootConfig {
166 Capítulo 5

El único aspecto que mencionar en R ootC onf ig es que se anota con ©Component Sean.
A lo largo del libro veremos muchos casos para añadir otros componentes a R ootC onf ig .
Prácticamente estamos listos para crear una aplicación Web con Spring MVC. La gran
duda es cuál.

La aplicación Spittr
En un intento por participar en el sector de las redes sociales, vamos a desarrollar una
sencilla aplicación de microblogging. En muchos aspectos, se parecerá a Twitter. Añadiremos
nuevas ideas y, evidentemente, la desarrollaremos con Spring.
Tomando prestadas varias ideas de Twitter e implementando la aplicación en Spring
conseguimos Spitter. Lo llevaremos un paso más allá y aplicaremos un nombre popular
de sitios como Flickr, por lo que eliminamos la "e" final: Spittr. Este nombre también nos
permite diferenciar la aplicación del tipo de dominio que vamos a crear: Spitter.
La aplicación Spittr tiene dos conceptos de dominio básicos: spitters (los usuarios de
la aplicación) y spittles (las breves actualizaciones de estado publicadas por los usuarios).
A lo largo del libro nos centraremos en estos dos conceptos mientras desarrollamos la
funcionalidad de la aplicación Spittr. Inicialmente crearemos el nivel Web de la aplicación,
crearemos controladores para mostrar spittles y procesaremos formularios para que los
usuarios se registren como spitters.
El escenario está listo. Hemos configurado D i s p a t c h e r S e r v l e t , hemos habilitado
componentes esenciales de Spring MVC y hemos establecido una aplicación de destino.
Nos centraremos en la parte importante del capítulo: el procesamiento de solicitudes Web
con controladores de Spring MVC.

Crear un sencillo controlador


En Spring MVC, los controladores son clases con métodos anotadas con @ R equ est
Mapping para declarar el tipo de solicitudes que pueden procesar.
Para empezar, imagine una clase de controlador que procesa solicitudes de / y representa
la página de inicio de la aplicación. Home C o n tr o l 1 e r, mostrada en el siguiente listado, es
un ejemplo de lo que seguramente sea la clase de controlador más sencilla de Spring MVC.

Listado 5.3. HomeController: un sencillo ejemplo de controlador.

package sp ittr.w eb ;
import s t a t ic org.springframework.web.bind.annotation.RequestMethod.*;
import org. springframework. stereotyp e. C ontroller;
import org. springf ramework.web.bind. annotation. RequestMapping;
import org. springframework.web.bind.annotation.RequestMethod;

©Controller / / Declarado para ser controlador,


public cla ss HomeController {

@RequestMapping(value="/", method=GET) / / Procesar so licitu d es GET de / .


Crear aplicaciones Web de Spring 167

public String home() { / / El nombre de la v is ta es home,


return "home";
}

Lo primero que vemos es que H o m e C o n tr o lle r se anota con @ C o n t r o l l e r . Aunque


es evidente que esta anotación declara un controlador, no tiene mucho que ver con Spring
MVC.
( » C o n t r o l l e r es un estereotipo de anotación, basado en la anotación ©Component.
Su presencia está relacionada con el análisis de componentes. Como H o m e C o n tr o lle r
se anota con © C o n t r o l l e r , el analizador de componentes seleccionará automáticamente
H o m e C o n tr o lle r y lo declarará como bean en el contexto de aplicaciones de Spring.
Podríam os haber anotado H o m e C o n t r o lle r con @Com ponent y el efecto sería el
mismo, pero no indicaría tan claram ente el tipo de componente que es.
El único m étodo de H o m e C o n tr o lle r , home ( ) , se anota con @ R eq u estM ap p in g .
El atributo v a lu é especifica la ruta de solicitud que procesará este m étodo y el atributo
m eth od detalla el m étodo HTTP que puede procesar. En este caso, siempre que llegue una
solicitud GET HTTP para / , se invocará el m étodo home ( ) .
Como puede apreciar, el método home () no hace gran cosa: devuelve el valor de
cadena "h om e ", que Spring MVC interpretará como el nombre de la vista que representar.
D i s p a t c h e r S e r v l e t pedirá al solucionador de vistas que resuelva este nombre de vista
lógico en una vista real.
Por la configuración de I n t e r n a l R e s o u r c e V i e w R e s o l v e r , el nombre de vista
"home" se resolverá como JSP en /W E B -IN F /v iew s/h o m e. js p . Por el momento, la
página de inicio de la aplicación Spittr será bastante básica, como se muestra a continuación.

Listado 5.4. Página de inicio de Spittr, definida como JSP.

<%@ ta g lib u ri= "h t t p :/ / ja v a .su n .co m /jsp /jstl/c o re " p re fix = "c" %>
<%@ page sessio n ="false" %>
<html>
<head>
< t i t l e > S p it t r < / t i t l e >
clin k re l= "sty le sh e e t"
ty p e="text/css"
href = "< c :u rl value=" /r e so u rc e s/sty le . c s s " /> " >
</head>
<body>
<hl>Welcome to S p ittr< /h l>

<a h re f= "< c:u rl v a lu e = "/sp ittle s" /> "> S p ittle s < /a > |
<a href = "<c-.url value = " /s p it te r /r e g is te r " />">R egister</a>
</body>
</html>

No hay nada destacable en esta JSP; simplemente da la bienvenida al usuario a la apli­


cación y le ofrece dos enlaces: uno para ver una lista de spittles y otro para registrarse. En
la figura 5.2 se muestra el aspecto de esta página de inicio.
168 Capítulo 5

Figura 5.2. La página de inicio de Spittr en funcionamiento.

Antes de acabar el capítulo habremos implementado los métodos de controlador para


procesar estas solicitudes. Por el momento, generaremos varias solicitudes para este
controlador y veremos su funcionamiento. La forma más evidente de probar un controlador
podría ser generar e implementar la aplicación y analizarla en un navegador Web, pero con
una prueba automatizada conseguiremos información más rápidamente y obtendremos
resultados más prácticos. Así pues, analicemos H o m e C o n tr o lle r con una prueba.

Probar ei controlador
Fíjese de nuevo en H o m e C o n t r o ll e r . Si se olvida de las anotaciones, lo que ve
es un sencillo POJO, y ya sabe lo fácil que resulta probarlos. Por ello, puede probar
H o m e C o n tr o lle r por medio de la siguiente prueba.

Listado 5.5. HomeControllerTest: una prueba de HomeController.


package spittr.web;
import s t a t i c org. j u n i t .Assert.assertEquals;
import org. ju n it.T e s t ;
import s p i t t r . web.HomeController;

public class HomeControllerTest {


@Test
public void testHomePage() throws Exception {
HomeController co n tro ller = new HomeController();
assertEquals( "home", c o n t r o ll e r .home( ) ) ;

Aunque esta prueba sea muy sencilla, solamente comprueba lo que sucede en el método
home ( ) . Invoca home () directamente y comprueba que se devuelva una cadena que
contenga el valor " home", no revisa qué convierte al método en un método de controlador
Crear aplicaciones Web de Spring 169

de Spring MVC. No hay nada en la prueba que teste que home () se invoque al recibir
una solicitud GET de / , y como devuelve "hom e", no hay nada para comprobar que sea
el nombre de una vista.
Sin embargo, desde Spring 3.2 existe una forma de probar controladores Spring MVC
como tales, no solo como POJO. Spring incluye ahora un mecanismo que imita el funcio­
namiento de Spring MVC y que ejecuta solicitudes HTTP sobre controladores. Esto le
permitirá probar sus controladores sin recurrir a un servidor Web ni a un navegador Web.
Para demostrar la prueba correcta de un controlador Spring MVC, podemos reescribir
H om eC on trol l e r T e s t para aprovechar las nuevas funciones de prueba de Spring MVC.
El siguiente código muestra la nueva versión de H om eC on trol l e r T e s t .

Listado 5.6. Nueva versión de HomeControllerTest.


package spittr.web;
import s t a t i c
org. springframework.test.web. s e r v l e t . request.MockMvcRequestBuilders.* ;
import s t a t i c
org.springframework.test.web.s e r v l e t . result.MockMvcResultMatchers.* ;
import s t a t i c
org. springframework. test.w eb. s e r v l e t . setup.MockMvcBuilders.* ;
import o r g .ju n it.T e s t ;
import org. springframework. t e s t .web. s e r v l e t .MockMvc;
import s p i t t r .web .HomeController

public class HomeControllerTest {


@Test
public void testHomePage() throws Exception {
HomeController co n tro ller = new HomeController();
MockMvc mockMvc = / / Crear MockMvc.
standaloneSetup(controller).b u ild ();

mockMvc.perform(get( " / " ) ) / / Ejecutar GET / .


. andExpect(view( ) . name( "home" ) ) ; / / Se espera la v is ta home.
}
}
Aunque esta nueva versión de la prueba sea más extensa que la anterior, comprueba
H o m e C o n t r o ll e r más profusamente. En lugar de invocar directamente home () y
comprobar su valor devuelto, genera una solicitud GET de / y comprueba que el nombre
de la vista resultante sea home. Primero pasa una instancia de H o m e C o n t r o ll e r a
M o ck M v cB u ild ers . s t a n d a lo n e S e t u p () e invoca b u i l d () para configurar la instancia
de MockMvc, a la que después pide que realice una solicitud GET de / y defina el nombre
de vista que espera.

Definir procesamiento de solicitudes en el nivel de la clase


Ahora que tenemos una prueba para H o m e C o n tro lle r, podemos hacer varios cambios
para evitar problemas. Podemos dividir @ R eq u estM ap p in g incluyendo la parte de la
asignación de ruta en el nivel de las clases, como se ilustra en el siguiente código.
170 Capítulo 5

Listado 5.7. División de @RequestMapping.en HomeController.


package spittr.web;
import s t a t i c org. springframework.web.bind. annotation. RequestMethod.* ;
import org.springframework.stereotype. Controller;
import org.springframework.web.bind. annotation.RequestMapping;
import org. springframework.web.bind. annotation.RequestMethod;

©Controller
@RequestMapping("/")
public class HomeController {

©RequestMapping(method=GET)
public String home() {
return "home";
}
}
En esta nueva versión de H o m e C o n t r o lle r , la ruta se ha ascendido a un nuevo
© R eq u estM a p p in g de nivel de clases, m ientras que el método HTTP sigue asignado en
el nivel de m étodos. Siempre que en una clase de controlador hay un @ R eq u estM ap p in g
de nivel de clase, se aplica a todos los m étodos de control del controlador. Después,
las anotaciones @ R e q u e s tM a p p in g en los m étodos de control com plem entarán el
@ R eq u estM ap p in g de nivel de clase. En el caso de H o m e C o n tro lle r, solo hay un método
de controlador. Su @ R eq u estM a p p in g , cuando se combina con el @ R eq u estM ap p in g del
nivel de clase, indica que el m étodo home () procesará las solicitudes GET de / .
Es decir, realmente no hemos variado nada. Hemos cambiado los componentes de
posición, pero H o m e C o n tr o lle r sigue haciendo lo mismo que antes. Como tenemos una
prueba, sabemos que no se ha estropeado nada en el proceso.
Mientras experimenta con las anotaciones @ R equ estM ap p in g, puede hacer otro cambio
en H o m e C o n tr o lle r . El atributo v a lu é de @ R eq u estM ap p in g acepta una matriz de
S t r i n g . Hasta el momento solo hemos asignado un valor S t r i n g , " / " , pero también
podemos asignarlo a solicitudes cuya ruta sea /hom epage si cambia el @ R eq u estM ap p in g
de nivel de clase por lo siguiente:
©Controller
@RequestMapping( { " / " , "/homepage"})
public class HomeController {

Ahora el método home () de H o m e C o n tr o lle r se asigna para procesar solicitudes GET


tanto para / como para /h o m ep ag e.

Pasar datos del modelo a la vista


H o m e C o n tr o lle r es un magnífico ejemplo de cómo crear un controlador extremada­
mente sencillo, pero la mayoría de controladores no lo son tanto. En la aplicación Spittr
necesitará una página que muestre la lista de los spittles más recientes que se hayan enviado.
Crear aplicaciones Web de Spring 171

Por lo tanto, necesitará un nuevo método que sirva para dicha página. Primero debe definir
un repositorio para el acceso a datos. Por motivos de desacoplamiento y para no tener
que preocuparse por detalles concretos de la base de datos, definiremos el repositorio
como interfaz y después crearemos su implementación. Por el momento, solo necesita un
repositorio que pueda obtener una lista de los spittles. S p i t t l e R e p o s i t o r y es el punto
de partida adecuado:
package s p itt r .d a t a ;
import j a v a . ú t i l . L i s t ;
import s p i t t r . S p i t t le ;

public in terface SpittleRepository {


L ist< S p ittle > fin d Spittles(lo ng max, in t count);
}
El método f i n d S p i t t l e s () acepta dos parámetros. El parámetro max es un ID de
S p i t t l e que representa el ID máximo de cualquier S p i t t l e que deba devolverse. El
parámetro c o u n t indica cuántos objetos S p i t t l e devolver. Para obtener los 20 últimos
objetos S p i t t l e , puede invocar f i n d S p i t t l e s () de esta forma:
L ist< S p ittle > recent =
SpittleRepository. findSpittles(Long.MAX_VALUE, 20);

Por el momento la clase S p i t t l e será muy sencilla, como se muestra a continuación.


Tendrá propiedades para transmitir un mensaje, una marca de tiempo y la latitud/longitud
de la ubicación desde la que se publica el s p i t t l e .

Listado 5.8. La clase Spittle: contiene un mensaje, una marca de tiempo y una ubicación.
package spittr,-
import ja v a .ú til.D a te ;

public class S p i tt le {
private fin a l Long id;
private fin a l String message;
private fin a l Date time;
private Double latitu d e ;
private Double longitude,-

public S p itt le (S tr in g message, Date time) {


this(message, time, nuil, n u il) ;
}
public S p i t t l e (
String message, Date time, Double longitude, Double latitude) {
t h i s .i d = nuil;
th i s . message = message,-
t h i s . time = time,-
t h i s . longitude = longitude;
th is . latitud e = latitude,-
}
public long getld() {
172 Capítulo 5

return id;
}
public String getMessageO {
return message;
}
public Date getTimeO {
return time;
}
public Double getLongitude() {
return longitude;
}
public Double getLatitude() {
return latitud e;
}
©Override
public boolean equals(Object that) {
return EqualsBuilder. re fle c tio n E q u a ls(th is, that, "id", "tim e");
}
@Override
public in t hashCodeO {
return HashCodeBuilder. reflectionHashCode(this, "id", "time");
}
}
En muchos casos, S p i t t l e es un objeto de datos POJO básico, nada complicado. Lo
único que destacar es que se usa Apache Commons Lang para facilitar la implementación
de los métodos e q u a l s () y h ash C o d e ( ) . Aparte del valor general de estos métodos, son
muy útiles para crear una prueba para el método de controlador.
Siguiendo con las pruebas, ahora crearemos una para el nuevo método de controlador.
El siguiente código usa MockMvc de Spring para comprobar el comportamiento deseado
del nuevo método de controlador.

Listado 5.9. Comprobar que SpittleController procesa solicitudes GET de /spittles.


@Test
public void shouldShowRecentSpittles() throws Exception {

L ist< S p ittle > expectedSpittles = cr e a te S p ittle L is t (20) ,-


SpittleRepository mockRepository = / / Repositorio f i c t i c i o ,
mock(SpittleRepository.class);
when(mockRepository.findSpittles(Long.MAX_VALUE, 20))
. thenReturn(expectedSpittles);

S p ittleC o n troller co n tro ller =


new SpittleController(mockRepository);

S pittleC o n troller co n tro ller =


new SpittleController(mockRepository);
Crear aplicaciones Web de Spring 173

MockMvc mockMvc = standaloneSetup(controller) / / MVC f i c t i c i o .


. setSingleView(
new InternalResourceView("/WEB-INF/views/spitt i e s . j s p " ))
.b u ild ();

mockMvc.perform(get(" / s p i t t l e s ")) / / Solicitu d GET de / s p i t t l e s .


. andExpect(view( ) .name(" s p i t t l e s " ))
. andExpect(model( ) . a t t r i b u t e E x is t s ("s p i t t l e L i s t " )) / / Confirmar expectativas.
. andExpect(model( ) . a t t r i b u t e (" s p i t t l e L i s t ",
hasltems(expectedSpittles. toArray( ) ) ) ) ;
}

private L ist< S p ittle > c r e a t e S p i t t l e L i s t (in t count) {


L ist< S p ittle > s p i t t le s = new A rrayL ist<Spittle>();
for (int i = 0 ; i < count; i++) {
s p i t t l e s . add(new S p i t t l e (" S p itt le " + i , new D ateO ));
}
return s p i t t l e s ;
}
Esta prueba empieza creando una implementación ficticia de la interfaz S p i t t l e
R e p o s i t o r y que devolverá una lista de 20 objetos S p i t t l e desde su método f i n d
S p i t t l e s ( ) . Después inyecta ese repositorio en una nueva instancia de S p i t t l e
C o n t r o l l e r y configura MockMvc para usar ese controlador.
Al contrario de lo que sucede con H o m e C o n tro lle rT e s t, esta prueba invoca s e t S i n g l e
V iew () en el generador MockMvc, para que el marco de trabajo mock no intente resolver
el nombre de vista que proviene del controlador. En m uchos casos no es necesario, pero
para este método de controlador el nombre de vista será similar a la ruta de la solicitud; con
su resolución de vista predeterm inada, MockMvc falla porque la ruta de vista se confunde
con la del controlador. La ruta proporcionada al construir I n t e r n a l R e s o u r c e V i e w
es irrelevante en esta prueba, pero es coherente con la forma en que hemos configurado
I n t e r n a l R e s o u r c e V i e w R e s o lv e r .
La prueba term ina realizando solicitudes GET de / s p i t t l e s y comprobando que el
nombre de vista es s p i t t l e s y que el modelo tiene un atributo s p i t t l e L i s t con los
contenidos esperados.
Evidentemente, si ahora ejecutara la prueba, fallaría no su ejecución, sino su compila­
ción. Se debe a que todavía no hemos escrito S p i t t l e C o n t r o l l e r . Lo crearemos para
satisfacer las expectativas de la prueba del listado 5.9. Veamos su implementación.

Listado 5.10. SpittleController: añade una lista de spittles recientes al modelo.


package spittr.web;
import ja v a .ú t i l . L i s t ;
import org.springframework.beans. fa c to ry . annotation.Autowired;
import org. springframework. stereotype. Controller;
import org. springframework.web.bind.annotation.RequestMapping;
import org. springframework. web.bind. annotation.RequestMethod;
import s p i t t r . S p i t t l e ;
import s p i t t r . data. SpittleRepository;

©Controller
174 Capítulo 5

@RequestMapping( " / s p i t t l e s " )


public cla ss SpittleC o n troller {

private SpittleRepository spittleRepository;

@Autowired
public S p ittle C o n tr o lle r( / / Inyectar SpittleRepository.
SpittleRepository spittleRepository) {
t h i s . spittleRepository = spittleRepository;
}
@RequestMapping(method=RequestMethod. GET)
public String spittles(Model model) {
model. addAttribute( / / Anadir s p i t t l e s al modelo.
sp ittle R e p o s ito ry .fin d S p ittle s (
Long.MAX_VALUE, 2 0 ) ) ;
return " s p i t t l e s " ; / / Devolver el nombre de la v is ta .
}

Como puede apreciar, SpittleController tiene un constructor anotado con


@Autowired para recibir un SpittleRepository, que se utilizará en el método
spittles () para obtener una lista de spittles recientes.
El método spittles () recibe Model como parámetro, para que pueda completar
el modelo con la lista de Spittle que recibe del repositorio. Model es básicamente un
mapa (es decir, una colección de pares de clave y valor) que se entrega a lavista para poder
representar los datos al cliente. Al invocar addAttribute () sin especificar una clave,
ésta se deduce del tipo de objeto establecido como valor. En este caso, como se trata de
List<Spittle>, se deduce que la clave es spittleList.
Lo último que hace s p i t t l e s () es devolver s p i t t l e s como nombre de la vista que
va a representar el modelo.
Si prefiere especificar la clave del modelo de forma explícita, no lo dude. Por ejemplo,
la siguiente versión de s p i t t l e s () equivale a la del listado 5.10:
@RequestMapping(method=RequestMethod. GET)
public String spittles(Model model) {
model. addAttribute("s p i t t l e L i s t ",
spittleRepository.findSpittles(Long.MAX_VALUE, 2 0 ));
return " s p i t t l e s " ;
}

Del mismo modo, si prefiere trabajar con un tipo que no sea Spring, puede solicitar
j ava.útil.Map en lugar de Model. La siguiente versión de spittles () es funcional­
mente equivalente a las demás:
@RequestMapping(method=RequestMethod. GET)
public String spittles(Map model) {
model.put(" s p i t t l e L i s t " ,
spittleRepository.findSpittles(Long.MAX_VALUE, 2 0 ));
return " s p i t t l e s " ;
}
Crear aplicaciones Web de Spring 175

Y como estamos hablando de implementaciones alternativas, veamos otra forma de


escribir el método spittles ():
©RequestMapping (method=RequestMethod. GET)
public L ist< S p ittle > s p i t t l e s () {
return spittleR ep osito ry . f indSpittles(Long.MAX_VALUE, 2 0 ));
}
Esta versión es ligeramente distinta a las anteriores. En lugar de devolver un nombre
de vista lógico y establecer el modelo de forma explícita, este método devuelve la lista de
S p i t t l e . Cuando un método de controlador devuelve un objeto o una colección como
ésta, el valor devuelto se añade al modelo y la clave del modelo se deduce de su tipo
( s p i t t l e L i s t , como en los demás ejemplos).
En cuanto el nombre de vista lógico, se deduce de la ruta de solicitud. Como este método
procesa solicitudes GET de / s p i t t l e s , elnombrede vista es s p i t t l e s (sin la barra inicial).
Independientemente de cómo decida escribir el método s p i t t l e s ( ) , el resultado es
el mismo. Se almacena una lista de objetos S p i t t l e en el modelo con la clave s p i t t l e
L i s t y se asignan a la vista con el nombre s p i t t l e s . Debido a la configuración
de I n t e r n a l R e s o u r c e V i e w R e s o l v e r , esa vista es una JSP en /WEB I N F / v ie w s /
s p i t t l e s . js p . Ahora que el modelo tiene datos ¿cómo accede la JSP a los mismos?
Cuando una vista es una JSP, los datos del modelo se copian en la solicitud como atributos
de solicitud. Por lo tanto, el archivo s p i t t l e s . j sp puede usar la etiqueta < c : f o r E a c h >
de JSTL (JavaServer Pages Standard Pag Líbrary, Biblioteca de etiquetas estándar de JavaServer
Pages) para representar la lista de sp ittles :
<c:forEach it e m s = " $ {s p itt le L is t } " v a r= "sp ittle " >
< l i id="s p i t t l e _ < c : out v a lu e = " s p it tle . id "/>">
<div class="spittleMessage">
< c : out value="${spittle.m essage}" />
</div>
<div>
<span cla s s = "s p ittle T im e "x c :o u t value="$ {s p i t t l e . time}" /></span>
<span cla ss= "sp ittle L o c a tio n ">
(< c : out value="$ {s p i t t l e . la t it u d e } " />,
< c : out va lu e= "$ {sp ittle .lo n g itu d e )" />)</span>
</div>
</li>
< / c : forEach>

En la figura 5.3 puede ver el aspecto que tendrá en su navegador Web.


A pesar de la sencillez de S p i t t l e C o n t r o l l e r , está un paso por encima de lo que
creamos en Home C ont r o 11 e r . No obstante, ni Home Con t r o 1 1 e r ni S p i t t l e Con t r o 11 e r
controlan ningún tipo de entrada. Ampliaremos S p i t t l e C o n t r o l l e r para que pueda
aceptar entradas del cliente.

Aceptar entradas de solicitud___________________


Algunas aplicaciones Web son de solo lectura. Los usuarios visitan el sitio Web desde
su navegador y leen el contenido que el servidor envía al navegador.
176 Capítulo 5

Figura 5.3. Los datos del modelo Spittle de un controlador se proporcionan como parámetros
de solicitud y se representan como lista en una página Web.

La buena noticia es que no tiene por qué ser así. Muchas aplicaciones Web permiten al
usuario enviar datos al servidor. Sin esta posibilidad, la Web sería muy diferente.
Spring MVC ofrece varias formas para que un cliente pase datos a un método de contro­
lador, como por ejemplo:

• Parámetros de consulta.
• Parámetros de formulario.
• Variables de ruta.

Veremos cómo crear controladores para procesar entradas con estos tres mecanismos.
Para empezar, nos centraremos en el procesamiento de solicitudes con parámetros de
consulta, la forma más sencilla de enviar datos desde el cliente al servidor.

Aceptar parámetros de consulta


La aplicación Spittr tiene que mostrar una lista paginada de spittles. Actualmente,
SpittleController solo muestra los spittles más recientes; no permite consultar el
historial de spittles escritos. Si tiene pensado permitir a sus usuarios recorrer el historial
de spittles página a página, necesitará una forma de pasar parámetros que determine qué
grupo de spittles mostrar.
Para decidir cómo llevarlo a cabo, piense que si está viendo una página de spittles, se
ordena con el más reciente en primer lugar. Por tanto, el primer spittle de la siguiente página
debería tener un ID anterior al ID del último spittle de la página actual. Así pues, para
mostrar la siguiente página de spittles, debe poder pasar un ID de spittle que sea inferior al
ID del último spittle de la página actual. También puede pasar un parámetro que indique
la cantidad de spittles que mostrar.
Crear aplicaciones Web de Spring 177

Para implementar esta solución de paginación tendrá que crear un método de contro­
lador que acepte lo siguiente:
• Un parámetro b e f o r e (que indica el ID del Spittle).
• Un parámetro co u n t (que indica cuántos spittles incluir en el resultado).
Para lograrlo, cambiaremos el método s p i t t l e s ( ) creado en el listado 5.10 por un
nuevo método s p i t t l e s ( ) que funcione con los parámetros b e f o re y co u n t. Primero
añadiremos una prueba para reflejar la funcionalidad que necesitamos del nuevo método
s p i t t l e s ( ).

Listado 5.11. Nuevo método para probar una lista paginada de spittles.

@Test
public void shouldShowPagedSpittles() throws Exception {
List<Spittle> expectedSpittles = createSpittleList(50);
SpittleRepository mockRepository = mock(SpittleRepository.class);
when(mockRepository.findSpittles(238900, 50)) // Esperar parámetros max y count.
.thenReturn(expectedSpittles);

SpittleController controller =
new SpittleController(mockRepository);
MockMvc mockMvc = standaloneSetup(controller)
.setSingleView(
new InternalResourceView("/WEB-INF/views/spittles.jsp"))
.build();

mockMvc.perform(get("/spittles?max=238900&count=50")) // Pasar parámetros max y count.


.andExpect(view().name("spittles"))
.andExpect(model().attributeExists("spittleList"))
.andExpect(model().attribute("spittleList",
hasltems(expectedSpittles.toArray())));
}
La principal diferencia entre este método de prueba y el del listado 5.9 es que realiza
una solicitud GET en / s p i t t l e s , pasando valores para los parámetros max y co u n t.
Prueba el método de controlador cuando existen estos parámetros; el otro método de
prueba comprueba si faltan dichos parámetros. Con ambas pruebas presentes, puede estar
seguro de que independientemente de los cambios que realice en el controlador, seguirá
procesando ambos tipos de solicitudes:
@RequestMapping(method=RequestMethod.GET)
public List<Spittle> spittles(
@RequestParam("max") long max,
©RequestParam("count") int count) {
return spittleRepository.findSpittles(max, count);
}
Si el método de controlador de SpittleController va a procesar solicitudes con o
sin los parámetros max y count, tendrá que cambiarlo para que los acepte pero que siga
teniendo como valor predeterminado Long.MAX_VALUE y 2 0 sila solicitud no los incluye.
El atributo def aultValue de @RequestParam se encarga de ello:
178 Capítulo 5

@RequestMapping(method=RequestMethod.GET)
public L ist< S p ittle > s p i t t l e s (
@RequestParam(value="max",
defaultValue=MAX_LONG_AS_STRING) long max,
@RequestParam(valué="count", defaultValue="20") int count) {
return spittleR ep ository . findSpittles(max, count);
}
Si no se especifica el parámetro max, de forma predeterminada será el valor máximo
de L ong, Como los parámetros de consulta siempre son de tipo S t r i n g , el atributo
d ef a u lt V a lu e requiere un valor S t r i n g . Por tanto, L o n g . MAX_VALUE no funcionará. En su
lugar, puede capturar L o n g . MAX_VALUE en la constante de cadena MAX_LONG_AS_STRING:
prívate s t a t i c f in a l String MAX_LONG_AS_STRING =
Long.toString(Long.MAX_VALUE);

Aunque d e f a u l t V a l u e reciba un valor S t r i n g , se convertirá en Long cuando se


vincule al parámetro max del método.
El parámetro co u n t será 2 0 de forma predeterminada si la solicitud no tiene un pará­
metro co u n t. Los parámetros de consulta son una forma habitual de pasar información a
un controlador en una solicitud. Otra, en especial en la creación de controladores orientados
a recursos, consiste en pasar parámetros como parte de la ruta de la solicitud. Veamos cómo
usar variables de ruta para aceptar entradas como parte de una ruta de solicitud.

Aceptar entradas a través de parámetros de ruta


Imagine que la aplicación tiene que admitir la representación de un único S p i t t l e ,
dado su ID. Una posibilidad sería crear un método de controlador que acepte el ID como
parámetro de consulta con @ R eq u estP a ra m :
@RequestMapping(value="/show", method=RequestMethod.GET)
public String showSpittle(
@RequestParam("spittle_id") long s p i t t le ld ,
Model model) {
model. addAttribute(spittleRepository.findOne(spittleld));
return " s p i t t l e " ;
}
Este método de controlador procesará solicitudes como / s p i t t l e s /sh o w ? s p i 1 1 1 e _
i d = l 2 3 4 5 . Aunque podría funcionar, no es adecuado desde una perspectiva orientada a
recursos. En teoría, el recurso identificado ( S p i t t l e ) se identificaría por la ruta de URL, no
por parámetros de consulta. Como regla general, no deben usarse parámetros de consulta
para identificar un recurso. Una solicitud GET de / s p i t t l e s / 1 2 3 4 5 es más adecuada
que una de / s p i t t l e s / s h o w ? s p i t t l e _ i d = 1 2 3 4 5 . La primera identifica un recurso
que recuperar y la segunda describe una operación con un parámetro, básicamente RPC
sobre HTTR
Con el objetivo de los controladores orientados a recursos en mente, capturemos este
requisito en una prueba. El siguiente código muestra un nuevo método de prueba para
comprobar el procesamiento de solicitudes orientadas a recursos en S p i t t l e C o n t r o l l e r .
Crear aplicaciones Web de Spring 179

Listado 5.12. Prueba de una solicitud para un Spittle con el ID especificado en una variable de ruta.

©Test
public void t e s t S p i t t l e () throws Exception {
S p i tt le expectedSpittle = new S p i t t l e ("H ello", new D ateO );
SpittleRepository mockRepository = mock(SpittleRepository.class);
when(mockRepository. findOne(12345)).thenReturn(expectedSpittle);

SpittleC on troller co n tro ller = new SpittleController(mockRepository);


MockMvc mockMvc = standaloneSetup(controller).build();

mockMvc.perform(get("/s p it tl e s /1 2 3 4 5 ")) / / S o l i c i t a r recurso a traves de la ru ta .


. andExpect(view( ) .name(" s p i t t l e " ))
. andExpect(model( ) . a t t r i b u t e E x is t s ( " s p i t t l e " ))
. andExpect(model( ) . a t t r i b u t e ("s p i t t l e ", e x p e cted S p ittle ));

Como puede apreciar, esta prueba establece un repositorio ficticio, un controlador


y MockMvc, como en las pruebas anteriores. La parte más importante de la prueba se
encuentra en las últimas líneas, donde realiza una solicitud GET de / s p i t t l e s / 1 2 3 4 5
y comprueba que el nombre de la vista es s p i t t l e y que el objeto S p i t t l e esperado
se añade al modelo. Como todavía no hemos implementado el método de controlador de
este tipo de solicitud, la solicitud fallará, pero podemos corregirlo añadiendo un nuevo
método a S p i t t l e C o n t r o l l e r .
Hasta el momento, todos los métodos de controlador se han asignado (a través de
@ R eq u estM a p p in g ) a una ruta definida estáticamente, pero para que la prueba sea satis­
factoria, necesitará crear un @ R eq u estM a p p in g que tenga una parte variable de la ruta
que represente al ID de S p i t t l e .
Para acomodar estas variables de ruta, Spring MVC permite el uso de marcadores de
posición en una ruta @ R eq u estM a p p in g . Los marcadores de posición son nombres entre
llaves ({ y }). Aunque las demás partes de la ruta deben coincidir a la perfección para poder
procesar la solicitud, el marcador de posición puede tener cualquier valor.
Veamos un método de controlador que usa marcadores de posición para aceptar un ID
de S p i t t l e como parte de la ruta:
@RequestMapping(value="/{spittleld}", method=ReguestMethod.GET)
public String s p i t t l e (
@PathVariable( " s p i t t l e l d " ) long s p i tt le ld ,
Model model) {
model. addAttribute(spittleRepository. find O ne(spittleld ));
return " s p i t t le " ,-
}
Por ejemplo, puede procesar solicitudes de / s p i t t l e s / 1 2 3 4 5 , la ruta que se prueba
en el listado 5.12.
Como puede apreciar, s p i t t l e () tiene un parámetro s p i t t l e l d anotado con
@ P a t h V a r i a b l e ( " s p i t t l e l d " ), lo que indica que el valor del marcador de posición
en la ruta de solicitud se pasará al parámetro s p i t t l e l d del método de controlador. Si
la solicitud es una solicitud GET de / s p i t t l e s / 5 4 3 2 1 , se pasará 5 4 3 2 1 como valor de
s p ittle ld .
180 Capítulo 5

Comprobará que la frase s p i t t l e l d s e repite varias veces en el ejemplo: en la ruta


@ R e q u e s tM a p p in g , como atributo v a l u é de @ P a t h V a r i a b l e y como nombre de
parámetro de método. Como el nombre de parámetro de método es el mismo que el del
marcador de posición, puede optar por omitir el parámetro v a lu é en @ P a t h V a r i a b l e :
@RequestMapping(value=" / { s p i t t l e l d } ", method=RequestMethod. GET)
public String spittle(@PathVariable long s p i t t le ld , Model model) {
model. addAttribute(spittleRepository. find O ne(spittleld ));
return " s p i t t l e " ;
}
Si no se proporciona un atributo v a l u é a @ P a t h V a r i a b l e , asume que el nombre
del marcador de posición es el mismo que el del parámetro de método. De este modo el
código es más limpio, ya que no se duplica innecesariamente el nombre del marcador de
posición, pero si decide cambiar el nombre del parámetro, también tendrá que cambiar el
del marcador de posición para que coincidan.
El método s p i t t l e () pasa el parámetro al método fin d O n e () d e S p i t t l e R e p o s i t o r y
para buscar un único objeto S p i t t l e y lo añade al modelo. La clave del modelo será
s p i t t l e , que se obtiene del tipo pasado a a d d A t t r i b u t e ( ) .
Después, los datos del objeto S p i t t l e se pueden representar en la vista haciendo refe­
rencia al atributo r e q u e s t con la clave s p i t t l e (la misma que en el modelo). El siguiente
fragmento de vista JSP representa el S p i t t l e :
<div class="spittleV iew ">
<div class="sp ittle M e ssage "x c:ou t value="${spittle.m essage}" /></div>
<div>
<span class="spittleTim e"><c:out value= "$ {s p i t t l e . time}" /x /s p a n >
</div>
</div>

Esta vista no tiene nada especial, como puede apreciar en la figura 5.4.

:ílS ? JÉ ÍC Ü Io calh o5t:8080/spittr/$pittl5/1

¡1 ® * » x| I
Helio World! The first ever spittle!
ft 2013-0^-02

Figura 5.4. Representación de un spittle en el navegador.

Los parámetros de consulta y los de ruta son muy útiles para pasar pequeñas cantidades
de datos en una solicitud, pero por lo general tendrá que pasar muchos datos (por ejemplo
los enviados en un formulario) y los parámetros de consulta resultan muy limitados para
ello. A continuación veremos cómo escribir métodos de controlador para procesar envíos
de formularios.
Crear aplicaciones Web de Spring 181

Procesar formularios
Las aplicaciones Web suelen realizar más tareas además de mostrar contenidos al usuario.
Muchas les permiten participar mediante formularios y el envío de datos a la aplicación. Los
controladores de Spring MVC sirven tanto para procesar formularios como para entregar
contenido. El uso de formularios se divide en dos aspectos: mostrar el formulario y procesar
los datos que el usuario envía a través del formulario. En la aplicación Spittr necesitará
un formulario para que los nuevos usuarios se registren. S p i t t e r C o n t r o l l e r es un
nuevo controlador con un único método de procesamiento de solicitudes para mostrar el
formulario de registro.

Listado 5.13. SpitterController muestra un formulario de registro de usuarios.

package spittr.w eb;


import s t a t i c org. springframework.web. bind.annotation.RequestMethod.*;
import org.springframework.beans. factory.annotation.Autowired;
import org. springframework. stereotype. Controller;
import org. springframework.u i .Model;
import org. springframework.web.bind. annotation. PathVariable;
import org. springframework. web. bind. annotation. RequestMapping
import org. springframework.web.bind. annotation.RequestMethod;
import s p i t t r . S p itte r;
import s p i t t r . data. SpitterRepository;

©Controller
@RequestMapping(" / s p i t t e r " )
public class SpitterController {
©RequestMapping(value="/ r e g i s t e r ", method=GET) / / Procesar solicitu d es GET
/ / de / s p i t t e r / r e g i s t e r .
public String showRegistrationForm() {
return "registerForm";
}
}
La anotación @ R eq u estM a p p in g del método s h o w R e g is tr a t io n F o r m ( ) , junto con
la anotación @ R eq u estM a p p in g del nivel de clases, declara que procesará solicitudes GET
HTTP de / s p i t t e r / r e g i s t e r . Es un método sencillo, que no acepta entradas y que solo
devuelve una vista lógica con el nombre r e g i s t e r F o r m . Debido a la configuración de
I n t e r n a lR e s o u r c e V ie w R e s o lv e r , significa que la JSPen /WEB I N F / v i e w s / r e g i s t e r
F o rm . j sp se invocará para representar el formulario de registro.
A pesar de la sencillez de s h o w R e g is tr a t io n F o r m ( ) , se merece una prueba, que
será igual de sencilla.

Listado 5.14. Prueba para un método de controlador de representación de formularios.

@Test
public void shouldShowRegistration() throws Exception {
SpitterController co n tro ller = new S p itterC o n tro ller{);
MockMvc mockMvc = standaloneSetup(controller).build(); / / Configurar MockMvc.
182 Capítulo 5

mockMvc.perform(get(" / s p i t t e r / r e g i s t e r " ))
. andExpect(view( ) .ñame("registerForm")); / / Confirmar v is ta registerForm.
}

Este método de prueba es muy similar al del controlador de página de inicio. Realiza
una solicitud GET de / s p i t t e r / r e g i s t e r y después comprueba que el nombre de la
vista resultante sea r e g i s t e r F o r m . Volvamos a la vista. Como el nombre de la vista es
r e g i s t e r F o r m , necesitará una JSP con el nombre r e g i s t e r F o r m . j sp , que debe incluir
un elemento < f orm> de HTML en el que el usuario introducirá la información necesaria
para registrarse en la aplicación. A continuación se muestra la JSP que vamos a utilizar.

Listado 5.15. JSP para representar un formulario de registro.

<%@ ta g lib u ri= "h tt p : / / j a v a . su n .co m /jsp /jstl/c o re " prefix="c" %>
<%@ page session="false" %>
<html>
<head>
<title>S p ittr</title>
clink re l="sty lesh e et" type="text/css"
h ref="<c:u rl v alue="/resou rces/sty le.css" />" >
</head>
<body>
<hl>Register</hl>

<form method="POST">
F ir s t Ñame: <input type="text'' name="firstName" /><br/>
Last Ñame: cinput type="text" name="lastName" /><br/>
Username: <input type="text" name="username" /><br/>
Password: cinput type="password" name="password" /><br/>
cinput type="submit" value="Register" />
< / form>
c/body>
c/html>

Como puede apreciar, es una JSP muy básica. Cuenta con campos de formulario HTML
para capturar el nombre y apellidos del usuario, el nombre de usuario y la contraseña, y
un botón para enviar el formulario. En la figura 5.5 puede ver su aspecto en un navegador.
La etiqueta < f orm> no tiene un parámetro a c t i o n configurado. Por ello, al enviar el
formulario, se publica en la misma ruta de URL que lo ha mostrado. Es decir, se vuelve a
publicar en / s p i t t e r s / r e g i s t e r , lo que significa que necesitamos algo en el servidor para
procesar la solicitud POST de HTTP. Añadiremos otros métodos a S p i t t e r C o n t r o l l e r
para procesar el envío de formularios.

Crear un controlador de procesamiento de formularios


Al procesar la solicitud POST del formulario de registro, el controlador debe aceptar
los datos y guardarlos como objeto S p i t t e r . Por último, para evitar envíos duplicados
(por ejemplo si el usuario pulsa el botón Actualizar de su navegador), debe redirigir el
navegador a la página de perfil del nuevo usuario creado. Este comportamiento se captura
y se prueba en s h o u l d P r o c e s s R e g i s t r a t i o n ( ) .
Crear aplicaciones Web de Spring 183

Figura 5.5. La página de registro ofrece un formulario que SpitterController procesará


para añadir nuevos usuarios a la aplicación.

Listado 5.16. Prueba de los métodos de controlador de procesamiento de formularios.

©Test
public void shouldProcessRegistration() throws Exception {
SpitterRepository mockRepository =
mock(SpitterRepository.class); / / Definir repositorio f i c t i c i o .
S p itte r unsaved =
new S p i t t e r ("jbauer", "24hours", "Jack", "Bauer");
S p itte r saved =
new Spitter(24L, "jbauer", "24hours", "Jack", "Bauer");
when(mockRepository.save(unsaved)) . thenReturn(saved);

SpitterController co n tro ller =


new SpitterController(mockRepository);
MockMvc mockMvc = standaloneSetup(controller).build( ); / / Definir MockMvc.

mockMvc.perform(post(" / s p i t t e r / r e g i s t e r " ) / / Realizar so licitu d ,


.param("firstName", "Jack")
.param("lastName", "Bauer")
.param("username", "jbauer")
.param("password", "24hours"))
. andExpect(redirectedUrl( "/ s p it t e r / jb a u e r " ) ) ;

verify(mockRepository, atLeastOnce( ) ) . save(unsaved); / / V e rifica r guardado.


}
Evidentemente, es una prueba más completa que la empleada para mostrar el formulario
de registro. Tras definir una implementación ficticia de S p i t t e r R e p o s i t o r y y de crear
un controlador y una configuración MockMvc sobre la que ejecutarse, s h o u l d P r o c e s ­
s R e g i s t r a t i o n () ejecuta una solicitud POST de / s p i t t e r / r e g i s t e r . Como parte de
esta solicitud, la información del usuario se pasa como parámetros para simular el envío
del formulario.
184 Capítulo 5

Al procesar una solicitud POST, es recomendable enviar una redirección después de que
se haya completado el procesamiento para que una actualización del navegador no envíe
accidentalmente el formulario por segunda vez. Esta prueba espera que la solicitud termine
en una redirección a / s p i t t e r / jb a u e r , la ruta de URL de la página del perfil del nuevo
usuario. Por último, la prueba verifica que se haya usado S p i t t e r R e p o s i t o r y para
guardar los datos del formulario. A continuación implementaremos el método de contro­
lador que procesará esta prueba de envío de formularios. Puede pensar por la presencia
de s h o u l d P r o c e s s R e g i s t r a t i o n () que se necesita mucho trabajo para superar la
prueba, pero como se aprecia en el nuevo S p i t t e r C o n t r o l l e r del siguiente código, no
es demasiado complicado.

Listado 5.17. Procesamiento del envío del formulario de registro de nuevos usuarios.

package spittr.web;

import s t a t i c org. springframework.web.bind.annotation.RequestMethod.*;


import org.springframework.beans. fa cto ry . annotation.Autowired;
import org.springframework.stereotype. Controller;
import org. springframework. u i .Model;
import org.springframework.web.bind.annotation.PathVariable;
import org. springframework. web.bind. annotation. RequestMapping;
import s p itt r .Sp itter;
import s p i t t r . data. SpitterRepository;

©Controller
@RequestMapping( " / s p i t t e r " )
public class SpitterC ontroller {
private SpitterRepository SpitterRepository;

@Autowired
public S p itterC o n tro ller( / / Inyectar SpitterRepository.
SpitterRepository SpitterRepository) {
t h i s . SpitterRepository = SpitterRepository;
}
@RequestMapping(value="/register", method=GET)
public String showRegistrationForm() {
return "registerForm";
}
@RequestMapping(value="/ r e g i s t e r ", method=POST)
public String processRegistration(Spitter s p itter) {
SpitterRepository. s a v e ( s p i t t e r ) ; / / Guardar S p itte r.
return " r e d ir e c t : / s p i t t e r / " + / / Redirigir a la página del p e r f i l ,
spitter.getüsername();
}
}
El método s h o w R e g is tr a t io n F o r m () sigue presente, pero fíjese en el nuevo método
p r o c e s s R e g i s t r a t i o n ( ) : recibe un objeto S p i t t e r como parámetro. Este objeto
tiene propiedades f ir s tN a m e , la s tN a m e , u se rn a ra e y p a ss w o rd que se completarán
con los parámetros de solicitud del mismo nombre. Al invocarse con el objeto S p i t t e r ,
Crear aplicaciones Web de Spring 185

p r o c e s s R e g i s t r a t i o n () invoca el m étodo s a v e () en el S p i t t e r R e p o s i t o r y que


ahora se inyecta en el constructor de S p i t t e r C o n t r o l l e r . Lo último que hace p r o c e s s
R e g i s t r a t i o n () es devolver una cadena que especifica la vista, pero esta especifica­
ción de vista es distinta a las anteriores. En lugar de devolver un nombre de vista y dejar
que se encargue de ello el solucionador de vistas, aquí devolvemos una especificación de
redirección.
Cuando I n t e r n a l R e s o u r c e V i e w R e s o l v e r ve el prefijo r e d i r e c t : en la especi­
ficación de la vista, sabe que debe interpretarlo como una especificación de redirección y
no como un nombre de vista. En este caso, redirige a la ruta de la página de perfil de un
usuario. Por el ejemplo, si la propiedad S p i t t e r . u se rn a m e es jb a u e r , la vista se redi­
rigirá a / s p i t t e r / j b a u e r .
Conviene mencionar que además de r e d i r e c t :, I n t e r n a lR e s o u r c e V ie w R e s o lv e r
también reconoce el prefijo f o r w a r d :. Cuando ve una especificación de vista con el prefijo
f o r w a r d :, la solicitud se reenvía a la ruta de URL proporcionada y no se redirige.
¡Perfecto! Ahora la prueba del listado 5.16 debería superarse, pero todavía no hemos
terminado. Como se redirige a la página de perfil del usuario, probablemente deberíamos
añadir un método de controlador a S p i t t e r C o n t r o l l e r para procesar solicitudes de la
página de perfil. El siguiente método s h o w S p i t t e r P r o f i l e () se encarga de ello:
©RequestMapping(value="/{username}", method=GET)
public String showSpitterProfile(
@PathVariable String username, Model model) {
S p itte r s p i tt e r = SpitterRepository. findByüsername(username);
model. ad d A ttribu te(spitter);
return " p r o f i l e " ;
}
s h o w S p i t t e r P r o f i l e () obtiene un objeto S p i t t e r de S p i t t e r R e p o s i t o r y por
nombre de usuario. Añade S p i t t e r al modelo y después devuelve p r o f i l e , el nombre
de vista lógico de la vista de perfil. Como sucede con las demás vistas que hemos visto
antes, ésta será muy sencilla por el momento:
<hl>Your P ro file</h l>
< c : out value="${spitter.username}" /><br/>
<c:out v a lu e = " $ {sp itte r. firstName}" />
< c : out value="${spitter.lastName}" />

En la figura 5.6 puede ver la página de perfil en un navegador Web.

Figura 5.6. Una página de perfil de Spittr que muestra información del usuario, añadida al modelo
por medio de SpitterController.
1 8 6 Capítulo 5

¿Qué sucede si el formulario no envía un nombre de usuario o una contraseña? ¿O


si el valor f irstN am e o lastN am e está vacío o es demasiado extenso? A continuación
veremos cómo añadir validación al envío del formulario para evitar incoherencias en los
datos presentados.

Validar formularios
Si un usuario deja en blanco el campo u se rn a m e o p a ss w o rd al enviar el formulario,
se podría crear un nuevo objeto S p i t t e r cuyo nombre de usuario y contraseña fueran
cadenas vacías. Como poco, sería un comportamiento extraño, pero si no se revisa, podría
convertirse en un problema de seguridad, ya que cualquiera podría registrarse en la apli­
cación enviando un formulario de registro vacío.
Además, debe intentar evitar que el usuario envíe f ir s tN a m e y /o la s tN a m e vacíos
para así mantener cierto nivel de anonimato, y probablemente sea recomendable limitar
la longitud de los valores de estos campos, para que tengan un tamaño razonable y evitar
así un mal uso de los mismos.
Una forma de realizar la validación, aunque resulte un tanto ingenua, consiste en añadir
código al método p r o c e s s R e g i s t r a t i o n () para comprobar la presencia de valores
incorrectos y devolver al usuario al formulario de registro si los datos no son válidos. Es
un método breve, por lo que no está de más añadir alguna instrucción adicional.
Sin embargo, en lugar de complicar los métodos de controlador con lógica de validación,
puede aprovechar la compatibilidad de Spring con el API de validación de Java (o JSR-303).
Desde Spring 3.0, Spring admite este API en Spring MVC. No se necesita configuración
adicional para que funcione. Basta con asegurarse de incluir una implementación del API
de Java, como Hibernate Validator, en la ruta de clases del proyecto.
El API de validación de Java define varias anotaciones que puede añadir a propiedades
para limitar sus valores. Todas pertenecen al paquete j a v a x . v a l i d a t i o n . c o n s t r a i n t s .
Se recogen en la tabla 5.1.

Tabla 5.1. Anotaciones de validación proporcionadas por el API de validación de Java.


..
.... ...-

Anotación Descripción
aÉíi— wBm HÉSBm m Se .81 BEBmm
©AssertFalse El elemento anotado debe ser un tipo Booleano y false.
©AssertTrue El elemento anotado debe ser un tipo Booleano y true.
@DecimalMax El elemento anotado debe ser un número con un valor menor o igual al valor
BigDecimalstring proporcionado.
@DecimalMin El elemento anotado debe ser un número con un valor mayor o igual al valor
BigDecimalstring proporcionado.
@Digits El elemento anotado debe ser un número cuyo valor tenga el número de
dígitos especificado.
@Future El elemento anotado debe ser una fecha futura.
Crear aplicaciones Web de Spring 187

” ...... :.... ..... ..... — - ------- - ----------- -------------- ~------.............. ' ........:.....:...... •
Anotación Descripción

@Max El elemento anotado debe ser un número con un valor menor o igual al valor
proporcionado.
@Min El elemento anotado debe ser un número con un valor mayor o Igual al valor
proporcionado.
@NotNull El valor del elemento anotado no puede ser nuil.
@Null El valor del elemento anotado debe ser nuil.
@Past El valor del elemento anotado debe ser una fecha pasada.
@Pattern El valor del elemento anotado debe coincidir con la expresión regular
proporcionada.
@Size El valor del elemento anotado debe ser una cadena, una colección o una
matriz cuya longitud coincida con el intervalo proporcionado.

Además de estas anotaciones, las implementaciones del API de validación de Java


pueden ofrecer otras adicionales, y también puede definir limitaciones propias. Para nuestro
ejemplo, nos centraremos en algunas de las validaciones básicas de la tabla.
Al pensar en las limitaciones que aplicar a los campos de S p i t t e r , seguramente necesite
las anotaciones @ N otN u ll y @ S iz e . Basta con añadirlas a las propiedades de S p i t t e r ,
como se ilustra en el siguiente código.

Listado 5.18. SpittleForm: solo incluye campos remitidos en una solicitud SpittlePOST.
package s p i t t r ;
import jav ax .v alid atio n . constraints.NotNull;
import j avax.v alid atio n . co n stra in ts. S iz e ,-
import org. apache. commons. Iang3.builder. EqualsBuilder;
import org.apache. commons. Iang3.builder.HashCodeBuilder;

public class S p itte r {

private Long id;

@NotNull
©Size(min=5, max=16) / / No es null, de 5 a 16 caracteres,
private String username;

@NotNull
@Size(min=5, max=25) / / No es null, de 5 a 25 caracteres,
private String password;

@NotNull
@Size(min=2, max=30) / / No es null, de 2 a 30 caracteres,
private String firstName;

@NotNull
@Size(min=2, max=30) / / No es null, de 2 a 30 caracteres.
188 Capítulo 5

private String lastName;

Todas las propiedades de S p i t t e r están ahora anotadas con @ N otN u ll para garantizar
que no estén vacías. Del mismo modo, se añade @ S iz e para limitar las propiedades a una
longitud máxima y mínima. Así pues, en la aplicación Spittr el usuario tendrá que rellenar
la totalidad del formulario con valores acordes a las limitaciones de tamaño.
Después de anotar S p i t t e r con limitaciones de validación, tiene que cambiar el método
p r o c e s s R e g i s t r a t io n () para aplicar la validación. A continuación se muestra el nuevo
método p r o c e s s R e g i s t r a t i o n O habilitado para la validación.

Listado 5.19. processRegistrationO garantiza que los datos enviados sean válidos.

©RequestMapping(value="/register", method=POST)
public String processRegistration(
@Valid Spitter spitter, // Validar entrada de Spitter.
Errors errors) {

if (errors.hasErrors()) {
return "registerForm"; // Volver al formulario si hay errores de validación.
}
spitterRepository.save(spitter);
return "redirect:/spitter/" + spitter.getüsername ()

Mucho ha cambiado desde el método p r o c e s s R e g i s t r a t i o n () original del listado


5.17. Ahora el parámetro S p i t t e r se anota con @ V a lid para indicar a Spring que el objeto
tiene limitaciones de validación que hay que aplicar.
Pero la mera presencia de limitaciones de validación en las propiedades de S p i t t e r
no impide que se envíe el formulario. Aunque el usuario no rellene un campo del
formulario o proporcione un valor que supere la longitud máxima, el método p r o c e s s
R e g i s t r a t i o n () se sigue invocando. De este modo puede solucionar los problemas de
validación como considere oportuno.
Si hay errores de validación, estarán en el objeto E r r o r s que ahora solicitam os como
parám etro de p r o c e s s R e g i s t r a t i o n O (es im portante que el parám etro E r r o r s
aparezca justo después del parám etro anotado con @ V a lid que se esté validando). Lo
primero que hace p r o c e s s R e g i s t r a t i o n () es invocar E r r o r s .h a s E r r o r s () para
comprobar si hay errores.
Si los hay, E r r o r s . h a s E r r o r s () devuelve r e g i s t e r F o r m , el nombre de vista del
formulario de registro. El usuario vuelve entonces al formulario de registro para corregir
los problemas e intentarlo de nuevo. Por el momento se muestra un formulario vacío, pero
en el siguiente capítulo lo adaptaremos para mostrar los valores remitidos originalmente
y para comunicar problemas de validación al usuario.
Si no hay errores, se guarda S p i t t e r a través del repositorio y el controlador redirige
a la página de perfil, como antes.
Crear aplicaciones Web de Spring 189

Resum en
En este capítulo hemos creado gran parte de la capa Web de la aplicación. Como hemos
podido ver, Spring cuenta con un marco de trabajo Web potente y flexible. Mediante anota­
ciones, Spring MVC ofrece un modelo de desarrollo prácticamente POJO, lo que facilita
el desarrollo de controladores que gestionen solicitudes. Asimismo, son fáciles de probar.
Para la creación de métodos de controlador, Spring MVC es muy flexible. Como regla
general, si su método de controlador necesita un objeto, debe solicitarlo como parámetro.
Del mismo modo, lo que no necesite debe excluirse de la lista de parámetros. Esto ofrece
infinidad de posibilidades para el procesamiento de solicitudes, al tiempo que simplifica
el modelo de programación.
Aunque gran parte del capítulo se ha centrado en el procesamiento de solicitudes
con controladores, la representación de respuestas también es importante. Hemos visto
brevemente cómo crear vistas para controladores por medio de JSP, pero hay soluciones
más avanzadas.
En el siguiente capítulo nos adentraremos en las vistas de Spring y veremos cómo
aprovechar las bibliotecas de etiquetas de Spring en JSP, cómo añadir diseños coherentes a
una vista por medio de Apache Tiles y cómo usar Thymeleaf, una apasionante alternativa
a JSP y que es compatible con Spring.
Representar
v ista s W eb

CONCEPTOS FUNDAMENTALES:

• Representación de datos de modelo como HTML.


• Uso de vistas JSR
• Definición de diseños de vista con mosaicos.
• Trabajar con vistas Thymeleaf.
En el capítulo anterior nos centramos en la creación de controladores para procesar
solicitudes Web. También creamos vistas sencillas para representar los datos de modelo
generados por dichos controladores pero no describimos las vistas ni lo que sucede desde
que el controlador finaliza el procesamiento de una solicitud hasta que los resultados se
muestran en el navegador Web de un usuario. Abordaremos este tema en este capítulo.

Resolución de vistas
Ninguno de los métodos de los controladores creados en el capítulo anterior genera
directamente el HTML que se representa en el navegador. Lo que hacen es completar el
modelo con datos y después pasarlo a una vista. Estos métodos devuelven un valor S t r in g
que es el nombre lógico de la vista pero que no hace referencia directamente a una imple-
mentación de vista concreta. Aunque creamos sencillas vistas JSP, ningún elemento de los
controladores lo sabe.
Una importante característica del MVC de Spring es que desacopla la lógica de procesa­
miento de solicitudes del controlador de la representación de una vista. Si los métodos de
controlador fueran directamente responsables de generar el HTML, resultaría complicado
mantener y actualizar la vista sin que la lógica de procesamiento de solicitudes se viera
afectada. Como mucho, los métodos de controlador y las implementaciones de vista acor­
darían los contenidos del modelo; en el resto, mantienen cierta distancia entre sí.
Pero si el controlador solo conoce la vista por su nombre lógico ¿cómo determina Spring
la implementación de vista que debe usar para representar el modelo? Esa es la labor
de los solucionadores de vistas de Spring. En el capítulo anterior utilizamos el solucio-
nador de vistas I n t e r n a l R e s o u r c e V i e w R e s o l v e r , configurado para aplicar el prefijo
/WEB- I N F / v ie w s / y el sufijo . j sp a un nombre de vista para llegar a la ubicación física
de la JSP que representa el modelo. Retrocedamos un paso para analizar la resolución de
vistas desde una perspectiva general y para ver otros solucionadores de vista de Spring.
El MVC de Spring define la interfaz V ie w R e s o lv e r , que tiene este aspecto:
public in terface ViewResolver {
View resolveViewName(String viewName, Locale locale)
throws Exception;

Cuando se asigna un nombre de vista y un L ó c a l e al método re so lv e V ie w N a m e ( ) ,


devuelve una instancia V iew , otra interfaz que tiene este aspecto:
public in terface View {
String getContentType();
void render(Map<String, ?> model,
HttpServletRequest request,
HttpServletResponse response) throws Exception;
}

La labor de la interfaz View es aceptar el modelo, además de la solicitud de servlet y


los objetos de respuesta, y representar un resultado en la respuesta.
192 Capítulo 6

Parece muy sencillo. Basta con crear implementaciones de V ie w R e so lv e r y View para


representar contenido en la respuesta que mostrar en los navegadores de los usuarios, ¿no?
No necesariam ente. Aunque pueda crear sus propias im plem entaciones de
V ie w R e s o lv e r y View , y aunque en casos concretos sea la solución adecuada, por lo
general no tendrá que preocuparse de estas interfaces. Solamente se mencionan para que
vea el funcionamiento de la resolución de vistas. Afortunadamente, Spring le ofrece diversas
implementaciones, descritas en la tabla 6.1, que le servirán para la mayoría de los casos.

Tabla 6.1. Spring cuenta con 13 solucionadores de vista que traducen nombres lógicos
en implementaciones de vistas físicas.
~ ~ ...7 ... ■ ..... ~ ”.. . ..... ~.~.
Solucionador de vista Descripción
. .... ..... ^...... ...... .... ......... .........................
BeanNameViewResolver Resuelve vistas como bean en el contexto de la
aplicación de Spring cuyo ID sea el mismo que el del
nombre de la vista.
ContentNegotiatingViewResolver Resuelve vistas analizando el tipo de contenido que
desea el cliente y delegando en otro solucionador de
vista la creación de dicho tipo.
FreeMarkerViewResolver Resuelve vistas como plantillas FreeMarker.
InternalResourceViewResolver Resuelve vistas como recursos internos a la aplicación
Web (por lo general JSP).
JasperReportsViewResolver Resuelve vistas como definiciones JasperReports.
ResourceBundleViewResolver Resuelve vistas a partir de un archivo de recursos (por
lo general, un archivo de propiedades).
TilesViewResolver Resuelve vistas como definiciones de Apache Tiles, en las
que el ID del mosaico es igual que el nombre de la vista.
Hay dos implementaciones TilesViewResolver
distintas: una para Tiles 2.0 y otra para Tiles 3.0.
UrlBasedViewResolver Resuelve vistas directamente desde el nombre de la
vista, que coincide con una definición de vista física.
VelocityLayoutViewResolver Resuelve vistas como diseños de Velocity para la
composición de páginas a partir de distintas plantillas
de Velocity.
VelocityViewResolver Resuelve vistas como plantillas Velocity.
XmlViewResolver R esuelve vistas como definiciones de bean a
partir de un archivo XML especificado. Es similar a
BeanNameViewResolver.
XsltViewResolver Resuelve vistas para su representación como resultado
de una transformación XSLT.

Todos los solucionadores de vistas de esta tabla están disponibles en Spring 4 y


Spring 3.2, y todos, menos T i l e s V i e w R e s o l v e r de Tiles 3, son compatibles con Spring
3.1. No tenemos espacio en el libro para describirlos todos pero únicamente necesitará
Representar vistas Web 193

algunos de ellos en la mayoría de aplicaciones. En muchos casos, cada solucionador de


vista se corresponde a una tecnología de vista concreta para aplicaciones Web de Java.
I n t e r n a l R e s o u r c e V i e w R e s o l v e r suele usarse con JSP, T i l e s V i e w R e s o l v e r con
vistas de Apache y F r e e M a r k e r V ie w R e s o lv e r y V e l o c i t y V i e w R e s o l v e r se asignan
a vistas de plantilla de FreeMarker y Velocity respectivamente.
En este capítulo nos centraremos en las tecnologías de vista más relevantes para la
mayoría de desarrolladores de Java. Como muchas aplicaciones Web de Java usan JSP,
comenzaremos con I n t e r n a l R e s o u r c e V i e w R e s o l v e r , el solucionador de vistas utili­
zado habitualmente para resolver vistas JSP. Después, probaremos T i l e s V i e w R e s o l v e r
para controlar el diseño a través de páginas JSP.
Para acabar el capítulo veremos un solucionador de vistas que no se incluye en la tabla
6.1. Thymeleaf es una atractiva alternativa a JSP que ofrece un solucionador de vistas para
trabajar con plantillas naturales de Thymeleaf, que tienen más en común con el código HTML
que generan que con el código Java subyacente. Thymeleaf es una opción tan interesante
para vistas que no me extrañaría que se saltase varias páginas y consultara directamente
el apartado que describe cómo usarlo con Spring.
Si prefiere esperar, seguramente es porque sabe que JSP ha sido y sigue siendo la tecno­
logía de vistas predominante para Java. Seguramente la haya usado en sus proyectos y la
volverá a necesitar. Así pues, veamos cómo usar vistas JSP con el MVC de Spring.*•

Lo crea o no, las páginas de JavaServer han sido la tecnología de vista estándar para las
aplicaciones Web basadas en Java durante casi 15 años. Aunque naciera como una variante
basada en Java de tecnologías de plantilla similares (como ASP de Microsoft), con el tiempo
JSP ha evolucionado hasta admitir un lenguaje de expresiones y bibliotecas de etiquetas
personalizadas. Spring admite vistas JSP de dos formas:

• Se puede usar I n te r n a lR e s o u r c e V ie w R e s o lv e r para resolver nombres de vista en


archivos JSP. Es más, si usa etiquetas JSTL (JavaServer Pages Standard Tag Libran/, Biblioteca
de etiquetas estándar de páginas JavaServer), I n t e r n a l R e s o u r c e V i e w R e s o lv e r
puede resolver nombres de vista en archivos JSP para mostrar variables de configu­
ración regional y de archivo de recursos a etiquetas de formato y mensajes JSTL.
• Spring proporciona dos bibliotecas de etiquetas JSP, una para la vinculación de
formularios al modelo y otra con funciones de utilidad generales.

Independientemente de que use JSTL o pretenda usar las bibliotecas de etiquetas JSP
de Spring, es importante configurar un solucionador de vistas para resolver vistas JSP.
Aunque se puedan usar algunos de los demás solucionadores de vistas de Spring para
asignar nombres de vista a archivos JSP, I n t e r n a l R e s o u r c e V i e w R e s o l v e r es el más
sencillo y el más utilizado para ello. Ya vimos brevemente su configuración en el capítulo
anterior para ejecutar controladores en un navegador Web. Veamos ahora cómo modificarlo
para que se encargue de la vinculación.
194 Capítulo 6

Configurar un solucionacior de vistas compatible con JSP


Mientras que algunos solucionadores de vistas como R e s o u r c e B u n d le V ie w R e s o lv e r
asignan directamente un nombre de vista lógico a una implementación concreta de la interfaz
V iew , I n t e r n a l R e s o u r c e V i e w R e s o l v e r adopta un enfoque menos directo. Usa una
convención en la que se añade un prefijo y un sufijo al nombre de la vista para determinar
la ruta física a un recurso de vista de la misma aplicación Web.
Como ejemplo, imagine que el nombre lógico de la vista es home. Es habitual añadir los
archivos JSP en la carpeta WEB - INF de la aplicación Web para evitar el acceso directo. Si
guardara todos los archivos JSP en /WEB - I N F /v ie w s / y el nombre de su página de inicio
JSP fuera home. j sp , podría derivar la ruta física de la vista del nombre lógico home con
/W E B -IN F /v ie w s / y añadiendo el sufijo . j sp (véase la figura 6.1).

Prefijo Sufijo

/WEB-INF/views/home. j sp

Nombre lógico de vista


Figura 6.1. InternalResourceViewResolver resuelve vistas añadiendo un prefijo y un sufijo
al nombre de la vista.

Puede configurar I n t e r n a lR e s o u r c e V ie w R e s o lv e r con el siguiente método anotado


con @ B ean para aplicar esta convención al resolver vistas:
@Bean
public ViewResolver viewResolver() {
InternalResourceViewResolver resolver =
new InternalResourceViewResolver();
re so lv e r. s e t P r e f i x ( " /WEB-INF/views/" ) ;
re so lv e r. s e t S u f f i x ( " . j s p " ) ;
return resolver;
}
Si prefiere usar la configuración de Spring basada en XML, puede configurar
I n t e r n a l R e s o u r c e V i e w R e s o l v e r de esta forma:
<bean id="viewResolver"
class="o rg . springframework. web. s e r v l e t .view.
InternalResourceViewResolver"
p:pre f ix = "/WEB- INF/views/"
p: s u f f i x = " . jsp" />

Con esta configuración de I n t e r n a l R e s o u r c e V i e w R e s o l v e r , debería resolver


nombres lógicos de vista en archivos JSP de esta forma:

• home e n /W E B -IN F /v ie w s /h o m e . j sp
• p r o d u c t L i s t en / W E B - I N F / v i e w s / p r o d u c t L i s t . j sp
• b o o k s / d e t a i l en / W E B - I N F / v i e w s / b o o k s / d e t a i l . j sp
Representar vistas Web 195

Fíjese especialmente en el último ejemplo. Cuando un nombre lógico de vista incluye


una barra, dicha barra se transfiere al nombre de ruta de recurso. Por lo tanto, se asigna a
un archivo JSP que es un subdirectorio del directorio al que hace referencia la propiedad
p r e f ix . Es una forma muy útil de organizar sus plantillas de vista bajo una jerarquía de
directorios en lugar de tenerlas todas en un único directorio.

Resolver vistas JSIL


Hasta el momento hemos configurado la variante básica de I n t e r n a l R e s o u r c e
V i e w R e s o l v e r . En última instancia resuelve nombres lógicos de vista en instan­
cias de I n t e r n a l R e s o u r c e V i e w que hacen referencia a archivos JSP, pero si dichos
archivos usan etiquetas JSTL para formato o mensajes, puede que desee configurar
I n t e r n a l R e s o u r c e V i e w R e s o l v e r para que resuelva un elemento J s t l V i e w .
Las etiquetas de formato de JSTL necesitan un elemento L ó c a l e para aplicar formato
correctamente a valores específicos de una configuración regional, como fechas o divisas,
y sus etiquetas de mensaje pueden usar un origen de mensajes Spring y un L ó c a l e para
seleccionar los mensajes que representar en HTML. Al resolver J s t l V i e w , las etiquetas
JSTL reciben el L ó c a l e y el origen de mensajes configurado en Spring.
Todo lo que necesita para que I n t e r n a lR e s o u r c e V ie w R e s o lv e r resuelva J s t l V i e w
en lugar de I n t e r n a l R e s o u r c e V i e w es establecer su propiedad v ie w C la s s :
@Bean
public ViewResolver viewResolver() {
InternalResourceViewResolver resolver =
new InternalResourceViewResolver();
re so lv e r. s e t P r e f i x ( "/WEB-INF/views/" ) ;
r e s o l v e r .s e t S u f f ix ( " . j s p " );
re so lv e r. setViewClass(
org.springframework.web. s e r v l e t .v ie w .JstlV iew .cíass);
return resolver;
}
Como antes, puede hacer lo mismo con XML:
<bean id="viewResolver"
cla ss= "o rg . springframework. web. servlet.view.
InternalResourceViewResolver"
p :pref ix = "/WEB- INF/views/"
p : suf f ix = " . j sp"
p:viewClass="org.springframework.web.servlet.view.JstlView" />

Independientemente de que use Java o XML, de este modo se asegura de que las etiquetas
JSTL de formato y de mensaje reciben el L ó c a l e y los orígenes de mensajes configurados
en Spring.

Utilizar las bibliotecas jSP de Spring


Las bibliotecas de etiquetas permiten añadir funcionalidad a una plantilla JSP sin nece­
sidad de escribir código Java directamente en bloques de secuencias de comandos. Spring
le ofrece dos bibliotecas de etiquetas JSP para definir la vista de sus vistas Web del MVC
196 Capítulo 6

de Spring. Una representa etiquetas de formato HTML vinculadas a un atributo model y


la otra contiene diversas etiquetas de gran utilidad. Seguramente la primera le será de más
utilidad que la segunda, de modo que empezaremos por ella. Veremos cómo vincular el
formulario de registro de la aplicación Spittr al modelo para poder completar el formulario
y mostrar errores de validación en caso de que falle el envío del formulario.

Vincular formularios al modelo


La biblioteca de etiquetas JSP de vinculación de formularios de Spring incluye 14
etiquetas y muchas de ellas representan etiquetas de formulario HTML, pero se diferencian
de las etiquetas HTML normales porque se vinculan a un objeto del modelo y porque se
pueden completar con valores de las propiedades del objeto de modelo. La biblioteca de
etiquetas también incluye una que se puede usar para comunicar errores al usuario repre­
sentándolos en el HTML resultante.
Para usar la biblioteca de etiquetas de vinculación de formularios necesita declararla
en las páginas JSP en las que se va a usar:
<%@ ta g lib u ri= "h tt p : //www.springframework. org/tags/form" p re fix = "sf" %>

Hemos especificado el prefijo s f , aunque también se puede usar f orm. Considero que
es más fácil de escribir, por lo que lo utilizaremos a lo largo del libro siempre que aparezca
la biblioteca de vinculación de formularios.
Tras declarar la biblioteca, dispone de 14 etiquetas, enumeradas en la tabla 6.2.

Tabla 6.2. La biblioteca de etiquetas de vinculación de formularios de Spring incluye etiquetas para
vincular objetos de modelo a y desde formularios HTML.

<sf:checkbox> Representa una etiqueta <input> de HTML con type establecido en


checkbox.
<sf:checkboxes> Representa varias etiquetas <input> de HTML con type establecido
en checkbox.
<sf:errors> Representa campos de error en una etiqueta <span> de HTML.
<sf:form> Representa una etiqueta <form> de HTML y una ruta de vinculación
expuesta a etiquetas internas para la vinculación de datos.
<sf:hidden> Representa una etiqueta <input> de HTML con type establecido en
hidden.
<sf:input> Representa una etiqueta <input> de HTML con type establecido en
text.
<sf :label> Representa una etiqueta <label> de HTML.
<sf :option> Representa una etiqueta <option> de HTML. El atributo selected
se establece en función del valor bound.
Representar vistas Web 197

Etiqueta JSP Descripción


■..
< s f :options> Representa una lista de etiquetas <option> de H T M L correspondientes
a la colección, matriz o mapa vinculado.
<sf:password> Representa una etiqueta <input> de HTML con type establecido en
password.

< s f :radiobutton> Representa una etiqueta <input> de H T M L con type establecido en


radio.

< s f :radiobuttons> Representa varias etiquetas <input> de HTML con type establecido
en radio.
< s f :select> Representa una etiqueta <select> de HTML.
< s f :textarea> Representa una etiqueta <textarea> de HTML.

Sería complicado ilustrarlas todas en un ejemplo. En el caso de Spittr solo usaremos las
más adecuadas para el formulario de registro de la aplicación, en concreto < s f : form >,
< s f : in p u t> y < s f :p assw o rd >. Tras aplicarlas, obtendrá lo siguiente:
<sf:form method="POST" commandName="spitter">
F ir s t Ñame: <sf:input path="firstName" /><br/>
Last Ñame: <sf:input path="lastName" /><br/>
Email: <s£:input path="email" /><br/>
Username: <sf:input path="username" /><br/>
Password: <sf:password path="password" /><br/>
<input type="submit" value="Register" />
< / s f : form>

La etiqueta < s f : f orm> representa una etiqueta < f orm> de HTML, pero también define
un contexto alrededor de un objeto de modelo designado en el atributo commandName. Se
hace referencia a las propiedades del objeto de modelo en las demás etiquetas de vincu­
lación de formularios.
En el código anterior, se establece commandName en s p i t t e r , por lo que en el modelo
debe haber un objeto con la clave s p i t t e r o no se podrá representar el formulario (y
se generarán errores JSP). Esto significa que debe modificar S p i t t e r C o n t r o l l e r para
garantizar la presencia de un objeto S p i t t e r con la clave s p i t t e r en el modelo:
@RequestMapping(value="/register", method=GET)
public String showRegistrationForm(Model model) {
model. addAttribute(new S p i t t e r ( ) ) ;
return "registerForm";
}
Con este cambio en s h o w R e g istra tio n F o rm () , el método añade una nueva instancia
S p i t t e r al modelo. La clave del modelo se obtiene del tipo de objeto, s p i t t e r , exacta­
mente lo que necesitamos.
Volviendo al formulario, en los tres primeros campos se cambia la etiqueta < in p u t>
de HTML por < s f : in p u t> . Esta etiqueta representa una etiqueta < in p u t> de HTML
con el atributo ty p e establecido en t e x t . Su atributo v a lu é se establece en el valor de la
198 Capítulo 6

propiedad del objeto de modelo especificado en el atributo p a th . Por ejemplo, si el objeto


S p i t t e r del modelo tiene J a c k como valor de su propiedad f irstN am e, < s f : in p u t
p a t h = " f irs tN a m e " / > representará una etiqueta c i n p u t > con v a lu é =" J a c k " .
El campo passw ord usa < s f :p assw o rd > en lugar de < s f : in p u t >. < s f :p assw o rd >
es similar a < s f : in p u t > pero reproduce < in p u t > de HTML con su atributo ty p e esta­
blecido en p assw ord para ocultar el valor mientras se escribe.
Para visualizar el HTML resultante, imagine que un usuario ya ha remitido el formulario
con valores incorrectos en todos los campos. Tras fallar la validación y después de devolver
el formulario al usuario, el elemento < f orm> resultante tendrá este aspecto:
<form id = "sp itter" a c t io n = " / s p it t e r / s p it t e r / r e g i s t e r " method="POST">
F ir s t Ñame:
cinput id="firstName"
name="firstName" type="text" v a l u e = " J " / x b r / >
Last Ñame:
<input id="lastName"
name="lastName" type="text" valu e="B "/x b r/>
Email:
cinput id="email"
name="email" type="text" v a lu e = " ja c k "/x b r/>
Username:
cinput id="username"
name= "username" type="text" v a lu e = " ja c k "/x b r />
Password:
cinput id="password"
name="password" type="password" value=" "/>cbr/>
cinput type="submit" value="Register" />
c/form>

Conviene recordar que desde Spring 3.1, la etiqueta < s f : in p u t > le permite especificar
un atributo ty p e para declarar campos de texto específicos de HTML 5 como d a ta , ra n g e
y e m a il, entre otros. Por ejemplo, puede declarar el campo e m a il de esta forma:
Email: c s f: input path=" email" type=" email" / x b r / >

En HTML, se representa así:


Email:
cinput id="email" name="email" type="email" v a lu e = " ja c k "/x b r/>

Las etiquetas de vinculación de formularios de Spring suponen una ligera mejora con
respecto al uso de etiquetas HTML estándar, ya que el formulario se completa con valores
previamente añadidos después de fallar la validación, pero no indica al usuario cuál ha sido
el error. Para ayudar al usuario a corregir sus errores necesitará la etiqueta c s f : e r r o r s > .

Mostrar errores
Cuando se producen errores de validación, sus detalles se pasan a la solicitud junto a
los datos del modelo. Basta con adentrarse en el modelo y extraer los errores que mostrar
al usuario. La etiqueta c s f : e r r o r s > facilita esta tarea. Por ejemplo, veamos cómo se usa
c s f : e r r o r s > en este fragmento de r e g is t e r F o r m . j sp:
Representan vistas Web 199

<sf:form method="POST" commandName="s p i t t e r " >


F ir s t Name: < s f : input path="firstName" />
< s f : errors path="firstName" /><br/>

< / s f : form>

Aunque solo mostramos < s f : e r r o r s > aplicada al campo First Ñ am e, es igual de


sencillo usarla en todos los campos del formulario de registro. Aquí, su atributo p a t h se
establece en f ir s tN a m e , el nombre de la propiedad del objeto de modelo S p i t t e r para
el que mostrar los errores. Sino hay errores para la propiedad f ir s tN a m e , < s f : e r r o r s >
no representa nada, pero si hay un error de validación, representa ese mensaje de error en
una etiqueta < s p a n > de HTML.
Por ejemplo, si el usuario remite J como nombre, se representa el siguiente código
HTML para el campo First Ñam e:

F ir s t Ñame: cinput id="firstName"


name="firstName" type="text" value="J"/>
<span id="firstName.errors">size must be between 2 and 30</span>

De esta forma se comunica el error al usuario para que pueda corregirlo. Puede dar un
paso más y cambiar el estilo del error para que destaque. Para ello, se establece el atributo
c s s C la s s :

<sf:form method="POST" commandName="spitter" >


F ir s t Name: < s f : input path="firstName" />
< sf:e rro rs path="firstName" cssClass="error" /><br/>

< / s f : form>

De nuevo, por m otivos de brevedad, solo m ostram os cóm o establecer el atributo


c s s C l a s s de la etiqueta < s f : e r r o r s > con la ruta f irs tN a m e . Si lo desea puede apli­
carlo a otros campos.
Ahora, e r r o r s < s p a n > tiene un atributo c l a s s establecido en e r r o r . Solo falta definir
un estilo CSS para la clase. El siguiente estilo CSS muestra el error en rojo:

span.error {
color: red;
}

En la figura 6.2 puede ver el aspecto actual del formulario en un navegador Web.
Los errores de validación mostrados junto a los campos incorrectos llaman la atención
del usuario sobre los problemas que debe corregir, pero puede generar problemas con el
diseño. Otra forma de procesar errores de validación consiste en mostrarlos todos juntos.
Para ello, puede eliminar el elemento < s f : e r r o r s > de cada campo y colocarlo en la parte
superior del formulario:

<sf:form method="POST" commandName="spitter" >


< sf:e rro rs path="*" element="div" cssClass="errors" />

</sf:form>
200 Capítulo 6

- = s a »
Iocalho5t:8080/jpittr/spitter/regi5ter P ^ {§}'
j Spittr
*1 1
Register
First Name: I |size must be between 2 and 30 1
Last Name: j |size must be between 2 and 30 j
i Username: | |ssze must be between 2 and 16 !
Password: | |size must be between 5 and 25 [
| Register |

Figura 6.2. Errores de validación mostrados junto a los campos del formulario.

La diferencia de este caso con respecto a los anteriores es que la ruta se establece en *.
Es un selector comodín que indica a < s f : e r r o r s > que muestre todos los errores de todas
las propiedades.
Además, se establece el atributo elem en t en d iv . De forma predeterminada, los errores
se representan en una etiqueta < span> de HTML, perfecto si solo hay un error que mostrar,
pero para representar errores de todos los campos, puede haber más de uno que mostrar, y
la etiqueta <sp an> no es la más indicada. Una etiqueta de bloque como < d iv > sería más
acertada, por lo que podemos establecer el atributo e le m e n t en d iv para que los errores
se muestren en una etiqueta < d iv > .
Como antes, se establece c s s C l a s s en e r r o r s para poder aplicar estilo a < d iv > . El
siguiente estilo CSS permite aplicar un borde rojo y un fondo de color rojo claro a < d iv > :
d iv .erro rs {
background-color: # ffc c c c ;
borden 2px so lid red;

Hemos desplazado todos los errores a la parte superior del formulario para facilitar la
composición de la página, pero hemos perdido la capacidad de resaltar los campos que
hay que corregir. Lo podemos solucionar si establecemos el atributo c s s E r r o r C l a s s
de cada campo. También puede envolver las etiquetas con < s f : l a b e l > y establecer su
c ssE rro rC la ss .
Veamos el campo F i r s t Ñame con los cambios aplicados:
<sf:form method="POST" commandName="spitter" >
< s f:la b e l path="firstName"
cssE rro rC lass="error">F irst Name</sf: la b e l> :
<sf.-input path="firstName" cssErrorC lass="error" /><br/>

</sf:form>
Representar vistas Web 201

La etiqueta < s f : 1 abe 1 >, como sucede con las demás etiquetas de vinculación de formu­
larios, tiene un atributo p a th para indicar a qué propiedad del objeto de modelo pertenece.
En este caso se establece en f irstN am e, por lo que se vincula a la propiedad f irstN am e
del objeto S p i t t e r . Si no hay errores de validación, se representará un elemento < l a b e l >
de HTML de esta forma:
clabel for="firstN arae">First Name</label>

La mera configuración del atributo p a th en < s f : l a b e l > no sirve de mucho, pero


también establecemos c s s E r r o r C la s s . Si la propiedad vinculada tiene errores, el atri­
buto c l a s s del elemento < l a b e l > representado se establecerá en e r r o r de esta forma:
clab el for="firstName" c la ss= "e rro r"> F irst Name</label>

Del mismo modo, ahora en la etiqueta < s f : in p u t> se establece c s s E r r o r C l a s s en


e r r o r . Si hay un error de validación, el atributo c l a s s de la etiqueta < in p u t > represen­
tada se establecerá en e r r o r . Ya puede aplicar estilo a la etiqueta y a los campos para que
el usuario sepa si hay errores. Por ejemplo, el siguiente estilo CSS muestra la etiqueta en
rojo y el campo de entrada con un fondo de color rojo claro:
la b e l.e r ro r {
co lo r: red;
}
input. error {
background-color: # ffc c c c ;
}

De este modo puede presentar errores de validación al usuario con cierto atractivo esté­
tico, pero hay algo más que puede hacer. Retomando la clase S p i t t e r , puede establecer
el atributo m essage de las anotaciones de validación para hacer referencia a un mensaje
definido en un archivo de propiedades:

ONotNull
@Size(min=5, max=16, message="{username. s i z e } ")
prívate Strin g username;

@NotNull
@Size(min=5, max=25, message="{password.size}")
prívate String password

@NotNull
@Size(min=2, max=30, message="{ firstName. s i z e } ")
prívate String firstName;

@NotNull
@Size(min=2, max=30, message=" { lastName. s i z e } ")
prívate String lastName;

©NotNull
@Email(message="{email.v a lid }")
prívate String email;
202 Capítulo 6

En cada campo, la anotación @ S iz e tiene un mensaje establecido en una cadena cuyo


valor se encierra entre llaves. Si omite las llaves, el valor asignado al mensaje sería el mensaje
de error mostrado al usuario, pero al incluirlas, se designa una propiedad de un archivo
de propiedades que contiene el mensaje.
Solo falta crear el archivo V a l i d a t i o n M e s s a g e s . p r o p e r t i e s en la raíz de la ruta
de clases:
firstName. size=
F ir s t name must be between {min} and {max} characters long.
lastName. size=
Last name must be between {min} and {max} characters long,
username. size=
Username must be between {min} and {max} characters long,
password. size=
Password must be between {min} and {max} characters long,
email.valid=The email address must be v alid .

La clave de cada mensaje de V a l i d a t i o n M e s s a g e s . p r o p e r t i e s se corresponde a los


valores de marcador de posición de los atributos m e s s a g e . Además, como las longitudes
mínima y máxima no se definen en V a l i d a t i o n M e s s a g e s . p r o p e r t i e s , el mensaje tiene
marcadores de posición propios ( { m i n } y { m ax }) que hacen referencia a los atributos min
y max proporcionados en la anotación @ S iz e .
Cuando el usuario envía un formulario de registro y la validación falla, su navegador
será similar al ilustrado en la figura 6.3.
—: —

_______________ ____
c£>)| <2 j Ioc aIh o5t:8080/spittr/s pitter/re g ister p- ■>!ffios
---- ---------- :
n i :
Spittr
_ _ _ _ _ _ _ _ _ _ _ x 1- r __
Register
Passw ord must be between 5 and 25 characters long.
Username must be between 5 and 16 characters long.
First name must be between 2 and 30 characters long.
Last name must be between 2 and 30 characters long.
First Name: |j__
Last Name: ¡8__
Username: [jack
Password: [~
Register

Figura 6.3. Errores de validación mostrados con mensajes de error obtenidos


de un archivo de propiedades.

La ventaja de extraer los mensajes de error de un archivo de propiedades es la posibilidad


de mostrar mensajes en idiomas y configuraciones regionales concretas creando un archivo
de propiedades específico de la configuración regional. Por ejemplo, para mostrar los errores
anteriores en español, puede crear el archivo V a l i d a t i o n E r r o r s _ e s . p r o p e r t i e s con
el siguiente contenido:
Representar vistas Web 203

firstName. size=
Nombre debe ser entre {min} y {max} caracteres largo.
lastName. size=
El apellido debe ser entre {min} y {max} caracteres largo,
username. size=
Nombre de usuario debe ser entre {min} y {max} caracteres largo,
password. size=
Contraseña debe esta r entre {min} y {max} caracteres largo,
em ail.valid=La d irección de email no es válida

Puede crear tantas variantes de V a l i d a t i o n M e s s a g e s . p r o p e r t i e s como necesite


para abarcar los idiomas y configuraciones regionales que su aplicación admita.

La biblioteca de etiquetas generales de Spring


Además de la biblioteca de etiquetas de vinculación de formularios, Spring también le
ofrece una biblioteca de etiquetas JSP más general. De hecho, fue la primera disponible en
Spring. Con el tiempo ha crecido considerablemente. Para usar la biblioteca de etiquetas
generales de Spring tendrá que declararla en las páginas en las que se utilice:
<%@ ta g lib u ri= "h ttp : //www. springframework.o rg /ta g s" p re fix = "s" %>

Como sucede con cualquier biblioteca de etiquetas JSP, puede utilizar el prefijo que desee.
Por lo general se emplea s p r in g , aunque en mi caso prefiero s por su sencillez y brevedad.
Una vez declarada la biblioteca de etiquetas, puede usar las 10 etiquetas JSP descritas
en la tabla 6.3.

Tabla 6.3. La segunda biblioteca de etiquetas JSP de Spring le ofrece diversas etiquetas de
utilidad además de otras de vinculación de datos.

■ .
i m WmSSsm_wfefówS.
<s :bind> Exporta un estado de propiedad vinculada a una propiedad status
con ámbito de página. Se usa junto a <s :path> para obtener un valor
de propiedad vinculada.
< s :escapeBody> Código HTMLy/o JavaScript que escapa el contenido del cuerpo de la
etiqueta.
<s:hasBindErrors> Representa contenido condicionalmente si el objeto de modelo
especificado (en un atributo de solicitud) tiene errores de vinculación.

< s :htmlEscape> Establece el valor de escape HTML predeterminado para la página


actual.
<s:message> Recupera el mensaje con el código indicado y lo representa
(comportamiento predeterminado) o lo asigna a una variable con
ámbito de página, solicitud, sesión o aplicación (al usar los atributos
var y scope).
<s :nestedPath> Establece una ruta anidada que usar con <s:bind>.
204 Capítulo 6

<s:theme> Recupera un mensaje de tema con el código indicado y lo representa


(comportamiento predeterminado) o lo asigna a una variable con ámbito
de página, solicitud, sesión o aplicación (al usar los atributos var y scope).
<s:transform> Transforma las propiedades no incluidas en un objeto de comando por
medio de editores de propiedades del objeto de comando.
<s:url> Crea URL relativas al contexto compatibles con variables de plantilla
de URI y escape HTML/XML/JavaScript. Puede representar la URL
(comportamiento predeterminado) o asignarla a una variable con ámbito
de página, solicitud, sesión o aplicación (al usar los atributos var y
scope).
<s:eval> Evalúa expresiones SpEL y representa el resultado (comportamiento
predeterminado) o lo asigna a una variable con ámbito de página,
solicitud, sesión o aplicación (al usar los atributos var y scope).

Algunas de estas etiquetas han quedado obsoletas tras la aparición de la biblioteca de


etiquetas de vinculación de formularios de Spring. Por ejemplo, <s b in d > fue la etiqueta
original de vinculación de formularios de Spring, mucho más compleja que las descritas
en el apartado anterior. Como esta biblioteca de etiquetas es mucho menos activa que la de
vinculación de formularios, no la veremos con excesivo detalle. Presentaremos brevemente
algunas de las etiquetas más valiosas y podrá consultar las restantes por su cuenta (es muy
probable que nunca las tenga que utilizar).

Mostrar mesisajes internacionalizados


Las plantillas JSP actuales contienen gran cantidad de texto predefinido. No hay nada
malo al respecto pero no facilita la modificación de dicho texto. Es más, no hay forma de
internacionalizarlo para ajustarlo al idioma del usuario. Por ejemplo, fíjese en el mensaje
de bienvenida de la página de inicio:
<hl>Welcome to S p ittr!< /h l>

La única forma de cambiarlo consiste en abrir home. j sp y modificarlo. Nada compli­


cado, imagino, pero si el texto de la aplicación está desperdigado entre varias plantillas,
habrá que cambiar varios archivos JSP para modificar los mensajes de la aplicación.
Un problema más grave es que independientemente del texto elegido para el mensaje de
bienvenida, todos los usuarios verán el mismo. La Web es una red global y es muy probable
que sus aplicaciones tengan un público global. Por lo tanto, es recomendable comunicarse
con los usuarios en su propio idioma y no obligarles a usar el mismo para todos.
La etiqueta < s :m essa g e> es perfecta para representar texto extemalizado en uno o
varios archivos de propiedades. Por medio de <s : m essage> se puede sustituir el mensaje
de bienvenida predefinido por lo siguiente:
<hl><s:message code="spittr.welcome" /></h l>
Representar vistas Web 205

En este caso, < s : m e ss a g e > representará el texto disponible desde un origen de mensajes
con la clave s p i t t r . we l e orne. Por lo tanto, tendrá que configurar dicho origen para que
< s : m e s s a g e > pueda realizar su labor.
Spring cuenta con varias clases de origen de mensajes y todas implementan la interfaz
M e s s a g e S o u rc e . Una de las más habituales y útiles es R e s o u r c e B u n d le M e s s a g e S o u r c e .
Carga mensajes desde un archivo de propiedades cuyo nombre se obtiene de un nombre
base. El siguiente método @ Bean configura R e s o u r c e B u n d l e M e s s a g e S o u r c e :
@Bean
public MessageSource messageSource() {
ResourceBundleMessageSource messageSource =
new ResourceBundleMessageSource();
messageSource. setBasename("messages" ) ;
return messageSource;

Lo más importante de esta declaración de bean es la configuración de la propiedad


b a s e n a m e . Puede establecerla en el valor que desee, pero hemos elegido m e s s a g e s ,
para que de esta forma R e s o u r c e B u n d l e M e s s a g e R e s o l v e r resuelva mensajes desde
archivos de propiedades en la raíz de la ruta de clases cuyos nombres se deriven del nombre
base.
Opcionalmente puede seleccionar R e l o a d a b l e R e s o u r c e B u n d l e M e s s a g e S o u r c e ,
similar a R e s o u r c e B u n d le M e s s a g e S o u r c e pero capaz de volver a cargar propiedades de
mensaje sin necesidad de compilar de nuevo ni reiniciar la aplicación. Veamos un ejemplo
de configuración de R e l o a d a b l e R e s o u r c e B u n d l e M e s s a g e S o u r c e :
@Bean
public MessageSource messageSource() {
ReloadableResourceBundleMessageSource messageSource =
new ReloadableResourceBundleMessageSource();
messageSource. setBasename("f i l e : / / / etc/sp ittr/m essa g es" ) ;
messageSource. setCacheSeconds(10);
return messageSource;
}

La principal diferencia es que la propiedad b a se n a m e se configura para mirar fuera de


la aplicación (no en la ruta de clases, como hace R e s o u r c e B u n d l e M e s s a g e S o u r c e ) . Se
puede configurar b a se n a m e para que busque mensajes en la ruta de clases (con el prefijo
c l a s s p a t h : ), en el sistema de archivos (con el prefijo f i l e : ) o en la raíz de la aplicación
Web (sin prefijo). Aquí se ha configurado para buscar mensajes en archivos de propiedades
del directorio / e t c / s p i t t r del sistema de archivos del servidor y que tengan el nombre
base "m e s s a g e s " .
A continuación crearemos los archivos de propiedades. Para empezar, crearemos el
predeterminado, m e s s a g e s . p r o p e r t i e s , que se cargará desde la raíz de la ruta de clases
(si usa R e s o u r c e B u n d l e M e s s a g e S o u r c e ) o desde la ruta especificada en la propiedad
b asen am e (si usa R e l o a d a b l e R e s o u r c e B u n d l e M e s s a g e S o u r c e ) . Necesita la siguiente
entrada para el mensaje s p i t t r . w elcom e:
s p i t t r .welcome=Welcome to S p ittr!
206 Capítulo 6

Si no crea más archivos de mensaje, lo único que habrá conseguido es extraer el mensaje
predefinido desde la JSP a un archivo de propiedades, también como mensaje predefinido,
poco más.
Sin embargo, cuenta con las piezas necesarias para iniciar la internacionalización del
mensaje. Si por ejemplo lo quiere mostrar en español, tendría que crear otro archivo de
propiedades, m e ss a g e s _ e s . p r o p e r t i e s , con esta entrada:
spittr.welcome=Bienvenidos a Spittr!

El logro es relevante. Su aplicación se encuentra a varias etiquetas <s :m essa g e> y


archivos de propiedades específicos para idiomas de convertirse en un éxito internacional.

Crear URL________________________ _______ ________________ _


La etiqueta < s : u r 1 > es muy modesta. Su principal labor consiste en crear una URL y
asignarla a una variable o representarla en la respuesta. Sustituye a la etiqueta < c : u r l >
de JSTL pero con nuevas funciones.
En su versión más sencilla, < s : u r l > adopta una URL relativa al contexto de servlet
y la representa con la ruta de dicho contexto como prefijo. Fíjese en el siguiente ejemplo
básico de < s :u r l > :
<a href="<s:url href="/spitter/register" />">Register</a>

Si el contexto de servlet de la aplicación es s p i t t r , se representará el siguiente código


HTML en la respuesta:
<a href="/spittr/spitter/register">Register</a>

Esto le permite crear URL sin preocuparse de la ruta del contexto de servlet; la etiqueta
< s : u r l > se encarga de ello. También la puede usar para crear la URL y asignarla a una
variable que usar después en la plantilla:
<s:url href="/spitter/register" var="registerürl" />

<a href="${registerürl}">Register</a>

De forma predeterminada, las variables de URL se crean en el ámbito de la página, pero


puede hacer que < s : u r l > las cree con ámbito de aplicación, sesión o solicitud si establece
el atributo s e ope:
<s:url href="/spitter/register" var="registerUrl" scope="request" />

Si desea añadir parámetros a la URL, utilice la etiqueta <s :param >. Por ejemplo, la
siguiente etiqueta <s : u r l > tiene dos etiquetas <s :param > anidadas para establecer los
parámetros max y co u n t de / s p i t t l e s :
<s:url href="/spittles" var="spittlesürl">
<s:param name="max" value="60" />
<s:param name="count" value="20" />
</s:url>
Representar vistas Web 207

Hasta el momento no hemos visto nada que < s : u r 1 > haga y que < c : u r 1 > no pueda
hacer. Imagine que tiene que crear una URL con un parámetro p a th . ¿Cómo podría escribir
el valor h r e f para que tuviera un parámetro p a th ?
Si tiene que crear una URL para una página de perfil de usuario concreta, la etiqueta
< s : param> será de nuevo su mejor aliada:
<s:url href="/spitter/{username}" var="spitterürl">
<s:param name="username" value="jbauer" />
</s:url>

Cuando el valor h r e f es un marcador de posición que coincide con un parámetro


especificado por <s :param >, el parámetro se añade en la posición del marcador. Si el
parámetro < s :param > no coincide con ningún marcador de posición de h r e f , se utiliza
como parámetro de consulta.
La etiqueta <s : u r l > también puede solucionar las necesidades de escape de la URL.
Por ejemplo, si quiere representar la URL para mostrarla como parte del contenido de una
página Web (y no para usarla como enlace de hipertexto), puede solicitar a < s : u r l > que
escape el HTML en la URL si establece el atributo h tm lE sc a p e en tr u e . Por ejemplo, la
siguiente etiqueta <s : u r l > representa una URL con escape HTML:
<s:url value="/spittles" htmlEscape="true">
<s:param name="max" value="60" />
<s:param name="count" value="20" />
</s:url>

Se genera una URL que se representa como / s p i t t e r / s p i t t l e s ? m a x = 6 0 & a m p ;


c o u n t = 2 0. Por otra parte, si quiere usar la URL en código de JavaScript, puede establecer
el atributo j a v a S c r i p t E s c a p e en t r u e :
<s:url value="/spittles" var="spittlesJSUrl" javaScriptEscape="true">
<s:param narae="max" value="60" />
<s:param name="count" value="20" />
</s:url>
<script>
var spittlesürl = "${spittlesJSUrl}"
</script>

Se genera lo siguiente en la respuesta:


<script>
var spittlesürl = "\/spitter\/spittles?max=60&count=20"
</script>

Hablando de escape, existe otra tarea para escapar contenido, como veremos a
continuación.

Escapar contenido
La etiqueta < s : e s c a p e B o d y > es una etiqueta de escape de propósito general.
Representa el contenido anidado en su cuerpo y escapa lo que sea necesario.
208 Capítulo 6

Imagine, por ejemplo, que desea mostrar un fragmento de código HTML en una página.
Para que se muestre correctamente, es necesario reemplazar los caracteres < and > por &lt ;
y &gt; o el navegador interpretará ese código HTML como cualquier otro de la página.
Nada le impide añadir &lt; y &gt; y escaparlos a mano pero resulta un tanto compli­
cado y no demasiado legible. En su lugar puede usar < s : escapeBody> y dejar que Spring
se encargue de ello:
< s : escapeBody htmlEscape="true">
<hl>H ello</hl>
< / s : escapeBody>

Se representa lo siguiente en el cuerpo de la respuesta:


&lt ,-hl&gt /H ello&lt; /hl&gt ;

Evidentemente, aunque tenga un aspecto terrible, el navegador lo mostrará como HTML


sin escapar para que lo vea el usuario.
La etiqueta <s :escapeBody> también admite escape JavaScript con el atributo
javaScriptEscape:
< s : escapeBody javaScriptEscape="tru e ">
<hl>H ello</hl>
< / s : escapeBody>

<s :escapeBody>, al contrario de lo que sucede con <s :url>, solamente representa
contenido y no le permite asignarlo a una variable.
Después de ver cómo usar JSP para definir vistas de Spring, veamos qué se necesita
para que resulten visualmente atractivas. Puede añadir numerosos elementos comunes a
las páginas, como por ejemplo un encabezado con el logotipo del sitio, aplicar una hoja
de estilo o incluso mostrar información de copyright en el pie de página, pero en lugar de
hacerlo en cada uno de los archivos JSP de la aplicación Spittr, usaremos Apache Tiles para
añadir diseños comunes y reutilizables a sus plantillas.

Definir un diseño con vistas Apache Tiles


Por el momento no hemos hecho mucho con el diseño de las páginas Web de la aplica­
ción. Cada JSP se encarga de definir su propio diseño y no se esfuerzan demasiado.
Imagine que quiere añadir un encabezado y un pie de página comunes a todas las
páginas de la aplicación. La forma más simple de hacerlo sería visitar todas las plantillas
JSP y añadir el código HTML para el encabezado y el pie de página, pero no es un enfoque
adecuado para el mantenimiento futuro de la aplicación. El coste inicial de añadir esos
elementos a todas las páginas se duplicará en el futuro cuando haya que modificarlas.
Un enfoque más adecuado consiste en usar un motor de diseño como Apache Tiles para
definir un diseño de página común y aplicarlo a todas las páginas. Spring MVC es compa­
tible con Apache Tiles a través de un solucionador de vistas que puede resolver nombres
lógicos de vistas en definiciones de mosaicos.
Representar vistas Web 209

Configurar un solucionador de vistas Tiles


Para poder usar Tiles con Spring necesita configurar un par de bean. Necesita un bean
T i l e s C o n f i g u r e r encargado de localizar y cargar definiciones de mosaico y de la coor­
dinación general de Tiles. Además, necesita un bean T i l e s V i e w R e s o l v e r para resolver
nombres lógicos de vista en definiciones de mosaico.
Este par de componentes presentan dos formas, una para Apache Tiles 2 y otra para
Apache Tiles 3. La principal diferencia entre ambas radica en el nombre de los paquetes. El
par T i l e s C o n f i g u r e r / T i l e s V i ewRe s o l v e r para Apache Tiles 2 pertenece al paquete
o r g . s p r i n g f r a m e w o rk . web . s e r v l e t . v i e w . t i l e s 2 , mientras que la versión para
Tiles 3 se incluye en el paquete o r g . s p r i n g f ra m e w o rk . w e b . s e r v l e t . v i e w . t i l e s 3 .
Para nuestro ejercicio, asumiré que utiliza Tiles 3.
En primer lugar, añadiremos el bean T i l e s C o n f i g u r e r , como se ilustra en el siguiente
código.

Listado 6.1. Configuración de TilesConfigurer para resolver definiciones de mosaico.

@Bean
public TilesConfigurer tile sC o n fig u re r() {
TilesConfigurer t i l e s = new T ilesC onfigurer();
t i l e s . setDef in itio n s (new string U { / / Especificar ubicaciones de definición de mosaicos.
"/W EB-INF/layout/tiles.xm l"
});
t i l e s . setCheckRefresh(true) ; / / H ab ilitar actu alización ,
return t i l e s ;
}

Al configurar T i l e s C o n f i g u r e r , la propiedad más importante es d e f i n i t i o n s .


Acepta una matriz de cadenas en la que cada entrada especifica la ubicación de los archivos
XML de definición de mosaicos. Para la aplicación Spittr tendrá que buscar el archivo
t i l e s . x m l en el directorio / W E B - I N F / l a y o u t / .
Aunque aquí no lo estemos aprovechando, puede especificar varios archivos de defi­
nición de mosaico e incluso usar comodines en la ruta de ubicación. Por ejemplo, podría
pedirle a T i l e s C o n f i g u r e r que busque archivos con el nombre t i l e s . xml en el direc­
torio /W E B -IN F / si establece la propiedad d e f i n i t i o n s de esta forma:
t i l e s . setDefinitions(new S tr in g [] {
"/W EB-IN F/**/tiles.xm l"
}>;
En este caso, usamos comodines de estilo Ant (* * ) para que T i l e s C o n f i g u r e r busque
definiciones de mosaico recursivamente en todos los subdirectorios de /W E B -IN F/.
A continuación, configuraremos T i l e s V i e w R e s o l v e r . Como puede apreciar, es una
definición de bean muy básica, sin propiedades que configurar:
@Bean
public ViewResolver viewResolver() {
return new TilesViewResolver();
}
210 Capítulo 6

Si prefiere trabajar con XML, puede configurar T i l e s C o n f i g u r e r y T i l e s V i e w


R e s o l v e r de esta forma:

cbean id ="tilesC onfigu rer" class=


"org.springframework.web. s e r v le t.v ie w .tile s 3 . TilesC onfigurer">
<property name="definitions">
< lis t>
<value>/W EB-INF/layout/tiles.xml,xml</value>
<value>/W EB-IN F/view s/**/tiles.xml< /valué>
< / l is t >
</property>
</bean>

<bean id="viewResolver" class=


"org . springframework.web. s e r v le t.v ie w .tile s 3 . TilesViewResolver" />

Mientras que T i l e s C o n f i g u r e r carga definiciones de mosaico y coordina con Apache


Tiles, T i l e s V i e w R e s o l v e r resuelve nombres lógicos de vista en vistas que hacen refe­
rencia a definiciones de mosaico. Para ello busca una definición de mosaico cuyo nombre
coincida con el nombre lógico de la vista. Para comprobar su funcionamiento tendrá que
crear varias definiciones de mosaico.

D e fin ir mosaicos
Apache Tiles cuenta con una definición de tipo de documento (DTD) para especificar
definiciones de mosaico en un archivo XML. Cada definición está formada por un elemento
< d e f i n i t io n > que suele tener uno o varios elementos <p u t - a t t r i b u t e >. Por ejemplo,
el siguiente documento XML define varios mosaicos para la aplicación Spittr.

Listado 6.2. Definición de mosaicos para la aplicación Spittr.

<?xml v e rsio n = "l.0" encoding="ISO-8859-l" ?>


<!DOCTYPE tile s -d e fin itio n s PUBLIC
"-//Apache Software Foundation//DTD T ile s Configuration 3.0//EN"
"http : / / t i l e s . apache. o rg /d td s/tiles-co n fig _ 3 _ 0 . dtd">
<ti le s -d é fin it io n s >

<d efin itio n name="base" / / D éfinir un mosaico base.


template="/WEB-INF/layout/page. j sp">
<p u t-attrib u te name="header"
value="/WEB-INF/layout/header. j sp" />
<put-a ttrib u te name="footer"
value="/W EB-INF/layout/footer. j sp" />
</d efin itio n >

<d efin itio n name="home" extends="base">


<p u t-attrib u te name="body"
value="/WEB- INF/views/home. j sp" />
</d efin itio n >

«d éfin itio n name="registerForm" extends="base">


<p u t-attrib u te name="body"
Representar vistas Web 211

value="/WEB-INF/views/registerForm. j sp" />


</d efin itio n >

<d efin itio n name="profile" extends="base">


<p u t-attribu te name="body"
value="/WEB-INF/views/profile. jsp " />
</d efin itio n >

<d efin itio n nam e="spittles" extends="base">


<p u t-attribu te name="body"
value="/W EB-INF/view s/spittles. j sp" />
< /d efin itio n>

<d efin itio n nam e="spittle" extends="base">


cp u t-attrib u te name="body"
value="/W EB-INF/view s/spittle. j sp" />
</d efin itio n >

< /tile s -d e fin itio n s >

Cada elemento < d e f i n i t i o n > define un mosaico que, en última instancia, hace refe­
rencia a una plantilla JSP. En el caso del mosaico b a s e , la plantilla a la que se hace referencia
se encuentra en /W E B -IN F /la y o u t/p a g e . js p .
Un mosaico también puede hacer referencia a otras plantillas JSP que incrustar en la
principal. Para el mosaico base, hace referencia a una plantilla JSP de encabezado y a otra
de pie de página.
A continuación le mostramos la plantilla p a g e . j sp a la que hace referencia el mosaico
base.

Listado 6.3. Plantilla de diseño principal: hace referencia a otras plantillas para crear una vista.

<%@ ta g lib u ri= "h ttp : //www.springframework.org/tags" p re fix = "s" %>


<%@ ta g lib u ri= "h ttp : / / t i l e s . a p a c h e .o rg /ta g s-tile s" p re fix = "t" %>
<%@ page sessio n ="false" %>
<html>
<head>
< t i t l e > S p it t r < / t i t l e >
<link re l= "sty le sh e e t"
type=" t e x t/ c s s "
h re f= "c s:u rl v a lu e= "/re so u rces/sty le.css" />" >
</head>
<body>
cdiv id= "header"> / / In se rta r el encabezado.
< t : in sertA ttrib u te name="header" />
</div>
cdiv id ="content">
c t : in sertA ttrib u te name="body" /> / / In se rta r e l cuerpo.
c/div>
cdiv id ="fo o te r">
c t : in sertA ttrib u te name="fo o te r" /> / / In se rta r e l pie de página.
c/div>
c/body>
c/html>
212 Capítulo 6

Lo más importante del listado 6.3 es cómo usa la etiqueta JSP < t : i n s e r t A t t r i b u t e >
de la biblioteca de etiquetas Tiles para añadir otras plantillas. Se utiliza para insertar los
atributos h e a d e r, body y f o o te r . En última instancia genera un diseño similar al ilus­
trado en la figura 6.4.

Figura 6.4. Un diseño general que define un encabezado, un cuerpo y un pie de página.

Los atributos h e a d e r y f o o t e r se establecen en la definición del mosaico base para


apuntar a /W E B -IN F /la y o u t/h e a d e r. j sp y /W E B -IN F /la y o u t/f o o t e r . j sp respec­
tivamente pero, ¿dónde se establece el atributo body?
El mosaico b a s e no se utilizará de forma independiente. Sirve de definición base (de
ahí su nombre) que otras definiciones de mosaico amplían. En el resto del listado 6.2 puede
ver que las demás definiciones de mosaico amplían b a s e , lo que significa que heredan sus
ajustes de los atributos h e a d e r y f o o t e r (aunque podrían haberlos reemplazado), pero
también establecen un atributo body para hacer referencia a una plantilla JSP específica
de ese mosaico. Si se fija en el mosaico home, verá que amplía b a s e y, por ello, hereda la
plantilla y todos los atributos de b a s e . Aunque la definición del mosaico home sea relati­
vamente sencilla, tiene la siguiente definición:
< d efin itio n name="home" template="/WEB- INF/layout/page. j sp">
<p u t-attrib u te name="header" value="/WEB-INF/layout/header.jsp" />
<p u t-attribu te name="footer" value="/W EB-INF/layout/footer.jsp" />
<p u t-attrib u te name="body" value="/WEB-INF/views/home.jsp" />
</d efin itio n >

Las plantillas concretas a las que hacen referencia los atributos son muy sencillas. Veamos
la plantilla h e a d e r .js p :
Representar vistas Web 213

<%@ ta g lib u ri= "http://www.springframework.org/tags" p re fix = "s" %>


<a h re f= "< s:u rl value="/" />"><img
src="<s :u rl value="/resources" />/im ages/spittr_logo_5 0 .png'1
border="0 "/></a>

La plantilla f o o t e r . j sp es todavía más simple:


Copyright &copy; Craig Walls

Cada mosaico que amplía b a s e define su propia plantilla de cuerpo, de modo que son
diferentes. Para completar la presentación del mosaico home, veamos home . j sp:
<%@ ta g lib u ri= "h ttp : / / ja v a . su n .co m /jsp /jstl/c o re " p refix = "c" %>
<%@ page sessio n ="false" %>
<hl>Welcome to S p ittr< /h l>

<a h re f= "< c:u rl v a lu e = "/sp ittle s" /> "> S p ittle s< /a > |
<a h re f= "< c:u rl value=" / s p it t e r /r e g is t e r " / > " >Register</a>

Lo más importante es que los elementos comunes de una página se capturan en p a g e .


j sp, h e a d e r . j sp y f o o t e r . j sp y se excluyen de las demás plantillas de mosaico. De
este modo se pueden reutilizar entre todas las páginas y se simplifica el mantenimiento
de estos elementos. En la figura 6.5 puede ver cómo se combinan las piezas. La página
incluye estilos e imágenes para aumentar el atractivo de la aplicación. Son irrelevantes
para la descripción del diseño de páginas con Tiles, de modo que no hemos abordado sus
detalles. No obstante, puede ver cómo se combinan los distintos elementos por medio de
las definiciones de mosaico para representa la página de inicio de Spittr.

Figura 6.5. La página de inicio de Spittr, diseñada por medio de Apache Tiles.
214 Capítulo 6

JSP ha sido durante mucho tiempo la opción estándar para plantillas de aplicaciones
Web en Java, pero un nuevo contrincante ha aparecido en escena: Thymeleaf. Veamos a
continuación cómo usar plantillas Thymeleaf con una aplicación de Spring MVC.

Trabajar con Thymeleaf_________________________


Aunque JSP lleve tiempo entre nosotros y se utilice en muchos servidores Web Java, tiene
fallos desafortunados. Uno de los más evidentes parece tener forma de HTML o XML, pero
no lo es. Muchas plantillas JSP adoptan la forma de HTML y están repletas de etiquetas
de diversas bibliotecas de JSP. Aunque brinden sucintas prestaciones de representación
dinámica a JSP, acaban con cualquier esperanza de crear un documento bien formado.
Como ejemplo extremo, piense que una etiqueta JSP incluso se puede usar como valor de
un parámetro HTML:
<input type="text" value="<c:out value="$ {th in g .ñame}"/> " />

Un efecto secundario de las bibliotecas de etiquetas y de la falta de corrección formal


en JSP es que una plantilla JSP suele parecerse al HTML que genera. Es cierto que la visua-
lización en una plantilla JSP sin representar en un navegador Web o un editor HTML tiene
resultados sorprendentes y nada atractivos, un desastre visual. Como JSP no es realmente
HTML, muchos navegadores y editores Web sufren a la hora de mostrar algo que se asemeje
estéticamente a la plantilla que van a representar. Además, JSP es una especificación direc­
tamente vinculada a la especificación de servlet, lo que significa que solo puede usarse con
vistas Web en una aplicación Web basada en servlet. Las plantillas JSP no son adecuadas
para la creación de plantillas de propósito general (como por ejemplo correos electrónicos
con formato) ni para aplicaciones Web que no estén basadas en servlets.
Durante años se ha intentado sustituir a JSP como tecnología de vista dominante para
las aplicaciones Web. Su competidor más reciente, Thymeleaf, parece muy prometedor y
es una atractiva alternativa que tener en cuenta. Las plantillas Thymeleaf son naturales y
no dependen de bibliotecas de etiquetas. Se pueden editar y representar donde se admita
HTML sin procesar, y al no estar vinculadas a la especificación del servlet, llegan a sitios
impensables para JSP. Veamos cómo usar Thymeleaf con Spring MVC.

Configurar un solucionado? de vistas Thymeleaf*•


Para poder usar Thymeleaf con Spring tendrá que configurar tres bean que permitan
la integración entre ambos:

• T h y m e l e a f V i ewRe s o l v e r para resolver vistas de plantillas Thymeleaf a partir de


nombres lógicos de vistas.
• S p r i n g T e m p l a t e E n g i n e para procesar las plantillas y mostrar los resultados.
• T e m p l a t e R e s o l v e r para cargar las plantillas Thymeleaf.

A continuación le mostramos la configuración de Java para declarar los bean.


Representar vistas Web 215

Listado 6.4. Configuración de compatibilidad con Thymeleaf para Spring en configuración de Java.

@Bean
public ViewResolver viewResolver( / / Solucionador de v is ta s Thymeleaf.
SpringTemplateEngine templateEngine) {
ThymeleafViewResolver viewResolver = new ThymeleafViewResolver( );
viewResolver. setTemplateEngine(templateEngine);
return viewResolver;
}
@Bean
public TemplateEngine templateEngine( / / Motor de p la n tilla s .
TemplateResolver templateResolver) {
SpringTemplateEngine templateEngine = new SpringTemplateEngine();
templateEngine. setTemplateResolver(templateResolver);
return templateEngine;
}

@Bean
public TemplateResolver templateResolver() { / / Solucionador de p la n tilla s .
TemplateResolver templateResolver =
new ServletContextTemplateResolver();
templateResolver. s e tP r e fix ( "/WEB-INF/templates/");
templateResolver. s e tS u ffix ( " .htm l") ;
templateResolver. setTemplateMode("HTML5" ) ;
return templateResolver;

Si prefiere configurar los bean en XML, utilice las siguientes declaraciones <bean>.

Listado 6.5. Configuración de Thymeleaf para Spring en XML.

cbean id="viewResolver" / / Solucionador de v is ta s Thymeleaf.


class="org .thym eleaf. spring3.view.ThymeleafViewResolver"
p : templateEngine-ref="templateEngine" />

<bean id="templateEngine" / / Motor de p la n tilla s .


cla ss= "o rg . thymeleaf. spring3. SpringTemplateEngine"
p : tem plateResolver-ref= "templateResolver" />

cbean id="templateResolver" class= / / Solucionador de p la n tilla s .


"org.thym eleaf. templateresolver.ServletContextTemplateResolver"
p :p re fix = " /WEB-IN F/tem plates/"
p : suf f ix = " . html"
p:templateMode="HTML5" />

Independientemente del estilo empleado, ahora Thymeleaf puede representar sus plan­
tillas como respuesta a solicitudes procesadas por controladores de Spring MVC.
T h y m e l e a f V ie w R e s o lv e r es una implementación de V i e w R e s o l v e r de Spring MVC.
Como sucede con cualquier solucionador de vistas, acepta un nombre lógico de vista y
lo resuelve en una vista, pero en este caso la vista es, en última instancia, una plantilla de
Thymeleaf.
216 Capítulo 6

El bean T h y m e l e a f V i e w R e s o l v e r se inyecta con una referencia al bean


S p r i n g T e m p l a t e E n g i n e , un motor de Thymeleaf compatible con Spring para analizar
plantillas y representar resultados basados en las mismas. Como puede apreciar, se inyecta
con una referencia al bean T e m p l a t e R e s o l v e r , el encargado de localizar las plantillas. Se
configura de forma similar a como hicimos con I n t e r n a l R e s o u r c e V i e w R e s o l v e r , con
propiedades p r e f i x y s u f f i x , que se aplican al nombre lógico de vista para localizar la
plantilla Thymeleaf. Su propiedad te m p la t e M o d e también se establece en HTML5, para
indicar que las plantillas resueltas deben representar resultados en HTML5.
Una vez configurados los bean de Thymeleaf, llega el momento de crear las plantillas.

Definir plantillas Thymeleaf


Las plantillas Thymeleaf son básicamente archivos HTML. No existen etiquetas ni
bibliotecas de etiquetas especiales como sucede con JSP. Thymeleaf añade atributos propios
al conjunto estándar de etiquetas HTML a través de un espacio de nombres personalizado.
El siguiente código muestra home. htm l, la plantilla de página principal que usa el espacio
de nombres Thymeleaf.

Listado 6.6. home.html: plantilla de página de inicio que usa el espacio de nombres Thymeleaf.

<html xmlns="http://www.w3. org/1999/xhtml"


xmlns: th="h ttp : //www.thymeleaf. org" > / / Declarar espacio de nombres Thymeleaf.
<head>
< t i t l e > S p it t r < / t i t l e >
<link re l= "sty le sh e e t"
ty p e="text/css"
th : h ref= "@{ / re sources/ s t y l e . c s s }" >< /lin k > / / Enlace th:href a la hoja de e s t ilo s .
</head>
<body>
<hl>Welcome to S p ittr< /h l>

<a th :h re f= "@ {/s p ittle s }" > S p ittle s < /a > | / / Enlace th :h ref a páginas.
<a th :h r e f= " @ {/s p itte r /r e g is te r }" >Register</a>
</body>
</html>

Es una plantilla relativamente sencilla que solamente usa el atributo t h : h r e f , muy


parecido a su equivalente HTML, h r e f , y que se puede usar de forma similar, pero t h : h r e f
es especial porque su valor puede contener expresiones Thymeleaf para evaluar valores
dinámicos. Representará un atributo h r e f estándar con un valor creado de forma dinámica
en tiempo de representación. Es el funcionamiento de muchos de los atributos del espacio
de nombres de Thymeleaf: imitan al atributo HTML estándar con el que comparten nombre
para representar un valor calculado. En este caso, los tres usos del atributo t h : h r e f emplean
las expresiones @ { } para calcular rutas de URL sensibles al contexto (de la misma forma
que utilizaríamos la etiqueta < c : u r l > de JSP o < s : u r l > de Spring en una página JSP).
Aunque hom e. h tm l sea un ejemplo muy básico de plantilla Thymeleaf, es una plantilla
HTML prácticamente pura. Lo único que destaca es el atributo t h : h r e f . Por lo demás, es
un archivo HTML convencional, lo que significa que las plantillas Thymeleaf, al contrario
Representar vistas Web 217

de lo que sucede con JSP, se pueden editar e incluso representar de forma natural, sin nece­
sidad de procesadores. Evidentemente necesitará que Thymeleaf procese las plantillas para
representar el resultado deseado, pero sin cambios especiales, home . htm l se puede cargar
en un navegador Web y tendrá un aspecto muy similar al resultado final. Para ilustrarlo,
en la figura 6.6 se comparan home . j sp y home . htm l.

Figura 6.6. Las plantillas Thymeleaf, al contrario de lo que sucede con las JSP, son HTML
y se pueden representar y editar como HTML.

Como puede apreciar, la plantilla JSP se representa de forma pobre en el navegador Web.
Aunque puede ver elementos conocidos, también se muestran las declaraciones de la biblio­
teca de etiquetas JSP y también hay marcado sin terminar justo antes de los enlaces, como
resultado de la incorrecta interpretación de la etiqueta <s : u r l > por parte del navegador.
218 Capítulo 6

Por el contrario, la plantilla Thymeleaf se representa a la perfección. El único problema


son los enlaces. El navegador no procesa el atributo t h : h r e f como h r e f , de modo que
los enlaces no se representan como tales. Aparte de este problema menor, la plantilla se
representa de la forma esperada.
Plantillas sencillas como home. h tm l sirven de introducción perfecta a Thymeleaf, pero
la vinculación de formularios es algo en lo que las etiquetas JSP de Spring sobresalen. Si
deja de usar JSP ¿también debe abandonar la vinculación de formularios? No se preocupe.
Thymeleaf se guarda un as en la manga.

Vincular formularios con Thymeleaf


La vinculación de formularios es una importante característica de Spring MVC. Permite
a los controladores recibir objetos de comando completados con datos remitidos en un
formulario y que cuando se muestre el formulario esté completado con valores del objeto
de comando. Sin una correcta vinculación de formularios tendría que comprobar que
los campos del formulario HTML tuvieran los nombres adecuados para asignarlos a las
propiedades del objeto de comando, y también tendría que comprobar que los valores de
esos campos se establecieran en propiedades del objeto de comando al volver a mostrar el
formulario tras un fallo de validación. Pero la vinculación de formularios se encarga de todo.
Para recordarle su funcionamiento, veamos el campo First Ñ am e de r e g i s t r a t i o n . j sp:
<sf:label path="firstName"
cssErrorClass="error">First Name</sf:label>:
<sf:input path="firstName" cssErrorClass="error" /><br/>

Aquí, se invoca la etiqueta < s f : i n p u t > de la biblioteca de etiquetas de vinculación


de formularios para representar una etiqueta < i n p u t > de HTML con su atributo v a l u é
establecido en el valor de la propiedad f ir s t N a m e del objeto de comando. También se
usa < s f : l a b e l > de Spring y su clase c s s E r r o r C l a s s para representar la etiqueta en
rojo si hay errores de validación.
Pero en este apartado no vamos a hablar de JSP, sino de todo lo contrario, de reemplazar
JSP con Thymeleaf. Por ello, en lugar de usar etiquetas JSP de Spring para conseguir la
vinculación de formularios, recurriremos a las funciones de Thymeleaf para Spring.
Como ejemplo, fíjese en este fragmento de una plantilla de Thymeleaf que representa
el campo First Ñ am e:
clabel th:class="${#fields.hasErrors('firstName')}? 'error'">
First Name</label>:
cinput type="text" th:field="*{firstName}"
th:class="${#fields.hasErrors('firstName')}? 'error'" /><br/>

En lugar de usar el atributo c s s C la s s N a m e como hicimos con las etiquetas JSP de


Spring, usamos el atributo t h : c l a s s de Thymeleaf en etiquetas HTML estándar. El atributo
t h : c l a s s representa un atributo c l a s s con un valor calculado a partir de la expresión
proporcionada. En ambos usos de t h : c l a s s , se comprueba directamente si hay errores
de campo en el campo f i r s t N a m e . Si los hay, se representa el atributo c l a s s con el valor
e r r o r . Si no los hay, no se representa.
Representar vistas Web 219

La etiqueta < in p u t> usa el atributo t h : f i e l d para hacer referencia al campo f i r s t


Ñame del objeto de comando. Puede que sea ligeramente distinto a lo que esperaba. En
muchas plantillas de Thymeleaf usará un atributo Thymeleaf similar a un atributo HTML
estándar, por lo que podría parecerle correcto usar el atributo t h : v a lu é para establecer
el atributo v a lu é de la etiqueta < in p u t> .
En su lugar, como estamos vinculando el campo a la propiedad f i r s t N a m e del objeto,
usamos el atributo t h : f ield, que hace referencia a dicho campo. Al usar t h : f i e l d , obte­
nemos tanto un atributo v a lu é establecido en el valor de f i r s t N a m e como un atributo
ñ a m e establecido en f irstName. Para ilustrar la vinculación de datos de Thymeleaf, el
siguiente código muestra la plantilla de formulario de registro completa.

Listado 6.7. Página de registro, que usa Thymeleaf para vincular un formulario a un objeto de
comando.

<form method="POST" th :o b je c t = " $ { s p it te r } ">


<div c la s s = "e rro rs" t h : i f = " $( # f ie ld s . hasE rrors( '* ') } " > / / Mostrar erro res.
<ul>
< l i th :each ="err : $ { # f ie ld s . e r r o r s ( ' * ' ) } "
t h : te x t= "$ { e r r } " >Input is in c o rre c t< /li>
</ul>
</div>
<lab el th -.class="${# f ield s .hasErrors ( 'firstN am e') }? 'e r r o r '"> / / Nombre.
F ir s t Name</label>:
cinput type="text" th :fie ld = "*{firstN a m e }"
th :c la s s = "$ {# fie ld s .h a s E rr o r s ( 'firstN am e')}? 'e r r o r '" /><br/>

<label th :c la s s = "$ {# fie ld s .h a s E rr o r s ( 'lastN ame')}? 'e r r o r '"> / / Apellido.


Last Name</label>:
cinput type="text" th :field ="*{lastN am e}"
th :c la s s = "$ {# fie ld s .h a s E rr o r s ( ' lastName')}? 'e r r o r '" /><br/>

clabel th :c la s s = "$ {# fie ld s .h a s E rr o r s { 'e m a il')}? 'e r r o r '"> / / Correo electró n ico .
E m ail</label>:
cinput type="text" th :fie ld = "* {e m a il}"
th :c la s s = " $ { # f ie ld s . hasE rrors( 'e m a il')}? 'e r r o r '" />cb r/>

clab el th :c la s s = "$ {# fie ld s .h a s E rr o r s ( 'username')}? 'e r r o r '"> / / Nombre de usuario.


Usernamec/label>:
cinput type="text" th :f ie ld = " * (username}"
th :c la s s = "$ (# fie ld s .h a s E rr o r s ( 'username')}? 'e r r o r '" /> c b r />

clab el th :c la s s = "$ (# fie ld s.h a sE rro rs( 'password')}? 'e r r o r '" > / / Contraseña.
Passwordc/label>:
cinput type="password" th :f ie ld = " * {password}"
th :c la s s = "$ {# fie ld s .h a s E rr o r s ( 'password')}? 'e r r o r '" />cb r/>

cinput type="submit" value="Register" />


c/form>

Este listado muestra que todos los campos del formulario usan los mismos atributos
de Thymeleaf y la expresión * { } para la vinculación al objeto. Se repite lo que ya hicimos
con el campo F irst Ñam e.
220 Capítulo 6

Pero también comprobará que se usa Thymeleaf cerca de la parte superior del formu­
lario para representar todos los errores. El elemento < d iv > tiene un atributo t h : i f que
comprueba si hay errores. Si los hay, se representa el < d iv > y, en caso contrario, no se
representa.
En < d i v> hay una lista sin ordenar para mostrar todos los errores. El atributo t h : e a ch
de la etiqueta < 1 i > indica a Thymeleaf que represente el < 1 i > una vez por cada error, y
asigne el error actual de cada iteración a la variable e r r .
La etiqueta < l i > también tiene un atributo t h : t e x t , que le indica a Thymeleaf que
evalúe una expresión (en este caso, el valor de la variable e r r ) y represente su valor como
cuerpo de la etiqueta < 1 i >. De hecho, habrá un elemento < 1 i > por cada error, que mostrará
el texto correspondiente.
Seguramente se pregunte qué diferencia hay entre las expresiones incluidas en $ { } y
las incluidas en * { } . Las expresiones $ { } (como $ { s p i t t e r }) son variables. Suelen ser
expresiones OGNL (O bject-G raph N avigation Language, Lenguaje de navegación Objeto-
Gráfico) (h t t p : / / commons. a p a c h e . o rg /p ro p e r /c o m m o n s -o g n l/), pero cuando se
usan con Spring, son expresiones SpEL. En el caso de $ { s p i t t e r }, resuelve en la propiedad
model con la clave s p i t t e r .
Las expresiones * { } son de selección. Mientras que las expresiones variables se evalúan
sobre la totalidad del contexto SpEL, las de selección se evalúan sobre un objeto seleccio­
nado. En el caso del formulario, el objeto seleccionado es el proporcionado en el atributo
t h : o b j e c t de la etiqueta < f orm>: un objeto S p i t t e r del modelo. Por lo tanto, la expresión
* { f i r s t N a m e } evalúa a la propiedad f ir s t N a m e del objeto S p i t t e r .

Resumen
El procesamiento de solicitudes es solo la mitad de Spring MVC. Si tiene pensado mostrar
resultados devueltos por los controladores que escribe, los datos de modelo generados
deben poder representarse en vistas y mostrarse en el navegador Web del usuario. Spring
es muy flexible en lo que a representación de vistas se refiere y le ofrece diversas opciones,
como páginas JSP convencionales y el conocido motor de diseño Apache Tiles.
En este capítulo hemos visto todas las opciones de vistas y resolución de vistas de Spring,
además de adentrarnos en el uso de JSP y Apache Tiles con Spring MVC.
También hemos visto cómo usar Thymeleaf, una alternativa a JSP, como capa de vista
de una aplicación de Spring MVC. Thymeleaf es una opción muy atractiva ya que permite
crear plantillas naturales que siguen siendo HTML y que se pueden ver y editar como si
fueran código HTML estático pero que representan datos de modelo dinámicos en tiempo
de ejecución. Además, las plantillas Thymeleaf no están vinculadas a los servlet, por lo que
pueden usarse allí donde JSP no se puede emplear. Tras definir la vista de la aplicación
Spittr, ahora dispone de una aplicación Web básica, pero totalmente funcional, escrita con
Spring MVC. Todavía tenemos que abordar problemas como la persistencia de datos y la
seguridad, como haremos en breve, pero la aplicación empieza a tomar forma.
Antes de adentramos en la pila de la aplicación, en el siguiente capítulo continuaremos
el análisis de Spring MVC y veremos algunas de sus prestaciones más útiles y avanzadas.
Capítulo
Operaciones avanzadas
con Spring MVC

CONCEPTOS FUNDAMENTALES:

• Configuraciones alternativas de Spring MVC.


• Control de transferencias de archivos.
• Procesamiento de excepciones en controladores.
• Atributos flash.
¡Espere, todavía hay más! Seguramente haya escuchado esta frase en espacios televisivos
en los que se vende un aparato o un dispositivo. Justo después de que el anuncio describa
el producto, pronuncian esa frase como un mantra y el anuncio continúa describiendo las
bondades del producto.
En muchos aspectos, Spring MVC (y en realidad la totalidad de Spring) se comporta
de la misma forma. Justo después de que crea saber todo lo que Spring MVC pueda hacer,
descubre que todavía hay más. En un capítulo anterior vimos los aspectos básicos de Spring
MVC y cómo crear controladores para procesar distintos tipos de solicitudes. Después,
aprendimos a crear las vistas JSP y Thymeleaf que presentan datos del modelo al usuario.
Seguramente crea saberlo todo sobre Spring MVC pero ¡espere, todavía hay más!
En este capítulo continuaremos con Spring MVC y analizaremos distintas funciones
que superan los aspectos básicos ya descritos. Aprenderemos a crear controladores para
aceptar transferencias de archivos, a solucionar excepciones generadas por controladores
y a pasar datos en el modelo para que sobrevivan a una redirección.
Pero antes, una promesa pendiente. En un capítulo anterior vimos cómo usar
A b s t r a c t A n n o t a t i o n C o n f i g D i s p a t c h e r S e r v l e t I n i t i a l i z e r para configurar
Spring MVC y prometí mostrarle opciones de configuración alternativas. Por ello, antes
de abordar las transferencias de archivos y el control de excepciones, nos detendremos en
otras formas de configurar D i s p a t c h e r S e r v l e t y C o n t e x t L o a d e r L i s t e n e r .

Configuración alternativa de Spring MVC


En un capítulo anterior vimos cómo configurar Spring MVC mediante la amplia­
ción de A b s t r a c t A n n o t a t i o n C o n f i g D i s p a t c h e r S e r v l e t I n i t i a l i z e r . Esta
clase asume que queremos una configuración básica de D i s p a t c h e r S e r v l e t y
C o n t e x t L o a d e r L i s t e n e r , y que la configuración de Spring se va a realizar con Java y
no con XML.
Aunque sea válido para muchas aplicaciones de Spring, puede que no siempre sea lo
que necesita. Puede que necesite servlets y filtros además de D i s p a t c h e r S e r v l e t , que
tenga que añadir configuración en D i s p a t c h e r S e r v l e t o, si desarrolla una aplicación
en un contenedor anterior a Servlet 3.0, que tenga que configurar D i s p a t c h e r S e r v l e t
en un archivo w e b . xml tradicional. Afortunadamente existen varias formas de que Spring
le devuelva parte del control cuando la configuración A b s t r a c t A n n o t a t i o n C o n f i g
D i s p a t c h e r S e r v l e t l n i t i a l i z e r estándar no se ajuste a sus necesidades. Empezaremos
por las distintas formas de personalizar la configuración de D i s p a t c h e r S e r v l e t .

Personalizar la configuración de DispatcherServlet


Aunque no sea evidente en el listado 7.1, la clase A b s t r a c t A n n o t a t i o n C o n f i g
D i s p a t c h e r S e r v l e t l n i t i a l i z e r es más completa de lo que parece. Los tres métodos
creados en S p i t t r W e b A p p I n i t i a l i z e r fueron los únicos abstractos que había que
reemplazar, pero hay otros que puede reemplazar para aplicar configuración adicional.
224 Capítulo 7

Uno de ellos es c u s t omi z eReg i s t r a t i o n ( ) . Después de que Ab s t r a e t Anno t a t i on


C o n f i g D i s p a t c h e r S e r v l e t I n i t i a l i z e r registre D i s p a t c h e r S e r v l e t con
el contenedor de servlet, invoca el método c u s t o m i z e R e g i s t r a t i o n () y pasa
S e r v l e t R e g i s t r a t i o n . D y n a m ic , generado tras el registro del servlet. Al reem­
plazar c u s t o m i z e R e g i s t r a t i o n () puede aplicar configuración adicional a
D i s p a t c h e r S e r v l e t . Por ejemplo, en un apartado posterior veremos cómo procesar
solicitudes multiparte y transferencias de archivos con Spring MVC. Si tiene pensado
usar Servlet 3.0 para la configuración multiparte, tendrá que habilitar el registro de
D i s p a t c h e r S e r v l e t para habilitar las solicitudes multiparte. Puede reemplazar el método
c u s t o m i z e R e g i s t r a t i o n () para establecer M u l t i p a r t Conf i g E l e m e n t de esta forma:
(»Override
protected void custom izeRegistration(Dynamic re g istra tio n ) {
reg istratio n .setM u ltip artC o n fig (
new MultipartConfigElement( " /tm p/spittr/up load s") ) ;
}

Con el S e r v l e t R e g i s t r a t i o n . D y n a m i c proporcionado a c u s t o m i z e
R e g i s t r a t i o n ! ) puede hacer varias cosas, como establecer la prioridad de carga
al inicio mediante la invocación de s e t L o a d O n S t a r t u p ( ) , establecer un parámetro
de inicialización mediante la invocación de s e t I n i t P a r a m e t e r () o invocar s e t
M u l t i p a r t C o n f i g () para configurar la compatibilidad multiparte de Servlet 3. En el
ejemplo anterior, establecemos la compatibilidad multiparte para almacenar temporalmente
archivos transferidos en / t m p / s p i t t r / u p l o a d s .

Añadir servlet y filtros adicionales


De acuerdo a la definición de A b s t r a c t A n n o t a t i o n C o n f i g D i s p a t c h e r S e r v l e t
I n i t i a l i z e r , crea un D i s p a t c h e r S e r v l e t y un C o n t e x t L o a d e r L i s t e n e r . ¿Y si
quiere registrar servlet, filtros o escuchadores adicionales?
Una de las ventajas de trabajar con un inicializador basado en Java es que puede definir
todas las clases de inicializador que desee, al contrario de lo que sucede con w e b . xml. Por
lo tanto, si tiene que registrar componentes adicionales en el contenedor Web, basta con
crear un nueva clase de inicializador. La forma más sencilla de hacerlo consiste en imple-
mentar la interfaz W e b A p p l i c a t i o n l n i t i a l i z e r de Spring.
Por ejem plo, el siguiente código m uestra cóm o crear una im plem en tación de
W e b A p p l i c a t i o n l n i t i a l i z e r para registrar un servlet.

Listado 7.1. Implementation de WebApplicationlnitializer para registrar un servlet.

package com.myapp. config;


import j avax. s e r v le t. ServletC ontext;
import ja v a x .s e r v le t. ServletException;
import j avax. s e r v le t. S e rv le tR eg istra tio n . Dynamic;
import org. springframework.w eb.W ebA pplicationlnitializer;
import com.myapp.MyServlet;
Operaciones avanzadas con Spring MVC

public cla ss M y S erv letln itializer implements W ebA pplicationlnitializer {

©Override
public void onStartup(ServletContext servletContext)
throws ServletException {

Dynamic myServlet = / / R eg istrar e l s e rv le t.


servletC ontext. addServlet("myServlet", MyServlet. c la s s ) ;

myServlet. addMapping( "/custom /**" ) ; / / Asignar e l s e rv le t.

El listado 7.1 es una clase de inicializador de registro de servlet bastante básica.


Registra un servlet y lo asigna a una única ruta. Puede usar esta técnica para regis­
trar D i s p a t c h e r S e r v l e t manualmente (pero no será necesario, ya que A b s t r a c t
A n n o t a t i o n C o n f i g D i s p a t c h e r S e r v l e t l n i t i a l i z e r sirve perfectamente sin tanto
código). Del mismo modo, puede registrar escuchadores y filtros si crea una nueva imple-
mentación de W e b A p p l i c a t i o n l n i t i a l i z e r . El siguiente código, por ejemplo, muestra
cómo registrar un filtro.

Listado 7.2. WebApplicationlnitializer para registrar filtros.

@Override
public void onStartup(ServletContext servletContext)
throws ServletException {

ja v a x .s e r v le t. F ilte r R e g is tra tio n . Dynamic f i l t e r =


servletC ontext. a d d F ilte r("m y Filter", M yFilter. c l a s s ) ; / / R eg istrar f i l t r o .

f i l t e r . addMappingForUrlPatterns(nuil, fa ls e , "/custom /*" ) ; / / Asignación de f i l t r o .

W e b A p p l i c a t i o n l n i t i a l i z e r es una forma genérica de registrar servlet, filtros y


escuchadores en Java en proyectos de desarrollo para un contenedor Servlet 3.0, pero si
quiere registrar un filtro y solo tiene que asignarlo a D i s p a t c h e r S e r v l e t , encontrará
una solución en A b s t r a c t A n n o t a t i o n C o n f i g D i s p a t c h e r S e r v l e t l n i t i a l i z e r .
Para registrar uno o más filtros y asignarlos a D i s p a t c h e r S e r v l e t , basta con reem­
plazar el método g e t S e r v l e t F i l t e r s () de A b s t r a c t A n n o t a t i o n C o n f i g D i s p a t c h e r
S e r v l e t l n i t i a l i z e r . Por ejemplo, el siguiente método g e t S e r v l e t F i l t e r s ()
reemplaza el de A b s t r a c t A n n o t a t i o n C o n f i g D i s p a t c h e r S e r v l e t l n i t i a l i z e r
para registrar un filtro:
@Override
protected F i l t e r [] g e tS e r v le tF ilte r s () {
return new F ilt e r [] { new M yFilter() };
}
Como puede apreciar, este método devuelve una matriz de j a v a x . s e r v l e t . F i l t e r .
Aquí solamente devuelve un filtro, pero podría devolver todos los que necesite. No
es necesario declarar la asignación para los filtros; todos los filtros devueltos por g e t
226 Capítulo 7

S e r v l e t F i l t e r s ( ) se asignan automáticamente a D i s p a t c h e r S e r v l e t . Al desarrollar


para un contenedor Servlet 3.0, Spring le ofrece varias formas de registrar servlet (incluido
D i s p a t c h e r S e r v l e t ) , filtros y escuchadores sin necesidad de crear un archivo web.
xm l, pero no tendrá que usarlas si no quiere. Si no piensa implementar su aplicación en
un contenedor Servlet 3.0 (o si prefiere trabajar con w eb . xml), puede configurar Spring
MVC con w eb . xm l, como veremos a continuación.

Declarar DispatcherServlet en web.xml


En una aplicación Spring MVC típica, necesita un elemento D i s p a t c h e r S e r v l e t
y otro C o n t e x t L o a d e r L i s t e n e r . A b s t r a c t A n n o t a t i o n C o n f i g D i s p a t c h e r
S e r v l e t I n i t i a l i z e r los registra automáticamente pero si quiere registrarlos en w e b .
xm l, tendrá que encargarse personalmente de ello. El siguiente archivo web .x m l básico
muestra una configuración típica para D i s p a t c h e r S e r v l e t y C o n t e x t L o a d e r L i s t e n e r .

Listado 7.3. Configuración de Spring MVC en web.xml.

<?xml v e rsio n = "l.0" encoding="UTF-8"?>


<web-app versio n ="2.5"
xmlns="h ttp : / / j ava. sun. com/xml/ns/javaee"
xm lns:xsi="h ttp : / /www.w3. org/2001/XMLSchema-in stan ce"
x s i : schemaLocation="h ttp : / / ja v a . sun.com/xml/ns/javaee
h tt p :/ /ja v a .sun.com/xml/ns/javaee/web-app_2_5.xsd">

<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/spring/root-context.xml</param-value> / / Establecer ubicación
/ / del contexto ra íz .
</co n te x t-param>

< liste n er>


<lis te n e r - c la s s >
org. springframework. web. co n tex t. ContextLoaderListener
/ / R egistrar ContextLoaderListener.
</ l is t e n e r - class>
< /liste n e r>

<servlet>
<servlet-name>appServlet</servlet-name>
<s e r v le t-c la s s >
org. springf ramework. web. s e r v le t. D ispatcherServlet / / Registrar DispatcherServlet.
</s e r v le t- c la s s >
<load-on-startup>l</load-on-startup>
< /serv let>

<servlet-mapping>
<servlet-name>appServlet</servlet-name> / / Asignar D ispatcherServlet a / .
<u rl-p attern >/</u rl-p attern >
< /servlet-mapping>

</web-app>
Operaciones avanzadas con Spring MVC 227

Como ya m encionam os en un capítulo anterior, C o n t e x t L o a d e r L i s t e n e r


y D i s p a t c h e r S e r v l e t cargan un contexto de aplicación de Spring. El parámetro
c o n t e x t Conf i g L o c a t i o n especifica la ubicación del archivo XML que define el contexto
de aplicación raíz cargado por C o n t e x t L o a d e r L i s t e n e r . Según su definición en el
listado 7.3, el contexto raíz se carga con definiciones de bean en / WEB - I N F / s p r i n g / r o o t -
c o n t e x t . xml. D i s p a t c h e r S e r v l e t carga su contexto de aplicación con bean definidos
en un archivo cuyo nombre se basa en el del servlet. En el listado 7.3, el nombre del servlet
es a p p S e r v l e t . Por lo tanto, D i s p a t c h e r S e r v l e t carga su contexto de aplicación desde
un archivo XML en / W E B - I N F / a p p S e r v l e t - c o n t e x t .xm l.
Si prefiere especificar la ubicación del archivo de configuración D i s p a t c h e r S e r v l e t ,
puede establecer un parámetro de inicialización c o n t e x t C o n f i g L o c a t i o n en el servlet.
Por ejemplo, la siguiente configuración de D i s p a t c h e r S e r v l e t carga sus bean desde
/W E B -IN F /s p rin g /a p p S e rv le t/s e rv le t-c o n te x t.x m l:
<servlet>
<servlet-name>appServlet</s e r v le t-name>
<s e r v le t-c la s s >
org. springframework.web. s e r v le t.D ispatcherServlet
< /s e r v le t-cíass>
<in .it-param>
<param-narae>contextConfigLocation</param-name>
<param-value>
/ web-INF/spring/appServlet/servlet-context.xm l
</param-value>
< /init-param>
<load-on-startup>l</load-on-startup>
< /servlet>

Evidentemente, es la forma de que D i s p a t c h e r S e r v l e t y C o n t e x t L o a d e r L i s t e n e r


carguen sus respectivos contextos de aplicación desde XML, pero a lo largo del libro nos
decantaremos por la configuración de Java, por lo que tendrá que configurar Spring MVC
para cargar la configuración desde clases anotadas con @Conf i g u r a t i o n .
Para usar configuración basada en Java en Spring MVC, tendrá que indicar a
D i s p a t c h e r S e r v l e t y C o n t e x t L o a d e r L i s t e n e r que usen A n n o t a t i o n C o n f i g
W e b A p p l ic a t io n C o n t e x t , una implementación de W eb A p p lic a t i o n C o n t e x t que carga
clases de configuración de Java en lugar de XML. Para ello puede establecer el parámetro
de contexto y parámetro de inicialización c o n t e x t C l a s s para D i s p a t c h e r S e r v l e t .
El siguiente código muestra un nuevo archivo w e b . xml que configura Spring MVC para
configuración de Spring basada en Java.

Listado 7.4. Configuración de web.xml para usar configuración de Java.

<?xml v e rsio n = "l.0" encoding="UTF-8"?>


<web-app version="2.5"
xmlns="h ttp : / / ja v a .sun.com/xml/ns/javaee"
xmlns :x si= "h ttp -. //www. w3 . org/20 01/XMLSchema-instance"
x si : schemaLocation="http.-/ / ja v a . sun.com/xml/ns/javaee
h ttp : / / ja v a . sun.com/xml/ns/javaee/web-app_2_5.xsd" >
<context-param>
<param-name>contextClass</param-name> / / Usar configuración de Java.
228 Capítulo 7

<param-value>
org.springframework. web. co n tex t. support.
AnnotationConfigWebApplicationContext
< /param-value >
< /context-param>
ccontext-param>
<param-name>contextConfigLocation</param-name>
<param-value>com.habuma. s p i t t e r . co n fig .RootConfig</param-value>
/ / E sp e cificar cla se de configuración ra íz .
</co n te x t-param>
< liste n er>
< lis te n e r-c la s s >
org. springframework. web. co n tex t. ContextLoaderListener
< / l i s te n er-c la s s >
< /liste n e r>

<servlet>
<servlet-name>appServlet</servlet-name>
<s e r v le t-c la s s >
org. springframework.web. s e r v le t. D ispatcherServlet
< /s e r v le t-c la s s >
<init-param>
<param-name>contextClass</param-name> / / Usar configuración de Java.
<param-value>
org. springframework. web. co n tex t. support.
Annotat ionConf igWebApplicationContext
</param-value >
</init-param >
cinit-param>
<param-name >contextConf igLocat ion</param-name >
//E s p e c ific a r clase de configuración D ispatcherServlet.
<param-value>
com.habuma. s p i t t e r . co n fig .WebConfigConfig
</param-value>
< /init-param>
<load-on-startup>l</load-on-startup>
< /serv let>

<s e r v le t-mapping>
<servlet-name>appServlet</servlet-name>
<u rl-p attern >/</u rl-p attern >
</servlet-mapping>
</web-app>

Después de presentar las distintas formas de configurar Spring MVC, veremos cómo
usar Spring MVC para procesar transferencias de archivos.

Procesar datos de formularios multi parte


Es habitual que una aplicación Web permita a sus usuarios transferir contenidos.
En sitios como Facebook y Flickr, los usuarios suben fotos y vídeos para compartir con
amigos y familiares. Existen otros servicios que permiten a los usuarios subir fotos para su
Operaciones avanzadas con Spring MVC 229

impresión en papel o en camisetas o tazas. La aplicación Spittr permite la transferencia de


archivos en dos puntos. Cuando un nuevo usuario se registra en la aplicación, querrá que
pueda proporcionar una imagen para asociarla a su perfil, y cuando un usuario publique
un nuevo S p i t t l e , puede subir una foto junto al mensaje.
La consulta resultante de un envío de formulario típico es muy sencilla y adopta la
forma de varios pares de nombre y valor separados por símbolos &. Por ejemplo, al enviar
el formulario de registro desde la aplicación Spittr, la solicitud puede tener este aspecto:
firstName=Charles&lastName=Xavier&email=professorx%40xmen.org
&username=professorx&password=letmein01

Aunque este sistema de codificación es sencillo y suficiente para enviar formularios


basados en texto, no es lo bastante robusto para incluir datos binarios como los de una
imagen. Por el contrario, los datos de formularios multiparte dividen un formulario en
varias partes, una por campo, cada una con su propio tipo.
Los campos de formulario típicos incluyen datos textuales pero cuando se transfiere
un contenido, esa parte puede ser binaria, como se muestra en el cuerpo de la siguiente
solicitud multiparte:
-------- WebKitFormBoundaryqgkaBn8IHJCuNmiW
Content-Disposition: form-data; name="firstName"

Charles
-------- WebKitFormBoundaryqgkaBn8IHJCuNmiW
Content-Disposition: form-data; name="lastName"

Xavier
-------- WebKit FormBoundaryqgkaBn8IHJCuNmiW
Content-Disposition: form-data; name="email"

[email protected]
-------- WebKit FormBoundaryqgkaBn81HJCuNmiW
Content-Disposition: form-data; name="username"

professorx
-------- WebKitFormBoundaryqgkaBn8IHJCuNmiW
Content-Disposition: form-data; name="password"

letmeinOl
-------- WebKi t FormBoundaryqgkaBn8IHJCuNmiW
Content-Disposition: form-data; nam e="profilePicture"; filename="me.jpg"
Content- Type: image/jpeg

[[ Añadir aquí datos binarios de imágenes ]]


-------- WebKi tFormBoundaryqgkaBn8IHJCuNmiW--

En esta solicitud multiparte, la parte p r o f i l e P i c t u r e es evidentemente diferente a


las demás. Entre otras cosas, tiene su propio encabezado C o n t e n t T y p e que indica que
es una imagen JPEG, y aunque no sea evidente, el cuerpo de la parte p r o f i l e P i c t u r e
son datos binarios en lugar de texto sencillo. Aunque las solicitudes multiparte parecen
complejas, su procesamiento en un controlador Spring MVC es muy sencillo, pero antes
230 Capítulo 7

de poder escribir métodos de controlador para procesar transferencias de archivos, tendrá


que configurar un solucionador multiparte para indicar a D i s p a t c h e r S e r v l e t cómo
leer solicitudes multiparte.

Configurar un solucionador multiparte


D i s p a t c h e r S e r v l e t no implementa lógica para analizar los datos de una solicitud
multiparte. En su lugar, delega en una implementación de la interfaz de estrategias
Mui t i p a r t Re s o l v e r de Spring la resolución de su contenido. Desde Spring 3.1 se incluyen
dos implementaciones de M u l t i p a r t R e s o l v e r entre las que elegir:

• C o m m o n s M u l t i p a r t R e s o l v e r : Resuelve solicitudes multiparte por medio de


Jakarta Commons FileUpload.
• S t a n d a r d S e r v l e t M u l t i p a r t R e s o l v e r : Recurre a compatibilidad con Servlet
3.0 para solicitudes multiparte (desde Spring 3.1).

En la mayoría de los casos, S t a n d a r d S e r v l e t M u l t i p a r t R e s o l v e r debería ser su


primera opción. Usa la compatibilidad existente en su contenedor de servlet y no requiere
dependencias de proyecto adicionales, pero podría optar por CommonsMul t i p a r t R e s o l v e r
si implementa su aplicación en un contenedor anterior a Servlet 3.0 o si todavía no usa
Spring 3.1 o superior.

Resolver solicitudes multiparte con Servlet 3.0


S t a n d a r d S e r v l e t M u l t i p a r t R e s o l v e r , compatible con Servlet 3.0, no tiene argu­
mentos de constructor ni propiedades que establecer, lo que facilita enormemente su
declaración como bean en su configuración de Spring, como se muestra a continuación:
@Bean
public MultipartResolver m ultipartResolver() throws IOException {
return new StandardServletM ultipartResolver();
}
A pesar de la sencillez del método @Bean, seguramente se pregunte cómo restringir el
funcionamiento de S t a n d a r d S e r v l e t M u l t i p a r t R e s o l v e r . ¿Cómo podría limitar el
tamaño máximo del archivo que puede subir el usuario? ¿Y especificar la ubicación temporal
de los archivos mientras se suben? Al carecer de propiedades y argumentos de constructor,
S t a n d a r d S e r v l e t M u l t i p a r t R e s o l v e r parece muy limitado, pero puede configurar
restricciones en S t a n d a r d S e r v l e t M u l t i p a r t R e s o l v e r . En lugar de configurarlo en su
configuración de Spring, tendrá que especificar la configuración multiparte en la del servlet.
Como mínimo, tendrá que especificar la ruta de archivos temporal en la que escribir los
archivos durante la transferencia. S t a n d a r d S e r v l e t M u l t i p a r t R e s o l v e r no funcionará
a menos que configure este mínimo detalle. En concreto, tendrá que configurar detalles de
multiparte como parte de la configuración de D i s p a t c h e r S e r v l e t en w e b . xml o en la
clase de inicialización de servlet.
Operaciones avanzadas con Spring MVC 231

Si configura D i s p a t c h e r S e r v l e t en una clase de inicialización de servlet que


implemente W e b A p p l i c a t i o n l n i t i a l i z e r , puede configurar detalles de multiparte
mediante la invocación de s e t M u l t i p a r t C o n f i g () en el registro del servlet, pasando
una instancia de M u l t i p a r t C o n f i g E l e m e n t . La siguiente configuración multiparte de
D i s p a t c h e r S e r v l e t establece la ubicación temporal en / t m p / s p i t t r / u p l o a d s :

D ispatcherServlet ds = new D ispatcherServlet();


Dynamic re g istra tio n = co n tex t. addServlet("appServlet", d s);
r e g is tr a tio n . addMapping( " / " ) ;
reg istratio n .setM u ltip artC o n fig (
new MultipartConfigElement( " /tm p/spittr/upload s" ) ) ;

Si ha configurado D i s p a t c h e r S e r v l e t en una clase de inicialización de servlet


que amplíe A b s t r a c t A n n o t a t i o n C o n f i g D i s p a t c h e r S e r v l e t I n i t i a l i z e r o
A b s t r a c t D i s p a t c h e r S e r v l e t l n i t i a l i z e r , no se crea la instancia de D i s p a t c h e r
S e r v l e t ni se registra directamente con el contexto de servlet. Por lo tanto, no hay una
referencia al registro de servlet Dynamic con la que trabajar, pero puede reemplazar el
método c u s t o m i z e R e g i s t r a t i o n ( ) , asignado como parámetro a Dynamic, para confi­
gurar detalles de multiparte:
©Override
protected void custom izeRegistration(Dynamic re g istra tio n ) {
reg istratio n .setM u ltip artC o n fig (
new M ultipartConfigElem ent("/tm p/spittr/uploads")) ;
}

El constructor de un solo argumento de M u ltip a rtC o n f ig E le m e n t que hemos usado


hasta ahora acepta la ruta absoluta a un directorio del sistema de archivos en el que se
escribirá temporalmente el archivo transferido, pero hay otro constructor que le permite
definir restricciones de tamaño adicionales para el archivo transferido. Además de la ruta
de ubicación temporal, el otro constructor acepta lo siguiente:

• El tamaño máximo (en bytes) de cualquier archivo transferido. De forma predeter­


minada no hay límite.
• El tamaño máximo (en bytes) de la solicitud multiparte completa, independiente­
mente de cuántas partes tenga o del tamaño de estas. De forma predeterminada no
hay límite.
• El tamaño máximo (en bytes) de un archivo que se puede transferir sin escribirse en
la ubicación temporal. El valor predeterminado es 0, lo que significa que todos los
archivos transferidos se escriben en disco.

Imagine por ejemplo que desea limitar los archivos a menos de 2 MB, la solicitud a menos de
4 MB y escribir todos los archivos en disco. El siguiente uso de M u ltip a rtC o n f ig E lem en t
define estos límites:
SOverride
protected void customizeRegistration(Dynamic re g istra tio n ) {
reg istratio n .setM u ltip artC o n fig (
232 Capítulo 7

new MultipartConfigElement("/tmp/spittr/uploads",
2097152, 4194304, 0) ) ;
}
Si configura D i s p a t c h e r S e r v l e t de forma más tradicional en w eb . xm l, puede espe­
cificar la configuración m ultiparte con el elemento < m u l t i p a r t - c o n f i g > del elemento
< s e r v le t> :
<servlet>
<servlet-name>appServlet</servlet-name>
<s e r v le t-class>
org. springframework.web. servlet.DispatcherServlet
< / se r v le t-cla ss>
<load-on-startup>l</load-on-startup>
<multipart-config>
<location>/tmp/spittr/uploads</location>
<m ax-file-size>2097152</m a x -file -s iz e >
<max-request-size>4194304</max-request-size>
</multipart-config>
< / servlet>

Los valores predeterminados de < m u l t i p a r t - c o n f i g > son los mismos que los de
M u lt ip a r t C o n f ig E le m e n t , y como sucede con M u lt ip a r t C o n f ig E le m e n t , tendrá
que configurar < l o c a t i o n > .

Configurar un solucionador multiparte Jakarta Commons Fileupload


S t a n d a r d S e r v l e t M u l t i p a r t R e s o l v e r suele ser la opción más acertada pero si no
desarrolla su aplicación para un contenedor Servlet 3.0, necesita una alternativa. Puede escribir
su propia implementación de la interfaz M u ít i p a r t R e s o l v e r si lo desea, pero al menos
que tenga que realizar un procesamiento especial durante el procesamiento de las solicitudes
multiparte, no hay necesidad de hacerlo. Spring le ofrece CommonsMult i p a r t R e s o l v e r
como alternativa a S t a n d a r d S e r v l e t M u l t i p a r t R e s o l v e r .
La forma más sencilla de declarar CommonsMult i p a r t R e s o l v e r como bean de Spring
es la siguiente:
@Bean
public MultipartResolver multipartResolver() {
return new CommonsMultipartResolver();
}
Al contrario de lo que sucede con S t a n d a r d S e r v l e t M u lt ip a r t R e s o lv e r , no es nece­
sario configurar una ubicación de archivos temporal con C o m m o n s M u ltip a r tR e s o lv e r.
De forma predeterm inada la ubicación es el directorio temporal del contenedor de servlet,
pero puede especificar otra ubicación diferente si establece la propiedad u p lo ad T em p D ir:

@Bean
public MultipartResolver multipartResolver() throws IOException {
CommonsMultipartResolver multipartResolver =
new CommonsMultipartResolver();
multipartResolver.setUploadTempDir(
Operaciones avanzadas con Spring MVC 233

new FileSystemResource("/tmp/spittr/uploads"));
return multipartResolver;
}
De hecho, puede especificar otros detalles de transferencia multiparte directamente en
la configuración de Spring si establece propiedades de C o m m o n s M u ltip a r tR e s o lv e r.
Por ejem plo, la siguiente configuración es prácticam ente equivalente a la de
S t a n d a r d S e r v l e t M u l t i p a r t R e s o l v e r a través de M u lt ip a r t C o n f ig E l e m e n t :

@Bean
public MultipartResolver multipartResolver() throws IOException {
CommonsMultipartResolver multipartResolver =
new CommonsMultipartResolver();
multipartResolver. setUploadTempDir(
new FileSystemResource ("/tmp/spittr/uploads") )
multipartResolver. setMaxüploadSize(2097152);
multipartResolver. setMaxInMemorySize(0);
return multipartResolver;

Aquí se establece el tamaño máximo de archivo en 2 MB y el tamaño máximo en


memoria en 0 bytes. Estas dos propiedades se corresponden directamente al segundo y
cuarto argumento de constructor de M u lt ip a r t C o n f ig E le m e n t , que indican que no
se pueden transferir archivos de más de 2 MB y que todos los archivos se escribirán en
disco independientemente de su tamaño. Sin embargo, al contrario de lo que sucede con
M u lt ip a r t C o n f ig E le m e n t , no hay forma de especificar el tamaño máximo de la soli­
citud multiparte.

Procesar solicitudes multiparte


Después de configurar la compatibilidad multiparte en Spring (y puede que en el
contenedor de servlet), ya puede crear métodos de controlador para aceptar los archivos
transferidos. La forma más habitual de hacerlo consiste en anotar con @ R e q u e s t P a r t ,
un parámetro de método de controlador. Imagine que desea permitir la transferencia de
una imagen cuando un usuario se registre en la aplicación Spittr. Tendrá que actualizar el
formulario de registro para que el usuario pueda seleccionar la imagen que subir y cambiar el
método p r o c e s s R e g i s t r a t i o n () de S p i t t e r C o n t r o l l e r para aceptarlo. El siguiente
fragmento de código de la vista de formulario de registro Thymeleaf ( r e g i s t r a t i o n
Form . h tm l) destaca los cambios necesarios:
<form method="POST" t h : o b j e c t = " $ { s p i t t e r } "
enctype="multipart/form-data">

<label>P rofile P ic tu re< /la b e l> :


<ínput ty p e="file"
name="profileP ictu re"
accept="image/jpeg,image/png,image/gif" / x b r / >

</form>
234 Capítulo 7

Ahora, en la etiqueta < fo rm > se establece el atributo e n c ty p e en m u l t i p a r t /


f o rm -d a ta para indicar al navegador que envíe el formulario como datos multiparte en
lugar de datos de formulario. Cada campo tiene su propia parte en la solicitud multiparte.
Además de todos los campos existentes en el formulario de registro, hemos añadido
un nuevo campo < in p u t> de tipo f i l e , que permite al usuario seleccionar una imagen
para transferir. El atributo a c c e p t se establece para limitar los tipos de archivo a imágenes
JPEG, PNG y GIF y, de acuerdo a su atributo ñame, los datos de imagen se envían dentro
de la solicitud multiparte en la parte p r o f i l e P i c t u r e .
Ahora basta con cambiar el método p r o c e s s R e g i s t r a t i o n () para que acepte la
imagen transferida. Puede hacerlo si añade un parámetro de matriz b y te anotado con @
R e q u e s tP a r t, como se muestra a continuación:
@RequestMapping(value="/register", method=P0ST)
public String processRegistration(
@RequestPart("p ro file P ic tu re ") byte[] p ro fileP ictu re,
@Valid S p itter s p itt e r ,
Errors errors) {

}
Al enviar el formulario de registro, se asigna al atributo p r o f i l e P i c t u r e una matriz
de b y t e con los datos de la parte de la solicitud (tal y como especifica @ R e q u e s t P a r t ).
Si el usuario envía el formulario sin seleccionar un archivo, la matriz estará vacía (pero
no será n u i l ) . Una vez obtenidos los datos de imagen, p r o c e s s R e g i s t r a t i o n () solo
tiene que guardar el archivo en alguna parte.
Más adelante continuaremos con la forma de guardar los datos de imagen, pero antes
¿qué sabemos de los datos de imagen enviados? O mejor todavía ¿qué desconocemos?
Aunque los tenemos como matriz de b y te y podamos deducir el tamaño de la imagen, no
sabemos mucho más. Desconocemos el tipo del archivo y su nombre original y tendremos
que averiguar cómo convertir esa matriz en un archivo para poder guardarlo.

Recibir un archivo multiparte


El uso de bytes sin procesar de un archivo transferido es muy sencillo, pero también
limitado. Por ello, Spring le ofrece M u l t i p a r t F i l e para obtener un objeto más completo
con el que procesar datos multiparte. Esta interfaz se ilustra en el siguiente código.

Listado 7.5. La interfaz MultipartFile de Spring para trabajar con archivos transferidos.

package org. springframework.web.multipart;


import ja v a . i o . F i l e ;
import ja v a . i o . IOException;
import ja v a . i o . InputStream;

public in terface MultipartFile {


String getNameO ;
String getOriginalFilename();
String getContentType();
boolean isEmptyO;
Operaciones avanzadas con Spring MVC 235

long g e t s i z e ();
byte[] getBytesO throws IOException;
InputStream getlnputStream() throws IOException;
void transferTo(File dest) throws IOException;

Como puede comprobar, M u l t i p a r t F i l e le permite acceder a los bytes del archivo


transferido, pero también le ofrece el nombre de archivo original, su tamaño y su tipo de
contenido, además de un I n p u t S t r e a m para leer los datos del archivo como flujo.
Es más, M u l t i p a r t F i l e cuenta con un método t r a n s f e r T o () para escribir el
archivo en el sistema de archivos. Por ejemplo, puede añadir el siguiente código a p r o c e s s
R e g i s t r a t i o n () para escribir el archivo de imagen en el sistema de archivos:

p r o f ile P ic tu r e . transferTo(
new F i l e ("/ d a t a / s p i t t r / " + p r o f ile P ic tu r e .getOriginalFilename( ) ) ) ;

Almacenar un archivo en el sistema de archivos local es sencillo pero le deja a cargo de


la gestión del sistema de archivos. Tendrá que asegurarse de que hay suficiente espacio,
de que existe una copia de seguridad en caso de que el hardware falle y de sincronizar los
archivos de imágenes entre los diferentes servidores de un clúster.

Guardar archivos en Ámazon §3


Otra opción es permitir que alguien se encargue de estas tareas. Con algo más de código
podemos guardar nuestras imágenes en la nube. El siguiente código, por ejemplo, muestra el
método s a v e F i l e () , que puede invocar desde p r o c e s s R e g i s t r a t i o n () para guardar
la imagen transferida en Amazon S3.

Listado 7.6. Guardar MultipartFile en Amazon S3.

private void savelmage(MultipartFile image)


throws ImageüploadException {
try {
AWSCredentials awsCredentials =
new AWSCredentials(s3AccessKey, s2SecretKey);
S3Service s3 = new RestS3Service(awsCredentials); / / Configurar servicio S3.

S3Bucket bucket = s3.getBucket("spittrlm ages"); / / Crear cubo y objeto S3.


S30bject imageObject =
new S 3 0 b je c t(image. getOriginalFilename 0 ) ;

imageObject. setDatalnputStream( / / Establecer datos de imagen.


image. getlnputStream( ) ) ;
imageObject. setContentLength(image. g e t s i z e ( ) ) ;
imageObj e c t . setContentType(image.getContentType() ) ;

AccessControlList acl = new AccessControlList(); / / Establecer permisos.


a c l . setOwner(bucket.getOwner( ) ) ;
acl.grantPermission(GroupGrantee.ALL_USERS,
Permission. PERMISSION_READ);
imageObject. s e t A c l( a c l ) ;
236 Capítulo 7

s3.putObject(bucket, imageObject); / / Guardar imagen.


} catch (Exception e) {
throw new ImageUploadException("Unable to save image", e ) ;
}
}
Lo primero que hace sa v e Im age () es configurar las credenciales del servicio Web
de Amazon. Para ello, necesitará una clase de acceso y una clase de acceso secreto de
S3. Podrá obtenerlas cuando se registre en el servicio. Estos valores se proporcionan a
S p i t t e r C o n t r o l l e r mediante inyección de valores.
A continuación, s a v e lm a g e () crea una instancia de R e s t S 3 S e r v i .c e de JetS3t, a
través de la cual va a operar con el sistema de archivos S3. Obtiene una referencia al cubo
s p i t t e r l m a g e s , crea un S 3 0 b j e c t para contener la imagen y, a continuación, lo llena
con datos de la imagen.
Antes de invocar el método p u t O b je c t () para escribir los datos de imagen en S3,
s a v e lm a g e () configura los permisos de S 3 0 b j e c t para permitir que todos los usuarios
lo vean. Esto es importante ya que, sin estos permisos, las imágenes no serían visibles
para los usuarios de nuestra aplicación. Si se produce algún fallo, se genera una excepción
Im a g e U p lo a d E x c e p tio n .

Recibir el archivo transferido com o parte


Si está desarrollando su aplicación en un contenedor Servlet 3.0, dispone de una alter­
nativa a M u l t i p a r t F i l e . Spring MVC también acepta j a v a x . s e r v l e t . h t t p . P a r t
como parámetro de método de controlador. Al utilizar P a r t en lugar de M u l t i p a r t F i l e ,
la firma del método p r o c e s s R e g i s t r a t i o n () es la siguiente:
@RequestMapping(value="/register", method=POST)
public String processRegistration(
@RequestPart("p ro file P ic tu re ") Part p ro fileP ictu re,
@Valid S p itte r s p itt e r ,
Errors errors) {

}
En su gran mayoría, la interfaz P a r t no difiere mucho de M u l t i p a r t F i l e . Como puede
comprobar en el siguiente código, la interfaz P a r t dispone de varios métodos idénticos a
los de M u l t i p a r t F i l e .

Listado 7.7. Interfaz Part: una alternativa a MultipartFile de Spring.

package javax. servlet .http


import j ava. i o .* ;
import j a v a . ú t i l . * ;

public in terface Part {


public InputStream getlnputStream() throws IOException;
public String getContentType();
public String getNameO;
public String getSubmittedFileName();
Operaciones avanzadas con Spring MVC 237

public long g etS ize O ;


public void w rite(String fileName) throws IOException;
public void d e l e t e () throws IOException;
public strin g getHeader(String name);
public Collection<String> getHeaders(String name);
public Collection<String> getHeaderNames();

En muchos casos, los métodos P a r t tienen nombres idénticos a los de M u lt i p a r t F i l e .


Otros tienen nombres sim ilares con mínimas diferencias; por ejemplo, g e t S u b m i t t e -
d F ileN a m e () equivale a g e t O r i g i n a l F i l e n a m e ( ) . Del mismo modo, w r i t e () se
corresponde a t r a n s f e rT o ( ) , lo que permite escribir el archivo transferido de esta forma:

p r o f ile P ic tu r e . w rite ("/ d a t a / s p i t t r / " +


p r o f ile P ic tu r e .getOriginalFilename( ) ) ;

Recuerde que si crea sus m étodos de controlador para aceptar transferencias de


archivos a través de un parám etro de P a r t no tendrá que configurar el bean S t a n d a r d
S e r v l e t M u l t i p a r t R e s o l v e r . Solo lo necesitará cuando trabaje con M u l t i p a r t F i l e .

Controlar excepciones
Hasta el momento hemos asumido que todo funciona correctamente en la aplicación
Spittr. ¿Y si se produce algún error? Imagine que al procesar una solicitud se genera una
excepción. ¿Qué respuesta se envía al cliente en ese caso?
Independientemente de lo que suceda, el resultado de una solicitud de servlet es una
respuesta de servlet. Si durante el procesamiento de solicitudes se genera una excepción,
el resultado seguirá siendo una respuesta de servlet. De algún modo la excepción debe
traducirse en una respuesta. Spring le ofrece diversas formas de traducir excepciones en
respuestas:

• Determinadas excepciones de Spring se asignan automáticamente a códigos de estado


HTTP concretos.
• Se puede anotar una excepción con @ R e s p o n s e S t a tu s para asignarla a un código
de estado HTTP.
• Se puede anotar un método con @ E x c e p t io n H a n d le r para procesar la excepción.

La forma más sencilla de procesar una excepción consiste en asignarla al código de


estado HTTP que se va a incluir en la respuesta, como veremos a continuación.

Asignar excepciones a códigos de estado HTTP


De forma predeterminada, Spring asigna automáticamente varias de sus propias
excepciones a los correspondientes códigos de estado. En la tabla 7.1 puede ver estas
asignaciones.
238 Capítulo 7

Tabla 7.1. Excepciones de Spring asignadas de forma predeterminada a códigos de estado HTTP.
'
Código de estado HTTP

BindException 400 - Solicitud incorrecta.


ConversionNotSupportedException 500 - Error interno del servidor.
HttpMediaTypeNotAcceptableException 406 - Inaceptable.
HttpMediaTypeNotSupportedException 415 - Tipo de soporte incompatible.
HttpMessageNotReadableException 400 - Solicitud incorrecta.
HttpMessageMotWritableException 500 - Error interno del servidor.
HttpRequestMethodNotSupportedException 405 - Método no permitido.
MethodArgumentNotValidException 400 - Solicitud incorrecta.
MissingServletRequestParameterException 400 - Solicitud incorrecta.
Mi ssingServletRequ.estPartExcept ion 400 - Solicitud incorrecta.
NoSuchReque stHandlingMethodExcept ion 404 - Página no encontrada.
TypeMismatchException 400 - Bad Request.

Estas excepciones las suele generar Spring como resultado de algún fallo produ­
cido en D i s p a t c h e r S e r v l e t o durante el proceso de validación. Por ejemplo, si
D i s p a t c h e r S e r v l e t no encuentra un método de controlador adecuado para procesar
una solicitud, se genera N o S u c h R e q u e stH a n d lin g M e th o d E x ce p tio n y, como resultado,
se muestra el código de estado 404 (Página no encontrada).
A pesar de la utilidad de estas asignaciones predeterminadas, no sirven para las excep­
ciones de aplicación que puedan generarse. Afortunadamente, Spring le ofrece una forma de
asignar excepciones a códigos de estado HTTP a través de la anotación © R e s p o n s e S ta tu s .
Para comprobarlo, fíjese en el siguiente método de procesamiento de solicitudes de
S p i t t l e C o n t r o l l e r que podría generar un estado HTTP 404, pero que no lo hace:

@RequestMapping(value="/{spittleld}", method=RequestMethod.GET)
public String s p i t t l e (
@PathVariable( " s p i t t l e l d " ) long s p i tt le ld ,
Model model) {
S p i t t le s p i t t l e = spittleR ep ository . fin d O ne(spittleld );
i f ( s p it tl e == nuil) {
throw new SpittleNotFoundException();
}
model. ad d A ttrib u te (sp ittle);
return " s p i t t l e " ;

Aquí, se recupera un objeto S p i t t l e de S p i t t l e R e p o s i t o r y por su ID. Si f indO ne ()


devuelve un objeto S p i t t l e , se añade al m odelo y la vista con el nombre s p i t t l e recibe
el encargo de representarlo en la respuesta, pero si f indO ne () devuelve n u i l , se genera
S p it t l e N o t F o u n d E x c e p t i o n , que por el momento es una excepción sin comprobar
con el siguiente aspecto:
Operaciones avanzadas con Spring MVC 239

package spittr.web;
public class SpittleNotFoundException extends RuntimeException {
}
Si se invoca el método s p i t t l e () para procesar una solicitud y el ID proporcio­
nado está vacío, S p it t l e N o t F o u n d E x c e p t i o n genera, de forma predeterminada, una
respuesta con el código de error 500 (Error interno del servidor). De hecho, siempre que se
trate de una excepción sin asignar, el código de estado es 5 00. Puede cambiarlo si asigna
S p it t l e N o t F o u n d E x c e p t i o n .
Cuando se genera S p it t l e N o t F o u n d E x c e p t i o n , significa que no se ha encontrado
el recurso de una solicitud. El código de estado HTTP 4 04 es precisamente el adecuado
para cuando no se encuentra un recurso. Por lo tanto, usaremos @ R e s p o n s e s t a t u s para
asignar S p it t l e N o t F o u n d E x c e p t i o n al código de estado HTTP 4 04.

Listado 7.8. La anotación @ResponseStatus asigna excepciones a un código de estado concreto.


package spittr.web;
import org. springframework.h tt p .HttpStatus;
import org. springframework.web.bind. annotation. ResponseStatus;

©ResponseStatus (value=HttpStatus.NOT_FOUND, / / Asignar excepción al código de estado HTTP 404


reason="Spittle Not Found")
public class SpittleNotFoundException extends RuntimeException {
}
Tras introducir esta anotación @ R e s p o n s e S t a t u s , si se generara S p i t t l e N o t
F o u n d E x c e p tio n desde un método de controlador, la respuesta tendría un código de
estado 4 04 e indicaría por qué no se ha encontrado S p i t t l e .

Crear métodos de control de excepciones


La asignación de excepciones a códigos de estado es en muchos casos sencilla y suficiente,
pero imagine que desea que la respuesta incluya algo más que un simple código de estado
para representar el error producido. En lugar de tratar la excepción de forma genérica como
un error HTTP, puede que prefiera procesarla de la misma forma que la propia solicitud.
Imagine por ejemplo que el método s a v e () de S p i t t l e R e p o s i t o r y genera
D u p l i c a t e S p i t t l e E x c e p t i o n si un usuario intenta crear un objeto S p i t t l e con el
mismo texto que otro ya existente. El método s a v e S p i t t l e () de S p i t t l e C o n t r o l l e r
tendría que controlar dicha excepción. Como se muestra en el siguiente código, s a v e
S p i t t l e () podría encargarse directamente de la excepción.

Listado 7.9. Control de una excepción directamente en un método de procesamiento de solicitudes.


@RequestMapping(method=RequestMethod. POST)
public String saveSpittle(SpittleForm form, Model model) {
try {
SpittleRepository.save(
new S p i t t l e ( n u i l , form. getMessage(), new Date(),
240 Capítulo 7

form.getLongitude(), form.getLatitude( ) ) ) ;
return "redirect : / s p i t t l e s ";
} catch (DuplicateSpittleException e) { / / Capturar la excepción,
return "erro r/d u p licate";

No hay nada especialmente reseñable en el listado anterior. Es un ejemplo básico de


control de excepciones de Java, simplemente. Funciona a la perfección, pero el método es
complejo. Hay dos posibilidades, cada una con un resultado distinto. Sería más sencillo
si s a v e S p i t t l e () se centrara en la posibilidad más sencilla y dejara que otro método se
encargase de la excepción.
Veamos primero el código de procesamiento de excepciones de s a v e S p i t t l e ():
@RequestMapping(method=RequestMethod. POST)
public String saveSpittle(SpittleForm form, Model model) {
spittleR ep ository . save(
new S p i t t l e ( n u i l , form.getMessage(), new Date(),
form.getLongitude(), form.getLatitude( ) ) ) ;
return " r e d ir e c t : / s p i t t l e s " ;
}

Como puede comprobar, ahora s a v e S p i t t l e () es mucho más sencillo. Como se


escribe para encargarse únicamente de guardar S p i t t l e , solo adopta un camino, fácil de
seguir (y de probar). Añadiremos un nuevo método a S p i t t l e C o n t r o l l e r para que se
encargue de D u p l i c a t e S p i t t l e E x c e p t i o n :
@ExceptionHandler(DuplicateSpittleException.class)
public String handleDuplicateSpittle() {
return "error/duplicate";
}

La anotación @ E x cep t io n H a n d le r se aplica al método h a n d l e D u p l i c a t e S p i t t l e ()


y lo designa como método que usar cuando se genere D u p l i c a t e S p i t t l e E x c e p t i o n .
Devuelve un valor S t r i n g que, como sucede con el método de procesamiento de solici­
tudes, especifica el nombre lógico de la vista que representar e indica al usuario que ha
intentado crear una entrada duplicada.
Lo más interesante de los métodos @ E x c e p t io n H a n d le r es que procesan sus excep­
ciones desde cualquier método de control del mismo controlador. Por ello, aunque hemos
creado el método h a n d l e D u p l i c a t e S p i t t l e () a partir del código extraído de s a v e
S p i t t l e ( ) , procesará las excepciones D u p l i c a t e S p i t t l e E x c e p t i o n que se generen
desde cualquier método de S p i 1 1 1 eC on t r o 11 e r . En lugar de duplicar el código de control
de excepciones en todos los métodos que puedan generar D u p l i c a t e S p i t t l e E x c e p t i o n ,
este método se encarga de todas ellas.
Si los métodos @ E x c e p t io n H a n d le r pueden controlar excepciones generadas desde
cualquier método de la misma clase de controlador, seguramente se pregunte si hay
alguna forma de controlar excepciones generadas desde métodos de control de cualquier
controlador. Desde Spring 3.2 se puede hacer, pero solo si se define una clase de consejo
de controlador. ¿Y qué es una clase de consejo de controlador? Lo veremos a continuación.
Operaciones avanzadas con Spring MVC 241

Aconsejar controladores
Determinados aspectos de las clases de controlador serían más útiles si se pudieran
aplicar entre todos los controladores de una misma aplicación. Por ejemplo, los métodos
@ E x c e p t io n H a n d le r serían muy útiles para el control de excepciones entre varios contro­
ladores. Si se genera una determinada excepción desde varias clases de controlador, acabaría
por duplicar el mismo método @ E x c e p t io n H a n d le r en todos esos controladores. O, para
evitar la duplicación, podría crear una clase de controlador base que todos los controladores
amplíen para heredar ese método @ E x c e p t io n H a n d le r común.
Spring 3.2 le ofrece una opción adicional: los consejos de controlador. Se trata de cual­
quier clase anotada con @ C o n t r o lle r A d v i c e con uno o varios de los siguientes tipos
de métodos:

• Anotados con @ E x c e p tio n H a n d le r .


• Anotados con @ I n i t B i n d e r .
• Anotados con @ M o d e lA ttr ib u te .

Estos métodos de una clase anotada con @ C o n t r o lle r A d v i c e se aplican globalmente


a todos los métodos anotados con @ R eq u estM a p p in g de todos los controladores de la
aplicación.
La propia anotación © C o n t r o l le r A d v ic e se anota con @Component. Por lo tanto, las
clases anotadas con @ C o n t r o lle r A d v i c e se seleccionan durante el análisis de compo­
nentes, como las clases anotadas con © C o n t r o l l e r .
Uno de los usos más prácticos de @ C o n t r o lle r A d v i c e es para recopilar todos los
métodos @ E x c e p tio n H a n d le r de una misma clase para que las excepciones de todos los
controladores se controlen de forma coherente en un mismo punto. Imagine por ejemplo
que desea aplicar el método de control de D u p l i c a t e S p i t t l e E x c e p t i o n en todos los
controladores de su aplicación. El siguiente código muestra A ppW ideExcept io n H a n d le r ,
una clase anotada con @ C o n t r o lle r A d v i c e que se encarga de ello.

Listado 7.10. Uso de ©ControllerAdvice para controlar una excepción en todos los controladores.
package spitter.web;
import org. springframework.web.bind. annotation. ControllerAdvice;
import org. springframework. web.bind. annotation.ExceptionHandler;

@ControllerAdvice / / Declarar consejo del controlador,


public cla ss AppWideExceptionHandler {

©ExceptionHandler(DuplicateSpittleException.class) / / Definir método de control


/ / de excepciones.
public String duplicateSpittleHandler() {
return "error/duplicate" ;
}

}
242 Capítulo 7

Si se genera D u p l i c a t e S p i t t l e E x c e p t i o n desde cualquier método de control,


independientemente del controlador al que pertenezca, se invoca este método d u p líc a t e
S p it t le H a n d l e r () para controlar la excepción.
El método anotado con @ E x c e p tio n H a n d le r se puede escribir de forma similar a
los métodos anotados con @ R eq u estM a p p in g . Como muestra el listado 7.10, devuelve
e r r o r / d u p l i c a t e como nombre lógico de la vista para así poder mostrar una página
de error al usuario.

Transferir datos entre solicitudes de redirección


Como ya hemos mencionado, es aconsejable realizar una redirección tras procesar una
solicitud POST. Entre otras cosas, impide que el cliente vuelva a emitir una solicitud POST
peligrosa si el usuario pulsa el botón Actualizar o Atrás de su navegador.
En un capítulo anterior utilizamos el prefijo r e d i r e c t : en los nombres de vista
devueltos de métodos de controlador. Cuando un método de controlador devuelve una
cadena cuyo valor comienza por r e d i r e c t :, esa cadena no se utiliza para buscar una vista,
sino como ruta a la que redirigir el navegador. Si se fija en el listado XREF e x _ S p i t t e r
C o n t r o l l e r _ jp r o c e s s R e g i s t r a t i o n _ v a l i d a t i o n , verá que la última línea del método
p r o c e s s R e g i s t r a t i o n () devuelve r e d i r e c t : S t r i n g :

return "r e d ir e c t: / s p i t t e r / " + spitter.getüsername();

El prefijo r e d i r e c t : facilita el trabajo con redirecciones. Podría pensar que Spring no


puede hacer que las redirecciones sean más sencillas, pero le ofrece una opción adicional.
En concreto ¿cómo puede un método de redirección enviar datos al método que se
encarga de procesar la redirección? Por lo general, cuando un método de controlador
finaliza, los datos de modelo especificados en el método se copian en la solicitud como
atributos de la misma y se reenvía a la vista para su representación. Al tratarse de la misma
solicitud procesada tanto por el método de controlador como por la vista, los atributos de
la solicitud sobreviven a la redirección.
Pero como se ilustra en la figura 7.1, cuando un método de controlador genera una
redirección, finaliza la solicitud original y se inicia una nueva solicitud GET de HTTP. Los
datos de modelo de la solicitud original perecen con la solicitud.
La nueva solicitud carece de datos de modelo en sus atributos y tiene que conseguirlos
por su cuenta.

R e d ire c c ió n
re a liz a d a

S o lic itu d o rig in a l R e d irig ir s o lic itu d

Modelo Modelo
s p itte r= S p itte r v a c ío

Figura 7.1. Los atributos del modelo se incluyen en la solicitud como atributos de la misma
y no sobreviven a una redirección.
Operaciones avanzadas con Spring MVC 243

Evidentemente, el modelo no ayuda a transmitir los datos en una redirección, pero


dispone de varias opciones para obtenerlos en el método de redirección y llevarlos al
método de control de la redirección:

• Pasar los datos como variables de ruta y /o parámetros de solicitud por medio de
plantillas de URL.
• Enviar los datos en atributos flash.

En primer lugar veremos cómo permite Spring enviar datos en variables de ruta y /o
parámetros de consulta.

Redirecciones con plantillas de URL


Parece muy sencillo pasar datos en variables de ruta y parámetros de consulta. Por
ejemplo, en el listado XREF e x _ S p i t t e r C o n t r o l l e r _ p r o c e s s R e g i s t r a t io n _
va 1 i da t io n , el nuevo nombre de usuario de S p i t t e r se pasa como variable de ruta,
pero tal y como está escrito, el valor u sern a m e se concatena a la cadena de redirección.
Funciona, pero no es demasiado seguro. La concatenación de cadenas es una operación
peligrosa a la hora de crear elementos como URL y consultas SQL.
En lugar de concatenar a una URL de redirección, Spring le ofrece la opción de usar
plantillas para definir URL de redirección. Por ejemplo, la última línea de p r o c e s s
R e g i s t r a t i o n () en el listado XREF e x _ S p i t t e r C o n t r o l l e r _ jo r o c e s s R e g i s t r a t i o n _
v a l i d a t io n se podría escribir de esta forma:

return "r e d i r e c t : / spitter/{username}";

Basta con establecer el valor en el modelo. Para ello, es necesario escribir el método
p r o c e s s R e g i s t r a t i o n () para que acepte M odel como parámetro y completarlo con el
nombre de usuario. El siguiente ejemplo muestra cómo establecer el valor u se rn a m e en el
modelo para que complete el marcador de posición de la ruta de redirección:
@RequestMapping(value="/register", method=POST)
public String processRegistration(
S p itte r s p i tt e r , Model model) {
spitterRepository. s a v e ( s p i t t e r ) ;
model. addAttribute("username", s p i t t e r . getUsername( ) ) ;
return " r e d ir e c t : / spitter/{username}";
}
Como se incluye en el marcador de posición de la plantilla de URL en lugar de conca­
tenarse a la cadena de redirección, los caracteres peligrosos de la propiedad usernam e se
escapan. Resulta más seguro que permitir al usuario introducir el valor que desee para el
nombre de usuario y después añadirlo a la ruta.
Es más, cualquier otro valor primitivo del modelo también se añade a la URL de
redirección como parámetro de consulta. Imagine que además del nombre de usuario, el
modelo contiene la propiedad i d del nuevo objeto S p i t t e r . Podríamos escribir el método
p r o c e s s R e g i s t r a t i o n () de esta forma:
Capítulo 7

@RequestMapping(value="/ r e g i s t e r ", method=POST)


public String processRegistration(
S p itte r s p i tt e r , Model model) {
spitterRepository. s a v e ( s p i t t e r ) ;
model. addAttribute("username", s p i t t e r .getUsername( ) ) ;
model. addAttribute("s p i t t e r l d " , s p i t t e r .g e t l d ( ) ) ;
return " r e d ir e c t: /spitter/{usernam e}";
}

No ha cambiado demasiado con respecto a la cadena de redirección devuelta, pero como


el atributo s p i t t e r l d del modelo no se asigna a marcadores de posición de URL en la
redirección, se añade automáticamente a la misma como parámetro de consulta.
Si el atributo u sern a m e es habuma y el atributo s p i t t e r l d es 4 2 , la ruta de redirección
resultante será / s p i t t e r / h a b u m a ? s p i t t e r l d = 4 2 .
El envío de datos en una redirección a través de variables de ruta y parámetros de
consulta es muy sencillo, pero en cierto modo limitado. Solo sirve para enviar valores
sencillos, como cadenas y valores numéricos, no para elementos más complejos. Para eso
contamos con los atributos flash.

Imagine que en lugar de enviar un nombre de usuario o un ID en la redirección desea


enviar el objeto S p i t t e r . Si solo envía el ID, el método que procesa la redirección tendrá
que buscar S p i t t e r en la base de datos, pero antes de la redirección ya tenemos el objeto
S p i t t e r . ¿Por qué no enviarlo al método de procesamiento de la redirección para que lo
muestre?
Un objeto S p i t t e r es más complejo que un valor S t r i n g o i n t , por lo que no se puede
enviar como variable de ruta o parámetro de consulta. Sin embargo, se puede establecer
como atributo en el modelo.
Como ya hemos mencionado, los atributos del modelo se copian en última instancia
como atributos de la solicitud y se pierden al producirse la redirección. Por lo tanto, tendrá
que incluir el objeto S p i t t e r en un punto en el que sobreviva a la redirección.
Una opción sería incluirlo en la sesión. La duración de una sesión abarca varias solici­
tudes, por lo que podría incluir S p i t t e r en su interior antes de la redirección y después
recuperarlo tras la misma. Evidentemente, también tendrá que limpiar la sesión una vez
concluida la redirección.
Spring coincide en que la inclusión de datos en la sesión es una forma perfecta de pasar
información para sobrevivir a una redirección pero no cree que deba ser responsable de
gestionar esos datos. Por ese motivo, le permite enviarlos como atributos flash. Estos atri­
butos, por definición, pasan los datos hasta la siguiente solicitud y después desaparecen.
Spring le permite establecer atributos flash a través de R e d i r e c t A t t r i b u t e s , una
subinterfaz de M odel añadida en Spring 3.1. R e d i r e c t A t t r i b u t e s ofrece lo mismo
que M odel más varios métodos adicionales para establecer atributos flash, en concreto un
par de métodos a d d F l a s h A t t r i b u t e () para añadir un atributo flash. Si volvemos al
método p r o c e s s R e g i s t r a t i o n ( ), puede usar a d d F l a s h A t t r i b u t e () para añadir el
objeto S p i t t e r al modelo:
Operaciones avanzadas con Spring MVC 245

@RequestMapping (value="/register" , method=POST)


public String processRegistration(
S p itte r s p i tt e r , RedirectAttributes model) {
spitterRepository. s a v e ( s p i t t e r ) ;
model. addAttribute("username", s p i t t e r . getUsername( ) ) ;
model. addFlashAttribute(" s p i t t e r " , s p i t t e r ) ;
return " r e d ir e c t : / spitter/{username}";
}
En este caso invocamos a d d F l a s h A t t r i b u t e () y le asignamos s p i t t e r como clave
y el objeto S p i t t e r como valor. También puede excluir el parámetro de clave y deducirlo
del tipo del valor:
model. addFlashAttribute(s p i t t e r ) ;

Como pasamos un objeto S p i t t e r a a d d F l a s h A t t r i b u t e ( ) , se deduce que la clave


es s p i t t e r . Antes de la redirección, todos los atributos flash se copian en la sesión. Tras
la redirección, los atributos flash almacenados en la sesión se pasan al modelo. De esta
forma el método que procesa la solicitud de redirección puede acceder a S p i t t e r desde
el modelo, como si fuera cualquier otro objeto del modelo (véase la figura 7.2).

Redirección
realizada

Solicitud original Redirigir solicitud

Atributos Flash spitter=Spitter Modelo spitter=Spitter

Sesión

Figura 7.2. Los atributos flash se almacenan en la sesión y después se pasan al modelo,
para que sobrevivan a la redirección.

Para completar la descripción de los atributos flash, la siguiente versión actualizada de


s h o w S p i t t e r P r o f i l e () comprueba si hay un objeto S p i t t e r en el modelo antes de
buscarlo en la base de datos:
@RequestMapping(value="/{username}", method=GET)
public String showSpitterProfile(
@PathVariable String username, Model model) {
i f ( ¡model. co n ta in sA ttrib u te ("sp itte r")) {
model. addAttribute(
spitterRepository. findByUsername(username));

return "p ro file ";


}
Como puede apreciar, lo primero que hace s h o w S p i t t e r P r o f i l e () es comprobar si
hay un atributo m od el con la clave s p i t t e r . Si el modelo contiene un atributo s p i t t e r ,
no hay más que hacer.
246 Capítulo 7

El objeto S p i t t e r que contiene se reenvía a la vista para su representación. Si el modelo


no contiene un atributo s p i t t e r , s h o w S p i t t e r P r o f i l e () busca el objeto S p i t t e r en
el repositorio y lo almacena en el modelo.

Resumen
En Spring siempre encontrará algo más: más funciones, más opciones y más formas de
lograr sus objetivos de desarrollo. Spring MVC se guarda multitud de ases en la manga.
La configuración de Spring MVC es sin duda un área para la que dispone de
múltiples alternativas. En este capítulo hemos visto diversas formas de configurar
D i s p a t c h e r S e r v l e t y C o n t e x t L o a d e r L i s t e n e r de Spring MVC. Hemos modificado
el registro de D i s p a t c h e r S e r v l e t y hemos aprendido a registrar nuevos servlet y filtros,
y si desarrolla aplicaciones para un servidor más antiguo, ahora ya sabe cómo declarar
D i s p a t c h e r S e r v l e t y C o n t e x t L o a d e r L i s t e n e r en web . xm l.
Hemos visto cómo procesar excepciones generadas por los controladores de Spring
MVC. Aunque un método @ R equ estM app ing pueda encargarse del control de excepciones,
el código del controlador será mucho más limpio si extrae el control de excepciones a un
método independiente.
Para procesar tareas comunes como el control de excepciones en todos los controladores
de su aplicación, Spring 3.2 presentó @ C o n t r o lle r A d v ic e para crear clases que recopilen
en un mismo punto comportamientos de controlador comunes.
Por último, hemos visto cómo transmitir datos entre redirecciones, incluida la compati­
bilidad de Spring con atributos flash: atributos similares a los del modelo que sobreviven
a una redirección. Esto le permite responder correctamente a solicitudes POST con una
redirección pero mantener los datos del modelo obtenidos al procesar la solicitud POST y
usarla para mostrarlos tras la redirección.
Y si se lo estaba preguntando, sí, todavía hay más. No hemos visto todo lo que Spring
MVC es capaz de hacer. Retomaremos su análisis en un capítulo posterior, cuando apren­
damos a utilizarlo para crear API REST.
Pero por el momento, nos centraremos en Spring Web Flow, un marco de trabajo con
flujos que se usa sobre Spring MVC para crear aplicaciones que invitan al usuario a seguir
una serie de pasos.
Capítulo
Trabajar con S p rin g
W eb Flow *•

CONCEPTOS FUNDAMENTALES:

• Creación de aplicaciones Web conversacionales.


• Definición de estados y acciones de flujos.
• Protección de flujos Web.
Una de las peculiaridades de Internet es que es muy fácil perderse. Hay muchísimas cosas
que ver y leer y, en el centro del potencial de Internet, se encuentra el vínculo. No resulta
extraño que lo llamen "la Red" porque, al igual que las de las arañas, atrapa a cualquiera
que acceda a ella. Tengo que confesar que una de las razones por las que tardé tanto en
escribir este libro es porque me perdí en una inacabable sucesión de enlaces de Wikipedia.
Hay ocasiones en las que una aplicación Web debe tomar control del viaje de un usuario
a través de Internet, llevándolo de un paso a otro dentro de una aplicación. El ejemplo más
claro de este proceso sería el pago en un sitio de comercio electrónico. Comenzaríamos por
la cesta de la compra y, a continuación, introduciríamos la información para el envío, la de
facturación y, por último, la confirmación del pedido.
Spring Web Flow es un marco de trabajo Web que permite el desarrollo de elementos
que siguen un flujo configurado. En este capítulo, vamos a aprender cómo funciona y cómo
encaja dentro del marco de trabajo Web de Spring.
Es posible crear aplicaciones de flujo en cualquier marco de trabajo Web. Incluso he visto
una aplicación de Struts con cierto flujo en ella. Sin embargo, sin una forma de separar el
flujo de la implementación, se encontrará con que la definición del flujo se reparte entre
los diferentes elementos que la conforman.
Spring Web Flow es una extensión de Spring MVC que permite el desarrollo de aplica­
ciones Web basadas en un flujo. Para ello, separa la definición del flujo de una aplicación
de las clases y vistas que implementan su comportamiento.
Para familiarizamos con el funcionamiento de Spring Web Flow, vamos a dejar descansar
la aplicación Spitter y trabajar en una nueva aplicación Web para aceptar pedidos de pizza.
Para definir el proceso de compra, vamos a utilizar Spring Web Flow.
El primer paso para trabajar con Spring Web Flow consiste en instalarlo en un proyecto,
como haremos a continuación.

co en
Spring Web Flow está basado en Spring MVC. Esto quiere decir que todas las solici­
tudes de un flujo pasan, en primer lugar, a través de D i s p a t c h e r S e r v l e t . Desde ahí, es
necesario configurar una serie de bean especiales en el contexto de aplicación de Spring
para gestionar la solicitud de flujo y ejecutarlo.
Actualmente no se puede configurar Spring Web Flow en Java, por lo que tendrá que
hacerlo en XML. Algunos de los bean de flujo Web se declaran utilizando elementos del
espacio de nombres XML de configuración de Spring de Spring Web Flow. Por tanto, vamos
a tener que añadir la declaración de espacio de nombres al archivo XML de definición de
contexto:
<?xml v e r sio n = "l. 0 "encoding="UTF-8"?>
cbeans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="h t t p : //www.w3. org/2001/XMLSchema-instance"
xmlns: flow="h ttp : //www. springframework. org/schema/Webflow-config"
x s i : schemaLocation=
"h tt p : //www.springframework. org/schema/webflow-config
http://www.springframework.org/schema/webflow-config/[CA]
250 Capítulo 8

spring-Webflow-config-2.3.xsd
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">

Una vez tenemos la declaración de espacio de nombres en su lugar, estamos listos para
comenzar a conectar los bean de flujo Web, comenzando por el ejecutor de flujo.

Conectar un ejecutor de flujo


Como su propio nombre indica, el ejecutor de flujo impulsa la ejecución de un flujo.
Cuando un usuario accede a un flujo, el ejecutor crea y ejecuta una instancia de la ejecución
del flujo para ese usuario. Cuando el flujo entra en pausa (por ejemplo, porque se muestra
una vista al usuario), el ejecutor reanuda el flujo una vez que el usuario ha realizado una
acción. El elemento < f lo w : f lo w - e x e c u to r > crea un ejecutor de flujo en Spring:
<flow:flow-executor id="flowExecutor" />

Aunque el ejecutor de flujo es responsable de la creación y de la ejecución de los


flujos, no lo es de su carga. Para ello, necesitamos un registro de flujo, que vamos a crear
a continuación.

Configurar un registro de flujo


La función de un registro de flujo es cargar las definiciones de flujo y hacer que se
encuentren disponibles para el ejecutor de flujo. Podemos configurar un registro de flujo
en la configuración Spring mediante el elemento < f lo w : f lo w - r e g i s t r y > :
<flow:flow-regístryid="flowRegistry" base-path="/WEB-INF/flows">
<flow:flow-location-patternvalue="*-flow.xml"/>
</flow:flow-registry>

Tal y como se declara aquí, el registro de flujo va a buscar definiciones en la carpeta


/WEB - IN F /f low s, tal y como se especifica en el atributo b a s e -p a th . Según el elemento
< f lo w : f l o w - l o c a t i o n - p a t t e r n > , cualquier archivo XML cuyo nombre acabe en
- f lo w . xml va a considerarse como una definición de flujo.
Para hacer referencia a cualquier flujo se utiliza su ID. Mediante < f low : flo w - lo c a t io n -
p a t t e r n > , el ID de flujo va a ser la ruta de directorio relativa a b a s e - p a t h (o a la parte
de la ruta representada mediante el doble asterisco). La figura 8.1 muestra cómo se calcula
el ID de flujo en esta situación. Otra posibilidad sería excluir el atributo b a s e - p a t h e
identificar de forma explícita la ubicación del archivo de definición de flujo:
<flow:flow-registry id="flowRegistry">
<flow:flow-location path="/WEB-INF/flows/springpizza.xml" />
</flow:flow-registry>

En este caso se utiliza el elemento < f l o w :flo w - l o c a t i on > en lugar de < f 1 ow: f 1ow-
l o c a t i o n - p a t t e r n > El atributo p a th apunta directamente al archivo /WEB-INF/
f lo w s / s p r i n g p i z z a . xml como el de definición de flujo. De esta forma, el ID del flujo
Trabajar con Spring Web Flow 251

se obtiene del nombre base del archivo de definición de flujo, en este caso s p r in g p iz z a .
Si quiere ser más explícito sobre el ID del flujo, puede establecerlo con el atributo id del
elemento < f low : f lo w - lo c a t io n > . Por ejemplo, para especificar p iz z a como el ID del
flujo, puede configurar < f low : f lo w - lo c a t io n > de la siguiente manera:
<f 1ow: flow -registry id="flowRegistry">
<flow:flow-location id="pizza"
path="/WEB-INF/flows/springpizza.xml" />
</flow:flow-registry>

Definición
Ruta base del del flujo
registro de flujo
\
/WEB-INF/flows/order/order-flow.xml

ID del flujo
Figura 8.1. Al utilizar un patrón de localización de flujo, la ruta al archivo de definición de flujo
relativa a la ruta base va a utilizarse como ID del flujo.

Procesar s o l ic i t u d e s de flojo
Como hemos visto en el capítulo anterior, D i s p a t c h e r S e r v l e t suele derivar las
solicitudes a los controladores. Sin embargo, para los flujos, necesitamos un elemento
F low H and lerM ap p in g para ayudar a que D is p a t c h e r H a n d le r sepa que debe enviar las
solicitudes de flujo a Spring Web Flow. F lo w H an d lerM ap p in g se configura en el contexto
de aplicación de Spring de la siguiente manera:
cbean class=
"org. springframework.webflow.mvc. s e r v l e t . FlowHandlerMapping">
<property name="flowRegistry" ref="flowRegistry" />
</bean>

Como puede ver, F lo w H a n d lerM a p p in g se conecta con una referencia al registro de


flujo para que sepa cuándo una URL de solicitud está asignada a un flujo. Por ejemplo,
si tenemos un flujo cuyo ID es p i z z a , F lo w H an d lerM ap p in g va a saber asignar una
solicitud a ese flujo si el patrón URL de la solicitud (relativa a la ruta de contexto de la
aplicación) es / p i z z a .
Si el trabajo de F lo w H a n d lerM a p p in g es dirigir las solicitudes de flujo a Spring Web
Flow, el de F lo w H a n d le rA d a p te r es responder a éstas. F lo w H a n d le rA d a p te r es el
equivalente a un controlador de Spring MVC, ya que gestiona solicitudes procedentes
de un flujo y las procesa. F lo w H a n d le rA d a p te r se conecta como bean de Spring de la
siguiente manera:
<bean class
="org. springframework.webflow.mvc. s e r v l e t . FlowHandlerAdapter">
<property ñame="f 1owExecutor" r e f = "flowExecutor" />
</bean>
252 Capítulo 8

Este adaptador de procesador es la conexión entre D i s p a t c h e r S e r v l e t y Spring


Web Flow. Gestiona las solicitudes de flujo y los manipula en función de esas solicitudes.
En este caso, está conectado a una referencia al ejecutor de flujo en aquellos flujos para los
que gestiona solicitudes.
Hemos configurado todos los bean y componentes necesarios para que Spring Web Flow
funcione. Ahora solo queda definir un flujo. Lo haremos en breve, pero antes conozcamos
los elementos que tenemos que conectar para formar un flujo.

Componentes de un flujo
Un flujo se define en Spring Web Flow mediante tres elementos principales: estados,
transiciones y datos de flujo. Los estados son puntos dentro de un flujo donde sucede algo.
Si comparamos un flujo con una carretera, los estados serían las ciudades, las gasolineras
o las áreas de descanso. En lugar de pararnos a repostar y comprar una bolsa de patatas,
un estado dentro de un flujo es el punto donde se lleva a cabo cierta lógica, donde se toma
una decisión o donde se muestra una página al usuario.
Si los estados son como los puntos del mapa en los que podemos detenernos, las tran­
siciones serían las carreteras que conectan esos puntos. En un flujo, pasamos de un estado
a otro mediante una transición.
A medida que viajamos de una ciudad a otra, podemos ir acumulando suvenires,
recuerdos y bolsas de patatas fritas vacías. Del mismo modo, cuando un flujo progresa,
recopila datos. En concreto, la situación actual del flujo. Podríamos denominarlo el estado
del flujo, pero el término estado tiene otro sentido cuando se utiliza con flujos.
Veamos con detalle cómo se definen estos tres elementos en Spring Web Flow.

Estados
Spring Web Flow define cinco tipos diferentes de estados, tal y como se muestra en la
tabla 8.1. Permiten crear prácticamente cualquier tipo de funcionalidad en una aplicación
Web. Aunque no todos los flujos requieren todos los estados descritos en la tabla, segura­
mente los utilizará todos en sus proyectos.

Tabla 8.1. Estados de Spring Web Flow.


mmm—
9
I Tipo de estado Función
........... .... . .. ....... . . . _____ _________ XOUÉ3H.________________________ Stt

Acción Donde tiene lugar la lógica de un flujo.


Decisión Dividen el flujo en dos direcciones, redirigiéndolo en función del resultado al
evaluar los datos del flujo.
Final La última parada del flujo. Una vez un flujo llega a su final, se termina.
Subflujo Inicia un nuevo flujo en el contexto de un flujo que ya está en marcha.
Vista Un estado de vista interrumpe el flujo e invita al usuario a que participe en éste.
Trabajar con Spring Web Flozv 253

En breve, vamos a ver cómo conectar estos elementos para obtener un flujo completo
pero, antes, vamos a aprender cómo se manifiestan cada uno de estos elementos en una
definición de Spring Web Flow.

Estados de vista
Los estados de vista se utilizan para mostrar información al usuario para que desempeñe
un papel activo en el flujo. La implementación de la vista puede ser cualquiera de las vistas
admitidas por Spring MVC aunque, en la mayoría de los casos, se utiliza JSP.
Dentro del archivo XML de definición del flujo, se utiliza el elemento < v i e w - s t a t e >
para definir un estado de vista:
<view-state id="welcome" />

En este ejemplo sencillo, el atributo id cumple una función doble. Por un lado, identi­
fica al estado dentro del flujo. Asimismo, al no haberse indicado ninguna vista, especifica
welcome como nombre lógico de la vista que se va a representar cuando el flujo alcance
este estado. Si prefiere identificar otro nombre de vista de forma explícita, puede hacerlo
con el atributo view :
<view-state id="welcome" view="greeting" />

Si un flujo muestra un formulario a un usuario, puede que quiera especificar el objeto


al que va a estar vinculado el formulario. Para ello, configure el atributo model:
<view-state id="takePayment" model="flowScope.paymentDetails"/>

En este caso, hemos especificado que el formulario de la vista tak eP ay m en t va a


vincularse con el objeto p a y m e n tD e ta ils en el ámbito del flujo.

Estados de acción
Mientras que los estados de vista implican a los usuarios de la aplicación en el flujo,
los de acción son donde la aplicación va a realizar su trabajo. Los estados de acción suelen
invocar algún método en un bean gestionado por Spring y, a continuación, realizan una
transición a otro estado en función del resultado de la ejecución del método.
En el código XML de definición del flujo, los estados de acción se expresan con el
elemento < a c t i o n - s t a t e > . Aquí tiene un ejemplo:
<actio n -state id="saveOrder">
<evaluate expression="pizzaFlowActions. saveOrder(order)" />
<tran sitio n to="thankYou" />
</actio n -state >

Aunque no es obligatorio, los elementos < a c t i o n - e l e m e n t s > suelen contar con un


subelemento < e v a l u a t e > , el cual asigna al estado de acción algo que hacer. El atributo
e x p r e s s i o n recibe una expresión que se evalúa cuando se entra en el estado. En este caso,
e x p r e s s i o n recibe una expresión SpEL, que indica que el método s a v e O r d e r () debe
invocarse sobre el bean cuyo ID es p iz z a F lo w A c t io n s .
254 Capítulo 8


Spring Web Flow y los lenguajes de expresiones
. ................................. ......
Spring Web Flow ha utilizado diversos lenguajes de expresiones a lo largo de su
existencia. En su versión 1.0, utilizó OGNL (O b ject-G rap h N a v ig a tio n L a n g u a g e ,
Lenguaje de navegación Objeto-Gráfico). Desde la versión 2.0 tuvo sus escarceos
con Unified EL (U n ified E x p ressio n L a n g u a g e, Lenguaje de expresiones unificadas)
y, desde la 2.1, perm anece fiel a SpEL.
Aunque se pueda configurar Spring Web Flow para usar cualquiera de ellos,
SpEL es el predeterminado y el más adecuado, por lo que nos centrarem os en
SpEL en lo que a la definición de flujos se refiere.

Estados de decisión
Un flujo puede ser completamente lineal y pasar de un estado a otro sin seguir ningún
camino alternativo. Sin embargo, lo más habitual es que un flujo se divida llegado a un
punto concreto en función de las circunstancias actuales del flujo.
Los estados de decisión permiten incluir una rama binaria en la ejecución de un flujo. Un
estado de decisión evalúa una expresión booleana y toma una de dos transiciones disponi­
bles, dependiendo de que el valor de la expresión se evalúe como t r u e o f a l se. Dentro
de la definición XML de flujos, los estados de decisión se definen mediante el elemento
< d e c i s i o n - s t a t e > . A continuación, puede ver un ejemplo típico:
<decision-State id="checkDeliveryArea">
< i f test="pizzaFlowActions. checkDeliveryArea{customer. zipCode)"
then="addCustomer"
else="deliveryWarning" />
</d ecísio n -state>

Como puede ver, el elemento < d e c i s i o n - d a t e > no trabaja de forma individual. El


elemento < i f > es el núcleo del estado de decisión, donde se evalúa la expresión. Si el
resultado de la evaluación de la expresión es t r u e , el flujo pasará al estado identificado
por el atributo th e n . Si es f a l s e , lo hará al estado indicado en el atributo e l s e .

Estados de subflujo
Lo más habitual es no incluir toda la lógica de una aplicación en un único método. En
lugar de esto, vamos a dividirla en diferentes clases, métodos y otras estructuras.
Del mismo modo, es una buena idea dividir los flujos en diferentes partes. El elemento
< s u b f l o w - s t a t e > permite ejecutar otro flujo desde dentro del flujo en ejecución. Es
similar a ejecutar un método desde dentro de otro método.
Podemos declarar un < s u b f lo w - s t a t e > de la siguiente manera:
<subflow-state id="order" subflow="pizza/order">
<input name="order" value="order"/>
<tran sition on="orderCreated" to="payment" />
</subflow-state>
Trabajar con Spring Web Flozv 255

En este caso, el elemento < in p u t> se utiliza para proporcionar el objeto del pedido
como entrada al subflujo. Asimismo, si el subflujo termina con un <en d - s t a t e > cuyo ID
es o r d e r C r e a te d , entonces el flujo va a pasar al estado cuyo ID es paym ent.
Sin embargo, estamos adelantando contenidos. Aún no hemos hablado de este elemento
ni de las transiciones (lo haremos más adelante, en este capítulo).

Estados finales
Todos los estados terminan antes o después. El fin de un flujo se indica mediante el
elemento < e n d - s t a t e > y suele utilizarse de la siguiente manera:
<end-State id="customerReady" />

Cuando el flujo alcanza un elemento < e n d - s t a t e > , finaliza. Lo que sucede a conti­
nuación depende de varios factores:

• Si el flujo que finaliza es un subflujo, el flujo que invoca pasará a < su b f low - S t a t e >.
El ID del <en d - s t a t e > se va a utilizar como evento para activar la transición desde
< s u b f lo w - s t a t e > .
• Si < e n d - s t a t e > tiene su atributo view configurado, se representará la vista espe­
cificada. La vista puede ser una ruta relativa al flujo de una plantilla de vista, con el
prefijo e x t e r n a l R e d i r e c t : para establecer una redirección a una página externa
al flujo, o con el prefijo f lo w R e d i r e c t : para establecer una redirección a otro
flujo.
• Si el flujo final no es un subflujo y no se especifica una vista, el flujo termina. El
navegador accederá a la URLbase del flujo y, sin un flujo activo, se iniciará una nueva
instancia de éste.

Es importante tener en cuenta que un flujo puede tener más de un estado final. Como
el ID del estado final determina el evento activado desde un subflujo, puede que quiera
finalizar el flujo mediante diferentes estados finales para activar diferentes eventos en el
flujo de invocación. Incluso en flujos en los que no existen subflujos pueden encontrarse
varias páginas de destino que aparezcan tras la finalización de un flujo, en función de la
dirección que el flujo haya tomado.
Ahora que hemos visto los diferentes tipos de estados de un flujo, deberíamos ver cómo
el flujo se mueve entre estados.

Transiciones
Como ya hemos indicado, las transiciones conectan los estados dentro de un flujo. Cada
estado de un flujo, a excepción de los estados finales, debe tener al menos una transición,
para que así el flujo sepa cuál es el siguiente paso que tomar una vez que se haya completado
el estado. Un estado puede tener varias transiciones, cada una de las cuales va a representar
una ruta diferente que puede tomarse tras la finalización de un estado.
256 Capítulo 8

Las transiciones se definen en el elemento c t r a n s i t io n > , un subelemento de los dife­


rentes elementos de estado ( < a c t i o n - s t a t e > , < v i e w - s t a t e > y < s u b f lo w - s t a t e > ) .
En su forma más sencilla, el elemento c t r a n s i t io n > identifica el siguiente estado dentro
del flujo:
ctran sitio n to="customerReady" />

El atributo t o se utiliza para especificar el siguiente estado del flujo. Cuando


< t r a n s i t i o n > se declara con un solo atributo to , la transiciones la predeterminada para
ese estado y se seleccionará si no hay otras disponibles.
Lo más habitual es que las transacciones se definan de forma que tengan lugar cuando
se produce un evento determinado. En un estado de vista, los eventos suelen ser alguna
acción llevada a cabo por el usuario. En un estado de acción, el evento es el resultado de
evaluar una expresión. En un estado de subflujo, el evento viene determinado por el ID
del estado final del subflujo. Con independencia del tipo de evento, puede especificar que
el evento active la transición indicándolo en el atributo on:
<tran sitio n on="phoneEntered" to="lookupCustomer"/>

En este ejemplo, el flujo va a realizar una transición hacia el estado con el ID lo o k u p


C u storn er si se activa un evento p h o n e E n te r e d .
El flujo también va a realizar una transición a otro estado en caso de que se genere una
excepción. Por ejemplo, si no se puede encontrar el registro de un cliente, puede que quiera
que el flujo realice una transición hacia un estado de vista para que muestre un formulario
de registro. El siguiente fragmento muestra este tipo de transición:
ctran sitio n
on-exception=
"com.springinaction.pizza. Service. CustomerNotFoundException"
to="registrationForm" />

El atributo on - e x c e p t io n se parece mucho al atributo on, con la diferencia de que espe­


cifica una excepción a la transición en lugar de un evento. En este caso, C ustom erN ot Found
va a hacer que el flujo realice una transición hacia el estado r e g i s t r a t i o n F o r m .

Transiciones globales
Después de crear un flujo, puede que descubra que hay varios estados que comparten
transiciones comunes. Por ejemplo, no me sorprendería encontrar el siguiente elemento
c t r a n s i t i o n > repartido a lo largo del flujo:
ctran sitio n on="cancel" to="endState" />

En lugar de repetir transiciones comunes en diferentes estados, puede definirlas


como transiciones globales si incluye el elemento < t r a n s i t i o n > como subelemento de
< g l o b a l t r a n s i t i o n s > . Por ejemplo:
cg lobal-transitions>
ctran sitio n on="cancel" to="endState" />
c/glo bal-tran sitio n s>
Trabajar con Spring Web Floiv 257

Al contar con esta transición global, todos los estados dentro del flujo van a contar con
una transición c a n c e l implícita.
Hemos visto los estados y las transiciones, pero antes de empezar a crear flujos, nos
centraremos en sus datos, el último miembro de un flujo Web.

Datos del flujo


Si en alguna ocasión ha jugado con uno de esos viejos juegos de aventura basados
en texto, probablemente sabrá lo que es moverse de un lugar a otro y, de vez en cuando,
encontrar objetos que puede recoger y llevar consigo. En ocasiones se necesita un objeto
al instante. En otras, se puede llevar un objeto todo el juego sin saber lo que es y descubrir
su utilidad justo al final.
En muchos aspectos, los flujos son muy parecidos a estos juegos de aventura. A medida
que el flujo pasa de un estado a otro, captura datos. Algunas veces, esos datos solo se
necesitan durante un corto periodo de tiempo (quizá solo lo suficiente para mostrar una
página a un usuario). Otras veces, los datos se transportan a lo largo de todo el flujo y solo
se utilizan cuando éste finaliza.

Declaración de variables
Los datos de los flujos se almacenan en variables, a las cuales se puede hacer referencia
en diferentes puntos del flujo. Pueden crearse y acumularse de diferentes maneras. La forma
más sencilla de crear una variable en un flujo es utilizando el elemento < v a r> :
<var name="customer" c l a s s = "com. springinaction.pizza.domain. Customer"/>

En este caso, se crea una nueva instancia de un objeto C ustom er y se incluye en la


variable llamada cu sto m er. Esta variable va a estar disponible para todos los estados de
un flujo.
También puede crear variables utilizando el elemento < e v a lu a te > como parte de un
estado de acción o al entrar en un estado de vista. Por ejemplo:
<evalúate r e s u lt= "viewScope. toppingsList"
expression="T(com.springinaction.pizza. domain.Topping). a s L i s t ()" />

En este caso, el elemento < e v a lu a te > evalúa una expresión (de tipo SpEL) e incluye
el resultado en una variable llamada t o p p in g s L i s t , que cuenta con ámbito de vista
(hablaremos sobre los ámbitos más adelante).
De forma similar, el elemento < s e t > puede configurar el valor de una variable:
<set name="flowScope.pizza"
value="new com.springinaction.pizza.domain.Pizza()" />

El elemento < s e t > trabaja de forma muy parecida al elemento < e v a lu a te > , configu­
rando una variable en el valor resultante de una expresión evaluada. En este caso, configu­
ramos la variable p iz z a con ámbito de flujo para una nueva instancia de un objeto P iz z a .
258 Capítulo 8

En un apartado posterior veremos los detalles de uso de estos elementos en un flujo


real, cuando creemos un flujo Web operativo, pero primero veremos qué significan los
ámbitos de variable.

Ámbitos de datos de flujo


Los datos de un flujo tienen diferentes duraciones y visibilidades, dependiendo del
ámbito de la variable donde se almacenen. Spring Web Flow define cinco ámbitos, que se
recogen en la tabla 8.2.

Tabla 8.2. Ámbitos de Spring Web Flow.

ibito Duración y visibilidad


__________*_______: . ¿ 2^ _______.......

Conversación Se crea cuando se inicia un flujo de nivel superior y se elimina cuando finaliza.
Se comparte con un flujo de nivel superior y con todos sus subflujos.
Flujo Se crea cuando se inicia un flujo y se elimina cuando finaliza. Solo es visible
dentro del flujo en el que se ha creado.
Solicitud Se crea cuando se realiza una solicitud y se elimina cuando se devuelve el
flujo.
Destello Se crea cuando se inicia un flujo y se elimina cuando finaliza. También se
elimina después de que se represente un estado de vista.
Vista Se crea cuando se accede a un estado de vista y se elimina cuando se sale
del estado. Solo es visible dentro del estado de vista.

Cuando se declara una variable utilizando el elemento < v a r >, la variable siempre tiene
ámbito de flujo dentro del flujo que la define. Cuando se utiliza < s e t > o < e v a lu a te > , el
ámbito se especifica como prefijo del atributo ñame o r e s u l t . Por ejemplo, para asignar
un valor a la variable theA nsw er con ámbito de flujo, utilizaríamos este código:
<set name="flowScope. theAnswer" value="42"/>

Ahora que ya conocemos todos los elementos que forman un flujo Web, es el momento
de unirlos para obtener un flujo Web completo. A medida que lo hagamos, preste atención
a los diferentes ejemplos de cómo almacenar datos en variables con ámbito.

Combinar todas las piezas: el flujo pizza


Como he mencionado antes, en este capítulo no vamos a utilizar la aplicación Spittr. En
su lugar, vamos a crear una aplicación en línea para pedir pizzas. Como el proceso para
pedir una pizza encaja a la perfección con las características de un flujo, vamos a comenzar
creando uno de nivel superior que defina todo el proceso. Después, lo dividiremos en
diferentes subflujos para definir los detalles del proceso.
Trabajar con Spring Web Flow 259

Definición de! flujo base


Spizza, una nueva cadena de pizzerías, ha decidido descargar de trabajo a las personas
que atienden el teléfono en sus tiendas. Para ello, va a permitir que sus clientes puedan
realizar pedidos a través de Internet. Cuando los clientes acceden a la página Web de Spizza,
pueden identificarse, seleccionar una o más pizzas, indicar la información de pago, realizar
el pedido y esperar a que llegue a casa. En la figura 8.2, podemos ver este flujo al completo.

Figura 8.2. El proceso para pedir una pizza recogido en un flujo sencillo.

Los cuadros de este diagrama representan estados y las flechas transiciones. Como
puede ver, el flujo general de la pizza es sencillo y lineal. Debería ser sencillo expresarlo
en Spring Web Flow. Lo único que hace todo este proceso más interesante es que los tres
primeros estados pueden requerir más trabajo del que parece a simple vista.
El listado 8.1 muestra el flujo de pedido de una pizza de nivel superior, utilizando la
definición basada en XML de Spring Web Flow.

Listado 8.1. El flujo para realizar el pedido de una pizza, definido mediante Spring Web Flow.

<?xml version="1.0" encoding="UTF-8"?>


<flow xmlns="h tt p : / /www. springframework. org/schema/webflow"
xmlns:xsi="h tt p : //www.w3.org/2001/XMLSchema-instance"
x s i : schemaLocation="h tt p : / /www.springframework. org/schema/webflow
h tt p : //www.springframework.org/schema/Webflow/spring-Webflow-2 . 3 . xsd" >
<var name="order"
c ía s s = "com.springinaction.pizza. domain.Order"/>
<subflow-State id="identifyCustomer" subílow="pizza/customer">
/ / Ejecutar subflujo de c lie n te s .
coutput name="customer" value="order. customer"/>
ctran sitio n on="customerReady" to="buildOrder" />
</subflow-state>
260 Capítulo 8

<subflow-state id="buildOrder" subflow="pizza/order" > / / Ejecutar subflujo de pedidos


<input name="order" value="order"/>
t r a n s i t i o n on="orderCreated" to="takePayment" />
</subflow-state>
<subflow-state id="takePayment" subflow="pizza/payment"> / / Ejecutar subflujo de pagos
<input name="order" value="order"/>
t r a n s i t i o n on="paymentTaken" to="saveOrder"/>
</subflow-state>
<actio n -state id="saveOrder"> / / Guardar pedido.
<evaluate expression="pizzaFlowActions. saveOrder(order)" />
<tran sition to="thankCustomer" />
</actio n -state >
<view-state id="thankCustomer"> / / Dar las gracias al c lie n te .
t r a n s i t i o n to="endState" />
</view-state>
<end-state id="endState" />
<global- tr a n s i ti o n s >
«tran sition on="cancel" to="endState" /> / / Transición de cancelación global.
</g lo b a l-tra n s itio n s >
< / flow>

Lo primero que puede ver en la definición del flujo es la declaración de la variable o r d e r .


Cada vez que el flujo se inicia, se crea una nueva instancia de O rd er. La clase O rd e r, tal
y como se muestra en el listado 8.2, cuenta con propiedades para transmitir toda la infor­
mación sobre un pedido, incluyendo la información del cliente, la lista de pizzas que ha
pedido y los detalles sobre el pago.

Listado 8.2. La clase Order incluye toda la información sobre el pedido.

package com.springinaction.pizza.domain;
import j ava. i o . Se rializab le
import ja v a .u til.A rray L ist;
import j a v a . u t i l . L i s t ;
public class Order implements S e ria liz a b le {
private s t a t i c fin a l long serialVersionUID = 1L;
private Customer customer;
private List<Pizza> pizzas;
private Payment payment;

public Order() {
pizzas = new ArrayList<Pizza>( );
customer = new Customer();
}
public Customer getCustomer() {
return customer;
}
public void setCustomer(Customer customer) {
t h i s . customer = customer;
}
public List<Pizza> getPizzasO {
return pizzas;
}
public void setPizzas(List<Pizza> pizzas) {
th is.p izzas = pizzas;
Trabajar con Spring Web Flow 261

}
public void addPizza(Pizza pizza) {
pizzas. add(pizza);
}
public flo a t getTotalO {
return O.Of;
}
public Payment getPaymentO {
return payment;
}
public void setPayment(Payment payment) {
this.payment = payment;

}
}

La parte principal de esta definición de flujo está formada por los estados de flujo. De
forma predeterminada, el primer estado del archivo de definición del flujo también es el
primer estado al que se va a acceder en éste. En este caso, sería i d e n t i f y C u sto m e r (un
estado de subflujo). Sin embargo, si lo desea, puede identificar de forma explícita cualquier
estado, como el de inicio, configurando el atributo s t a r t - S t a t e en el elemento < f low >:
<?xml v e r sio n = "l.0" encoding="UTF-8"?>
<flow xmlns="h tt p : / /www. springframework. org/schema/webflow"
xmlns:xsi="h tt p : //www.w3. org/2001/XMLSchema-instance"
x s i : schemaLocation="h tt p : //www.springframework.org/schema/webflow
h tt p : //www.springframework.org/schema/Webflow/spring-Webflow-2. 3 . xsd"
start-state="identifyCustomer">

< / flow>

Identificar un cliente, crear el pedido de una pizza y aceptar un pago son actividades
demasiado complejas como para incluirlas en un único estado. Por eso vamos a definirlas
más adelante y con mayor detalle como flujos individuales. Sin embargo, para los obje­
tivos del flujo de pizza de nivel superior, estas actividades van a expresarse mediante el
elemento < s u b f l o w - s t a t e >.
La variable de flujo o r d e r va a incluir la información de los tres primeros estados
y, a continuación, se va a guardar en el cuarto estado. El estado de subflujo i d e n t i f y
C u sto m e r va a utilizar el elemento c o u tp u t > para completar la propiedad c u s to m e r de
o r d e r , configurándola con el resultado recibido tras invocar el subflujo de clientes. Los
estados b u i ld O r d e r y ta k e P a y m e n t utilizan un enfoque diferente, utilizando c in p u t >
para pasar la variable de flujo o r d e r como entrada para que dichos subflujos puedan
completarla internamente.
Una vez que el pedido recibir un cliente, las pizzas encargadas y los detalles del pago,
hay que guardarlo. El estado s a v e O r d e r es un estado de acción que se encarga de esta
tarea. Utiliza < e v a l u a t e > para invocar el método s a v e O r d e r () en el bean cuyo ID es
p iz z a F lo w A c t io n s , transmitiendo el pedido que se va a guardar. Cuando termina de
guardar el pedido, pasa a th a n k C u sto m e r.
El estado th a n k C u s to m e r es un estado de vista sencillo, apoyado por el archivo JSP en
/WEB - I N F / f lo w s / p iz z a / t h a n k C u s t o m e r . j sp , tal y como se muestra en el listado 8.3.
262 Capítulo 8

Listado 8.3. Una vista JSP que da las gracias al cliente por realizar su pedido.

chtml xmlns: jsp="h t t p : / / j a v a . sun. com/JSP/Page">


<j s p : output omit-xml-declaration="yes"/>
<j s p : d ir e c t iv e .page contentType="text/htm l; charset=UTF-8" />
< h e a d x title > S p iz z a < /tit le x /h e a d >
<body>
<h2>Thank you for your order!</h2>
< ! [ CDATA[
<a href='${flowExecutionUrl}&_eventId=finished'>Finish</a>
/ / Ejecutar evento finished
]]>
</body>
</html>

Esta página da las gracias al cliente por realizar su pedido y le proporciona un enlace
para que finalice el flujo. Este enlace es la parte más interesante de la página, ya que muestra
un método con el que el usuario puede interactuar con el flujo.
Spring Web Flow proporciona una variable f lo w E x e c u t io n U r l, que contiene la URL
del flujo, la cual se va a utilizar en la vista. El enlace de finalización adjunta un parámetro
_ e v e n t l d a la URL para activar un evento f i n i s h e d , que nos devuelve al flujo Web. Ese
evento envía el flujo al estado final.
En el estado final, el flujo finaliza. Como no hay más información sobre dónde ir después
de que el flujo finalice, éste comienza de nuevo en el estado id e n tify C u s to m e r , listo
para aceptar otro pedido de pizza.
Esto cubre el flujo general para pedir una pizza. Sin embargo, hay mucho más en el
flujo que lo que podemos ver en el listado 8.1. Aún tenemos que definir los subflujos para
los estados id e n t if y C u s t o m e r , b u i ld O r d e r y ta k e P a y m e n t.

Obtener información del cliente


Si alguna vez ha pedido una pizza, probablemente conocerá cómo funciona el proceso.
Lo primero que van a pedirle es el número de teléfono. Además de ser una forma de
comunicarse con usted, en caso de que el repartidor no pueda encontrar su casa, también
sirve como identificación para la pizzería. Si es un cliente habitual, van a poder utilizar
su número de teléfono para obtener su dirección y así saber dónde tienen que llevar el
pedido.
Para un cliente nuevo, el número de teléfono no va a generar ningún resultado. Por tanto,
la siguiente información que le van a pedir es su dirección. En este punto, la pizzería sabe
quién es y dónde tiene que llevarle las pizzas. A continuación, antes de que le pregunten
qué tipo de pizza quiere, tienen que asegurarse de que su dirección se encuentra en el área
de reparto. De no ser así, tendrá que ir a la pizzería y recogerla usted mismo.
En la figura 8.3 podemos ver un diagrama de flujo que representa esta fase de preguntas
y respuestas iniciales para cada pedido de pizza.
Este flujo es más interesante que el flujo de pizza de nivel superior. No es lineal y se
divide en un par de ubicaciones en función de diferentes condiciones. Por ejemplo, después
de buscar el cliente, el flujo puede finalizar (si lo encuentra) o pasar a un formulario de
Trabajar con Spring Web Flow 263

registro (si no le encuentra). Asimismo, en el estado c k e c k D e li v e r y A r e a , al cliente se le


puede advertir (o no) de que su dirección no se encuentra en el área de reparto. El listado
8.4 muestra la definición del flujo para identificar al cliente.

Inicio

Listado 8.4. Identificamos al cliente mediante un flujo Web.

<?xml v e r sio n = "l.0" encoding="UTF-8"?>


< f low xmlns="h ttp : //www. springframework. org/schema/webflow"
xmlns:xsi="h tt p : //www.w3. org/2001/XMLSchema-instance"
x s i : schemaLocation="h ttp : //www.springframework.org/schema/webflow
h tt p : / /www.springframework.org/schema/webflow/spring-Webflow-2 . 3 .xsd" >
<var name="customer"
c l a s s = "com.springinaction.pizza. domain. Customer"/>
<view-state id="welcome"> / / Dar la bienvenida al c lie n te .
«¡transition on= "phoneEntered" to="lookupCustomer"/>
</view-state>
<actio n -state id="lookupCustomer"> / / Buscar al c lie n te .
«evaluate result= "customer" expression««
"pizzaFlowActions. lookupCustomer(requestParameters.phoneNumber)" />
«tran sition to="registrationForm" on-exception=
"com.springinaction.pizza. se r v ic e . CustomerNotFoundException" />
«tran sition to="customerReady" />
264 Capítulo 8

< /a c tio n -sta te >


<view -state id="registrationForm " model="customer"> / / R eg istrar nuevo c lie n te .
<on-entry>
<evaluate expression=
"customer.phoneNumber = requestParameters.phoneNumber" />
</on-entry>
ctra n sitio n on="submit" to="checkDeliveryArea" /> / / Comprobar área de reparto.
</view -state>
<d ecisio n -state id="checkDeliveryArea">
< if test="pizzaFlow Actions. checkDeliveryArea(customer. zipCode)"
then="addCustomer"
else="d eliv e ryWarni ng"/>
< /d ecisio n -state>
<view -state id="deliveryWarning">
//M ostrar advertencia de dirección fuera del área de reparto
ctra n sitio n on="accept" to="addCustomer" />
</view -state>
c a c tio n -sta te id ="addCustomer" > / / Añadir c lie n te .
cevaluate expression="pizzaFlowActions.addCustomer(customer)" />
ctra n sitio n to="customerReady" />
c /a c tio n -s ta te >
cend-state id="cancel" />
cend-state id="customerReady">
coutput name="customer" />
c/end -state>
cg lo b al-tran sitio n s>
ctra n sitio n on="cancel" to="cancel" />
c/g lo b a l-tra n sitio n s>
c/flow>

Este flujo introduce elementos nuevos, incluyendo el primer uso del elemento
< d e c i s i o n - s t a t e > . Asimismo, como se trata de un subflujo del flujo p iz z a , espera
recibir un objeto O rd er como entrada. Al igual que antes, vamos a dividir esta definición
de flujo en estados, comenzando por welcome.

Solicitar un número de teléfono


El estado welcom e es un estado de vista que da la bienvenida al cliente al sitio Web
de Spizza y le pide que introduzca su número de teléfono. El estado en sí no es muy inte­
resante. Cuenta con dos transiciones: una que dirige el flujo al estado looku pC ustom er
si se activa un evento p h o n e E n te re d desde la vista, y una segunda transición c a n c e l ,
definida como transición global que reacciona a un evento c a n c e l.
Donde el estado welcome se vuelve interesante es en la vista, definida /W EB-IN F/
f lo w s /p iz z a /c u s to m e r/w e lc o m e . j spx, tal y como se muestra en el listado 8.5.

Listado 8.5. Damos la bienvenida al cliente y le pedimos su número de teléfono.


<html xmlns: jsp = "h t t p :/ / ja v a . sun.com/JSP/Page
xmlns: form="h ttp : / /www. springframework. org/tags/form " >
<js p : output om it-xm l-d eclaration="yes"/>
< jsp :d irectiv e.p ag e contentType="text/html;charset=UTF-8" />
< h e a d x title > S p iz z a < /title x /h e a d >
Trabajar con Spring Web Flow 265

<body>
<h2>Welcome to Spizza! ! ! </h2>
<form:form>
<input type="hidden" name="_flowExecutionKey"
value="${flowExecutionKey } "/> / / Clave de ejecución del flu jo
<input type="text" name="phoneNumber"/xbr/>
<input type="submit" name="_eventId_phoneEntered"
value="Lookup Customer" /> / / E jecu tar evento phoneEntered.
< /form:form>
</body>
</html>

Este sencillo formulario solicita al usuario que introduzca su número de teléfono. El


formulario cuenta con dos elementos especiales que controlan el funcionamiento del flujo.
En primer lugar, fíjese en el campo oculto _ f lo w E x e c u tio n K e y . Cuando se entra a un
estado de vista, el flujo se interrumpe y espera a que el usuario lleve a cabo alguna acción.
La clave de ejecución del flujo se proporciona a la vista como una especie de "resguardo"
para el flujo. Cuando el usuario envía el formulario, la clave de ejecución del flujo se envía
junto a éste en el campo _ f lo w E x e c u t io n K e y , y el flujo continúa donde lo dejó.
Preste especial atención al nombre del botón para realizar el envío. El fragmento
_ e v e n t l d _ del nombre del botón es una pista para Spring Web Flow, que le indica que
lo que sigue es un evento que debe activarse. Cuando el formulario se envíe, al hacer
clic en ese botón, se activa el evento p h o n e E n te r e d , causando una transición a looku p
C u sto m er.

Buscar el cliente
Después de enviar el formulario de bienvenida, el número de teléfono del cliente va a
encontrarse entre los parámetros solicitados y va a estar listo para utilizarse con el fin de
encontrar un cliente. El elemento < e v a l u a t e > del estado lo o k u p C u sto m e r es donde
sucede todo. Obtiene el número de teléfono de los parámetros de la solicitud y lo transmite
al método lo o k u p C u sto m e r () del bean p iz z a F lo w A c t io n s .
La implementación de lo o k u p C u sto m e r () no es importante ahora mismo. Basta con
saber que va a devolver un objeto C u sto m e r o generar una excepción C u sto m erN o t
F o u n d E x c e p tio n .
En el caso anterior, el objeto C u sto m e r se asigna a la variable c u s to m e r (en función
del atributo r e s u l t ) y la transición predeterminada lleva el flujo al estado c u s to m e r
R e a d y . Sin embargo, si el cliente no se puede encontrar, se genera una excepción
C u s t o m e r N o t F o u n d E x c e p t i o n y el flujo realizará una transición al estado
r e g is tr a tio n F o r m .

Registrar un nuevo cliente


El estado r e g i s t r a t i o n F o r m es donde se le pide al usuario que introduzca la direc­
ción de entrega. Al igual que otros estados de vista que hemos visto, va a representar una
vista JSP. El archivo JSP se muestra en el Estado 8.6.
266 Capítulo 8

Listado 8.6. Registro de un nuevo cliente.


chtml xmlns: c="h ttp : / / ja v a . sun.com/js p / js t l / c o r e "
xmlns: j sp="h ttp : / /ja v a . sun.com/JSP/Page"
xmlns: spring="h ttp : //www. springframework. o rg /tag s"
xmlns: form="h ttp : //www.springframework.org/tags/form">
< js p :output o m it-x m l-d eclaratio n "y e s"/>
< js p : d irective.page contentType="text/htm l; charset=UTF-8" />
< h e a d x title > S p iz z a < /title x /h e a d >

<body>
<h2>Customer R egistration</h2>
<form:form commandName="customer">
<input type="hidden" name="_flowExecutionKey"
value="${flowExecutionKey}"/>
<b>Phone number: </bxform :inpu t path="phoneNumber"/xbr/>
<b>Name : </b xform : input path="nam e"/xbr/>
<b>Address: </b><form:input p ath ="ad d ress"/xb r/>
<b>City: </bxform : input p a th = "c ity "/x b r />
<b>State: </bxform : input p a th = "s ta te "/x b r />
<b>Zip Code: </bxform :inpu t path="zipC ode"/xbr/>
<input type="submit" name="_eventld_submit"
value="Submit" />
<input type="submit" name="_eventld_cancel"
value="Cancel" />
</form:form>
</body>
</html>

No es el primer formulario que hemos visto en nuestro flujo. El estado de vista welcome
también mostraba un sencillo formulario al cliente, con un único campo. Era bastante
sencillo obtener el valor de ese campo para los parámetros de la solicitud. El formulario
de registro, por otro lado, parece más complicado.
En lugar de gestionar los campos de uno en uno mediante los parámetros de solicitud,
tiene más sentido vincular el formulario a un objeto Custom er, dejando que el marco de
trabajo realice el trabajo duro.

Comprobar el área de reparto


Una vez que el cliente nos ha dado su dirección, tenemos que asegurarnos de que vive
dentro del área de reparto. Si Spizza no puede llevarle las pizzas, tiene que saberlo para
que pueda venir a recogerlas personalmente.
Para tomar esa decisión, utilizamos un estado de decisión. c h e c k D e l i v e r y A r e a
cuenta con un elemento < i f > que transmite el código postal del cliente al método c h e c k
D e liv e r y A r e a () del bean p iz z a F lo w A c t io n s . Este método va a devolver un valor
Booleano: t r u e si el cliente se encuentra en el área de reparto y f a l s e si no es así.
Si el cliente se encuentra en el área de reparto, el flujo realiza una transición al estado
ad d C u sto m er. De lo contrario, el cliente pasa al estado de vista d e liv e r y W a r n in g . La
vista tras d e liv e r y W a r n in g se encuentra en /WEB - I N F / f l o w s / p i z z a / c u s t o m e r /
d e l iv e r y W a r n in g . j sp x y se m uestra en el listado 8.7.
Trabajar con Spring Web Flow 267

Listado 8.7. Advertimos a un cliente de que la pizza no se puede entregar en su dirección.

<html xmlns: jsp = "h ttp : / / ja v a . sun.com/JSP/Page">


<js p : output om it-xm l-declaration="yes"/>
< js p :d irective.p ag e contentType="text/html;charset=UTF-8" />
< h e a d x title > S p iz z a < /title x /h e a d >
<body>
<h2>Delivery Unavailable</h2>

<p>The address is outside of our delivery area. You may


s t i l l place the order, but you w ill need to pick i t up
y o u rse lf. </p>
< ! [ CDATA[
<a h re f= "$ {flowExecutionUrl} &_eventId=accept">
Continue, I ' l l pick up the order</a>
<a href="${flowExecutionUrl}&_eventId=cancel">Never mind</a>
]]>
</body>
</html>

Los elementos esenciales relacionados con el flujo en d e l i v e r y W a r n i n g . j sp x son


los dos enlaces que ofrecen al cliente una posibilidad de continuar realizando el pedido
o de cancelarlo. Utilizando la misma variable f lo w E x e c u t io n U r l que utilizamos en el
estado w elcom e, estos enlaces van a generar un evento a c c e p t o c a n c e l en el flujo. Si
se envía un evento a c c e p t , el flujo realiza una transición al estado ad d C u sto m er. De lo
contrario, se realizará una transición a la cancelación global y el subflujo pasará al estado
de finalización c a n c e l . Vamos a hablar sobre los estados de finalización en breve. Antes,
echemos un rápido vistazo al estado ad d C u stom er.

Almacenar los datos del cliente


Cuando el flujo llega al estado a d d C u sto m er, el cliente ya ha introducido su dirección.
Para referencias futuras, esa dirección tiene que almacenarse (probablemente en una base de
datos). El estado ad d C ustom er cuenta con un elemento < e v a lu a t e > que invoca el método
ad d C u sto m er () sobre e lb e a n p iz z a F lo w A c tio n s , proporcionando la variable de flujo
c u s to m e r . Una vez que la evaluación finaliza, se produce la transición predeterminada y
el flujo realiza una transición al estado final cuyo ID es cu s to m e rR e a d y .

Finalizar el flujo
Normalmente, un estado de flujo final no es muy interesante. Sin embargo, en este flujo
no hay un único estado final, sino dos. Cuando un subflujo finaliza, activa un evento de
flujo que es equivalente a su ID de estado final. Si el flujo solo tiene un estado final, siempre
va a activar el mismo evento. Sin embargo, con dos o más estados, un flujo puede afectar
a la dirección del flujo de invocación. Cuando el flujo de cliente sigue cualquiera de las
rutas normales, al final siempre se termina en el estado final cuyo ID es c u s to m e rR e a d y .
Cuando el flujo de la pizza continua, recibe un evento c u s to m e rR e a d y , que termina en
una transición al estado b u ild O r d e r .
268 Capítulo 8

El estado final c u s to m e rR e a d y incluye un elemento c o u tp u t >, el equivalente en un


flujo a la instrucción r e t u r n de Java. Devuelve datos desde el subflujo al flujo de invoca­
ción. En este caso, c o u tp u t > devuelve la variable de flujo c u s to m e r , para que el estado
de subflujo i d e n t i f y C u s t o m e r del flujo de pizza pueda asignarla al pedido. Por otro
lado, si se activa un evento c a n c e l en cualquier momento durante el flujo cliente, sale
del flujo a través del estado final con el ID c a n c e l . Esto activa un evento c a n c e l en el
flujo de la pizza y realiza una transición (mediante la transición global) al estado final del
flujo de la pizza.

Crear un pedido
Una vez se identifica el cliente, el siguiente paso en el flujo principal es saber qué pizzas
quiere. En el subflujo de pedidos (véase la figura 8.4) se pide al usuario que cree pizzas y
las añada al pedido.

Inicio

Figura 8.4. Las pizzas se añaden a través del subflujo de pedidos.

Como puede ver, el estado sh ow O rd er es la pieza central del subflujo de pedidos. Es


el primer estado que el usuario ve al acceder al flujo y el que se envía al usuario después
de añadir una pizza al pedido. Muestra el estado actual del pedido y ofrece al usuario la
posibilidad de añadir otra pizza.
Después de añadir una pizza al pedido, el flujo realiza una transición al estado c r e a t e
P iz z a . Es otro estado de vista que proporciona al usuario una selección de tamaños de
pizza e ingredientes entre los que elegir. Desde aquí, el usuario puede añadir una pizza o
cancelarla. En cualquier evento, el flujo realiza una transición al estado show O rder.
Desde este estado, el usuario puede elegir entre enviar el pedido o cancelarlo. Cualquier
elección va a finalizar el subflujo de pedidos. Sin embargo, el flujo principal continuará
por diferentes rutas en función de la opción que se tome.
El listado 8.8 muestra cómo se convierte el diagrama en una definición de Spring Web
Flow.
Trabajar con Spring Web Flow 269

Listado 8.8. Los estados de vista del subflujo de pedidos para mostrar el pedido y crear una pizza.
<?xml v e rsio n = "l. 0"encoding="UTF-8"?>

<flow xmlns="h ttp : //www.springframework. org/schema/webflow"


xmlns:x s i= "h ttp : / /www.w3. org/2001/XMLSchema-instance"
x si -. schemaLocation="http: / /www. springframework.org/schema/webflow
h ttp : //www.springframework.org/schema/webflow/spring-webflow-2 . 3 .xsd" >

cinput name="order"required="true"/> / / Aceptar pedido como entrada.

<view-stateid="showOrder"> / / Estado de representación del pedido.


< tra n sitio n on="createPizza" to ="createP izza"/>
< tra n sitio n on="checkout" to="orderCreated"/>
< tra n sitio n on="cancel" to ="can cel"/>
</view -state>

<view -stateid="createPizza"m odel="flow Scope.pizza"> / / Estado de creación de la pizza.


<on-entry>
<setname="flowScope.pizza"
value="new com .springinaction.pizza. domain.Pizza()" />
<evaluateresult="view Scope. toppingsList"expression=
"T(com .springinaction.pizza.dom ain.Topping).asList()" />
</on-entry>
ctra n sitio n on="addPizza" to="showOrder">
<evaluateexpression="order. addPizza(flowScope.pizza)"/>
< /tran sitio n >
< tran sitio n on="cancel" to="showOrder"/>
</view -state>

<end -stateid ="cancel"/> / / Estado fin a l de cancelación.

<end-stateid="orderCreated"/> / / Estado fin a l de creación de pedidos.

< /flow>

Este subflujo va a trabajar sobre el objeto O rd e r creado en el flujo principal. Por tanto,
necesitamos una forma de transmitir el elemento O rd e r desde el flujo principal al subflujo.
Como recordará del listado 8.1, hemos utilizado el elemento c i n p u t > para transmitir
O rd e r al flujo. En este caso lo estamos utilizando para aceptar ese objeto. Si analiza este
subflujo como si fuera un método en Java, el elemento < inpu t > se utiliza aquí para definir,
de forma efectiva, la firma del subflujo. Este subflujo requiere un parámetro único llamado
o rd er.
A continuación, vemos el estado showOrder, un estado de vista básico con tres transi­
ciones diferentes: una para crear la pizza, otra para enviar el pedido y otra para cancelarlo.
El estado c r e a t e P i z z a es más interesante. Su vista es un formulario que envía un
nuevo objeto P iz z a para añadirse al pedido. El elemento < o n - e n tr y > añade un nuevo
objeto P iz z a al ámbito del flujo para que almacene la información cuando se envíe el
formulario.
Tenga en cuenta que el elemento model de este estado de vista hace referencia al mismo
objeto con ámbito de flujo P iz z a . Este objeto P iz z a va a estar vinculado para crear el
formulario de pizzas, tal y como se muestra en el listado 8.9.
270 Capítulo 8

Listado 8.9. Añadimos pizzas con un formulario HTML vinculado a un objeto con ámbito de flujo.
<div xmlns: form="h ttp : / /www. springframework. org/tags/form "
xmlns: j sp="h ttp : / / ja v a . sun. com/JSP/Page">
<j sp: output om it-xm l-declaration="yes"/>
< jsp :d irectiv e.p ag e contentType="text/htm l; charset=UTF-8" />
<h2>Create Pizza</h2>
<form:form commandName="pizza">
<input type="hidden" name="_flowExecutionKey"
value="${flowExecutionKey}"/>
<b>Size: < /b x b r />
<form:radiobutton path="size"
label="Sm all (12-inch) " value="SMALL"/xbr/>
<form.*radiobutton path="size"
lab el = "Medium (14-inch) " value= "MEDIUM"/xbr/>
<form:radiobutton path="size"
lab el = "Large (16-inch) " value="LARGE"/xbr/>
<form:radiobutton path="size"
label="Ginormous (20-in ch )" value="GINORMOUS"/>
<br/>
<br/>
<b>Toppings: < /b x b r />
<form: checkboxes path="toppings" item s="$ {toppingsList}"
d elim iter="& lt ;br/& gt; " / x b r / x b r / >
<input type="submit" class="button"
name="_eventId_addPizza" valúe="Continué"/>
<input type="submit" class="button"
name="_eventId_cancel" value="Cancel"/>
</form:form>
</div>

Cuando el formulario se envía haciendo clic en el botón Continué (Continuar), las


elecciones de tamaño y de ingredientes se van a vincular al objeto P i z z a y se elegirá la
transición a d d P iz z a . El elemento < e v a lu a te > asociado a esa transición indica que el
objeto P i z z a de ámbito de flujo debe pasarse en una invocación del método addP i z z a ()
del pedido antes de realizar la transición al estado show O rder.
Hay dos formas de finalizar el flujo. El usuario puede hacer clic en el botón Cancel
(Cancelar) de la vista sh ow O rd er, o hacer clic en el botón Checkout (Pago). En cualquier
caso, el flujo realiza una transición hacia < e n d - s t a t e > . Sin embargo, el id d e l estado final
seleccionado determina el evento activado al salir de este flujo y, en definitiva, determina
el paso siguiente en el flujo principal, que realizará una transición hacia c a n c e l o hacia
o r d e r C r e a t e d . En el primer caso, el flujo exterior finaliza. En el segundo, se realiza una
transición hacia el subflujo ta k e P a y m e n t, el cual vamos a ver a continuación.

Gestión de los pagos


Lo habitual es que las pizzas no sean gratis; la pizzería Spizza no duraría mucho si
dejara que sus clientes pudieran pedir pizzas sin pagarlas. A medida que el flujo de pizza
se aproxima a su final, el subflujo final solicita al usuario que introduzca la información
de pago. Este flujo sencillo es el que se muestra en la figura 8.5
Trabajar con Spring Web Flow 271

Inicio

Figura 8.5. El paso final en el proceso de pedir una pizza es aceptar el pago por parte del cliente
mediante el subflujo de pagos.

Al igual que el subflujo de pedidos, el de pagos también acepta un objeto O r d e r como


entrada utilizando el elemento < i n p u t >. Como puede ver, tras acceder al subflujo de pagos,
el usuario llega al estado ta k e P a y m e n t, un estado de vista en el que el usuario puede
optar por pagar con tarjeta de crédito, con cheque o en efectivo. Tras el envío de la infor­
mación de pago, se pasa al estado v e r i f y P a y m e n t , un estado de acción que comprueba
que la información de pago es correcta. El subflujo de pagos se define en el código XML
del listado 8.10.

i.líAáwtl' A M*. B■sj.ihfli.jir'xte/aapns mienta ron un estado de vista v otro de acción.

<?xml version="1.0" encoding="UTF-8"?>


<flow xmlns="http://www.springframework.org/schema/webflow"
xm lns:xsi="h ttp : / /www.w3. org/200l/XMLSchema-instance"
x s i : schemaLocation="h ttp : //www.springframework.org/schema/webflow
h ttp : //www. springframework.org/schema/webflow/spring-webflow-2 .3 -xsd" >
<input name="order" required="true"/>
<view- State id =11takePayment" model=" flowScope. paymentDetails " >
<on-entry>
<set name= ■'flowScope.paymentDetails"
value="new com.springinaction.pizza.domain.PaymentDetails()" />
<evalúate result="viewScope.paymentTypeList" expression=
" T (com. springinaction. pizza, domain. PaymentType) .a s L is tO " />
</on-entry>
< tra n sitio n on="paymentSubmitted" to="verifyPayment" />
< tra n sitio n on="cancel" to="cancel" />
</view -state>
< a ctio n -sta te id="verifyPayment">
<evaluate re s u lt= "order.payment" expression=
"pizzaFlowActions.verifyPayment(flowScope.paymentDetails)" />
< tra n sitio n to="paymentTaken" />
< /a c tio n -sta te >
<end-state id="cancel" />
<end-State id="paymentTaken" />
</flow>
272 Capítulo 8

Cuando el flujo entra en el estado de vista takePayment, el elemento <on-entry>


configura el formulario de pagos utilizando, en primer lugar, una expresión SpEL para
crear una nueva instancia PaymentDetails en el ámbito del flujo. Esto va a actuar como
objeto de apoyo del formulario. También va a configurar la variable paymentTypeList
con ámbito de vista con una lista con los valores de PaymentTypeenum (que se muestra
en el listado 8.11). En este caso, el operador T () de SpEL se utiliza para obtener la clase
Payment, para que así pueda invocarse el método toList ( ) .

Listado 8.11. La enumeración PaymentType define la elección del método de pago del cliente.

package com.springinaction.pizza.domain;
import s t a t ic org.apache.commons.1ang. WordUti1s .* ;
import j a v a .ú til.A rra y s;
import ja v a .u ti1 .L is t ;
public enum PaymentType {
CASH, CHECK, CREDIT_CARD;
public s t a t ic List<PaymentType> asL istO {
PaymentType[] a l l = PaymentType.valúes();
return A r r a y s .a s L is t(a ll);
}■
@Override
public String toStringO {
return capitalizeFully(ñam e 0 . re p la c e ( '_ ', ' '));
}
i
Una vez se le muestra al usuario el formulario de pago, éste puede realizar uno o
cancelar el proceso. En función de la elección, el subflujo de pagos va a terminar en
paymentTaken<end-state> o en cancel<end-state>. Al igual que con los otros
subflujos, cualquiera de los elementos <end-state> va a finalizar el subflujo y devolver
el control al flujo principal. Sin embargo, el id del <en d - state> seleccionado va a deter­
minar la transición que se realice a continuación, en el flujo principal.
Llegados a este punto, hemos recorrido todos los pasos del flujo principal de pizza, así
como sus subflujos. Hemos podido ver todo de lo que es capaz Spring Web Flow y, antes
de terminar, veamos cómo podemos proteger el acceso a un flujo o a uno de sus estados.

Proteger flujos Web


En el siguiente capítulo vamos a ver cómo podemos proteger aplicaciones de Spring
con Spring Security. Sin embargo, ya que estamos hablando de Spring Web Flow, vamos
a ver con rapidez cómo admite la seguridad a nivel de flujo cuando se utiliza de forma
conjunta con Spring Security.
Los estados, las transiciones e incluso los flujos al completo pueden protegerse utili­
zando Spring Web Flow y el elemento < s e c u re d > como subelemento de todos ellos. Por
ejemplo, para proteger el acceso a un estado de vista, podemos utilizar < s e c u re d > de la
siguiente manera:
Trabajar con Spring Web Flow 273

<view-State id = "re stric te d ">


<secured attributes="ROLE_ADMIN" m atch="all"/>
</view -state>

Tal y como está configurado aquí, el estado de vista va a quedar restringido solo
a aquellos usuarios a los que se les otorgue el acceso ROLE_ADMIN (con el atributo
a t t r i b u t e s ) . El atributo a t t r i b u t e s acepta una lista de autoridades, separadas por
comas, que el usuario debe tener para contar con acceso al estado, la transición o el flujo. El
atributo m atch puede configurarse con los valores any o a 11. Si se configura como any, el
usuario debe contar con, al menos, una de las autoridades que aparecen en a t t r i b u t e s ,
mientras que si se configura como a l l , debe contar con todas las autoridades. Puede que
se esté preguntando cómo se le conceden al usuario las autoridades comprobadas por el
elemento < se cu re d > . E, incluso, puede que esté pensando en cómo inicia sesión el usuario
en la aplicación. No se preocupe, lo veremos en el siguiente capítulo.

Resumen
No en todas las aplicaciones el acceso es libre. En algunas se debe guiar al usuario a lo
largo de una serie de pasos, realizar una serie de preguntas y mostrar páginas específicas
en función de sus respuestas. En estas situaciones, una aplicación parece menos un menú
de opciones y más una conversación entre la aplicación y el usuario.
En este capítulo hemos hablado sobre Spring Web Flow, un marco de trabajo Web que
permite el desarrollo de aplicaciones conversacionales. Hemos creado una aplicación
basada en un flujo para aceptar pedidos de pizzas. Para ello, hemos comenzado definiendo
la ruta general que la aplicación debe seguir, comenzando por el sistema de obtención de
información y finalizando con el almacenamiento del pedido en el sistema.
Un flujo está formado por diferentes estados y transiciones que definen la forma en que
la conversación va a pasar de un estado a otro. Los propios estados se encuentran disponi­
bles en diferentes tipos: estados de acción que realizan una lógica de negocio, estados de
vista que implican al usuario en el flujo, estados de decisión que dirigen el flujo de forma
dinámica y estados de finalización que indican el fin de un flujo. Además, hay estados de
subflujo, los cuales se definen mediante un flujo.
Por último, hemos visto de forma breve cómo el acceso a un flujo, estado o transición
puede restringirse a aquellos usuarios con autoridades específicas. Sin embargo, no hemos
mencionado cómo se identifica el usuario en la aplicación y cómo se conceden determinadas
autoridades a un usuario. En este punto es donde Spring Security entra en acción, tal y
como veremos en el siguiente capítulo.
Capítulo
Se gu rid a d
en S p rin g

CONCEPTOS FUNDAMENTALES:

• Presentación de Spring Security.


• Protección de aplicaciones Web con filtros de servlet.
• Autenticación con bases de datos y LDAP.
¿Se ha fijado que en las series de televisión los personajes no suelen cerrar las puertas?
Por ejemplo, en F rien ds, los diferentes protagonistas entran en las casas de los otros sin
avisar y sin ningún tipo de reparo. De hecho, en un capítulo que tenía lugar en Londres,
Ross entraba en la habitación de hotel donde estaba alojado Chandler y casi lo encontraba
en una situación comprometida con su hermana.
En el pasado, no era tan raro encontrar a gente que dejara las puertas sin cerrar. Sin
embargo, en la actualidad, como estamos tan preocupados por la privacidad y la seguridad,
parece una locura ver que los protagonistas de nuestras series favoritas dejen las puertas
de sus casas abiertas.
En la actualidad, la información es uno de los elementos más valiosos con los que
contamos. Por ese motivo, los ladrones buscan formas de robar nuestros datos y de usurpar
nuestra identidad y, para ello, acceden a aplicaciones sin proteger. Como desarrolladores
de software debemos adoptar los pasos necesarios para proteger la información almace­
nada en nuestras aplicaciones. Ya sea una cuenta de correo electrónico protegida con un
nombre de usuario y contraseña o una cuenta para compraventa de acciones protegida
con un número PIN, la seguridad es un aspecto esencial de la mayoría de las aplicaciones.
No crea que estoy utilizando la palabra "aspecto" por casualidad para describir la
seguridad de las aplicaciones. Es una preocupación que va más allá de la funcionalidad
de la aplicación. En su mayor parte, una aplicación no debería desempeñar ningún papel
en la seguridad. Aunque es posible crear código relativo a la seguridad directamente en la
aplicación (y es algo que suele encontrarse), es mejor mantener los aspectos de la seguridad
separados de los de la aplicación.
Si piensa que podemos aplicar nuestras medidas de seguridad utilizando técnicas orien­
tadas a aspectos, está en lo cierto. En este capítulo vamos a aprender formas de proteger
nuestras aplicaciones mediante aspectos. Sin embargo, no vamos a tener que desarrollarlos
por nosotros mismos, sino que en su lugar vamos a utilizar Spring Security, un marco de
trabajo de seguridad implementado con AOP de Spring y filtros de servlet.

Introducción a Spring Security ________


Spring Security es un marco de trabajo de seguridad que proporciona seguridad decla­
rativa para sus aplicaciones basadas en Spring. Constituye una solución de seguridad
integral que gestiona la autenticación y la autorización, tanto a nivel de las solicitudes Web
como a nivel de invocación de métodos. Basándose en el marco de trabajo Spring, Spring
Security saca el máximo partido de la inyección de dependencias (DI) y de las técnicas
orientadas a aspectos.
Spring Security comenzó su andadura con el nombre Acegi Security. Era un marco de
trabajo de seguridad muy potente, aunque tenía una gran desventaja: su configuración
requería gran cantidad de código XML. Basta con decir que la configuración típica de Acegi
requería varios cientos de líneas de código XML.
Con la versión 2.0, Acegi Security se convirtió en Spring Security. Sin embargo, la versión
2.0 trajo algo más que un cambio de nombre. Introdujo un nuevo espacio de nombres XML
específico para seguridad para configurarla en Spring. El nuevo espacio de nombres, junto
276 Capítulo 9

con las anotaciones y una serie de valores predeterminados, permitió reducir la configura­
ción de seguridad típica de cientos a unas pocas líneas de XML. Spring Security 3.0 añadió
SpEL, para simplificar la configuración de seguridad aún más.
Ahora, en su versión 3.2, Spring Security hace frente a la seguridad desde dos ángulos.
Para proteger solicitudes Web y restringir el acceso al nivel de URL, utiliza filtros de
servlet. También puede proteger las invocaciones de métodos utilizando AOP de Spring,
aplicando proxy sobre objetos y aplicando consejos que garanticen que el usuario cuenta
con la autoridad adecuada para invocar métodos protegidos.
En este capítulo nos centraremos en la seguridad en el nivel Web. En un capítulo posterior
veremos cómo usar Spring Security para proteger la invocación de métodos.

Módulos de Spring Security


Con independencia del tipo de aplicación que quiera proteger con Spring Security, lo
primero que tiene que hacer es añadir sus módulos a la ruta de clases de la aplicación.
Spring Security 3.3 se divide en once módulos, recogidos en la tabla 9.1.

Tabla 9.1. Spring Security se divide en once módulos.

ACL Permite el uso de seguridad en objetos de dominio mediante Listas de


control de acceso (ACL).
Aspectos Un pequeño módulo para admitir aspectos basados en AspectJ en lugar
de AOP estándar de Spring a la hora de usar anotaciones de Spring
Security.
Cliente CAS Permite la integración con el Servicio de autenticación central (CAS)
de Jasig.
Configuración Permite configurar Spring Security con XML y Java (la compatibilidad
con Java se ha incluido en Spring Security 3.2).
Núcleo Proporciona la biblioteca esencial de Spring Security.
Criptografía Permite usar encriptación y codificación con contraseñas.
LDAP Permite la autenticación basada en LDAP.
OpenID Permite la autenticación centralizada con OpenID.
Dispositivos remotos Permite la integración con Spring Remoting.
Biblioteca de etiquetas La biblioteca de etiquetas JSP de Spring Security.
Web Proporciona el soporte para la seguridad Web basada en filtros de
Spring Security.

Como mínimo, va a querer incluir los módulos del núcleo y de configuración en la ruta
de clases de su aplicación. Spring Security suele utilizarse para proteger aplicaciones Web
y, sin duda, éste es el caso de la aplicación Spittr, por lo que también vamos a necesitar el
módulo Web. También vamos a sacar partido de la biblioteca de etiquetas JSP de Spring
Security, por lo que tendremos que añadir ese módulo.
Seguridad en Spring 277

Filtrar solicitudes Web


Spring Security utiliza varios filtros de servlet para proporcionar diferentes aspectos de
seguridad. Como puede imaginar, esto va a implicar diferentes declaraciones < f ilter>
en el archivo w eb. xml de su aplicación o quizás en WebApplicationlnitializer. Sin
embargo, gracias a la magia de Spring solo tendremos que configurar uno de estos filtros.
DelegatingFilterProxy es un filtro de servlet especial que, por sí solo, no hace
gran cosa. Delega a una implementación de javax.servlet.Filter registrada como
<bean> en el contexto de la aplicación de Spring, tal y como se muestra en la figura 9.1.

Figura 9.1. Los proxy DelegatingFilterProxy filtran la gestión a un bean de filtro delegado
en el contexto de la aplicación de Spring.

Si prefiere configurar servlet y filtros en un archivo w eb. xml tradicional, puede hacerlo
con el elemento < f i l t e r > de esta forma:
< f ilte r >
<filter-nam e>springSecu rityFilterC hain</filter-nam e>
< f ilte r - c la s s >
o rg . springframework.Web. f i l t e r .D elegatingFilterProxy
< / f i lt e r - c l a s s >
< / f i lt e r >

Aquí, lo más importante es establecer < f ilter-name> en springSecurityFilter


Chain. Pronto configuraremos Spring Security para la seguridad Web y veremos un bean de
filtro con el nombre springSecurityFilterChainenel que DelegatingFilterProxy
tendrá que delegar. Si prefiere configurar DelegatingFilterProxy en Java con
WebApplicationlnitializer, basta con crear una nueva clase que amplíe Abstract
SecurityWebApplicationlnitializer:
package s p i t t e r . config;
import org. springframework. se c u rity .web. co n tex t.
A bstractSecurityW ebA pplicationlnitializer;
public cla ss Secu rityW eblnitializer
extends A bstractSecurityW ebA pplicationlnitializer {}

AbstractSecurityWebApplicationlnitializer implementa WebApplication


Initializer, por lo que será detectable por Spring y se usará para registrar Delegating
FilterProxy con el contenedor Web. Aunque pueda reemplazar sus métodos append
Filters () o insertFilters () para registrar filtros propios, no tendrá que reem­
plazar nada más para registrar DelegatingFilterProxy. Independientemente de que
configure DelegatingFilterProxy en web.xml o creando AbstractSecurityWeb
Applicationlnitializer como subclase, interceptará las solicitudes entrantes en la
aplicación y las delegará al bean con el ID springSecurityFilterChain.
278 Capítulo 9

Respecto al propio bean s p r i n g S e c u r i t y F i l t e r C h a i n , se trata de otro filtro espe­


cial denominado F ilte r C h a in P r o x y . Es un filtro único que conecta entre sí uno o más
filtros adicionales. Spring Security se basa en varios filtros de servlet para proporcionar
diferentes características de seguridad. Sin embargo, casi nunca tendrá que conocer estos
detalles, ya que probablemente no va a tener que declarar de forma explícita el bean s p r in g
S e c u r i t y F i l t e r C h a i n ni ninguno de los filtros conectados, ya que se crearán al habilitar
la seguridad Web. Para empezar, crearemos la configuración de seguridad más sencilla
posible.

Crear una sencilla configuración de seguridad


Cuando Spring Security se llamaba Acegi Security, había que escribir cientos de líneas
de configuración XML para habilitar la seguridad en una aplicación Web. Spring Security
2.0 facilitó el proceso incluyendo un espacio de nombres de configuración XML específico
para la seguridad. Spring 3.2 presentó una nueva opción de configuración de Java que
eliminaba la necesidad de XML. El siguiente código muestra la configuración de Java más
sencilla posible para Spring Security.

Listado 9.1. La clase de configuración más sencilla para habilitar la seguridad Web para Spring MVC.

package spitter.config;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.
conf igurat ion.EnableWebSecurity;
import org.springframework.security.config.annotation.web.
conf igurat ion.WebSecurityConf igurerAdapter

@Configuration
@EnableWebSecurity // Habilitar seguridad Web.
public class SecurityConfig extends WebSecurityConfigurerAdapter {
}
Como su nombre sugiere, la anotación @EnableWebSecurity habilita la segu­
ridad Web. De forma aislada, no sirve de nada. Es necesario configurar Spring Security
en un bean que implemente WebSecurityConf igurer o (por comodidad) amplíe
WebSecurityConf igurerAdapter. Cualquier bean del contexto de la aplicación de
Spring que implemente WebSecurityConf igurer puede contribuir a la configura­
ción de Spring Security, pero suele ser más cómodo que la clase de configuración amplíe
WebSecurityConf igurerAdapter, como sucede en el listado 9.1.
Por lo general, @EnableWebSecurity es muy útil para habilitar la seguridad en
cualquier aplicación Web, pero para desarrollar una aplicación Spring MVC, puede usar
@EnableWebMvcSecurity, como se ilustra a continuación.

Listado 9.2. La clase de configuración más sencilla para habilitar la seguridad Web para Spring MVC.

package spitter.config;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.
Seguridad en Spring 279

configu ration.WebSecurityConf igurerAdapter;


import org. springframework. se c u rity . co n fig . annotation.web. s e r v le t.
configuration.EnableWebMvcSecurity;

eConfiguration
@EnableWebMvcSecurity / / H ab ilitar seguridad de Spring MVC.
public cla ss SecurityConfig extends WebSecurityConfigurerAdapter {
}
Entre otras cosas, la anotación @ E n a b le W e b M v cS e c u rity configura un solucionador
de argumentos de Spring MVC para que los métodos de controlador puedan recibir el
principal (o nombre de usuario) del usuario autenticado a través de parámetros anotados
con @ A u t h e n t i c a t i o n P r i n c i p a l . También configura un bean que añade automáti­
camente un campo de token CSRF (C ross-site request fo rg ery , Falsificación de petición en
sitios cruzados) a los formularios por medio de la biblioteca de etiquetas de vinculación
de formularios de Spring. Puede no parecerle demasiado, pero la clase de configuración
de seguridad de los dos ejemplos anteriores es muy potente. En ambos casos se bloquea la
aplicación para que nadie pueda acceder a la misma.
Aunque no sea necesario, probablemente le interese especificar puntos de seguridad Web
más precisos reemplazando uno o varios métodos de W e b S e cu rity C o n f ig u r e r A d a p te r .
Puede configurar la seguridad Web si reemplaza tres de sus métodos c o n f i g u r e () y
establece el comportamiento en el parámetro que se pasa. En la tabla 9.2 se describen estos
tres métodos.

Tabla 9.2. Reemplazos de los métodos configure!) de WebSecurityConfigurerAdapter.

s Método Descripción
............... ........ ..
É_.
configure(WebSecurity) Se reemplaza para configurarla secuen­
cia de filtros de Spring Security.
configure (HttpSecurity) Se reemplaza para configurar la protec­
ción de las solicitudes por parte de los
interceptores.
configure (AuthenticationManagerBuilder) Se reemplaza para configurar servicios
de detalles de usuarios.

Si se fija en el listado 9.2, verá que no se reemplaza ninguno de estos tres métodos
c o n f i g u r e ( ) , de ahí que la seguridad de la aplicación sea tan robusta. Aunque la cadena
de filtros predeterminada es correcta para nuestras necesidades, el aspecto predeterminado
de c o n f i g u r e ( H t t p S e c u r i t y ) es el siguiente:
protected void conf igure (HttpSecurity http) throws Exception {
http
. authorizeRequests()
. anyRequest( ) . au thenticated ()
. and()
. formLogin( ) . and()
.h ttp B a sic();
}
280 Capítulo 9

Esta sencilla configuración predeterminada especifica cómo deben protegerse las soli­
citudes HTTP y qué opciones tiene un cliente para autenticar al usuario. La invocación de
a u t h o r i z e R e q u e s t s () y a n y R e q u e s t () . a u t h e n t i c a t e d () exige que se autentiquen
todas las solicitudes HTTP que lleguen a la aplicación. También configura Spring Security
para admitir la autenticación a través de un inicio de sesión basado en formularios (desde
una página de inicio predefinida) además de HTTP básico.
Mientras tanto, como no hemos reemplazado el método c o n f i g u r e (A u th e n t i c a t io n
M a n a g e r B u ild e r ) , no hay un almacén de usuarios que respalde el proceso de autenti­
cación. Por ello, no hay usuarios, por lo que todas las solicitudes requieren autenticación
aunque no haya nadie para iniciar sesión. Necesitará añadir cierta configuración para
adecuar Spring Security a las necesidades de su aplicación. En concreto, tendrá que:

• Configurar un almacén de usuarios.


• Especificar qué solicitudes requieren autenticación y cuáles no, así como las autori­
dades que requieren.
• Proporcionar una pantalla de inicio de sesión personalizada para sustituir a la
predeterminada.

Además de estas facetas de Spring Security, puede que también desee representar selecti­
vamente determinado contenido en sus vistas Web en función de limitaciones de seguridad.
Para empezar, configuraremos servicios de usuario para acceder a datos de usuario
durante el proceso de autenticación.

Seleccionar servicios de detalles de usuario


Imagine que sale a cenar a un restaurante de lujo, Evidentemente, ha hecho la reserva
con varias semanas de antelación para asegurarse de tener mesa. Al entrar en el restau­
rante, se identifica pero su reserva no aparece por ningún lado. Esa noche especial corre
peligro. Como no se rinde fácilmente, pide que vuelvan a comprobar el libro de reservas,
y ahí se empieza a complicar todo. Le responden que no hay libro de reservas. Su nombre
no aparece en el libro, ni el de nadie más, porque no existe, lo que explicaría por qué no le
dejan entrar aunque el local esté vacío. Semanas después, descubre que es el motivo por
el que el restaurante ha cerrado y han abierto un kebab.
Es lo mismo que le sucede actualmente a su aplicación. No hay forma de acceder a la
misma porque aunque el usuario crea que debe poder acceder, no existe un registro de su
acceso a la misma. Al carecer de un repositorio de usuarios, la aplicación es tan exclusiva
que es totalmente inútil.
Necesita un repositorio de usuarios en el que se guarden nombres de usuarios, contra­
señas y otros datos, y desde el que se recuperen al tomar decisiones de autenticación.
Afortunadamente, Spring Security es extremadamente flexible y capaz de autenticar
usuarios sobre prácticamente cualquier almacén o repositorio de datos. Existen diversas
soluciones, como repositorios en memoria, bases de datos relaciones y LDAP, pero también
puede crear y conectar implementaciones personalizadas.
Seguridad en Spring 281

La configuración Java de Spring Security facilita la configuración de una o varias solu­


ciones de repositorio de datos. Comenzaremos por la más sencilla, la que mantiene su
repositorio de usuarios en memoria.

Trabajar con un repositorio de usuarios en memoria


Como nuestra clase de configuración de seguridad amplía W e b S e c u r ity C o n f i g u r e r
A d a p te r , la forma más sencilla de configurar un repositorio de usuarios consiste en
reemplazar el método c o n f i g u r e () que acepta A u t h e n t ic a t io n M a n a g e r B u il d e r
como parámetro. A u t h e n t ic a t io n M a n a g e r B u il d e r cuenta con varios métodos que se
pueden usar para configurar la compatibilidad con autenticación de Spring Security. Con
in M e m o r y A u th e n tic a tio n () puede habilitar, configurar y opcionalmente completar
un repositorio de usuarios en memoria.
Por ejemplo, en el siguiente código, S e c u r i t y C o n f i g reemplaza c o n f i g u r e () para
configurar un repositorio de usuarios en memoria con dos usuarios.

Listado 9.3. Configuración de Spring Security para usar un repositorio de usuarios en memoria.

package s p i t t e r . config;

import o rg .sp rin gframework.beans. factory.annotation.Autow ired;


import org. springframework. co n tex t. annotation. Configuration;
import org.springframework. se c u rity . co n fig . annotation.
authentication.builders.A uthenticationM anagerBuilder;
import org.springfram ework.security. config.annotation.web.
configuration.WebSecurityConfigurerAdapter;
import org. springframework. se c u rity . config.annotation.w eb. s e r v le t.
configuration. EnableWebMvcSecurity;

©Configuration
@EnableWebMvcSecurity
public cla ss SecurityConfig extends WebSecurityConfigurerAdapter {

©Override
protected void configure(AuthenticationManagerBuilder auth)
throws Exception {
auth
. inMemoryAuthentication( ) / / H ab ilitar un rep o sitorio de usuarios en memoria.

.withUser( "u ser") .password("password") . r o le s ("USER") .and()


.withUser("admin") .password("password") . r o le s ("USER", "ADMIN");
}
}
Como puede apreciar, el A u th e n t ic a t io n M a n a g e r B u ild e r asignado a c o n f ig u r e ( )
utiliza una interfaz de tipo generador para crear una configuración de autenticación. La
simple invocación de in M e m o r y A u th e n tic a tio n ( ) habilita un repositorio de usuarios
en memoria, pero también necesitará usuarios o el repositorio no servirá de nada.
282 Capítulo 9

Por lo tanto, tendrá que invocar el método w i t h ü s e r () para añadir un nuevo usuario
al repositorio de usuarios en memoria. El parámetro asignado es el nombre de usuario,
w i t h ü s e r () devuelve U s e r D e t a i l s M a n a g e r C o n f i g u r e r .U s e r D e t a i l s B u i l d e r ,
que cuenta con varios métodos para la configuración del usuario, como p a s s w o r d f)
para establecer la contraseña del usuario o r o l e s () para asignar al usuario una o varias
autoridades.
En el listado anterior se añaden dos usuarios, u s e r y admin, ambos con la contraseña
passw ord . El usuario u s e r tiene la función USER, mientras que admin tiene las funciones
USER y ADMIN. Como puede comprobar, se usa el método and () para combinar varias
configuraciones de usuario.
Además de p a ss w o rd ( ) , r o l e s () y and ( ) , existen otros métodos para configurar
detalles de usuario en repositorios de usuarios en memoria. En la tabla 9.3 se resumen todos
los métodos disponibles de U s e rD e ta ils M a n a g e rC o n f i g u r e r .U s e r D e t a i l s B u i l d e r .

Tabla 9.3. Métodos para configurar detalles de usuarios.

Descripción

accountExpired(boolean) Define si la cuenta ha caducado o no.


accountLocked(boolean) Define si la cuenta está bloqueada o no.
and () Se utiliza para encadenar configuraciones.
authorities(GrantedAuthority...) Especifica una o varias autoridades que conceder
al usuario.
authorities (List<? extends Especifica una o varias autoridades que conceder
GrantedAuthority>) al usuario.
authorities(String...) Especifica una o varias autoridades que conceder
al usuario.
credentialsExpired(boolean) Define si las credenciales han caducado o no.
disabled(boolean) Define si la cuenta se ha deshabilitado o no.
password(String) Especifica la contraseña del usuario.
roles(String...) Especifica una o varias funciones que asignar al
usuario.

El método r o l e s () es un acceso directo del método a u t h o r i t i e s () . Los valores


asignados a r o l e s () tienen el prefijo ROLE_ y se asignan como autoridades al usuario.
De hecho, la siguiente configuración de usuario equivale a la del listado 9.3:
auth
. inMemoryAuthentication{)
.w ithüser("u ser") .password("password")
. au th o ritie s ("ROLE__USER") . and ()
.w ithüser( "admin") .password("password")
. a u th o r itie s ("ROLE_USER", "ROLE_ADMIN") ;
Seguridad en Spring 283

Aunque un repositorio de usuarios en memoria es muy útil para tareas de depuración


y pruebas de desarrollo, probablemente no sea la opción más acertada para una aplicación
de producción. En ese caso, es más recomendable mantener datos de usuarios en algún
tipo de base de datos.

Autenticar sobre tablas de base de datos


Es muy habitual que los datos de usuarios se almacenen en una base de datos relacional
a la que se accede a través de JDBC. Para configurar Spring Security para la autentica­
ción sobre un repositorio de usuarios respaldado por JDBC puede usar el método j d b c
A u t h e n t i c a t i o n () . La configuración mínima requerida es la siguiente:

@Autowired
DataSource dataSource;

@Override
protected void configure(AuthenticationManagerBuilder auth)
throws Exception {
auth
. j dbcAuthentication()
. dataSource(dataSource);
}
Lo único que debe configurar es un DataSource que pueda acceder a la base de datos
relacional. En este caso se proporciona a través de la conexión automática.

Reemplazar fas consultas de usuario predeterminadas


Aunque esta configuración mínima funciona, asume ciertos detalles sobre su esquema
de base de datos. Espera que existan determinadas tablas para guardar datos de usuarios.
En concreto, el siguiente fragmento de código de Spring Security muestra las consultas
SQL que se ejecutarán al buscar detalles de usuarios:
public s t a t ic fin a l String DEF_USERS_BY_USERNAME_QÜERY =
"s e le c t usernamepassword,enabled " +
"from users " +
"where username = ? ";
public S ta tic f in a l String DEF_AUTHORITTES_BY_USERNAME__QUERY =
"s e le c t username,authority " +
"from au th o ritie s " +
"where username = ? ";
public s t a t ic fin a l String DEF_GROUP_AUTHORITIES_BY_USERNAME_QUERY =
"s e le c t g .id , g .group_name, ga.au thority " +
"from groups g, group_members gm, group_authorities ga " +
"where gm.username = ? " +
"and g .id = ga.group_id " +
"and g .id = gm. group_id";

La primera consulta recupera el nombre del usuario, su contraseña y si está o no habi­


litado, información que se usa para autenticar al usuario. La siguiente consulta busca las
autoridades concedidas al usuario para su autorización y la última busca las autoridades
284 Capítulo 9

concedidas al usuario como miembro de un grupo. Si quiere definir y completar tablas de


la base de datos para satisfacer estas consultas, no tendrá mucho más que hacer, pero es
muy probable que su base de datos no sea parecida y que necesite más control sobre las
consultas. En ese caso, puede configurar sus propias consultas de esta forma:
@Override
protected void configure(AuthenticationManagerBuilder auth)
throws Exception {
auth
. j dbcAuthentication()
.dataSource(dataSource)
.usersByUsernameQuery(
"s e le c t username, password, true " +
"from S p itte r where username^?")
. authoritiesByUsernameQuery(
"s e le c t username, 'ROLE_USER' from S p itte r where username=?" ) ;

En este caso, solamente reemplazamos las consultas de autenticación y de autorización


básica, pero también puede reemplazar las de autoridades de grupos si invoca group
A u th o ritie s B y U s e rn a m e () con una consulta personalizada.
Al reemplazar las consultas SQL predeterminada por las suyas propias, es importante
ajustarse al contrato básico de las mismas. Todas aceptan el nombre de usuario como único
parámetro. La consulta de autenticación selecciona el nombre de usuario, la contraseña y el
estado. La de autoridades selecciona cero o más filas que contengan el nombre de usuario
y una autoridad asignada, y la de autoridades de grupo selecciona cero o más filas que
tengan un ID de grupo, un nombre de grupo y una autoridad.

Trabajar con contraseñas codificadas


Si se fija en la consulta de autenticación, comprobará que se espera que las contraseñas
de usuario se almacenen en la base de datos. El único problema es que si se almacenan en
texto sencillo estarán expuestas a cualquier hacker, pero si las codifica la autenticación falla
ya que no coincidirán con la contraseña en texto sencillo remitida por el usuario.
Para solucionar este problema tendrá que especificar un codificador de contraseñas
mediante la invocación del método p assw ord E n cod er ():
@Override
protected void configure(AuthenticationManagerBuilder auth)
throws Exception {
auth
. j dbcAuthentication()
.dataSource(dataSource)
.usersByUsernameQuery(
"s e le c t username, password, true " +
"from S p itte r where username=?")
. authoritiesByUsernameQuery(
"s e le c t username, 'ROLE_USER' from S p itte r where username=?")
.passwordEncoder(new StandardPasswordEncoder("5 3 c r3 t") ) ;
Seguridad en Spring 285

El método p a s s w o r d E n c o d e r acepta cualquier implementación de la interfaz


P a ssw o rd E n co d er de Spring Security. El módulo de criptografía de Spring Security incluye
tres de estas implementaciones: B C ry p tP a s s w o rd E n c o d e r , N oO p P assw ord E n cod er y
S ta n d a r d P a s s w o r d E n c o d e r .
En el código anterior se usa S ta n d a rd P a s s w o rd E n c o d e r pero siempre puede propor­
cionar su propia im plem entación. La interfaz P a s s w o rd E n c o d e r es muy sencilla:

public in te rfa c e PasswordEncoder {


String encode(CharSequence rawPassword);
boolean matches(CharSequence rawPassword, Strin g encodedPassword);
}
Independientemente del codificador de contraseñas que utilice, recuerde que nunca
se descodifica la contraseña en la base de datos, sino que la contraseña introducida por el
usuario al iniciar sesión se codifica con el mismo algoritmo y después se compara con la
almacenada en la base de datos. Esta comparación se realiza en el método m a tc h e s () de
P a ssw o rd E n c o d e r.
Las bases de datos relaciónales son una de las opciones de almacenamiento para datos
de usuario. Otra alternativa muy habitual es utilizar un repositorio LDAP.

Aplicar autenticación basada en LDAP


Para configurar Spring Security para autenticación basada en LDAP, puede usar el
método I d a p A u t h e n t i c a t i o n ( ) , el equivalente LDAP a j d b c A u t h e n t i c a t i o n (). El
siguiente método c o n f i g u r e () muestra una sencilla configuración para autenticación
LDAP:
@Override
protected void configure(AuthenticationManagerBuilder auth)
throws Exception {
auth
. IdapAuthentication()
.u serS e a rch F ilte r( " (uid={0 })")
.groupSearchFilter("member={ 0 } " ) ;

Los métodos u s e r S e a r c h F i l t e r () y g r o u p S e a r c h F i l t e r () se utilizan para


proporcionar un filtro para las consultas LDAP básicas, que se utilizan para buscar usuarios
y grupos. De forma predeterminada, las consultas de ambos están vacías, lo que indica que
la búsqueda se realizará desde el nivel raíz de la jerarquía LDAP. Sin embargo, podemos
cambiarlo con una consulta básica:
@Override
protected void configure(AuthenticationManagerBuilder auth)
throws Exception {
auth
. IdapAuthentication()
.userSearchBase("ou=people")
286 Capítulo 9

.u serS ea rch F ilter( " (uid={0}) ")


.groupSearchBase("ou=groups")
.groupSearchFilter ( ,,member={o} " ) ;

El método u s e r S e a r c h B a s e ( ) proporciona una consulta base para encontrar usua­


rios. De forma similar, g ro u p S e a rc h B a s e ( ) especifica la consulta base para encontrar
grupos. En lugar de buscar desde el nivel raíz, hemos especificado que los usuarios se
busquen para la unidad de organización p e o p le . Asimismo, los grupos se van a buscar
para la unidad organizativa grou p s.

Configurar fa comparación de contraseñas


La estrategia predeterminada para llevar a cabo la autenticación con LDAP es ejecutar
una operación de vinculación, autenticando al usuario directamente en el servidor LDAP.
Otra opción es llevar a cabo una operación de comparación, que implica enviar la contraseña
proporcionada al directorio LDAP y solicitar al servidor que compare la contraseña con la de
un atributo de contraseña del usuario. Como la comparación se realiza en el servidor LDAP,
la contraseña no se muestra y permanece secreta. Si prefiere llevar a cabo la autenticación
mediante comparación de contraseñas, puede hacerlo con el método passwordCompare ():
@Override
protected void configure(AuthenticationManagerBuilder auth)
throws Exception {
auth
. IdapAuthentication()
.userSearchBase("ou=people")
.u serSearch F ilter (11(uid={o}) M)
.groupSearchBase("ou=groups")
. groupSearchFilter ( ,,member= {0} " )
.passwordCompare();
)
De forma predeterminada, la contraseña proporcionada en el formulario de inicio de
sesión se va a comparar con el valor del atributo u se rP a ssw o rd en la entrada LDAP del
usuario. Si la contraseña se almacena en un atributo diferente, especifique el nombre del
atributo de contraseña con passw ordA t t r i b u t e ():
@Override
protected void configure(AuthenticationManagerBuilder auth)
throws Exception {
auth
. IdapAuthentication()
.userSearchBase("ou=people")
.u serS e a rch F ilte r( " (uid={0 })")
.groupSearchBase("ou=groups")
.groupSearchFilter("members{0 }")
.passwordCompare()
.passwordEncoder(new Md5PasswordEncoder())
.passwordAttribute("passcode" ) ;
Seguridad en Spring 287

En este ejemplo, especificamos que el atributo " p a s s c o d e " es lo que se compara con
el atributo especificado. Es más, también se especifica un codificador de contraseñas. Es
perfecto que la contraseña actual se mantenga secreta en el servidor al llevar a cabo opera­
ciones de comparación de contraseñas en éste. Sin embargo, la contraseña proporcionada
aún se transmite a través de la conexión al servidor LDAP y podría ser interceptada por un
atacante. Para evitarlo, puede especificar una estrategia de cifrado mediante la invocación
del método p a s s w o rd E n c o d e r ( ) .
En el ejemplo, hemos cifrado las contraseñas utilizando MD5, lo que asume que en el
servidor LDAP también se han codificado con MD5.

Hacer referencia a un servidor LDAP remoto


El único aspecto que no hemos abordado hasta ahora es la ubicación del servidor LDAP
y de los datos. Hemos configurado Spring sin problemas para que lleve a cabo la autenti­
cación mediante un servidor LDAP pero ¿dónde se encuentra?
De forma predeterminada, la autenticación LDAP de Spring Security asume que el
servidor LDAP escucha en el puerto 33389 de l o c a l h o s t . Sin embargo, si su servidor
LDAP se encuentra en otro equipo, puede utilizar el método c o n te x tS o u r c e ( ) para
configurar esa ubicación:
@Override
protected void configure(AuthenticationManagerBuilder auth)
throws Exception {
auth
. IdapAuthentication()
.userSearchBase("ou^people")
.u serS e a rch F ilte r( " (uid={0 })")
. groupsearchBase("ou=groups")
.groupSearchFilter("member={0 }")
. contextSource()
. u r l ("ldap: //habuma.com:389/dc=habuma,dc=com") ;

El m étodo c o n t e x t S o u r c e () devuelve un C o n te x t S o u r c e B u i l d e r que, entre otras


cosas, ofrece el método u r 1 () para que podamos especificar la ubicación del servidor LDAP.

Configurar un servidor LDAP integrado


Si no cuenta con un servidor LDAP para llevar a cabo la autenticación, Spring Security
puede proporcionarle uno integrado. En lugar de establecer la URL en un servidor LDAP
remoto, puede especificar el sufijo de raíz para el servidor integrado a través del método
r o o t () :
@Override
protected void configure(AuthenticationManagerBuilder auth)
throws Exception {
auth
. IdapAuthentication()
.userSearchBase("ou=people")
288 Capítulo 9

. u serS e a rch F ilte r( " (uid={ O})" )


. groupSearchBase("ou=groups")
. groupSearchFilter("member={0 }")
. contextSource()
. r o o t("dc=habuma,dc=com") ;

Cuando el servidor se inicia, intenta cargar datos desde cualquier archivo LDIF que
pueda encontrar en la ruta de clases. LDIF (LD A P D ata Interchange Form at, Formato de inter­
cambio de datos LDAP) es una forma estándar de representar datos LDAP en un archivo
de texto sin formato. Cada registro está formado por una o más líneas y cada una contiene
un par n om bre: v a l o r . Los registros están separados entre sí mediante líneas en blanco.
Si prefiere que Spring no busque todos los archivos LDIF en su ruta de clases, puede
indicarle cuáles desea cargar si invoca el método l d í f ():
@Override
protected void configure(AuthenticationManagerBuilder auth)
throws Exception {
auth
. IdapAuthenticatión()
.userSearchBase("ou=people")
.u serS e a rch F ilte r( " (uid={0 })")
.groupSearchBase("ou=groups")
. groupSearchFilter("member={0} ")
. contextSource()
. r o o t( "dc=habuma,dc=com")
. I d i f ("cla ssp a th :u sers. I d i f " ) ;
}

Aquí solicitamos de forma específica al servidor LDAP que cargue su contenido desde
la raíz de la ruta de clases. Si tiene curiosidad, puede usar el siguiente archivo LDIF para
cargar el servidor LDAP integrado con datos de usuario:
dn: ou=groups,dc=habuma,dc=com
o b je c tc la s s : top
o b je c tc la s s : organizationalUnit
ou: groups
dn: ou=people, dc=habuma,dc=com
o b je c tc la s s : top
o b je c tc la s s : organizationalUnit
ou: people
dn: uid=habuma, ou=people, dc=habuma, dc=com
o b je c tc la s s : top
o b je c tc la s s : person
o b je c tc la s s : organizationalPerson
o b je c tc la s s : inetOrgPerson
en: Craig Walls
sn: Walls
uid: habuma
userPassword: password
dn: uid=jsmith,ou=people,dc=habuma,dc=com
o b je c tc la s s : top
o b je c tc la s s : person
o b je c tc la s s : organizationalPerson
Seguridad en Spring 289

o b je c tc la s s : inetOrgPerson
en: John Smith
sn: Smith
uid: jsm ith
userPassword: password
dn: cn=spittr,ou=groups,dc=habuma,dc=com
o b je c tc la s s : top
obj e c tc la s s : groupOfÑames
en: s p itt r
member: uid-habuma, ou=people, dc=habuma, dc=com

Los repositorios de usuarios de Spring Security son muy útiles y sirven para la mayoría
de los casos, pero si su aplicación necesita algo distinto, puede que tenga que crear y confi­
gurar un servicio de detalles de usuario personalizado.

Configurar un servicio de us so la iza lo


Imagine que tiene que autenticar usuarios sobre una base de datos no relacional como
Mongo o Neo4j. En ese caso, tendría que incorporar una implementación personalizada
de la interfaz U s e r D e t a i l s S e r v i c e , que es muy sencilla:
public in te rfa ce U serD etailsService {
UserDetails loadUserByUsername(String username)
throws UsernameNotFoundExcept ion ;
i
Basta con implementar el método lo a d U se rB y U se rn a m e () para buscar un usuario
con el nombre de usuario proporcionado. Tras ello, lo a d U se rB y U se rn a m e () devuelve
un objeto U s e r D e t a i l s que representa al usuario indicado. El siguiente código muestra
una implementación de U s e r D e t a i l s S e r v i c e que busca a un usuario a partir de la
implementación proporcionada de S p i t t e r R e p o s i t o r y .

Listado 9.4. Recuperación de un objeto UserDetails a partir de SpitterRepository.

package s p i t t r . secu rity ;


import org. springframework. se c u rity . co re.GrantedAuthority;
import org. springframework. se c u rity . co re. au th o rity . SimpleGrantedAuthority;
import org.springfram ew ork.security. co re.u serd eta ils.U ser;
import org. springframework. se c u rity . co re.u s e rd e ta ils .U serD etails;
import org. springframework. se c u rity . co re.u s e rd e ta ils .U serD etailsService;
import org. springframework. se c u rity . c o re .u se rd e ta ils .UsernameNotFoundException;
import s p i t t r . S p itte r;
import s p i t t r . d ata. SpitterR epository;

public cla ss SpitterU serService implements U serD etailsService {

private fin a l SpitterR epository SpitterR epository;


public SpitterU serService(SpitterR ep ository SpitterRepository) {
/ / Inyectar SpitterR epository.
t h i s . SpitterR epository = SpitterR epository;
}
290 Capítulo 9

(©Override
public UserDetails loadUserByUsername(String username)
throws UsernameNotFoundException {
S p itte r s p itte r = sp itterR ep o sito ry . findByUsername(username); / / Buscar S p itte r.
i f (s p itte r 1= null) {
List<GrantedAuthority> au th o ritie s =
new ArrayList<GrantedAuthority>(); / / Crear l i s t a de autoridades,
a u th o ritie s . add(new SimpleGrantedAuthority("ROLE_SPITTER") ) ;

return new User( / / Devolver un usuario.


spitter.getU sernam e(),
spitter.getPassw ord (),
a u th o r itie s );
}
throw new UsernameNotFoundException(
"User '" + username + "' not fou nd .");
}
}
Lo más interesante de S p i t t e r ü s e r S e r v i c e es que desconoce cómo se guardan los
datos de usuario. S p i t t e r R e p o s i t o r y podría buscar S p i t t e r en una base de datos rela-
cional, en una de documentos, de gráficos o podría inventárselo. S p i t t e r ü s e r S e r v i c e
no conoce el alm acén de datos subyacente; simplemente recupera el objeto S p i t t e r y lo
usa para crear un objeto U s e r (el cual es una im plem entación concreta de U s e r D e t a i l s ) .
Para usar S p i t t e r ü s e r S e r v i c e para autenticar usuarios, puede configurarlo en su
configuración de seguridad con el método u s e r D e t a i l s S e r v i c e ( ) :
@Autowired
SpitterR epository sp itterR epository;

@0verride
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(new S p itterü serS erv ice(sp itterR ep o sito ry )) ;
i
El método u s e r D e t a i l s S e r v i c e ( ) , como j d b c A u t h e n t i c a t i o n ( ) , ld a p
A u t h e n t i c a t i o n y in M e m o r y A u th e n tic a tio n ( ), configura un repositorio de confi­
guración, pero en lugar de usar uno de los repositorios de usuarios proporcionados por
Spring, acepta cualquier implementación de U s e r D e t a i l s S e r v i c e .
Otra opción que podría usar consiste en cambiar S p i t t e r para que implemente
U s e r D e t a i l s S e r v i c e . De ese modo, podría devolver S p i t t e r directamente desde el
método lo a d U se rB y U s e rn a m e () sin copiar sus valores a un objeto U se r.

Interceptar solicitudes
En el apartado anterior vimos una configuración muy sencilla de Spring Security y
aprendimos que recurre a una configuración predeterminada cuando todas las solicitudes
requieren autenticación. Hay quien dirá que el exceso de seguridad es mejor que la falta de
Seguridad en Spring 291

seguridad, pero también se puede opinar sobre la aplicación de la cantidad de seguridad


adecuada. En una aplicación, no todas las solicitudes deben protegerse de la misma forma.
Algunas necesitarán autenticación y otras no. Algunas solo estarán disponibles para usuarios
con determinadas autoridades y no disponibles para los que no tengan dichas autoridades.
Fíjese en las solicitudes de la aplicación Spittr. Evidentemente, la página de inicio es
pública y no es necesario protegerla. Del mismo modo, como todos los objetos S p i t t l e
son esencialmente públicos, la página que los muestra no necesita seguridad. Sin embargo,
las solicitudes que crean un objeto S p i t t l e , solo debe realizarlas un usuario autenticado.
Igualmente, aunque las páginas de perfil de usuario sean públicas y no requieran autenti­
cación, si tuviera que procesar una solicitud de / s p i t t e r s / m e para mostrar el perfil del
usuario actual, necesitaría autenticación para saber qué perfil mostrar.
La clave para definir la seguridad en cada solicitud consiste en reemplazar el método
c o n f ig u r e ( H t t p S e c u r i t y ). En el siguiente fragmento de código puede ver cómo
reemplazarlo para aplicar seguridad selectivamente a distintas rutas de URL.

@Override
protected void configure(H ttpSecurity http) throws Exception {
http
. authorizeRequests()
. antMatchers( "/sp itte rs/m e ") . au thenticated ()
. antMatchers(HttpMethod.POST, " / s p i t t l e s " ) . au thenticated ()
. anyRequest( ) . perm itA ll();
i

El objeto H t t p S e c u r i t y proporcionado a c o n f i g u r e () se puede usar para configurar


distintos aspectos de seguridad HTTR En este caso se invoca a u t h o r i z e R e q u e s t s ()
y después se invocan métodos en el objeto que devuelve para indicar que queremos
configurar detalles de seguridad en el nivel de las solicitudes. La primera invocación de
a n t M a t c h e r s () especifica que deben autenticarse las solicitudes cuya ruta sea
/ s p i t t e r s / m e . La segunda invocación es incluso más específica e indica que se auten­
tique cualquier solicitud POST de HTTP a / s p i t t l e s . Por último, la invocación de
a n y R e q u e s t s () afirma que deben permitirse las demás solicitudes sin necesidad de
autenticación ni autoridades.
La ruta asignada a a n tM a tc h e r s () admite comodines de tipo Ant. Aunque no lo
hagamos aquí, podríamos especificar una ruta con un comodín de esta forma:

.a n tM a tc h e rs ("/s p itte r s /**"). au thenticated ();

También podría especificar varias rutas en una misma invocación de a n tM a t c h e r s ( ) :

. antMatchers(" / s p i t t e r s / * * " , " /s p ittle s /m in e ") . au th en ticated ();

Mientras que el método a n tM a t c h e r s () funciona con rutas que pueden incluir como­
dines de tipo Ant, también existe un método r e g e x M a t c h e r s () que acepta expresiones
regulares para definir rutas de solicitud. Por ejemplo, el siguiente fragmento usa una
expresión regular que equivale a / s p i t t e r s / * * (de tipo Ant):

. regexMatchers(" / s p i t t e r s / . * " ) . au thenticated ();


292 Capítulo 9

Además de seleccionar rutas, también hemos usado a u t h e n t i c a t e d () y p e rm it


A l l () para definir cómo proteger las rutas. El método a u t h e n t ic a t e d () exige que el
usuario haya iniciado sesión en la aplicación para realizar la solicitud. Si el usuario no está
autenticado, los filtros de Spring Security capturarán la solicitud y redirigirán al usuario
a la página de inicio de sesión de la aplicación. Mientras tanto, el método p e rm it A l l ()
permite solicitudes sin exigencias de seguridad.
Además de a u t h e n t i c a t e d ( ) y p e r m i t A ll () puede usar otros métodos para definir
la forma de proteger la solicitud. En la tabla 9.4 se describen todas las opciones disponibles.

Tabla 9.4. Métodos de configuración para definir cómo proteger una ruta.

Metodo Funcionamiento

access(String) Permite el acceso si la expresión SpEL proporcionada evalúa


a true.
anonymous() Permite el acceso a usuarios anónimos.
authenticated() Permite el acceso a usuarios autenticados.
denyAl1 () Impide el acceso sin condiciones.
fullyAuthenticated() Permite el acceso si el usuario está totalmente autenticado
(no se recuerda de antes).
hasAnyAuthority(String... ) Permite el acceso si el usuario tiene alguna de las autoridades
indicadas.
hasAnyRole(String...) Permite el acceso si el usuario tiene alguna de las funciones
indicadas.
hasAuthority(String) Permite el acceso si el usuario tiene la autoridad indicada.
hasIpAddress(String) Permite el acceso si la solicitud proviene de la dirección IP
indicada.
hasRole(String) Permite el acceso si el usuario tiene la función indicada.
not () Niega el efecto de cualquiera de los demás métodos de
acceso.
permitAll() Permite el acceso sin condiciones.
rememberMe() Permite el acceso a todos los usuarios autenticados tras
recordar un acceso anterior.

Con los métodos de la tabla 9.4 puede configurar la seguridad y exigir algo más que
un usuario autenticado. Por ejemplo, podría cambiar el método c o n f ig u r e () anterior
para exigir no solo la autenticación del usuario sino que también tenga la autoridad
ROLE_SPITTER:

(©Override
protected void config u re(HttpSecurity http) throws Exception {
http
Seguridad en Spring 293

. authorizeRequests()
.antM atchers("/sp itte rs/m e ") . hasAuthority("ROLE_SPITTER")
. antMatchers(HttpMethod.POST, " / s p i t t l e s " )
.hasAuthority("ROLE_SPITTER")
. anyRequest( ) . perm itA ll();
}

Opcionalmente puede usar el método h as R o le () para aplicar automáticamente el


prefijo ROLE_:
@Override
protected void configure(HttpSecurity http) throws Exception {
http
. authorizeRequests()
.antMatchers( " / sp itter/m e" ) . hasRole("SPITTER")
.antMatchers(HttpMethod.POST, "/spittles").h asR ole("SP IT T E R ")
.anyRequest( ) .perm itA ll();
}

Puede combinar todas las invocaciones de a n t M a t c h e r s ( ) , r e g e x M a t c h e r s () y


a n y R e q u e s t () que necesite para establecer las reglas de seguridad de su aplicación Web.
Recuerde, no obstante, que se aplicarán en el orden indicado. Por ese motivo, es importante
configurar primero los patrones de las rutas de solicitudes más específicas y después las
menos específicas (como a n y R e q u e s t ()). En caso contrario, las menos específicas supe­
rarán a las otras.

Seguridad con expresiones de Spring


Muchos de los métodos de la tabla 9.4 son unidimensionales, es decir, puede usar
h a s R o le ( ) para exigir una determinada función pero no puede usar h a s I p A d d r e s s ()
para exigir una dirección IP concreta en la misma ruta.
Es más, no se puede trabajar con condiciones que no estén definidas por los métodos
de la tabla 9.4. ¿Y si quiere limitar el acceso a determinadas funciones?
En un capítulo anterior vimos cómo utilizar el Lenguaje de expresiones de Spring (SpEL)
como técnica avanzada para conectar propiedades de bean. Con el método a c c e s s ()
también puede usar SpEL para declarar requisitos de acceso. El siguiente ejemplo muestra
cómo usar una expresión SpEL para exigir acceso RO LE_SPITTER para el patrón de URL
/s p itte r /m e :

.antM atch ers("/sp itter/m e").access("h asR o le( 'ROLE_SPITTER')")

Esta limitación de seguridad impuesta en / s p i t t e r / m e es prácticamente igual a la


que usamos antes, con la diferencia de que utiliza SpEL. La expresión h a s R o le () evalúa
a t r u e si el usuario actual cuenta con la autoridad dada.
Lo que convierte a SpEL en una potente alternativa es que h a s R o le () es solo una de
las expresiones específicas para seguridad admitidas. En la tabla 9.5 puede ver todas las
expresiones SpEL disponibles en Spring Security.
294 Capítulo 9

Tabla 9.5. Spring Security amplía el lenguaje de expresiones de Spring con una serie de
expresiones específicas para seguridad.

Expresión de seguridad Se evalúa como

authentication El objeto de autenticación del usuario.


denyAll Siempre como false.
hasAnyRole (lista de funciones) true si al usuario se le han otorgado alguna de las
funciones especificadas.
hasRole (función) true si al usuario se le ha otorgado la función especificada.
hasipAddress (dirección ip) true si la solicitud proviene de la dirección IP indicada.
IsAnonymous () true si el usuario actual es anónimo.
isAuthenticated () true si el usuario actual no está autenticado.
isFullyAuthenticated () true si el usuario está totalmente autenticado (no es un
usuario recordado).
isRememberMe () true si el usuario actual se autenticó de forma automática
al ser recordado.
permitAll Siempre como true.
principal El objeto principal del usuario

Con las expresiones SpEL de Spring Security a nuestra disposición, podemos hacer algo
más que limitar el acceso en función de las autoridades con las que cuenta un usuario.
Por ejemplo, si quiere bloquear las URL de / s p i t t e r / m e para que no solo requieran
ROLE_S PITTER sino también para que solo se permitan para una dirección IP determinada,
puede invocar el método a c c e s s () de esta forma:
. antMatchers ( " / sp itter/m e11)
.a c c e ss("hasRole( ' ROLE_SPITTER') and hasIpAddress( ' 192.168.1.2')")

Con las restricciones de seguridad basadas en SpEL, las posibilidades son prácticamente
ilimitadas. Estoy seguro de que ahora mismo está pensado en restricciones de seguridad
muy interesantes basadas en este lenguaje. Por el momento, vamos a ver otra forma de
interceptar solicitudes para reforzar la seguridad del canal.

Reforzar la seguridad del canal


El envío de datos mediante HTTP puede ser arriesgado. Quizás no lo sea si lo que envía
es un mensaje cualquiera. Sin embargo, cuando se trata de información sensible, como
contraseñas o números de tarjeta de crédito, utilizar HTTP como medio de transmisión es
buscarse problemas. Los datos se envían sin codificar por HTTP, de modo que cualquier
atacante puede interceptar la solicitud y ver su información. Por ese motivo, la información
delicada debe enviarse cifrada a través de HTTPS.
Seguridad en Spring 295

Trabajar con HTTPS es bastante sencillo. Todo lo que tiene que hacer es añadir una s
detrás de h t t p en una URL. ¿Verdad? Cierto, aunque deja la responsabilidad del uso del
canal HTTPS en el lugar equivocado. Si cuenta con varios enlaces en su aplicación que
requieran HTTPS es muy fácil olvidarse de añadir la s. O, quizás, quiera ser demasiado
correcto e incluir HTTPS en ubicaciones en las que no es necesario. Además del método
a u t h o r iz e R e q u e s t s ( ) , el objeto H t t p S e c u r i t y pasado a c o n f ig u r e () dispone
de un método r e q u ir e s C h a n n e l () que le permite declarar requisitos de canal para
distintos patrones de URL. A modo de ejemplo, piense en el formulario de registro de la
aplicación Spittr. Aunque no pide números de tarjetas de crédito, de DNI o de cualquier
otro elemento parecido, es probable que los usuarios quieran mantener su información
privada. Para garantizar que el formulario de registro se envíe a través de HTTPS, puede
añadir r e q u ir e s C h a n n e l () a la configuración, como en el siguiente código:

Listado 9.5. El método requ¡resChannel() fuerza el uso de HTTPS para seleccionar URL.
@Override
protected void configure(H ttpSecurity http) throws Exception {
http
. authorizeRequests()
. antMatchers("/sp itte r/m e ") .hasRole("SPITTER")
. antMatchers(HttpMethod.POST, " / s p i t t l e s " ) .hasRole{ "SPITTER")
. anyRequest( ) . perm itA ll();
. and()
. requiresChannel()
.an tM atch ers("/sp itter/fo rm "). requiresSecure( ) / / / E x ig ir HTTPS.
i
Cada vez que se reciba una solicitud de / s p i t t e r / f orín, Spring Security detecta que
se requiere un canal seguro (por la invocación de r e q u ir e s S e c u r e ( ) ) y redirige la soli­
citud de forma automática a través de HTTPS.
Por el contrario, algunas páginas no hay que enviarlas sobre HTTPS. Por ejemplo, la
página de inicio no contiene información delicada y no es necesario enviarla por HTTPS,
por lo que vamos a declararla para que siempre se envíe a través de HTTP por medio de
r e q u i r e s l n s e c u r e () en lugar de r e q u i r e s S e c u r e ():
. antMatchers(" /" )• requ ireslnecu re();

Si llega una solicitud para / sobre HTTPS, Spring Security la redirigirá a HTTP.
Las opciones de selección de rutas para forzar el uso de un canal son las mismas que
para a u t h o r i z e R e q u e s t s ( ) . En el listado 9.5 usamos a n tM a tc h e s ( ) , pero también
dispone de r e g e x M a tc h e r s () para seleccionar patrones de ruta con expresiones regulares.

Evitar falsificaciones de petición en sitios cruzados


Como recordará, S p i 1 1 1 eC o n tro 1 1 e r crea un nuevo objeto S p i1 1 1 e para un usuario
cuando se envía una solicitud POST a / s p i t t l e s . ¿Y si esa solicitud POST proviene de
otro sitio Web? ¿Y si esa solicitud POST es el resultado de enviar el siguiente formulario
a otro sitio?
296 Capítulo 9

<form method="POST" actio n ="h ttp://w w w .spittr. co m /sp ittles">


<input type=nhidden,, name="message" value="I'm stupidl" />
<input type="submit" value="Click here to win a new c a r !" />
</form>

Imagine que le vence la tentación de ganar un coche nuevo y pulsa el botón; el formulario
se envía a h t t p : / /www. s p i t t r . c o m / s p i t t l e s . Si ya está conectado en s p i t t r . com,
difundirá un mensaje que revelará su desafortunada decisión a todo el mundo.
Es un sencillo ejemplo de falsificación de petición en sitios cruzados (CSRF). Básicamente,
el ataque CSRF se produce cuando un sitio engaña a un usuario para que envíe una soli­
citud a otro servidor, posiblemente con un resultado negativo. Aunque la publicación de
un mensaje como éste en un blog no sea el peor ejemplo de CSRF, se puede imaginar casos
más graves, relacionados por ejemplo con operaciones en su cuenta bancaria.
Desde Spring Security 3.2, la protección contra CSRF se habilita de forma predetermi­
nada. De hecho, a menos que la manipule o la desactive, le resultará complicado enviar
correctamente los formularios de su aplicación.
Spring Security implementa la protección contra CSRF mediante un token de sincro­
nización. Las solicitudes de cambio de estado (por ejemplo, las que no sean GET, HEAD,
OPTIONS o TRACE) se interceptan y se comprueba si tienen un token CSRF. Si no lo incluyen
o si no coincide con el del servidor, la solicitud falla con Csrf Exception.
Esto significa que los formularios de su aplicación deben enviar un token en un campo
_ c s r f y que dicho token debe ser el mismo que haya calculado y almacenado el servidor
para que coincida al enviar el formulario.
Afortunadamente, Spring Security facilita la inclusión del token en la solicitud bajo los
atributos de la misma. Si utiliza Thymeleaf para su plantilla de página, obtendrá automá­
ticamente el campo oculto _ c s r f siempre que el atributo a c t i o n de la etiqueta < f orm>
tenga un prefijo del espacio de nombres de Thymeleaf:
<form method="POST" th :a c tio n = " @ {/s p ittle s }">

</form>

Si usa JSP para las plantillas de página, puede hacer algo similar:
cinpiit type="hidden"
name="${_csrf.parameterNarae}"
v a lu e= "$ {_ csrf. token}" />

Mejor todavía, si usa la biblioteca de vinculación de formularios de Spring, la etiqueta


< s f : form > añade automáticamente la etiqueta del token CSRF oculto.
Otra forma de trabajar con CSRF consiste en ignorarlo totalmente. Puede desactivar la
protección contra CSRF de Spring Security si invoca c s r f () . d i s a b l e () en la configu­
ración como se indica a continuación:

Listado 9.6. Desactivación de la protección contra CSRF de Spring Security.


@Override
protected void configure(HttpSecurity http) throws Exception {
http
Seguridad en Spring 297

.csrf ()
.disable(); // Desactivar protección CSRF.
}
Sin embargo, no es recomendable desactivar la protección contra CSRF. Si lo hace, su
aplicación se expone a un ataque CSRF. Piénselo bien antes de utilizar la configuración
del listado 9.6. Después de configurar un repositorio de usuarios y Spring Security para
interceptar solicitudes, nos centraremos en cómo solicitar al usuario sus credenciales.

Autenticar usuarios
En la sencilla configuración de Spring Security del listado 9.1, conseguimos una página
de inicio de sesión. De hecho, hasta que no reemplazamos c o n f i g u r e ( H t t p S e c u r i t y ) ,
contábamos con una página de inicio de sesión totalmente funcional, pero al reemplazar el
método la perdimos. Es muy sencillo recuperarla. Basta con invocar f o rm L o g in () en el
método c o n f i g u r e ( H t t p S e c u r i t y ) , como se muestra en el siguiente código.

Listado 9.7. El método formLoginQ habilita una página de inicio de sesión básica.
@0verride
protected void configure(HttpSecurity http) throws Exception {
http
.formLogin() // Habilitar página de inicio de sesión predeterminada.
.and()
.authorizeRequests()
.antMatchers("/spitter/me").hasRole("SPITTER")
.antMatchers (HttpMethod.POST, "/spittles'1) .hasRole ("SPITTER")
.anyRequest () .permitAll () ;
.and()
.requiresChannel()
.antMatchers("/spitter/form").requiresSecure();
i
Como antes, se invoca and () para combinar las distintas instrucciones de configuración.
Si en la aplicación hay un enlace a / l o g i n o si el usuario se desplaza a una página que
requiera autenticación, la página de inicio de sesión se muestra en el navegador. Como
puede apreciar en la figura 9.2, la página no es especialmente estética, pero realiza la tarea
para la que ha sido creada. Seguramente prefiera mejorar el aspecto de su página de inicio
de sesión. Sería una pena que arruinara el atractivo diseño de su sitio Web. No se preocupe.
Veamos cómo añadir una página de inicio de sesión personalizada a su aplicación.

Añadir una página de inicio de sesión personalizada


El primer paso para crear una página de inicio de sesión personalizada consiste en saber
qué incluir en el formulario de inicio de sesión. Lo encontrará en el código HTML de la
página de inicio de sesión predeterminada:
298 Capítulo 9

<html>
<headxtitle>Login Page</title></head>
<body onload=' document. f .username. fo cu s( ) ; '>
<h3>Login with Username and Password</h3>
<form narae='f' action='/spittr/login' method='POST'>
<table>
<tr><td>User:</tdxtd>
<input type='text' name='username' value=''></tdx/tr>
<trxtd>Password:</td>
<tdxinput type='password' name='password'/></tdx/tr>
ctrxtd colspan='2'>
<input name="submit" type="submit" value= "Login"/></tdx/tr>
<input name="_csrf" type="hidden"
value="6829blae-0al4-4920-aac4-5abbd7eeb9ee" />
</table>
</form>
</body>
</html>

Login with Username and Password


User. I
Password:!

| Login j

Figura 9.2. La página predeterminada de inicio de sesión es muy sencilla estéticamente,


pero totalmente funcional.

Lo más importante es el destino de < f orm>. Fíjese también en los campos de nombre
de usuario y contraseña, ya que los va a necesitar en su página de inicio de sesión. Por
último, si no ha deshabilitado CSRF, tendrá que incluir un campo _ c s r f con el token CSRF.
El siguiente código muestra una plantilla Thymeleaf que proporciona una página de
inicio de sesión con el estilo de la aplicación Spittr.

Listado 9.8. Una página de inicio de sesión personalizada para la aplicación Spittr
(como plantilla Thymeleaf).

chtml xmlns="http://www.w3.org/1999/xhtml"
xmlns:th="http://www.thymeleaf.org">
<head>
<title>Spitter</title>
clink rel="stylesheet"
type="text/css"
th:href="@{/resources/style.css)"></link>
</head>
<body onload='document.f .username.focus()/'>
Seguridad en Spring 299

<div id="header" th:include="page :: header"></div>

<div id="content">
<form name='f' th:action='@{/login}' method='POST'> // Enviar a /login.
<table>
<trxtd>User:</tdxtd>
cinput type='text' name=,username' value='' /></tdx/tr>
<trxtd>Password: </td>
<tdxinput type='password' name='password'/></tdx/tr>
ctrxtd colspan='2'>
<input name="submit" type="submit" value="Login"/x/tdx/tr>
</table>
</form>
</div>
<div id="footer" th:include="page :: copy"x/div>
</body>
</html>

La plantilla Thymeleaf tiene campos de nombre de usuario y de contraseña, como la


página de inicio de sesión personalizada. También se envía a la página / l o g i n relativa
al contexto, y al ser una plantilla Thymeleaf, el campo oculto h id d en _ c s r f se añade
automáticamente al formulario.

Habilitar autenticación HTTP básica


La autenticación basada en formularios es ideal para los usuarios humanos de una
aplicación. Sin embargo, en un capítulo posterior, aprenderemos a convertir algunas de las
páginas de nuestra aplicación Web en un API REST. Cuando el usuario de una aplicación
es otra aplicación, el inicio de sesión mediante un formulario es algo que no funciona.
La autenticación HTTP básica es una forma de autenticar a un usuario en una aplicación
directamente a través de la solicitud HTTP. Puede que haya visto este tipo de autenticación
antes. Por ejemplo, en un navegador Web, cuando solicita información al usuario mediante
un cuadro de diálogo.
Sin embargo, es solo la forma en que se manifiesta en un navegador. En realidad, se
trata de una respuesta HTTP 401, que indica al usuario que debe proporcionar un nombre
de usuario y una contraseña junto a la solicitud. Esto la convierte en un método adecuado
para que los clientes REST puedan autenticarse en los servicios que utilizan.
Para habilitar la autenticación HTTP básica basta con invocar h t tp B a s i c ( ) en el objeto
H t t p S e c u r i t y pasado a c o n f i g u r e ( ) . También puede especificar un reino si invoca
realm Nam e ( ). A continuación le mostramos un ejemplo típico de configuración de Spring
Security para habilitar HTTP básico:
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.formLogin()
.loginPage("/login")
.and()
.httpBasic()
300 Capítulo 9

. realmName(" S p itt r " )


.and()

}
De nuevo se usa el método and () para combinar distintas directivas de configuración
en c o n f ig u r e (). h t t p B a s i c () no ofrece ni requiere demasiada personalización. La
autenticación HTTP básica se habilita o no se habilita, por lo que en lugar de adentrarnos
en el tema, veamos cómo autenticar a un usuario mediante la función de recuerdo.

Activar la característica para recordar información


Para una aplicación es importante poder autenticar a los usuarios. Sin embargo, desde
el punto de vista del usuario, sería una buena idea que la aplicación no mostrase una
pantalla de Inicio de sesión cada vez que se utilizase. Es el motivo por el que muchos sitios
Web cuentan con características que permiten recordar la información de acceso, de forma
que solo es necesario iniciar sesión una vez. Spring Security facilita la tarea de añadir esta
funcionalidad a una aplicación. Para activarla, basta con invocar rememberM e () en el
objeto H t t p S e c u r i t y pasado a c o n f i g u r e ():
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.formLogin()
.loginPage("/login")
.and()
.rememberMe()
.tokenValiditySeconds(2419200)
.key("spittrKey")

}
En este caso, además de activar la característica para recordar información, hemos
añadido una configuración especial. De forma predeterminada, se almacena un token en
una cookie que tiene una validez de dos semanas. Sin embargo, en este caso, lo hemos
ampliado a cuatro semanas (2.149.200 segundos).
El token almacenado en la cookie está formado por el nombre de usuario, la contraseña,
una fecha de caducidad y una clave privada (todos estos elementos se codifican mediante
un código hash MD5, antes de almacenarse en la cookie). De forma predeterminada, la
clave privada es S p rin g S e c u re d , aunque la hemos configurado como s p i t t e r K e y para
hacerla más específica para nuestra aplicación.
Bastante sencillo. Recuerde que, después de activar esta característica, tenemos que
permitir que los usuarios puedan decidir si quieren que la aplicación recuerde sus datos o
no. Para ello, la solicitud de inicio de sesión tiene que incluir un parámetro remember_me.
Una casilla de verificación en el formulario de inicio de sesión debería ser suficiente:
cinput id="remember_me" name="remember-me11 type="checkbox"/>
clabel for="remember_rae" class="inline">Remember me</label>
Seguridad en Spring 301

Poder cerrar sesión en una aplicación es igual de importante que poder iniciarla,
en especial si ha habilitado la función de recordar el inicio; en caso contrario, el cierre
de sesión será definitivo. A continuación veremos cómo añadir la posibilidad de cerrar
sesión.

Cerrar sesión
En la aplicación actual, la capacidad de cerrar sesión ya está habilitada. Basta con que
añada un enlace que la utilice.
El cierre de sesión se implementa como filtro de servlet que, de forma predeterminada,
intercepta solicitudes a /1 o g o u t. Por lo tanto, basta con añadir el siguiente enlace (mostrado
como fragmento de Thymeleaf):
<a th:2iref ="@{ /logou t}" >Logout</a>

Cuando el usuario hace clic en el enlace, L o g o u t F i l t e r de Spring Security procesa la


solicitud de /I o g o u t. Se cierra la sesión del usuario y se borran los token de recuerdo de
credenciales. Una vez completado el cierre de sesión, el navegador del usuario se redirige
a / l o g i n ? l o g o u t para que el usuario pueda volver a iniciar sesión si lo desea.
Si prefiere redirigir al usuario a otra página, por ejemplo a la página de inicio de la
aplicación, puede configurarlo en c o n f ig u r e () de esta forma:
@Override
protected void configure(H ttpSecurity http) throws Exception {
http
. formLogin()
. loginPage("/lo g in ")
. and{)
.1ogout()
. logoutSuccessUrl("/")

}
Como antes, and () combina una invocación de 1 o g o u t ( ) , el cual ofrece métodos
para configurar el comportamiento de cierre de sesión. En este caso, la invocación de
lo g o u t S u c c e s s U r l () indica que el navegador debe redirigirse a / tras un cierre de
sesión satisfactorio.
Además de l o g o u t S u c c e s s U r l ( ), puede reemplazar la ruta predeterminada inter­
ceptada por L o g o u t F i l t e r , por medio de una invocación de l o g o u t ü r l ():
.1 ogout()
. logoutSuccessUrl("/")
. lo g o u tü rl("/sig n o u t")

Hasta el momento hemos visto cómo proteger aplicaciones Web al realizarse una soli­
citud. Hemos asumido que la seguridad consiste en impedir que un usuario acceda a una
URL de la que no tiene autorización, pero también es recomendable ocultar enlaces que el
usuario no pueda seguir, como haremos a continuación.
302 Capítulo 9

Proteger la vista
Al representar HTML para mostrarlo en el navegador, puede que le interese que la
vista refleje las limitaciones de seguridad y su información. Un sencillo ejemplo podría ser
representar el principal del usuario autenticado, o mostrar condicionalmente determinados
elementos de la vista, en función de las autoridades concedidas al usuario.
En un capítulo anterior vimos dos opciones para representar vistas en una aplicación
de Spring MVC: JSP y Thymeleaf. Independientemente de la opción seleccionada, hay
una forma de trabajar con seguridad en la vista. Spring Security ofrece una biblioteca de
etiquetas JSP propia, mientras que Thymeleaf ofrece integración con Spring Security a
través de un dialecto especial. Veamos cómo trabajar con Spring Security en nuestras vistas,
comenzando con la biblioteca de etiquetas JSP de Spring Security.

Utilizar la biblioteca de etiquetas JSP de Spring Security


La biblioteca de etiquetas JSP de Spring Security no es demasiado extensa; solamente
cuenta con las tres etiquetas mostradas en la tabla 9.6.

Tabla 9.6. Spring Security admite seguridad en la capa de la vista con una biblioteca
de etiquetas JSP.
........... .................. ............................ í - T p i .............. ■ ............... ... ' -------......................... ..................- : ................................................- ...... — ....................................................

Etiqueta JSP Función

<security:accesscontrollist> Representa condicionalmente su contenido si el usuario


recibe autoridades por parte de una lista de control de
acceso.
<security:authentication> Representa detalles sobre la autenticación actual.
<security:authorize> Representa condicionalmente su contenido si el usuario
recibe determinadas autoridades o si una expresión SpEL
evalúa a true.

Para poder utilizar la biblioteca de etiquetas JSP, tenemos que declararla en los archivos
JSP donde la utilicemos:
<%@ ta g lib p re fix ="secu rity "
u ri= "h ttp : / /www. springframework. o rg /secu rity /ta g s" %>

Una vez se declara la biblioteca de etiquetas en el archivo JSP, estamos listos para utili­
zarla. Veamos cada una de las etiquetas JSP que incluye Spring Security y su funcionamiento.

Acceder a la información de autenticación


Una de las tareas más sencillas que la biblioteca de etiquetas JSP de Spring Security
puede hacer es proporcionar un cómodo acceso a la información de autenticación del
usuario. Por ejemplo, es habitual que los sitios Web muestren un mensaje de bienvenida,
Seguridad en Spring 303

en el encabezado de la página, en el que se identifica al usuario por su nombre de usuario,


precisamente lo que « s e c u r i t y : a u t h e n t i c a t i o n puede hacer por nosotros. Veamos
un ejemplo:

Hello < se c u rity :authentication property^"p rin cip a l.username" /> !

El atributo p r o p e r ty identifica una propiedad del objeto de autenticación del usuario.


Las propiedades disponibles varían en función de la forma en que se autentique al usuario.
Sin embargo, puede contar con disponer con algunas propiedades comunes, incluyendo
las que se recogen en la tabla 9.7.
En nuestro ejemplo, la propiedad representada es en realidad la propiedad u sernam e
anidada de la propiedad p r i n c i p a l .

Tabla 9.7. Puede acceder a varios de los detalles de autenticación del usuario por medio de la
etiqueta de JSP <security:authentication>.
í.......... .. ............... ......... ......... .... ■.................. .......~................. .... ...... ' .......... .............. ..

; Propiedad de autenticación Descripción

authorities Una colección de objetos GrantedAuthority que representa


los privilegios otorgados al usuario.
credentials Las credenciales utilizadas para verificar el elemento principal
(por lo general, la contraseña del usuario).
details Información adicional sobre la autenticación (dirección IP, número
de serie del certificado, ID de sesión, etc.).
principal El principal del usuario.

Cuando se utiliza como en el ejemplo anterior, « s e c u r i t y : a u th e n t i c a t io n > repre­


senta el valor de la propiedad en la vista. Sin embargo, si prefiere asignarla a una variable,
solo tiene que especificar el nombre de ésta en el atributo v a r. El siguiente ejemplo la
asigna a la propiedad lo g in ld :

< secu rity : authentication p roperty="principal.username"


var=" lo g in ld "/>

La variable se crea en el ámbito de página de forma predeterminada. Si prefiere crearla


en otro ámbito como en el de solicitud o en el de sesión (o en cualquiera disponible
en j a v a x . s e r v l e t . j sp . P a g e C o n t e x t ) , puede especificarlo mediante el atributo
s c o p e . Por ejemplo, para crear la variable en el ámbito de la solicitud, utilice la etiqueta
« s e c u r i t y : a u t h e n t i c a t i o n de la siguiente manera:

< secu rity : authentication p ro perty="principal.username"


var="loginld" scope="request" />

La etiqueta « s e c u r i t y : a u t h e n t i c a t i o n > es útil, aunque solo es el principio de lo


que la biblioteca de etiquetas JSP de Spring Security puede hacer. Veamos cómo representar
contenido de forma condicional en función de los privilegios del usuario.
304 Capítulo 9

Representación condicional
Hay ocasiones en las que determinados fragmentos de la vista deben representarse, o
no, en función de los privilegios de un usuario. No tiene sentido mostrar un formulario de
inicio de sesión a un usuario que ya ha accedido, o mostrar una bienvenida personalizada
a alguien que no ha iniciado sesión.
La etiqueta JSP de Spring Security < s e c u r i t y : a u t h o r iz e > representa de forma
condicional un fragmento de la vista en función de las autoridades con las que cuente el
usuario. Por ejemplo, en la aplicación Spittr no queremos mostrar el formulario para añadir
un nuevo spittle, a menos que el usuario cuente con la función ROLE_SPITTER. El listado
9.9 muestra cómo utilizar < s e c u r i t y : a u t h o r iz e > para mostrar el formulario del spittle
si el usuario cuenta con la autoridad ROLE_SPITTER.

Listado 9.9. <sec:authorize> representa contenido condicionalmente en función de SpEL.

< sec: authorize access="hasRole( 'ROLE_SPITTER')"> / / Solo con la autoridad ROLE_SPITTER.


< s :u rl v a lu e = "/sp ittle s" v a r= "sp ittle _ u rl" />
< s f : form m od elA ttribute="spittle"
actio n ="$ {s p itt le _ u r l}">
< s f:la b e l p a th = "te x t"x s rmessage cod e="lab el. s p itt le "
text="E n ter s p i t t l e : "/ > < / s f : lab el>
< s f:te x ta re a path="text" rows="2" cols="40" />
< s f:e rro rs p ath="text" />

<br/>
<div c la ss= "sp itItS u b m itIt">
cinput type="submit" value="Spit i t ! "
cla ss= "sta tu s-b tn round-btn disabled" />
</div>
< /s f : form>
< /s e c : authorize>

Al atributo a c c e s s se le proporciona una expresión SpEL cuyo resultado determina


si el cuerpo de < s e c u r i t y : a u t h o r i z e > se va a representar. En este caso, utilizamos la
expresión h a s R o le ( ' RO LE_SPITTER' ) para asegurarnos de que el usuario cuenta con
la función RO LE_SPlTTER, pero cuenta con toda la potencia de SpEL a su disposición al
configurar el atributo a c c e s s , incluyendo las expresiones de Spring Security de la tabla 9.5.
Con estas expresiones disponibles, puede establecer interesantes restricciones de segu­
ridad. Por ejemplo, imagine que la aplicación cuenta con funciones administrativas que
solo están disponibles para el usuario cuyo nombre es habuma. En este caso, podría utilizar
las expresiones i s A u t h e n t i c a t e d () y p r i n c i p a l de la siguiente manera:
< secu rity : authorize
access="isA u thenticated () and principal.username==' habuma'">
<a h re f= " /admin">Administration</a>
< /s e c u rity : authorize>

Estoy seguro de que se le ocurren expresiones más interesantes. Lo dejo a su imagina­


ción, ya que las opciones con SpEL son prácticamente ilimitadas. Sin embargo, el ejemplo
cuenta con un elemento que aún me molesta. Aunque puede que quiera restringir las
Seguridad en Spring 305

funciones administrativas a habuma, quizá hacerlo con una etiqueta JSP no sea la mejor
opción. Sin duda, evitará que el enlace se represente en la vista. Sin embargo, no hay
nada que impida introducir de forma manual la URL / admin en la barra de dirección del
navegador.
Teniendo en cuenta lo que hemos aprendido con anterioridad, este problema debería
ser fácil de resolver. Basta con añadir una nueva invocación a al método a n tM a tc h e rs ()
a la configuración de seguridad para reforzarla alrededor de la URL / admin:
. antMatchers( " / admin")
. a c c e s s ("isA uthenticated() and principal.username=='habuma'" ) ;

Ahora, la funcionalidad de administración está bloqueada. La URL está protegida y el


enlace a la URL no se mostrará a menos que el usuario cuente con autorización para usarla.
Sin embargo, para hacerlo tenemos que declarar la expresión SpEL en dos puntos: en la confi­
guración de seguridad y en el atributo a c c e s s de la etiqueta < s e c u r i t y : a u th o r iz e > .
¿Hay alguna forma de eliminar la duplicación y al mismo tiempo impedir que se represente
el enlace a las funciones administrativas a menos que se cumpla la regla?
Es la función del atributo u r l de la etiqueta < s e c u r i t y : a u th o r iz e > . A diferencia
del atributo a c c e s s , donde la restricción de seguridad se declara de forma explícita, el
atributo u r l hace referencia de forma indirecta a las restricciones de seguridad para un
patrón URL dado. Como ya hemos declarado las restricciones de seguridad para /adm in
en la configuración de seguridad de Spring, podemos utilizar el atributo u r l de la siguiente
manera:
<secu rity : authorize u rl= "/admin">
<spring:url value="/admin" var="admin__url" />
< b r / x a href="${admin_url} ">Admin</a>
< /s e c u rity : authorize>

Como la URL / admin queda restringida a aquellos usuarios autenticados que tengan el
nombre de usuario habuma, el cuerpo de la etiqueta < s e c u r i t y : a u t h o r i z e > solo se va
a representar si esas condiciones se cumplen. La expresión se ha configurado en un punto
(en la configuración de seguridad) pero se ha utilizado en dos.
La biblioteca de etiquetas JSP de Spring Security es muy útil, en especial para repre­
sentar condicionalmente elementos de vista solo para los usuarios que tengan permiso
para verlos, pero si ha elegido Thymeleaf en lugar de JSP para sus vistas, también tiene
opciones. Ya hemos visto cómo el dialecto Spring de Thymeleaf añade automáticamente un
campo de token CSRF oculto a los formularios. Veamos ahora cómo se admite Thymeleaf
en Spring Security.

Trabajar con eS dialecto Spring Security de Thymeleaf


Como sucede con la biblioteca de etiquetas JSP de Spring Security, el dialecto de segu­
ridad de Thymeleaf ofrece representación adicional y la posibilidad de representar detalles
de autenticación. La tabla 9.8 enumera los atributos proporcionados por el dialecto de
seguridad.
306 Capítulo 9

Tabla 9.8. El dialecto de seguridad de Thymeleaf ofrece atributos similares a los de la biblioteca
de etiquetas de Spring Security.
1 '

Atributo Función

s e c :authentication Representa propiedades del objeto de autenticación. Similar a la


etiqueta JSP <sec:authentication/> de Spring Security.

s e c :authorize Representa contenido condicionalmente en función de la evaluación


de una expresión. Similar a la etiqueta JSP <sec.-authorize/>
de Spring Security.
sec:authorize-acl R epresenta contenido condicionalm ente en función de la
evaluación de una expresión. S im ilar a la etiqueta JSP
< s e c :accesscontrollist/ > de Spring Security.

s e c :authorize-expr Un alias del atributo s e c :authorize.


s e c :authorize-url Representa contenido condicionalmente en función de la evaluación
de reglas de seguridad asociadas a la ruta de URL proporcionada.
Similar a la etiqueta JSP <sec: author i ze/> de Spring Security
cuando se usa el atributo url.

Para poder usar el dialecto de seguridad, tendrá que incluir el módulo Extras Spring
Security de Thymeleaf en la ruta de clases de su aplicación. Después tendrá que registrar
S p r i n g S e c u r i t y D i a l e c t con S p r in g T e m p la te E n g in e en su configuración. El listado
9.10 muestra el método @ B ean que declara el bean S p r in g T e m p la t e E n g in e , incluido
S p r in g S e c u r ity D ia le c t.

Listado 9.10. Registro del dialecto Spring Security de Thymeleaf.

@Bean
public SpringTemplateEngine templateEngine(
TemplateResolver templateResolver) {
SpringTemplateEngine templateEngine = new SpringTemplateEngine();
templateEngine. setTemplateResolver(templateResolver);
templateEngine. addDialect(new SpringSecurityD ialectO ) ;
/ / R eg istrar e l d ialecto de seguridad,
return templateEngine;
}
Una vez añadido el dialecto de seguridad, ya puede usar sus atributos en las plantillas
Thymeleaf. En primer lugar, declare el espacio de nombres de seguridad en las plantillas
en las que vaya a usar dichos atributos:
< !DOCTYPE html>
chtml xmlns="http://www.w3. o rg /1999/xhtml"
xmlns: th="h ttp : //www. thymeleaf. org"
xmlns: sec=
"h ttp : / /www.thym eleaf.org/thym eleaf-extras-springsecurity3">

</html>
Seguridad en Spring 307

Aquí se asigna el dialecto Thymeleaf estándar al prefijo t h como antes, y el dialecto de


seguridad se asigna al prefijo s e c .
Ya puede usar los atributos de Thymeleaf donde considere oportuno. Imagine por
ejemplo que quiere representar el texto " H e lio " al usuario si está autenticado. El siguiente
fragmento de una plantilla Thymeleaf se encarga de ello:
<div s e c : authorize="isA uthenticated()">
Helio <span s e c : authentication="ñame">someone</span>
</div>

El atributo s e c : a u t h o r i ze acepta una expresión SpEL que, si evalúa a t r u e , se repre­


senta el cuerpo del elemento. En este caso, la expresión es is A u t h e n t i c a t e d ( ) , de modo
que se representa el cuerpo de la etiqueta < d iv > solo si el usuario está autenticado. Con
respecto al cuerpo, muestra " H e lio " a la propiedad ñame de la autenticación.
Si recuerda, la etiqueta JSP < s e c : a u th o r i ze> de Spring Security tiene un atributo u r l
que hace que su cuerpo se represente condicionalmente en función de las autorizaciones
asociadas a una ruta de URL concreta. Puede lograr lo mismo con Thymeleaf con ayuda
del atributo s e c : a u t h o r i z e - u r l . Por ejemplo, el siguiente fragmento de Thymeleaf
realiza la misma función que la etiqueta < s e c : a u t h o r iz e > de JSP y el atributo u r l que
usamos antes:
<span sec:authorize-url="/adm in">
<br/><a ththref="@{/adm in}" >Admin</a>
< /span>

Si el usuario tiene autorización para acceder a /adm in, se mostrará un enlace a la página
administrativa; en caso contrario no se mostrará.

Resumen
La seguridad es un aspecto crucial de muchas aplicaciones. Spring Security cuenta con
un mecanismo sencillo, flexible y potente para proteger su aplicación.
Utilizando una serie de filtros de servlet, Spring Security puede controlar el acceso a los
recursos Web, incluyendo controladores de Spring MVC. Gracias al modelo de configuración
Java de Spring Security, no tendrá que gestionar directamente esos filtros. De esta forma,
la seguridad Web se puede declarar de forma concisa.
En lo relativo a la autenticación de usuarios, Spring Security ofrece varias opciones.
Hemos visto cómo configurar la autenticación respecto a un repositorio de usuarios en
memoria, una base de datos relacional y servidores de directorio LDAP, y cuando no pueda
cubrir sus necesidades de autenticación con estas opciones, puede crear y configurar un
servicio personalizado de detalles de usuario.
En los últimos capítulos hemos descrito el papel de Spring como interfaz de sus aplica­
ciones. En la siguiente parte del libro nos adentraremos en la pila para analizar la presencia
de Spring en el servidor. Comenzaremos por la abstracción JDBC de Spring.
Parte III.
Spring
en el servidor

Aunque las páginas Web que sirve una aplicación Web son todo lo que los usuarios
ven, el trabajo real se realiza entre bastidores en el servidor, donde los datos se procesan
y conservan. En la Parte III del libro veremos cómo usar Spring para trabajar con datos en
el servidor.
Durante décadas, las bases de datos relaciónales han sido el motor de las aplicaciones
empresariales. En el capítulo 10 aprenderemos a usar la abstracción JDBC de Spring para
consultar bases de datos relaciónales de una forma más sencilla que la que ofrece JDBC.
Si JDBC no es su estilo, puede que prefiera trabajar con un marco de trabajo ORM (asig­
naciones relaciónales de objetos). En el capítulo 11 veremos la integración de Spring con
marcos de trabajo ORM como Hibernate y otras implementaciones del API de persistencia
de Java (JPA). Además, aprenderemos a usar JPA de Spring Data para generar automática­
mente implementaciones de repositorios sobre la marcha en tiempo de ejecución.
Las bases de datos relaciónales no siempre son la opción perfecta. Por ello, en el capítulo
12 veremos otros proyectos de datos de Spring muy útiles para la persistencia de datos en
diferentes bases de datos no relaciónales como MongoDB, Neo4j y Redis.
El capítulo 13 añade el almacenamiento en caché a los capítulos anteriores sobre persis­
tencia, para mejorar el rendimiento de sus aplicaciones ignorando totalmente la base de
datos si los datos necesarios ya están disponibles.
La seguridad es un aspecto tan importante en el servidor como en las aplicaciones, por lo
que en el capítulo 14 veremos cómo aplicar Spring Security en el servidor para interceptar
invocaciones de métodos y garantizar que el invocador disponga de la autoridad adecuada.
Capítulo

10 A c c e s o a b a se s
de d a to s con
S p rin g y J D B C

CONCEPTOS FUNDAMENTALES:

• Definición de la compatibilidad del acceso a datos.


• Configuración de recursos de base de datos.
• La plantilla JDBC de Spring.
Ahora que ya domina el uso del contenedor de Spring, es el momento de comenzar a
trabajar con aplicaciones reales. Un punto de partida perfecto sería un requisito que tienen
prácticamente todas las aplicaciones empresariales: la persistencia de datos. Todos hemos
trabajado con el acceso a bases de datos dentro de una aplicación. En la práctica, sabemos
que el acceso a los datos presenta muchas dificultades. Tenemos que inicializar nuestro
marco de trabajo de acceso a datos, abrir conexiones, gestionar excepciones y cerrar cone­
xiones. Si alguno de estos pasos no se realiza de forma correcta, podríamos corromper o
eliminar valiosos datos de nuestra empresa. Si nunca ha sufrido las consecuencias de un
acceso a datos gestionado de manera inadecuada, créame: es algo malo.
Como buscamos todo lo contrario, recurrimos a Spring. Cuenta con una familia de
marcos de trabajo de acceso a datos que se integran con diversas tecnologías de acceso a
datos. Con independencia de que conserve sus datos mediante JDBC directo o un marco
de trabajo de Asignación de objetos relaciónales (ORM) como Hibernate, Spring elimina el
trabajo pesado del acceso a datos desde el código de persistencia. En su lugar, puede dejar
a Spring la tarea del acceso a datos de bajo nivel para así poder centrarse en la gestión de
sus datos de aplicación.
A medida que desarrollemos la capa de persistencia de la aplicación Spittr, vamos a
tener que tomar varias decisiones. Podríamos usar JDBC, Hibernate, el API Java Persistence
(JPA) o cualquier marco de trabajo de persistencia, o decantarnos por las nuevas bases de
datos NoSQL (o sin esquema, como prefiero denominarlas) tan de moda en la actualidad.
Independientemente de su elección, Spring es compatible con todos estos mecanismos.
En este capítulo nos centraremos en su compatibilidad con JDBC. Sin embargo, antes de
continuar, vamos a familiarizarnos con la filosofía de persistencia de Spring.

Filosofía de acceso a datos ele Spring____________


En los capítulos anteriores ha aprendido que uno de los principales objetivos de Spring es
permitir el desarrollo de aplicaciones siguiendo el principio de programación de interfaces
orientadas a objetos (OO). El acceso de datos de Spring no es una excepción.
Como muchas otras aplicaciones, Spittr tiene que leer y escribir datos en una base de
datos. Para evitar que la lógica de persistencia esté desperdigada en todos los componentes
de la aplicación, conviene factorizar el acceso a la base de datos en uno o varios de estos
componentes que realicen dicha tarea específica: los denominados objetos de acceso a datos
(DAO) o repositorios.
Para evitar que la aplicación esté sometida a una determinada estrategia de acceso a
datos, los repositorios deben exponer esta funcionalidad mediante una interfaz. En la figura
10.1 se muestra el enfoque adecuado para diseñar su nivel de acceso a datos.
Como puede ver, los objetos de servicio acceden a los repositorios a través de interfaces.
Esto tiene algunas ventajas. En primer lugar, permite probar sus objetos de servicio con
facilidad, ya que no están acoplados a una implementación específica de acceso a datos.
De hecho, puede crear implementaciones de prueba de estas interfaces de acceso a datos.
Esto le va a permitir comprobar su objeto de servicio sin tener que conectarlo a la base de
datos, lo que acelera sus pruebas de forma considerable, y descartar la posibilidad de un
fallo debido a datos incoherentes.
312 Capítulo 10

Figura 10.1. Los objetos de servicio no gestionan su propio acceso a los datos.
En su lugar, lo delegan en repositorios, cuya interfaz los mantiene acoplados de forma débil
con el objeto de servicio.

En segundo lugar, el acceso al nivel de acceso a datos es independiente de las tecnologías


empleadas. El enfoque de persistencia seleccionado se aísla con el repositorio, mientras
que solo los métodos de acceso a datos relevantes se exponen mediante la interfaz. Esto
permite un diseño flexible de la aplicación y el cambio del marco de trabajo de persistencia
con un impacto mínimo sobre el resto de la aplicación. Si los detalles de la implementación
del nivel de acceso a datos se incluyeran en otras partes de la aplicación, toda la aplicación
quedaría acoplada con el nivel de acceso a datos, con lo que tendríamos un diseño de
aplicación rígido.

Nota

Si después de leer los últimos párrafos siente un fuerte odio hacia la acción de
ocultar la capa de persistencia tras una interfaz, me alegra que sea así. Creo
que las interfaces son esenciales para escribir código acoplado de forma débil y
deberían utilizarse en todas las capas de la aplicación y no solo en la de acceso
a datos. Dicho esto, es importante m encionar que Spring promueve el uso de
interfaces (aunque no obliga a ello). Si lo desea, puede utilizar Spring para
conectar un bean (de repositorio o de otro tipo) directam ente con una propiedad
o con otro bean sin incluir una interfaz entre ellos.

Una de las formas en las que Spring le ayuda a aislar su capa de acceso a datos del resto
de su aplicación es proporcionando una jerarquía de excepciones coherente, que puede
utilizarse en todas sus opciones de persistencia admitidas.

Jerarquía de excepciones de Spring


Hay un viejo chiste sobre un paracaidista que, al descender, se equivoca de destino
y termina colgado de un árbol. Tras pasar un rato, pasa una persona y el paracaidista le
pregunta: "¿Dónde estoy?", a lo que esta persona responde: "A unos 10 metros sobre el
suelo". El paracaidista responde: "Vaya, seguro que eres analista de software". "Sí, ¿cómo
Acceso a bases de datos con Spring y JDBC 313

lo sabe?", pregunta la persona. A lo que el paracaidista responde: "Porque lo que me has


dicho es totalmente exacto, aunque completamente inútil". Este chiste tiene varias versiones,
donde cambia la profesión o la nacionalidad de la persona que pasa debajo del paracai­
dista. En cualquier caso, esta historia me recuerda al caso de S Q L E x c e p tio n de JDBC. Si
alguna vez ha escrito código JDBC (sin Spring), probablemente sepa que no puede hacer
nada en JDBC sin que, antes o después, se encuentre con S Q L E x c e p tio n . Indica que se ha
producido una excepción al acceder a la base de datos. Sin embargo, apenas se proporciona
información sobre qué ha ido mal o cómo solucionar el problema.
Algunos de los problem as más habituales que pueden generar una excepción
S Q L E x c e p tio n son:

• La aplicación no puede conectarse con la base de datos.


• La consulta que se está ejecutando tiene errores en su sintaxis.
• Las tablas o las columnas a las que se hace referencia en la consulta no existen.
• Se ha intentado insertar o actualizar valores que incumplen una limitación de la base
de datos.

La gran pregunta sobre S Q L E x c e p tio n es cómo gestionarlo cuando se produce. Lo


cierto es que m uchos de los problemas que generan S Q L E x c e p tio n no pueden evitarse
dentro de un bloque c a t c h . La mayoría de excepciones S Q L E x c e p tio n indica una condi­
ción fatal. Si la aplicación no puede conectarse a la base de datos, eso suele im plicar que
no podrá continuar. Del m ism o modo, si se producen excepciones en la consulta, podrá
hacerse m uy poco durante el tiem po de ejecución.
Si no podemos hacer nada para recuperarnos de una excepción S Q L E x c e p tio n , ¿por
qué nos la encontram os una y otra vez?
Aunque tenga un plan para controlar determinadas excepciones S Q L E x ce p tio n , tendrá
que enfrentarse a ellas y examinar sus propiedades para determinar el problema. Se debe a
que S Q L E x c e p tio n es una excepción de tipo general que aparece para indicar problemas
relacionados con el acceso a datos. En lugar de contar con un tipo de excepción diferente
para cada problema posible, S Q L E x c e p tio n es la excepción que se muestra para todos los
problemas relacionados con el acceso a datos. Algunos marcos de trabajo de persistencia
ofrecen una amplia jerarquía de excepciones. Por ejemplo, Hibernate cuenta con un par
de decenas, y cada una indica un problema de acceso a datos específico. Esto hace posible
escribir bloques c a t c h para las excepciones que tenga que solucionar.
Aún así, las excepciones de Hibernate son específicas de este modelo. Como ya hemos
indicado, nos gustaría aislar los elementos específicos del mecanismo de persistencia en
la capa de acceso de datos. Si se producen excepciones específicas de Hibernate, tener que
gestionarlos es algo que va a filtrarse en el resto de la aplicación. Eso o estará obligado a
gestionar excepciones de plataforma de persistencia y convertirlas en excepciones inde­
pendientes de la plataforma.
Por un lado, la jerarquía de excepciones de JDBC es muy genérica (de hecho, apenas
llega a serlo). Por otro, la jerarquía de Hibernate es exclusiva de este lenguaje. Lo que nece­
sitamos es una jerarquía de excepciones de acceso a datos que sea descriptiva pero que no
esté asociada directamente a un marco de trabajo de persistencia específico.
Capítulo 10

Excepciones de persistencia en Spring independientes


de la plataforma
Spring JDBC cuenta con una jerarquía de excepciones de acceso a datos que resuelve
estos dos problemas. En comparación con JDBC, Spring cuenta con varias excepciones de
acceso a datos, y cada una indica el tipo de problema de forma descriptiva. En la tabla 10.1
se muestran algunas de ellas comparadas con las excepciones que ofrece JDBC.

Tabla 10.1. Jerarquía de excepciones de JDBC frente a las excepciones de acceso


a datos en Spring.

BatchUpdateException BadSqlGrammarException
DataTruncation CannotAcquireLockException
SQLException CannotSerializeTransactionException
SQLWarning CannotGetJdbcConnectionException
CleanupFailureDataAccessException
ConcurrencyFailureException
DataAccessException
DataAccessResourceFailureException
DatalntegrityViolationException
DataRetrievalFailureException
DataSourceLookupApiUsageException
DeadlockLoserDataAccessException
DuplicateKeyException
EmptyResultDataAccessException
IncorrectResultSizeDataAccessException
IncorrectUpdateSemanticsDataAccessException
InvaiidDataAccessApiUsageException
InvaiidDataAccessResourceUsageException
InvaiidResultSetAccessException
JdbcUpdateAffectedlncorrectNumberOfRowsException
LobRetrievalFailureException
NonTransientDataAccessResourceException
OptimisticLockingFailureException
PermissionDeniedDataAccessException
PessimisticLockingFailureException
QueryTimeoutException
RecoverableDataAccessException
SQLWarningException
SqlXmlFeatureNotlmplementedException
TransientDataAccessException
TransientDataAccessResourceException
TypeMismatchDataAccessException
UncategorizedDataAccessException
UncategorizedSQLException
Acceso a bases de datos con Spring y JDBC 315

Como puede comprobar, Spring cuenta con una excepción para virtualmente cualquier
error de lectura o escritura en una base de datos, y la lista no termina con las opciones de la
tabla 10.1 (podría haberlas incluido todas, pero no quería que JDBC se sintiera acomplejado).
Aunque la jerarquía de excepciones de Spring es mucho más amplia que la de JDBC,
no está asociada a ninguna solución de persistencia concreta. Esto quiere decir que puede
utilizar Spring para generar un conjunto de excepciones coherente, con independencia del
proveedor de persistencia que seleccione, lo que le permite limitar la persistencia al nivel
de acceso a datos.

Ausencia de bloques catch


Lo que no se ve a primera vista a partir de las excepciones de la tabla 10.1 es que se encuen­
tran en la raíz D a t aA cces s E x c e p t io n . Lo que hace especial a Data A c c e s s E x c e p tio n e s
que es una excepción no comprobada. Es decir, no tiene que capturar todas las excepciones
de acceso a datos que se generan en Spring (aunque puede hacerlo si lo desea).
D a t a A c c e s s E x c e p t i o n es solo un ejemplo de la filosofía de Spring de excepciones
comprobadas frente a no comprobadas. La postura de Spring es que muchas excepciones
son el resultado de problemas que no pueden solucionarse en un bloque c a t c h . En lugar
de obligar a los desarrolladores a que escriban estos bloques (que a menudo se dejan
vacíos), Spring promueve el uso de excepciones no comprobadas. Esto deja en las manos
del desarrollador la decisión de capturar una excepción.
Para sacar partido a las excepciones de acceso a datos de Spring, debe utilizar una de
las plantillas de acceso a datos admitidas de Spring.

Acceso a datos mediante plantillas


Probablemente haya viajado alguna vez en avión (y si no lo ha hecho, seguro que conoce
el proceso). Estará de acuerdo en que uno de los aspectos más importantes es el de llevar el
equipaje del punto A al punto B. Este proceso consta de varios pasos. Al llegar a la terminal,
en primer lugar pasa por el mostrador para facturar su equipaje. A continuación, el equipo
de seguridad escanea sus maletas.
A continuación, su maleta se coloca en el "tren del equipaje", que lo lleva al avión, donde
se almacena en la bodega. Si tiene que hacer una escala, su equipaje también tendrá que
moverse. Por último, al llegar a su destino final, el equipaje tiene que sacarse del avión y
colocarse en la cinta. Por último, acude a la zona de recogida, donde podrá recuperar su
maleta.
Aunque este proceso consta de muchos pasos, solo participamos de forma activa en
algunos de ellos. El transportista es el responsable de la mayor parte del proceso y usted
solo participa cuando tiene que hacerlo. Este tipo de proceso es similar a un potente patrón
de diseño: el patrón de métodos de plantilla.
Un método de plantilla define el esqueleto de un proceso. En nuestro ejemplo, el proceso
se encarga de transferir equipaje desde la ciudad de origen hasta la de destino. En sí, el
proceso es fijo y nunca cambia. La secuencia de eventos para gestionar el equipaje siempre
tiene lugar del mismo modo y en cada ocasión: el equipaje se factura, se carga en el avión,
316 Capítulo 10

etc. Asimismo, algunos pasos del proceso también son fijos y otros siempre tienen lugar
al mismo tiempo. Cuando el avión llega a su destino, todas las maletas se descargan y se
colocan en un tren de transporte para llevarlas al área de recogida.
En algunos puntos, el proceso delega su trabajo en una subclase para cumplir algunos de
los detalles específicos de la implementación. Es la parte variable del proceso. Por ejemplo,
la gestión del equipaje comienza cuando un pasajero factura su maleta en el mostrador.
Esta parte del proceso siempre tiene que tener lugar en primer lugar, por lo que su posición
en el proceso es fija.
Como la facturación del equipaje de cada pasajero es diferente, la implementación de
esta parte viene determinada por el proceso. En términos de software, un método de plan­
tilla delega aquellas partes específicas de la implementación del proceso en una interfaz.
Diferentes implementaciones de esta interfaz definen implementaciones específicas de
esta parte del proceso.
Es el mismo patrón que Spring aplica al acceso a los datos. Con independencia de la
tecnología que utilizamos, es necesario llevar a cabo una serie de pasos para acceder a los
datos. Por ejemplo, siempre tenemos que obtener una conexión con nuestro almacén de
datos y limpiar los recursos cuando hayamos terminado. Son los pasos fijos del proceso de
acceso a datos. Sin embargo, cada método de acceso a datos que escribimos es ligeramente
diferente. Hacemos consultas para diferentes objetos y actualizamos los datos de diferentes
maneras. Son los pasos variables del proceso de acceso a datos.
Spring divide las partes fijas y variables del proceso de acceso a datos en dos clases
diferentes: plantillas y retrollamadas. Las plantillas se encargan de gestionar la parte fija
del proceso, mientras que el código de acceso personalizado a datos es el trabajo de las
retrollamadas. En la figura 10.2 se muestran las tareas de las que se encargan cada uno de
estos elementos.

Figura 10.2. Las clases de plantilla de acceso a datos de Spring se encargan de las tareas
comunes de acceso a datos. Para tareas específicas de la aplicación recurre a un objeto
de retrollamada personalizado.

Como puede ver en la figura 10.2, las clases de plantilla de Spring se encargan de
gestionar las partes fijas del acceso a datos: control de transacciones, administración de
recursos y gestión de excepciones. Mientras tanto, los aspectos específicos del acceso de
datos de su aplicación, como la creación de instrucciones, la vinculación de parámetros o
Acceso a bases de datos con Spring y JDBC 317

la recopilación de conjuntos de resultados, se gestionan con la implementación de retro-


llamadas. En la práctica, conseguimos un marco de trabajo elegante ya que, de esta forma,
de lo único que tiene que preocuparse es de su lógica de acceso a datos. Spring incluye
varias plantillas entre las que puede elegir en función de su plataforma de persistencia. Si
va a utilizar JDBC, recurrirá a J d b c T e m p la te . Si prefiere uno de los marcos de trabajo de
asignación de objetos relaciónales, probablemente H ib e r n a t e T e m p la te o Jp a T e m p la te
serán opciones más adecuadas. En la tabla 10.2 se muestran todas las plantillas de acceso
a datos de Spring y su función.

Tabla 10.2. Spring cuenta con varias plantillas de acceso a datos, cada una de las cuales resulta
adecuada para un mecanismo de persistencia diferente.

Clase de plantilla (org.springframework.*) Se aplica a

jca.cci.core.CciTemplate Conexiones JCA CCI.

jdbc.core.JdbcTemplate Conexiones JDBC.

jdbc .core .namedparam .NamedParameterJdbcTemplate Conexiones JDBC que admiten


parámetros con nombre.

jdbc.core.simple.SimpleJdbcTemplate Conexiones JDBC simplificadas


con construcciones de Java 5
(no se incluyen desde Spring
3.1).

orm.hibernate3.HibernateTemplate Sesiones de Hibernate 3.x.

orm.ibatis.SqlMapClientTemplate Clientes SqlMap ¡BATIS.

orm.jdo.JdoTemplate Implementaciones de objetos


de datos de Java.

orm.jpa.JpaTemplate Administradores de entidades


del API Java Persistence.

Aunque Spring permite el uso de varios marcos de trabajo de persistencia, en este


capítulo no disponemos del espacio necesario para hablar sobre todos ellos. Por tanto, voy
a centrarme en las opciones de persistencia que considero más interesantes y en las que,
probablemente, vaya a utilizar más.
Vamos a comenzar con el acceso básico JDBC, la forma más sencilla de leer y escribir
datos en una base de datos. A continuación, pasaremos a Hibernate y JPA, dos de las solu­
ciones ORM basadas en POJO más populares. Terminaremos el análisis de la persistencia
en Spring en un capítulo posterior, donde veremos Spring Data y el mundo de los datos
sin esquema en Spring.
Pero lo primero es lo primero. La mayoría de las opciones de persistencia de Spring
dependen de un origen de datos. Por tanto, antes de comenzar a crear plantillas y reposi­
torios tenemos que configurar Spring con un origen de datos para poder acceder a la base
de datos.
318 Capítulo 10

Configurar un origen de datos


Con independencia de la forma de acceso a datos que utilice en Spring, lo más probable es
que tenga que configurar una referencia a un origen de datos. Spring ofrece varias opciones
para configurar bean de origen de datos en su aplicación Spring, incluyendo:

• Orígenes de datos definidos por un controlador JDBC.


• Orígenes de datos buscados por JNDI.
• Orígenes de datos que agrupan conexiones.

Para aquellas aplicaciones listas para el entorno de producción, le recomiendo utilizar


un origen de datos que obtenga sus conexiones de una agrupación. Siempre que es posible,
prefiero recuperar el origen de datos agrupado de un servidor de aplicaciones mediante
JNDI. Teniendo en cuenta esta preferencia, veamos cómo configurar Spring para recuperar
un origen de datos desde JNDI.

U tilizar orígenes de ciatos JNDI


Las aplicaciones Spring suelen implementarse para su ejecución en un servidor de apli­
caciones Java EE como WebSphere, JBoss o incluso en un contenedor Web como Tomcat.
Estos servidores le permiten configurar la obtención de los orígenes de datos mediante
JNDI. La ventaja que ofrece configurar los orígenes de datos de esta forma es que pueden
administrarse de forma completamente externa a la aplicación, lo que permite que ésta
solicite un origen de datos cuando esté lista para acceder a la base de datos. Asimismo,
los orígenes de datos gestionados en un servidor de aplicaciones suelen agruparse para
obtener un mayor rendimiento y los administradores de sistemas pueden intercambiarlos
directamente.
Con Spring, podemos configurar una referencia a un origen de datos que se va a
conservar en JNDI y conectarlo a las clases que necesite, como si fuera cualquier otro bean
de Spring. El elemento < j e e : j n d i - lo o ku p > del espacio de nombres j e e de Spring nos
permite recuperar cualquier objeto, incluyendo orígenes de datos desde JNDI, y los hace
disponibles en forma de bean de Spring. Por ejemplo, si el origen de datos de nuestra apli­
cación se configurara en JNDI, podríamos utilizar < j e e : jn d i- lo o k u p > de la siguiente
manera para conectarlo en Spring:
< je e : jndi-lookupid="dataSource"
jndi-name="/ jdbc/SpitterD S"
re so u rce-ref="tru e"/>

El atributo j n d i-ñ am e se utiliza para especificar el nombre del recurso en JNDI. El


origen de datos solo se va a buscar utilizando el nombre proporcionado si se ha configu­
rado la propiedad j n d i-ñ am e. Sin embargo, si la aplicación se está ejecutando dentro de
un servidor de aplicaciones Java, querrá establecer la propiedad r e s o u r c e - r e f en t r u e
para que el valor proporcionado en jn d i-n a m e se anteponga a j a v a : co m p t/e n v /.
Acceso a bases de datos con Spring y JDBC 319

Si usa configuración de Java puede utilizar Jn d iO b j e c t F a c t o r y B e a n para buscar el


D a t a S o u r c e en JNDI:

@Bean
publie JndiObjectFactoryBean dataSourceí) {
JndiObjectFactoryBean jndiObjectFB = new JndiObjectFactoryBean();
jndiObjectFB.setJndiName("jd bc/Sp ittrD S") ;
jndiO bjectFB.setResourceR ef(true);
jndiO bjectFB. setP ro xy ln terface(javax.sql.D ataSou rce. c l a s s ) ;
return jndiObjectFB;

Evidentemente, la configuración Java de bean obtenidos de JNDI es más compleja. En


muchos casos, es más sencilla que la de XML, pero en este caso concreto tendrá que crear
más código en Java. No obstante, se aprecia el paralelismo con su equivalente en XML, y
tampoco resulta una configuración excesiva.

Utilizar un origen de datos agrupado*•


Si no puede obtener un origen de datos desde JNDI, la segunda mejor opción es confi­
gurar un origen de datos agrupado directamente en Spring. Aunque Spring no proporciona
un origen de datos de este tipo, dispone de varias opciones, incluidas las siguientes de
código abierto:

• Apache Commons DBCP ( h t t p : / / j a k a r t a . a p a c h e . org/com m ons/dbcp).


• c3p0 (h t t p : / / s o u r c e f o r g e . n e t / p r o je c t s / c 3 p 0 / ).
• BoneCP (h t t p : / / jo lb o x .c o m / ).
Muchas de estas agrupaciones de conexiones se pueden configurar como origen de
datos en Spring de forma similar a D riv e rM a n a g e rD a ta S o u rce de Spring (del que
hablaremos en breve). Por ejemplo, podríamos configurar B a s ic D a ta S o u r c e de DBCP
de la siguiente manera:
<bean id ="dataSource" c la s s = "org. apache. commons. dbcp. BasicDataSource"
p: driverClassName=,,org.h2 .Driver"
p :u rl= "jd b c:h 2 : tc p : / /lo c a lh o s t/-/s p itte r "
p ;username="sa "
p:password=""
p : in itia lS iz e = " 5 "
p:maxActive="10" />

Si prefiere la configuración de Java, puede declarar el bean D a t a S o u r c e agrupado de


esta forma:
@Bean
publie BasicDataSource dataSource() {
BasicDataSource ds = new BasicDataSource();
d s. setDriverClassName("org.h2.D river" ) ;
d s. s e t ü r l("jd b c:h 2 : tc p : // lo c a lh o s t / - / s p it t e r " ) ;
320 Capítulo 10

d s. setUsername("s a ") ;
d s. setPassword( " " ) ;
d s. s e t ln i t i a lS i z e (5);
d s. setMaxActive(10);
return ds;
i
Las cuatro primeras propiedades son básicas para configurar un B a s i c D a t a S o u r c e .
La propiedad d r iv e r C la s s N a m e especifica el nombre completo cualificado de la clase
de controlador JDBC. En este caso, la hemos configurado con el controlador JDBC para la
base de datos H2. La propiedad u r l es donde configuramos la URL JDBC completa para
la base de datos. Por último, las propiedades u sern a m e y p a ss w o rd se utilizan para
autenticación cuando nos conectemos a la base de datos.
Estas cuatro propiedades básicas definen la inform ación de conexión para
B a s i c D a t a S o u r c e . Asimismo, podemos utilizar varias propiedades para configurar la
propia agrupación de orígenes de datos. En la tabla 10.3 se recogen algunas de las propie­
dades de configuración de B a s i c D a t a S o u r c e más útiles.

Tabla 10.3. Propiedades de configuración de agrupaciones de BasicDataSource.

Propiedad de configuración de agrupación Qué especifica

in itia lS iz e El número de conexiones que se crean cuando


se inicia la agrupación.
maxActive El número máximo de conexiones que pueden
asignarse para la agrupación a la vez. Si el valor
es cero, no hay límite.
m axldle El número máximo de conexiones que pueden
estar inactivas en la agrupación sin que tengan
que liberarse conexiones adicionales. Si el valor
es cero, no hay límite.
m axOpenPreparedStatem ents El número máximo de instrucciones preparadas que
pueden asignarse a la agrupación de instrucciones
a la vez. Si el valor es cero, no hay límite.
maxWait Tiempo de espera máximo de la agrupación para
que una conexión se devuelva a la misma (cuando
no hay conexiones disponibles) antes de que se
genere una excepción. Si el valor es - 1 , el tiempo
de espera es indefinido.
m in E v ic ta b le ld le T im e M illis El tiempo que una conexión va a permanecer
inactiva en la agrupación antes de que pueda
seleccionarse para su desalojo.
m in ld le El número mínimo de conexiones que pueden
permanecer inactivas en la agrupación sin que
se creen otras nuevas.
p o o lP rep a re d S ta tem en ts Establece si se van a agrupar las instrucciones
preparadas (valor booleano).
Acceso a bases de datos con Spring y JDBC 321

Para nuestros objetivos, hemos configurado la agrupación para que comience con cinco
conexiones. Si necesitamos más, B a s ic D a t a S o u r c e puede crear hasta un máximo de diez
conexiones activas.

Orígenes de datos basados en controladores jDBC


El origen de datos más sencillo que puede configurar en Spring es el definido mediante
un controlador JDBC. Spring ofrece tres de estas clases de origen de datos entre las que
puede elegir (del paquete o r g . s p r in g f ram ew ork. j dbc . d a ta s o u r c e ):

• D riv e M a n a g e rD a ta S o u rc e : Devuelve una nueva conexión cada vez que se solicita


una conexión. A diferencia de B a s i c D a t a S o u r c e de DBCP, las conexiones propor­
cionadas por D r iv e r M a n a g e r D a ta S o u r c e no están agrupadas.
• S im p le D r iv e r D a ta S o u r c e : Muy similar a D r iv e r M a n a g e r D a ta S o u r c e pero
trabaja directamente con el controlador JDBC para solucionar problemas de carga de
clases que pueden surgir en determinados entornos, como en un contenedor OSGi.
• S i n g le C o n n e c t io n D a t a S o u r c e : Devuelve la misma conexión cada vez que se
solicita una conexión. Aunque S in g le C o n n e c t io n D a t a S o u r c e no es exactamente
un origen de datos agrupado, puede pensar en él como un origen de datos, con una
agrupación que cuenta con solo una conexión.

La configuración de cualquiera de esos orígenes de datos se realiza de forma similar


a la que empleamos con B a s i c D a t a S o u r c e de DBCP. Por ejemplo, puede configurar el
bean D r iv e r M a n a g e r D a ta S o u r c e de esta forma:
@Bean
public DataSource dataSource() {
DriverManagerDataSource ds = new DriverManagerDataSource{);
ds .setDriverClassName ("org.h.2 .Driver") ;
d s .setUrl("j dbc:h2:tcp://localhost/-*/spitter");
d s .setüsername("sa");
d s .setPassword("");
return ds;
}
En XML, la configuración de D r iv e r M a n a g e r D a ta S o u r c e sería la siguiente:
cbean id="datasource"
c la s s = "org. springframework. j dbc. datasource. DriverManagerDataSource"
p : driverClassName= "org .h.2 .Driver"
p :u rl= "jd b c:h 2 : tc p : //lo c a lh o s t/-/s p itte r "
p:username="sa"
p:password="" />

La única diferencia de estos bean de origen de datos es que al no proporcionar una


agrupación de conexiones, no hay propiedades de agrupación que configurar. Aunque
estos orígenes de datos funcionan muy bien con aplicaciones pequeñas y ejecuciones
en desarrollo, quizás deba pensarlo dos veces antes de utilizarlos en una aplicación de
322 Capítulo 10

producción. Como S i n g le C o n n e c t io n D a t a S o u r c e solo tiene una conexión de base de


datos con la que trabajar, no va a trabajar bien en una aplicación con varios procesos. Al
mismo tiempo, aunque D r iv e r M a n a g e r D a ta S o u r c e y S im p le D r iv e r D a t a S o u r c e
pueden admitir varios subprocesos, lo hacen con un coste en términos de rendimiento al
crear una nueva conexión cada vez que se necesita una. Por estos motivos, le recomiendo
que utilice orígenes de datos agrupados.

Utilizar un origen de datos incrustado


Existe otro tipo de origen de datos que debe conocer: las bases de datos incrustadas.
Se ejecutan como parte de la aplicación y no como un servidor de bases de datos indepen­
diente al que la aplicación se conecta. Aunque no resulte demasiado útil en entornos de
producción, una base de datos incrustada es la opción perfecta para tareas de desarrollo y
pruebas, ya que se puede completar con datos de prueba cada vez que se reinicia la apli­
cación o se ejecuten las pruebas.
El espacio de nombres jd b c de Spring facilita considerablemente la configuración
de una base de datos incrustada. Por ejemplo, el siguiente código muestra cómo usar el
espacio de nombres jd b c para configurar una base de datos H2 incrustada precargada
con datos de prueba.

Listado 10.1. Configuración de una base de datos incrustada con el espacio de nombres jdbc.
<?xml v e rsio n = "l.0" encoding="UTF-
8"?> cbeans xmlns="h ttp : //www.springframework.org/schema/beans"
xm lns:xsi="h ttp : / /www.w3.org/2001/XMLSchema-instance"
xmlns: jdbc="http://www.springframework.org/schema/jdbc"
xmlns: c= "h ttp : / /www.springframework. org/schema/c"
x s i : schemaLocation="http://www.springframework.org/schema/jdbc
h ttp : //www.springframework.org/schem a/jdbc/spring-jdbc- 3 .1 .xsd
h ttp : //www.springframework.org/schema/beans
h tt p :/ /www.springframework.org/schema/beans/spring-beans.xsd" >

<j dbc .*embeddeddatabase


id="dataSource" type="H2"> <j dbc-.script location="com/habum
a/sp itter/d b /jd b c/sch em a.sq l"/> <jd bc: s c rip t location="com/habuma/sp
itte r /d b /jd b c /te s t-d a ta .s q l" /> < /jd b c: embedded-database>

</beans>

La propiedad ty p e de < j d b c : em b ed d ed -d atab ase> se establece en H2 para indicar


que la base de datos incrustada será H2 (no olvide incluir H2 en la ruta de clases de su
aplicación). También puede establecer ty p e en DERBY para usar una base de datos Apache
Derby incrustada.
En < j d b c : e m b e d d e d - d a t a b a s e > puede configurar cero o más elementos
< j dbc : s c r i p t > para configurar la base de datos. El listado 10.1 incluye dos: el primero
hace referencia a s c h e m a . s q l , que contiene el código SQL para crear las tablas de la base
Acceso a bases de datos con Spring y JDBC 323

de datos, y el segundo hace referencia a t e s t - d a t a . s q l , p a r a completar la base de datos


con datos de prueba. Además de establecer una base de datos incrustada, el elemento
< j d b c : e m b e d d e d -d a ta b a s e > también expone un origen de datos que se puede usar
como cualquier otro bean de origen de datos que ya conoce. El atributo i d se establece en
d a t a S o u r c e , que será el ID del bean de origen de datos expuesto. Por lo tanto, siempre
que necesite un j a v a x . s q l . D a t a S o u r c e , puede inyectar el bean d a t a S o u r c e .
Al configurar una base de datos incrustada en configuración de Java, no dispone del
espacio de nom bres jd b c . En su lugar tendrá que usar E m b e d d e d D a ta b a s e B u ild e r
para crear D a ta S o u r c e :
@Bean
public DataSource dataSource() {
return new EmbeddedDatabaseBuilder()
. setType(EmbeddedDatabaseType.H2)
. addScript("classp ath : schema. s q l")
.ad d Scrip t("c la s s p a th :te s t-d a ta . s q l")
.b u ild ();
i
Como puede apreciar, el método s e tT y p e () es el equivalente al atributo t y p e del
elemento < j d b c : e m b e d d e d d a ta b a s e > , y en lugar de usar el elemento < j d b c : s c r i p t >
para especificar código SQL de inicialización, se invoca a d d S c r i p t ( ) .

Utilizar perfiles para seleccionar un origen de datos


Hemos visto diversas formas de configurar orígenes de datos en Spring y seguramente
haya identificado alguna como la ideal para su aplicación. De hecho, probablemente vea
la necesidad de uno de estos bean de origen de datos en un entorno y de otros distintos
para otro.
Por ejemplo, el elemento < j d b c -.e m b e d d e d -d a ta b a s e > es perfecto para tareas de
desarrollo, pero puede que le interese usar B a s i c D a t a S o u r c e de DBCP en su entorno
de QA o < j e e : j n d i - lo o k u p > para las tareas de producción.
Los perfiles de bean de Spring, que describimos en un capítulo anterior, son perfectos
para esto. Basta con configurar cada uno de estos orígenes de datos en diferentes perfiles,
como se muestra a continuación.

Listado 10.2. Perfiles de Spring para seleccionar un origen de datos en tiempo de ejecución.
package com. habuma. s p i t t r . co n fig ;
import o rg . apache. commons. dbcp. BasicDataSource;
import j avax. s q l.DataSource;
import org.springframework.context. annotation.Bean;
import org.springframework.context. annotation.Configuration;
import o rg . springframework. co n tex t. annotation. P r o file ;
import
org. springframework. j dbc. datasource. embedded. EmbeddedDatabaseBuilder;
import
org. springframework. j dbc. datasource. embedded. EmbeddedDatabaseType;
324 Capítulo 10

import org. springframework. jn d i. JndiObjectFactoryBean;

@Conf igurat ion


public cla ss DataSourceConfiguration {

@ P ro file ("development") / / Origen de datos para d esarrollo .


@Bean
public DataSource embeddedDataSource() {
return new EmbeddedDatabaseBuilder()
. setType(EmbeddedDatabaseType.H2)
. addScript("classp ath : schema. s q l")
.add Scrip t(" c la s s p a th :te s t-d a ta .s q l")
.build () ;
}
@ P ro file ( "qa") / / Origen de datos para QA.
@Bean
public DataSource DataO {
BasicDataSource ds = new BasicDataSource( );
d s. setDriverClassName("org .h 2.D river") ;
d s. s e t ü r l(" jd b c:h 2 : tc p : // lo c a lh o s t/ ~ /s p itt e r " ) ;
ds . setüsername("s a ") ;
d s. setPassword( " " ) ;
d s. s e t ln i t i a lS i z e (5) ;
d s. setMaxActive(10) ;
return ds;
}
@ P ro file ("production") / / Origen de datos para producción.
@Bean
public DataSource dataSource() {
JndiObj ectFactoryBean j ndiObj ectFactoryBean
= new JndiObjectFactoryBean( );
jndiObj ectFactoryBean. setJndiName("j dbc/SpittrDS") ;
jndiO bjectFactoryBean.setResourceRef(true);
jndiO bjectFactoryBean.setProxylnterface(javax.sql.D ataSource. c l a s s ) ;
return (DataSource) jndiO bjectFactoryBean.getO bject();
}
}

Con los perfiles, el origen de datos se selecciona en tiempo de ejecución, en función


del perfil activo. Según la configuración del listado 10.2, la base de datos incrustada solo
se crea si está activo el perfil d e v e lo p m e n t. Del mismo modo, B a s i c D a t a S o u r c e de
DBCP solo se crea si el perfil qa está activo, y el origen de datos se recupera de JNDI solo
si el perfil p r o d u c t io n está activo.
Para completar el ejemplo, el siguiente código muestra la misma configuración contro­
lada por perfiles pero usando configuración XML en lugar de Java.

Listado 10.3. Configuración de orígenes de datos seleccionados mediante perfiles en XML.


<?xml version="1.0" encoding="UTF-
8"?> cbeans xmlns="h ttp : //www.springframework. org/schema/beans"
xm lns:xsi="http://www.w3. org/2001/XMLSchema-instance"
xmlns: j dbc="h ttp : / /www. springframework. org/schema/jdbc"
Acceso a bases de datos con Spring y JDBC 325

xmlns: je e = "h ttp : //www.springframework.org/schema/j ee"


xmlns:p="h ttp : / /www. springframework. org/schema/p"
x s i : schemaLocation="h ttp :/ /www.springframework. org/schema/jdbc
h ttp : //www.springframework.org/schema/jdbc/spring-jdbc-3.1 .xsd
h ttp :/ /www.springframework.org/schema/jee
http://www.springframework.org/schema/j ee/sp rin g -j e e -3 . 1 .xsd
h ttp : / /www.springframework. org/schema/beans
h ttp : / /www. springframework. org/schema/beans/spring-beans.xsd”>

cbeans profile="development”> / / Origen de datos para d esarrollo .


<jd b c: embeddeddatabase id="dataSource" type="H2">
cjd b c: s c rip t location="com/habuma/spitter/db/jdbc/schema. s q l"/>
<jd b c: s c rip t location="com /habum a/spitter/db/jdbc/test-data. s q l"/>
< /j dbc: embeddeddatabase>
</beans>
cbeans profile="q a"> / / Origen de datos para QA.
cbean id="dataSource"
class="org.apache. commons.dbcp.BasicDataSource"
p : driverCíassName=no rg .h2.Driver"
p :u rl= "jd b c:h 2 : tc p : //lo c a lh o s t/ ~ /s p itt e r "
p : username="sa "
p:password=""
p :in itia lS iz e = " 5"
p:maxActive="10" /> </beans>
cbeans p ro fi l e = "production"> / / Origen de datos para producción,
c je e : jn d i- lookup id="dataSource"
jndi-nam e="/jdbc/SpitterDS"
resource- re f= "true" /> </beans>
</beans>

Ahora que ya contamos con una conexión con la base de datos mediante un origen de
datos, estamos listos para acceder a la base de datos. Como ya he mencionado, Spring cuenta
con diferentes opciones para trabajar con bases de datos, incluyendo JDBC, Hibernate y
el API Java Persistence. En el siguiente apartado, aprenderemos a crear la capa de persis­
tencia de una aplicación Spring utilizando su compatibilidad con JDBC, y si le gusta más
trabajar con Hibernate o con JPA, no se preocupe, porque veremos cómo hacerlo en el
siguiente capítulo.

Utilizar JDBC con Sprin


Tiene a su disposición un gran número de tecnologías de persistencia: Hibernate, iBATIS
y JPA solo son algunas. A pesar de ello, hay muchas aplicaciones que crean objetos Java en
una base de datos utilizando el método antiguo.
¿Y por qué no? Después de todo, JDBC no requiere conocer el lenguaje de consultas
de otro marco de trabajo. Se construye sobre SQL, el lenguaje de acceso a datos. Además,
con JDBC puede ajustar el rendimiento de su acceso a datos de una forma que no puede
hacer con ninguna otra tecnología. Del mismo modo, JDBC le permite sacar partido a las
características exclusivas de su base de datos, mientras que otros marcos de trabajo pueden
no recomendarlo o incluso prohibirlo.
326 Capítulo 10

Es más: JDBC le permite trabajar con sus datos a un nivel mucho menor que el que ofrecen
los marcos de trabajo de persistencia, lo que le permite acceder y manipular columnas
individuales en una base de datos. Este enfoque individual hacia el acceso a datos es útil
en aplicaciones como las de notificación, en las que no tiene sentido organizar los datos en
objetos para, a continuación, volver a convertirlos en datos.
Sin embargo, no todo son ventajas en JDBC. Su potencia y flexibilidad y otras ventajas
también implican una serie de desventajas.

Gestionar código JDBC clescontrolado


Aunque JDBC le proporciona un API que trabaja mano a mano con su base de datos,
tendrá que encargarse de gestionar todos los aspectos relacionados con el acceso a la base
de datos, como los recursos de la base de datos y las excepciones. Si ha escrito código
JDBC que inserta datos en una base de datos, el siguiente ejemplo no debería resultarle
extraño.

Listado 10.4. Uso de JDBC para insertar una fila en una base de datos.

p riv ate s t a t ic f in a l Strin g SQL_INSERT_SPITTER =


"in se rt into s p itte r (username, password, fullname) values (?, ?, ?) " ;
priv ate DataSource dataSource;
public void ad d Sp itter(Sp itter s p itte r) {
Connection conn = n u ll;
PreparedStatement stmt = n u ll;
try {
conn = dataSource.getConnection( ) ; / / Obtener conexión
stmt = conn.prepareStatement(SQL_INSERT_SPITTER); / / Crear in stru cción ,
stm t. s e t s tr in g (1, spitter.getU sernam e( ) ) ; / / Vincular parámetros,
stm t. s e t s tr in g (2, s p i t t e r . getPassword( ) ) ;
stm t. s e t s tr in g (3, s p i t t e r . getFullName 0 ) ;
stm t. execu te( ) ; / / E jecu tar in stru cción .
} catch (SQLException e) {
/ / Hacer algo, no estoy seguro de qué
} fin a lly }
try {
i f (stmt != null) { / / Tareas de limpieza,
stm t. c lo s e () ;
}
i f (conn != null) {
co n n .clo se();
}
} catch (SQLException e) {
/ /Aquí tengo incluso menos idea de lo que hacer
}
}
i

Vaya, hemos utilizado más de 20 líneas de código para insertar un objeto en la base de
datos. En lo que concierne a JDBC, es tan simple como parece, así que ¿por qué necesitamos
tanto código para hacer una tarea tan sencilla? En realidad, no lo necesitamos. La inserción
Acceso a bases de datos con Spring y JDBC 327

se lleva a cabo mediante solo unas pocas líneas de código. Sin embargo, JDBC requiere
que gestione de manera adecuada las conexiones y las instrucciones y, de alguna forma, la
excepción S Q L E x c e p tio n que puede generarse.
Hablando de S Q L E x c e p tio n : no solo no está claro cómo debe gestionarla (porque no
queda claro qué ha ido mal), sino que además está obligado a capturarla dos veces. Debe
hacerlo una vez si se produce un error al insertar el registro, y una segunda vez si algo va
mal al cerrar la instrucción y la conexión. Mucho trabajo para gestionar algo que, por lo
general, no puede gestionarse mediante programación.
Ahora, echemos un vistazo al listado 10.5, donde hemos utilizado JDBC tradicional para
actualizar una fila en la tabla S p i t t e r de la base de datos.

Listado 10.5. Uso de JDBC para actualizar una fila en la base de datos.

prívate static final String SQL_UPDATE_SPITTER =


"update spitter set username = ?, password = ?, fullname = ?"
+ "where id = ?";
public void sa v e S p itte r(S p itte r s p itte r) {
Connection conn = nuil;
PreparedStatement stmt = nuil;
try {
conn = dataSource.getConnection(); // Obtener conexión.
stmt = conn.prepareStatement(SQL_UPDATE_SPITTER); // Crear instrucción.
stmt.setString(1, spitter.getUsername()); // Vincular parámetros.
,
stmt.setString(2 spitter.getPassword());
stmt.setString(3, spitter.getFullName () );
stmt.setLong(4, spitter.getld());
stmt.execute(); // Ejecutar instrucción.
} catch (SQLException e) {
//Sigo sin tener claro qué tengo que hacer aquí
} finally {
try {
i f (stmt != nuil) { // Tareas de limpieza.
stmt.cióse();
}
if (conn != nuil) {
conn.cióse();
}
} catch (SQLException e) {
/ / Ni aquí
}
}
}
Aprimera vista, el listado 10.5 puede parecer idéntico al listado 10.4. De hecho, obviando
el S t r i n g SQL y la línea en la que se crea la instrucción, lo son. De nuevo, es mucho código
para hacer algo tan sencillo como actualizar urna sola fila de una base de datos. Además, hay
mucho código repetido. Lo ideal es que solo tuviéramos que escribir las líneas específicas
para la tarea que estamos llevando a cabo. Después de todo, son las que van a distinguir
el listado 10.5 del 10.4. El resto es código reutilizable.
Para finalizar nuestro paseo por JDBC convencional, veamos cómo recuperar datos de
la base de datos. Como se muestra a continuación, no es una forma muy elegante.
328 Capítulo 10

Listado 10.6. Uso de JDBC para realizar una consulta sobre una fila de una base de datos.
prívate s t a t ic fin a l String SQL_SELECT_SPITTER =
"s e le c t id, username, fullname from s p itte r where id = ? ";
public S p itte r getSpitterByld(long id) {
Connection conn = n u ll;
PreparedStatement stmt = n u ll;
ResultSet rs = n u ll;
try {
conn = dataSource.getConnection(); // Obtener conexión,
stmt = conn.prepareStatement(SQL_SELECT_SPITTER); / / Crear in stru cción ,
stm t. setLong(1, id ); // Vincular parámetros,
rs = stm t. executeQuery( ) ; // E jecu tar consulta.
S p itte r s p itte r = n u ll;
i f (rs.n e x tO ) { // Procesar lo s resultados,
s p itte r = new S p itt e r ( );
s p i t t e r . s e t I d ( r s .getLong(" id ") ) ;
sp itter.setU sern am e(rs.g etStrin g ("username")) ;
s p i t t e r . setPassword(rs.g e tS trin g ("password") ) ;
sp itter.setF u llN am e(rs.g etStrin g ("fu lln am e")) ;
}
return s p itte r ;
} catch (SQLException e) { / / Gestionar excepciones.
} f in a lly {
/ / Tareas de limpieza.
i f ( r s != null) {
try {
r s . c lo s e ( );
} ca tch (SQLException e) {}
}
if(stm t != null) {
try {
stm t. c lo s e ();
} catch(SQLException e) {}
}
if(conn != null) {
try {
co n n .clo se();
} catch(SQLException e) {}
}
}
return n u ll;
}

Como puede ver, este ejemplo es tan profuso como los de inserción y actualización.
Aproximadamente, el 20 por cien del código se utiliza para realizar la consulta sobre la
fila, mientras que el 80 por cien restante es código reutilizable.
Llegados a este punto, se habrá dado cuenta de que gran parte del código JDBC es
reutilizable y se usa para la creación de conexiones e instrucciones, así como para el manejo
de excepciones. Como esto ha quedado claro, vamos a terminar la tortura aquí y no le
obligaremos a leer más este código. Sin embargo, lo cierto es que el código reutilizable es
importante. Limpiar los recursos y gestionar los errores es lo que convierte en robusto al
Acceso a bases de datos con Spring xj JDBC 329

acceso de datos. Sin éste, los errores no se detectarían y los recursos quedarían abiertos, lo
que generaría código no predecible y fugas de recursos. No solo necesitamos este código,
sino que además debemos asegurarnos de que es correcto. Es el motivo por el que vamos
a permitir que el marco de trabajo se encargue del código reutilizable para que sepamos
que se ha escrito una vez y bien.

Trabajar con plantillas JDBC


El marco de trabajo JDBC de Spring va a encargarse de limpiar su código JDBC y le
librará de la carga de administrar los recursos y de encargarse del manejo de excepciones.
Esto le deja libre para escribir solo el código que necesita para mover datos dentro y fuera
de la base de datos. Como he explicado antes, Spring abstrae el código reutilizable de
acceso a datos tras clases de plantilla. Para JDBC, Spring cuenta con tres clases de plantilla
entre las que puede elegir:

• Jd b c T e m p la te : Es la plantilla JDBC de Spring más básica. Esta clase proporciona


un acceso sencillo a una base de datos mediante JDBC y consultas de parámetros
indexados.
• N a m e d P a ra m e te rsJd b c T e m p la te : Esta clase de plantilla JDBC le permite realizar
consultas cuando los valores están vinculados a parámetros con nombre en SQL, en
lugar de a parámetros indexados.
• S im p le Jd b c T e m p la te : Esta versión de la plantilla JDBC saca partido a las carac­
terísticas de Java 5 como la creación automática de paquetes, elementos genéricos y
listas de parámetros variables, que permiten simplificar el uso de la plantilla JDBC.

Antes, era necesario seleccionar la plantilla JDBC con prudencia. Sin embargo, desde
Spring 3.1, esta decisión es mucho más sencilla. S im p le J d b c T e m p la t e ha quedado
obsoleta y sus funciones de Java 5 se han pasado a Jd b c T e m p la te . Es más, solo necesita
N am ed P aram eter J d b c T e m p la te para trabajar con parámetros con nombre en consultas,
lo que nos deja con Jd b c T e m p la t e como mejor opción para trabajar con JDBC, por lo que
la describiremos en el siguiente apartado.

Añadir datos con JdbcTemplate


Todo lo que J d b c T e m p la t e necesita para trabajar es D a t a S o u r c e . Esto facilita la
configuración de un bean Jd b c T e m p la t e en Spring con el siguiente código XML:
@Bean
public JdbcTemplate jdbcTemplate(DataSource dataSource) {
return new JdbcTemplate(dataSource);
}
Aquí, D a ta S o u r c e se añade m ediante inyección de constructores. El bean d a t a S o u r c e
al que se hace referencia puede ser cualquier implementación de j a v a x . s q l . D a ta S o u rc e ,
incluyendo las creadas en un apartado anterior.
330 Capítulo 10

Ahora, podemos conectar el bean jd b c T e m p la te a nuestro repositorio y utilizarlo


para acceder a la base de datos. Por ejemplo, suponga que el repositorio Spitter se escribe
para que utilice J d b c T e m p la te :
@Repository
public class JdbcSpitterRepository implements SpitterRepository {

prívate JdbcOperations jdbcOperations;

@Inject
public JdbcSpitterRepository{JdbcOperations jdbcOperations) {
this.jdbcOperations = jdbcOperations;
}

i
Aquí, se anota J d b c S p i t t e r R e p o s i t o r y con @ R e p o s i t o r y para que se cree auto­
máticamente durante el análisis de componentes, y su constructor se anota con @ ln j e c t
para que al crearse se le asigne un objeto J d b c O p e r a t io n s , el cual es una interfaz que
define operaciones implementadas por J d b c T e m p la te . Al inyectar J d b c O p e r a t io n s en
lugar de Jd b c T e m p la t e , J d b c S p i t t e r R e p o s i t o r y puede conservar su acoplamiento
débil con J d b c T e m p la t e a través de la interfaz J d b c O p e r a t io n s .
Como alternativa al análisis de componentes y a la conexión automática, podría declarar
explícitamente J d b c S p i t t e r R e p o s i t o r y como bean en Spring, de esta forma:
@Bean
public SpitterRepository SpitterRepository(JdbcTemplate jdbcTemplate) {
return new JdbcSpitterRepository(jdbcTemplate);
i
Con Jd b c T e m p la te a disposición de nuestro repositorio podemos simplificar de forma
considerable el método a d d S p i t t e r ( ) del listado 10.4. El nuevo método a d d S p i t t e r ( ) ,
basado en Jd b c T e m p la t e , se muestra a continuación.

Listado 10.7. Un método addSpitter() basado en JdbcTemplate.


public void addSpitter (Spitter spitter){
jdbcOperations.update(INSERT_SPITTER, // Insertar Spitter.

spitter.getüsername(),
spitter.getPassword() ,
spitter.getFullName(),
spitter.getEmail() ,
spitter.isüpdateByEmail());
i
Seguro que está de acuerdo conmigo en que esta versión de a d d S p i t t e r () es mucho
más sencilla. No hay código de creación de instrucciones ni de conexión. Tampoco
tenemos código de control de excepciones. No hay nada excepto el código utilizado para la
inserción. Que no veamos el código reutilizable no quiere decir que no esté ahí. De hecho,
Acceso a bases de datos con Spring y JDBC 331

se encuentra inteligentemente oculto en la clase de plantilla JDBC. Al invocar el método


u p d a te ( ) , Jd b c T e m p la te obtiene una conexión, crea una instrucción y ejecuta la inserción
SQL. Lo que tampoco ve es la forma en que se gestiona S Q L E x c e p tio n . A nivel interno,
J d b c T e m p la t e captura cualquier S Q L E x c e p tio n que se produzca. A continuación,
traduce la S Q L E x c e p tio n genérica en una o más de las excepciones de acceso a datos de la
tabla 10.1 y la vuelve a generar. Como las excepciones de acceso a datos de Spring se refieren
al tiempo de ejecución, no hemos tenido que capturarlas en el método a d d S p i t t e r ( ) .

Leer ciatos con JdbcTemplate


La lectura de datos también se simplifica con JdbcTemplate. El listado 10.8 muestra
una nueva versión de f indOne () que utilizaretrollamadas JdbcTemplate para consultar
un objeto Spitter por ID y asignar el conjunto de resultados a un objeto Spitter.

Listado 10.8. Una consulta de Spitter mediante JdbcTemplate.


public S p itte r findone(long id) {
return jdbcOperations.queryForObject( / / Consulta de S p itte r.
SELECT_SPITTER_BY_ID/ new SpitterRowMapper(),
Id / / Asignar resultados a l objeto
);
}

private s t a t ic f in a l cla ss SpitterRowMapper


implements RowMapper<Spitter> {
public S p itte r mapRow(ResultSet r s , in t rowNum)
throws SQLException {

return new S p it t e r ( / / Vincular parámetros,


r s . getLong("id " ),
r s . g e tS trin g ("username"),
r s .g e tS trin g ("password") ,
r s . g e tS trin g ("fullName"),
r s .g e tS tr in g ("em ail"),
r s .getBoolean("updateByEmail" ) ) ;
}
}

Este método f ind O ne () utiliza el método q u e r y F o r O b je c t () de Jd b c T e m p la te


para ejecutar una consulta para S p i t t e r en la base de datos. El método q u e r y F o r
Ob j e c t () acepta tres parámetros:

• Un S t r i n g que contiene el código SQL que utilizar para seleccionar los datos de la
base de datos.
• Un objeto RowMapper que extrae valores de R e s u l t S e t y crea un objeto de dominio
(en este caso, S p i t t e r ) .
• Una lista de argumentos de variables que vincular a los parámetros indexados de la
consulta.
332 Capítulo 10

La verdadera m agia tiene lugar en el objeto S p itte rR o w M a p p e r, que im plem enta la


interfaz RowMapper. Para cada fila que se obtiene con la consulta, Jd b c T e m p la t e invoca
el método mapRow () de RowMapper, y pasa un R e s u l t S e t y un entero con el número de
la fila. En el m étodo mapRow () de S p itte rR o w M a p p e r se encuentra el código que crea
un objeto S p i t t e r y lo com pleta con valores de R e s u l t S e t .
Al igual que a d d S p i t t e r ( ) , el m étodo f indO ne () no incluye código reutilizable
JDBC. A diferencia del código JDBC tradicional, no hay código de control de excepciones
ni de adm inistración de recursos. Los métodos que utilizan Jd b c T e m p la t e se centran en
la recuperación de un objeto S p i t t e r de la base de datos.

Usar expresiones ¡arríbela de java. 8 con JdbcTemplate


Como la interfaz RowMapper solo declara el método addRow ( ), es perfecta como
interfaz funcional. Por ello, si desarrolla su aplicación con Java 8, puede expresar la imple-
mentación de RowMapper con una expresión lambda en lugar de usar una implementación
de clase concreta.
Por ejemplo, el método f in d O n e () del listado 10.8 se puede volver a escribir con
expresiones lambda de Java 8:
public Spitter findOnedong id) {
return jdbcOperations.queryForObj e c t {
SELECT_SPITTER_BY_ID,
(rs, rowNum) -&gt; {
return new Spitter(
r s .getLong("id"),
r s .getString("username"),
r s .getString("password"),
rs .getString (11fullName") ,
rs.getString("email"),
rs.getBoolean("updateByEmail"));
L
id) ;
}
Comprobará que la expresión lambda es mucho más sencilla de leer que la implementa­
ción completa de RowMapper pero resulta igual de eficaz. Java añade la expresión lambda
a RowMapper para satisfacer el parámetro al que se pasa.
Podría también usar referencias de métodos de Java 8 para definir la asignación en un
método independiente:
public Spitter findOne(long id) {
return jdbcOperations.queryForObject(
SELECT_SPITTER_BY_ID, this::mapSpitter, id);
}
private Spitter mapSpitter(ResultSet rs, int row) throws SQLException {
return new Spitter{
r s .getLong("id"),
r s .getString("username"),
r s .getString{"password"),
Acceso a bases de datos con Spring y JDBC 333

r s . g e tS trin g ("fullName"),
r s .g e tS tr in g ("em ail"),
r s . getBoolean("updateByEmail" ) ) ;

En cualquier caso, no tiene que implementar explícitamente la interfaz RowMapper.


Tendrá que proporcionar una expresión lambda o un método que acepte los mismos pará­
metros y que devuelva el mismo tipo que si hubiera implementado RowMapper.

Usar parámetros con nombre


El método a d d S p it t e r () del listado 10.7 utiliza p a r á m e t r o s indexados. Esto quiere
decir que debemos tener en cuenta el orden de los parámetros de la consulta e incluir los
valores en el orden correcto al proporcionarlos al método u p d a te () . Si cambiáramos el
código SQL de tal forma que el orden de los parámetros variase, también tendríamos que
cambiar el orden de los valores.
De forma opcional podríamos utilizar parámetros con nombre, que nos permiten asignar
a cada parámetro del código SQL un nombre explícito y hacer referencia al parámetro por
ese nombre al vincular valores con la instrucción. Por ejemplo, supongamos que la consulta
SQ L_IN SERT_SPITTER se definiese de la siguiente manera:

prívate s t a t ic f in a l String SQL_INSERT_SPITTER =


"in se rt in to s p itte r (username, password, fullname) " +
"valúes (:username, rpassword, ifullnam e)";

Si utilizamos consultas con parámetros con nombre, el orden de los valores vinculados
no es importante. Podemos vincular cada valor por nombre. Si la consulta se modifica y el
orden de los parámetros ya no es el mismo, no tendremos que modificar el código vinculante.
N a m e d P a r a m e te r Jd b c T e m p la te es una clase de plantilla especial de JDBC que
permite trabajar con parámetros con nombre y que en Spring se puede declarar de forma
muy similar a J d b c T e m p la te :
@Bean
public NamedParameterJdbcTemplate jdbcTemplate(DataSource dataSource) {
return new NamedParameterJdbcTemplate(dataSource);
}
Si hubiera inyectado N am ed P aram eter Jd b c O p e r a t io n s (la interfaz que implementa
N a m e d P a ra m e te rJd b c T e m p la te ) en su repositorio en lugar de J d b c O p e r a t io n s , el
método a d d S p i t t e r () sería el siguiente.

Listado 10.9. Uso de parámetros con nombre con plantillas JDBC de Spring.

prívate s t a t ic fin a l String INSERT_SPITTER =


"in se rt into S p itte r " +
" (username, password, fullname, email, updateByEmail) " +
"values " +
" (fusername, :password, rfullname, : email, : updateByEmail)";

public void ad d Sp itter(Sp itter s p itte r) {


334 Capítulo 10

Map<String, Object> paramMap = new HashMap<String, Object>();


paramMap.put("username”, spítter.getUsername()); // Vincular parámetros.
paramMap.put("password", spitter.getPassword());
paramMap.put("fulIname", spitter.getFullName 0);
paramMap.put{"email", spitter.getEmail());
paramMap.put("updateByEmail", spitter.isüpdateByEmailO);

jdbcOperations.update(INSERT_SPITTER, paramMap)? // Realizar inserción.


}
Lo primero que va a advertir en esta versión de addSpitter () es que es más extensa
que la anterior. Se debe a que los parámetros con nombre están vinculados a java.útil.
Map. En cualquier caso, cada línea se centra en el objetivo de insertar un objeto Spitter
en la base de datos. Además, no hay código de control de excepciones ni de administración
de recursos que afecte al objetivo principal del método.

Resumen
Los datos son la sangre de una aplicación. De hecho, los datacéntricos podemos afirmar
que los datos son la aplicación. Teniendo en cuenta su importancia, es fundamental desa­
rrollar el segmento de acceso a datos de nuestras aplicaciones de una forma que sea sólida,
sencilla y clara.
JDBC es la forma más sencilla de trabajar con datos relaciónales en Java, pero de acuerdo
a su definición en la especificación, puede resultar un tanto difícil de manejar. Spring facilita
considerablemente el trabajo con JDBC, elimina el código predefinido y simplifica el control
de excepciones para que solo tenga que crear el código SQL que realmente debe ejecutarse.
En este capítulo hemos visto la compatibilidad de Spring con la persistencia de datos,
además, de su abstracción de JDBC basada en plantillas, lo que simplifica considerable­
mente las tareas con JDBC.
En el siguiente capítulo continuaremos nuestro viaje por la persistencia de datos en
Spring y veremos las opciones que ofrece para JPA, el API de persistencia de Java.
Capítulo

11 P ersisten cia de
d a to s con a sig n a c ió n
relacional de objetos

CONCEPTOS FUNDAMENTALES:

• Spring e Hibernate.
• Repositorios sin Spring mediante sesiones contextúales.
• Uso de JPA con Spring.
• Repositorios JPA automáticos con Spring Data.
Cuando éramos niños, montar en bicicleta era divertido. íbamos a la escuela por la
mañana y, cuando terminábamos, íbamos a casa de nuestro mejor amigo. Cuando era tarde
y nuestros padres empezaban a gritarnos, pedaleábamos de vuelta a casa.
Después, seguimos creciendo y ahora necesitamos algo más que una bici. A menudo,
tenemos que conducir largas distancias para ir a trabajar. Tenemos que traer la compra a
casa y llevar a nuestros hijos al entrenamiento de fútbol. Simplemente, nuestras necesidades
han superado lo que nuestras bicis pueden ofrecernos. JDBC es la bici del mundo de la
persistencia. En lo que hace es muy bueno y en algunas tareas funciona de forma correcta.
Sin embargo, a medida que nuestras aplicaciones se vuelven cada vez más complejas,
sucede lo mismo con nuestros requisitos de persistencia. Tenemos que poder asignar propie­
dades de objetos con columnas de bases de datos y no tener que crear las instrucciones y
las consultas, de forma que evitemos la pesada tarea de tener que escribir una inacabable
cadena de signos de interrogación. Asimismo, necesitamos características más sofisticadas:

• Carga diferida: A medida que nuestros gráficos de objetos se vuelven más complejos,
no vamos a querer obtener todas las relaciones de forma inmediata. Por ejemplo,
supongamos que estamos seleccionando una colección de objetos P u r c h a s e O r d e r
y cada uno de ellos contiene una colección de objetos L in e lte m . Si solo estamos
interesados en los atributos P u r c h a s e O r d e r , no tiene sentido obtener los datos
L in e lte m . La carga diferida nos permite seleccionar los datos.
• Carga activa: El opuesto a la carga diferida. Le permite seleccionar un gráfico de objeto
completo en una consulta. En los casos en los que sepamos que vamos a necesitar
un objeto P u rch a seO rd er y sus L in e lte m asociados, la carga activa nos permite
obtenerlos desde la base de datos con una sola operación, lo que nos ahorra tiempo.
• Secuenciación: A menudo, los cambios que se producen en la tabla de una base de
datos deberían traducirse en cambios en otras tablas. Volviendo a nuestro ejemplo del
pedido de compra, cuando se elimina un objeto O rd e r, también queremos eliminar
los L in e l t e m asociados en la base de datos.

Dispone de varios marcos de trabajo que proporcionan estos servicios. Su nombre


general es Asignación relacional de objetos (ORM). Si utiliza una herramienta ORM en su
capa de persistencia, podrá ahorrar miles de líneas de código y muchas horas de tiempo
de desarrollo. Esto le permite centrarse en el código SQL, en el que suelen producirse más
errores, para hacer frente a los requisitos de su aplicación. Spring es compatible con varios
marcos de trabajo de persistencia, como Hibernate, iBATIS, JDO (Java D ata O bjects, Objetos
de datos Java) y JPA (A P I Java P ersisten ce, API de persistencia de Java).
Al igual que con la compatibilidad de Spring con JDBC, cuenta con puntos de integración
con los marcos de trabajo ORM, así como algunos servicios adicionales:

• Compatibilidad integrada para transacciones declarativas de Spring.


• Gestión de excepciones transparente.
• Clases de plantilla ligeras y compatibles con subprocesos.
• Clases de apoyo DAO.
• Gestión de recursos.
338 Capítulo 11

No dispongo de espacio en este capítulo para hablar de todos los marcos de trabajo
ORM compatibles con Spring. Esto no supone muchos problemas, ya que la compatibilidad
de Spring con los diferentes marcos de trabajo es similar. Una vez que sepa utilizar uno,
le será muy fácil pasar a otro.
Vamos a comenzar echando un vistazo a cómo se integra Spring con dos de los marcos
de trabajo ORM más populares: Hibernate y JPA. También entraremos en contacto con
el proyecto Spring Data por medio de Spring Data JPA. No solo verá cómo Spring Data
JPA reduce gran parte del código predefinido de sus repositorios JPA, sino que también
contará con la base necesaria para el siguiente capítulo, en el que usaremos Spring Data
para opciones de almacenamiento sin esquemas, pero antes nos detendremos en la compa­
tibilidad de Spring con Hibernate.

Integración de Hibernate con Spring____________


Hibernate es un marco de trabajo de persistencia de código abierto que se ha hecho
popular entre la comunidad de desarrolladores. No solo le proporciona asignación relacional
de objetos básica, sino también otras características sofisticadas que esperaría encontrar
en una herramienta ORM completa, tales como almacenamiento en caché, carga diferida,
carga activa y almacenamiento en caché distribuido.
En este apartado vamos a centrarnos en la forma en que Spring se integra con Hibernate,
sin detenernos demasiado en los detalles del manejo de Hibernate. Si necesita más
información, le recomiendo que visite el sitio Web del marco de trabajo ( h t t p : / /www.
h i b e r n a t e . org).

Declarar una sesión de fábrica de Hibernate*•


De forma nativa, la interfaz principal para trabajar con Hibernate es o r g . h i b e r n a t e .
S e s s io n . La interfaz S e s s i o n proporciona funcionalidades básicas de acceso a datos, como
por ejemplo la capacidad de guardar, actualizar, eliminar y cargar objetos desde la base de
datos. El repositorio de una aplicación va a cubrir todas sus necesidades de persistencia a
través de S e s s i o n de Hibernate.
El método estándar para conseguir una referencia a un objeto S e s s i o n de Hibernate es
mediante una implementación de la interfaz S e s s i o n F a c t o r y de Hibernate. Entre otras
cosas, S e s s i o n F a c t o r y es la encargada de abrir, cerrar y gestionar S e s s i o n de Hibernate.
En Spring, la forma de conseguir un S e s s i o n F a c t o r y de Hibernate es mediante uno
de los bean de fábrica de sesión de Hibernate de Spring. Desde la versión 3.1, Spring ofrece
tres bean de fábrica de sesión entre los que elegir:

• o r g . s p r in g f r a m e w o r k . o r m . h i b e r n a t e 3 . L o c a l S e s s i o n F a c t o r y B e a n
• o r g . s p r in g fr a m e w o r k . o rm . h i b e r n a t e 3 . a n n o t a t i o n . A n n o ta tio n
S e s s io n F a c to ry B e a n
• o r g . s p r in g f r a m e w o r k . o r m . h i b e r n a t e 4 . L o c a l S e s s i o n F a c t o r y B e a n
Persistencia de datos con asignación relacional de objetos 339

Son implementaciones de la interfaz F a c t o r y B e a n de Spring, que producen un


S e s s i o n F a c t o r y de Hibernate cuando se conecta a cualquier propiedad de tipo
S e s s i o n F a c t o r y . Esto permite configurar su fábrica de sesión de Hibernate junto con el
resto de bean del contexto de Spring de su aplicación.
La elección del bean de fábrica de sesión que usar dependerá de la versión de Hibernate
que utilice y si tiene pensado definir la asignación de objetos a la base de datos en XML o
mediante anotaciones. Si utiliza Hibernate 3.2 o superior (hasta Hibernate 4.0, exclusive) y
realiza la asignación en XML, tendrá que configurar L o c a l S e s s i o n F a c t o r y B e a n desde
el paquete o r g . s p r i n g f ra m ew o rk . orm . h i b e r n a t e 3 en Spring:

@Bean
public LocalSessionFactoryBean sessionFactory(DataSource dataSource) {
LocalSessionFactoryBean sfb = new LocalSessionFactoryBean();
s f b . setDataSource(dataSource);
sfb.setMappingResources(new Strin g [] { "S p itte r .hbtn.xml" } ) ;
Properties props = new P ro p ertie s();
props. setP roperty(" d ia le c t", "o rg .h ibern ate. d ia lect.H 2 D ia lect") ;
s f b . setH ibernateProperties(props);
return sfb ;
}

En este caso, L o c a l S e s s i o n F a c t o r y B e a n se configura con tres propiedades. La


propiedad d a t a S o u r c e se conecta con urna referencia a un bean D a t a S o u r c e . m apping
R e s o u r c e s muestra uno o más archivos de asignación de Hibernate que definen la
estrategia de persistencia para la aplicación. Por último, h i b e r n a t e P r o p e r t i e s nos
permite configurar los aspectos del funcionamiento de Hibernate. En este caso, estamos
indicando que Hibernate va a trabajar con una base de datos H2 y que debe utilizar
H 2 D ia l e c t para crear el código SQL. Si prefiere la persistencia orientada a anotaciones
y si no utiliza Hibernate 4, tendrá que utilizar A n n o t a t i o n S e s s i o n F a c t o r y B e a n en
lugar de L o c a l S e s s i o n F a c t o r y B e a n :

@Bean
public AnnotationSessionFactoryBean sessionFactory(DataSource ds) {
AnnotationSessionFactoryBean sfb = new AnnotationSessionFactoryBean();
s f b . setDataSource(ds);
s f b . setPackagesToScan(new Strin g [] { "com.habuma.spittr.domain" } ) ;
Properties props = new P ro p erties( );
props. setP roperty("d ia le c t", "o rg .h ibern ate. d ia le c t.H2Dialect" ) ;
s f b . setH ibernateProperties(props);
return sfb ;
}

Si utiliza Hibernate 4, debe usar L o c a l S e s s i o n F a c t o r y B e a n del paquete o r g .


s p r i n g f ram ew o rk . orm . h i b e r n a t e 4 . Aunque comparta el nombre con L o c a l S e s s i o n
F a c t o r y B e a n del paquete Hibernate 3, este nuevo bean de fábrica de sesión añadido en
Spring 3.1 es una especie de híbrido entre L o c a l S e s s i o n F a c t o r y B e a n de Hibernate 3 y
A n n o t a t io n S e s s io n F a c t o r y B e a n . Tiene muchas de las mismas propiedades y se puede
configurar tanto para asignaciones basadas en XML como en anotaciones. A continuación
le mostramos su configuración para asignaciones basadas en anotaciones:
340 Capítulo 11

@Bean
public LocalSessionFactoryBean sessionFactory(DataSource dataSource) {
LocalSessionFactoryBean sfb = new LocalSessionFactoryBean();
s f b . setDataSource(dataSource);
sfb.setPackagesToScan(new Strin g [] { "com.habuma. s p i t t r . domain" } ) ;
Properties props = new P ro p ertie s();
props. setP roperty("d ia le c t", "org.h ibern ate.d ia le c t.H2Dialect" ) ;
s f b . setH ibernateProperties(props);
return sfb ;
}
Las propiedades d a t a S o u r c e e h i b e r n a t e P r o p e r t i e s indican la ubicación de la
conexión con la base de datos y el tipo de base de datos con la que vamos a trabajar. Sin
embargo, en lugar de mostrar los archivos de asignación de Hibernate, podemos utilizar
la propiedad p a c k a g e s T o S c a n para indicar a Spring que analice uno o más paquetes en
busca de las clases de dominio que están anotadas para la persistencia en Hibernate. Esto
incluye aquellas anotadas con @ E n t i t y o @ M a p p e d S u p p e rcla ss de JPA y la anotación
@ E n t i t y de Hibernate. Si lo prefiere, puede mostrar de forma explícita todas las clases
persistentes de su aplicación especificando una lista de nombres de clases cualificadas
completas en la propiedad a n n o t a t e d C l a s s e s :
s f b . setAnnotatedClasses(
new Class<?>[] { S p itt e r . c l a s s , S p i t t le . cla ss }
);
La clase a n n o t a t e d C l a s s e s es adecuada para seleccionar clases de dominio. En
caso contrario, es mejor utilizar p a c k a g e s T o S c a n , ya que permite seleccionar una gran
cantidad de clases de dominio, no tiene que mostrarlas todas y va a disponer de la libertad
de añadirlas o eliminarlas sin tener que acceder de nuevo a la configuración de Spring.
Con un bean de fábrica de sesión de Hibernate declarado en el contexto de aplicación
de Spring, estamos listos para comenzar a crear nuestras clases DAO.

Hibernate sin código de Spring


En los inicios de Spring e Hibernate, para crear una clase de repositorio era necesario
trabajar con H ib e r n a t e T e m p la te de Spring, que garantizaba el uso de una única sesión
de Hibernate por transacción. El inconveniente de este enfoque es que la implementación
del repositorio estaría directamente acoplada a Spring.
Ahora, sin embargo, la práctica más recomendada consiste en aprovechar las sesiones
contextúales de Hibernate y olvidarse de H ib e r n a t e T e m p la te . Para ello se conecta un
S e s s i o n F a c t o r y de Hibernate directamente al repositorio y se utiliza para obtener una
sesión, como se muestra a continuación.

Listado 11.1. Repositorios de Hibernate sin Spring, habilitados mediante sesiones de Hibernate.

public H ibernateSpitterRepository(SessionFactory sessionFactory) {


t h i s . sessionFactory = sessionFactory; / / Inyectar SessionFactory.

}
Persistencia de datos con asignación relacional de objetos 341

private Session cu rrentSession() {


return sessionFactory.getC urrentSession( ); / / Recuperar sesión actual
/ / d e SessionFactory.
}
public long count() {
return fin d A ll( ) . s i z e ();
}
public S p itte r sav e(Sp itter s p itte r) {
S e ria liz a b le id = cu rrentSession( ) . s a v e (s p itte r ); / / Usar sesión actu al,
return new S p itt e r ( (Long) id,
s p i t t e r . getUsername(),
spitter.getPassw ord (),
s p itt e r .g e tFulIName(),
sp itte r.g e tE m a il(),
s p i t t e r . isUpdateByEmail( ) ) ;

public S p itte r findOne(long id) {


return (S p itter) cu rrentSession( ) .g e t(S p itte r . c la s s , id );
}
public S p itte r findByüsername(String username) {
return (S p itter) cu rrentSession()
. c r e a te C r ite r ia (S p itte r . class)
. add(Restrictions.eq("usernam e", username))
. l i s t ( ) .g e t (0);
}
public L ist< S p itte r> findAHO {
return (L ist<S p itter>) cu rrentSession()
. c r e a te C r ite r ia (S p itte r . c l a s s ) . l i s t ( ) ;
}
}
Hay varios aspectos que tener en cuenta en el listado 11.1. En primer lugar, estamos
utilizando la anotación @ In j e c t de Spring para hacer que Spring inyecte, de forma automá­
tica, un S e s s i o n F a c t o r y en la propiedad s e s s i o n F a c t o r y de S p i t t e r R e p o s i t o r y .
A continuación, en el método c u r r e n t S e s s i o n ( ), utilizamos S e s s i o n F a c t o r y para
obtener la sesión de la transacción actual. Tenga también en cuenta que hemos anotado la
clase con @ R e p o s ito r y . Esto permite conseguir dos objetivos. Por un lado, @Repos i t o r y
es otra de las anotaciones estereotipo de Spring que, entre otras cosas, se analiza mediante
el análisis de componentes de Spring, de modo que no vamos a tener que declarar un bean
H i b e r n a t e S p i t t e r R e p o s i t o r y de forma explícita siempre que la clase de repositorio
se encuentre en un paquete seleccionado por el análisis de componentes.
Además de ayudarnos a reducir la configuración explícita, @ R e p o s i t o r y cumple
otra función. Recordará que una de las funciones de una clase de plantilla es capturar las
excepciones específicas de plataforma y generarlas de nuevo como una de las excepciones
no comprobadas y unificadas de Spring. Sin embargo, si utilizamos las sesiones contex­
túales de Hibernate y no una de sus plantillas, ¿cómo puede tener lugar la traducción de
la excepción?
342 Capítulo 11

Para añadir la traducción de excepciones a un repositorio de Hibernate sin plantilla,


tenemos que añadir unbean P e r s i s t e n c e E x c e p t i o n T r a n s l a t i o n P o s t P r o c e s s o r
al contexto de aplicación de Spring:
@Bean
public BeanPostProcessor p ersisten ceT ran slatio n () {
return new PersistenceExceptionTranslationPostProcessor( );
}

P e r s i s t e n c e E x c e p t i o n T r a n s l a t i o n P o s t P r o c e s s o r es un postprocesador de
bean que añade un asesor a cualquier bean que se anota con @ R e p o s it o r y , de forma que
se capturen las excepciones específicas de la plataforma y, a continuación, se generen de
nuevo como una de las excepciones de acceso a datos no comprobadas de Spring.
De esta forma, la versión en Hibernate de nuestro repositorio estaría completa y hemos
podido desarrollarla sin depender directamente de clases específicas de Spring (a excep­
ción de la anotación @ R e p o sito ry ). Este mismo enfoque sin plantillas puede aplicarse al
desarrollar un repositorio basado en JPA, como haremos en el siguiente apartado.

Spring y el API java Persistence


JPA (A P I Jav a P ersistence, API de persistencia de Java) surgió de las cenizas de los bean
de entidad de EJB 2 como el nuevo estándar de persistencia de última generación en Java.
JPA es un mecanismo de persistencia basado en POJO que aglutina ideas de Hibernate y
de los Objetos de datos de Java (JDO), combinándolo con anotaciones de Java 5.
Con la versión Spring 2.0 se produjo la integración de Spring con JPA, motivo por el que
muchos culpan (o agradecen) a Spring de la pérdida de popularidad de EJB. Sin embargo,
ahora que Spring es compatible con JPA, muchos desarrolladores recomiendan el uso de
JPA para la persistencia en aplicaciones basadas en Spring. De hecho, hay quien dice que
Spring y JPA son el equipo perfecto para el desarrollo de POJO.
El primer paso para poder utilizar JPA con Spring es configurar una fábrica de admi­
nistradores de entidades como bean en el contexto de aplicación de Spring.

Configurar una fabrica ie atVsmMr^dores de entidades•*


Las aplicaciones basadas en JPA utilizan una implementación de E n tity M a n a g e rF a c to ry
para obtener una instancia de un E n tity M a n a g e r . La especificación JPA define dos tipos
de administradores de entidades:

• Gestionados por la aplicación: Los administradores de entidades se crean cuando


una aplicación solicita uno directamente a una fábrica de administradores de
entidad. Con los administradores gestionados por la aplicación, ésta es responsable
de abrirlos y cerrarlos, y de implicarlos en las transacciones. Este tipo de adminis­
trador de entidad es el más adecuado para su uso en aplicaciones individuales que
no se ejecutan dentro de un contenedor Java EE.
Persistencia de datos con asignación relacional de objetos 343

• Gestionados por el contenedor: Los administradores de entidades son creados y


gestionados por un contenedor Java EE. La aplicación no interactúa con la fábrica de
administradores de entidad. En su lugar, los administradores de entidad se obtienen
directamente mediante inyección o desde JNDI. El contenedor es responsable de
configurar las fábricas de administradores de entidad. Este tipo de administrador
de entidad es el más adecuado para utilizarlo en un contenedor Java EE que quiera
mantener cierto control sobre la configuración de JPA, más allá de lo indicado en
p e r s i s t e n c e . xml.
Am bos tipos de adm inistrador de entidad im plem entan la m ism a interfaz
E n tity M a n a g e r. La diferencia principal no está en el propio E n tity M a n a g e r, sino en cómo
se crea y gestiona. Los E n tit y M a n a g e r gestionados por una aplicación son creados por
un E n t it y M a n a g e r F a c t o r y y obtenidos al invocar el método c r e a t e E n t ity M a n a g e r
F a c t o r y () de P e r s i s t e n c e P r o v i d e r . Por otro lado, los E n t i t y M a n a g e r F a c t o r y
gestionados por un contenedor se obtienen mediante el método c r e a t e C o n t a i n e r
E n t i t y M a n a g e r F a c t o r y () de P e r s i s t e n c e P r o v i d e r .
¿Qué implica esto para los desarrolladores de Spring que quieran utilizar JPA? En
realidad, no mucho. Con independencia de la variedad de E n t i t y M a n a g e r F a c t o r y
que desee utilizar, Spring se va a hacer cargo de gestionar los E n tit y M a n a g e r por usted.
Si utiliza uno gestionado por una aplicación, Spring desempeña el papel de la aplicación
y gestiona de forma transparente el E n tit y M a n a g e r en su nombre. En caso de que lo
gestione un contenedor, Spring desempeña el papel de este último.
Cada tipo de fábrica de administradores de entidad se genera mediante el correspon­
diente bean de fábrica de Spring:
• L o c a l E n t i t y M a n a g e r F a c t o r y B e a n : Genera un E n t i t y M a n a g e r F a c t o r y
gestionado por una aplicación.
• L o c a lC o n t a in e r E n t it y M a n a g e r F a c t o r y B e a n : Genera un E n tity M a n a g e r
F a c t o r y gestionado por un contenedor.
Es importante destacar que la elección entre un E n t i t y M a n a g e r F a c t o r y gestionado
por una aplicación o por un contenedor es completamente transparente en una aplicación
basada en Spring. Al trabajar con Spring y JPA, se ocultan los detalles complejos de la
gestión de cualquiera de ellos y se permite que el código de acceso a datos pueda centrarse
en su verdadero propósito: el acceso a datos. La única diferencia real entre ambos tipos de
fábricas de administradores de entidad, al menos en lo que concierne a Spring, es la forma
en que se configuran en el contexto de aplicación de Spring. Por ello, vamos a aprender
a configurar L o c a lE n t it y M a n a g e r F a c t o r y B e a n (gestionado por una aplicación) y
L o c a lC o n t a in e r E n t it y M a n a g e r F a c t o r y B e a n (gestionado por un contenedor).

Configurar JPA gestionado por una aplicación


Las fábricas de administradores de entidades gestionadas por una aplicación derivan la
mayor parte de su información de configuración de un archivo de configuración llamado
p e r s i s t e n c e . xm l. Este archivo debe incluirse en el directorio META- INF de la ruta de
clases.
344 Capítulo 11

El objetivo del archivo p e r s i s t e n c e . xml es definir una o más unidades de persistencia.


Una unidad de persistencia sería una agrupación de una o más clases de persistencia que se
corresponden con un único origen de datos. El archivo p e r s i s t e n c e . xml enumera una o
más clases de persistencia junto a otras configuraciones, como por ejemplo los orígenes de
datos y los archivos de asignación basados en XML. A continuación, se muestra un ejemplo
de archivo p e r s i s t e n c e . xm l para la aplicación Spittr:
<persistencexmlns=,lh ttp : / /ja v a . sun.com/xml/ns/persistence"
version="1.0">
<persistence~unitnam e="spitterPU">
<class>com.habuma. s p i t t e r . domain. S p itte rc /c la s s >
<class>com .habum a.spitter.domain.S p ittle c /c la s s >
<properties>
<property name=11toplink.jdbc.driver"
value="org.hsqldb.jdbcDriver"/>
<property nam e="toplink.jdbc.url"value=
"jd b c:hsqld b:hsql: // lo c a lh o s t/ s p itte r /s p it te r " />
<property name="to p iin k . j dbc. u ser"
value="sa" />
<property nam e="toplink.jdbc.password"
value="" />
</properties>
</p ersistence-u nit>
</p ersistence>

Debido a la gran cantidad de configuración que se lleva a cabo en el archivo


p e r s i s t e n c e . xm l, en Spring hay que realizar muy poca. El siguiente < b e a n > declara
un L o c a lE n t it y M a n a g e r F a c t o r y B e a n en Spring:
@Bean
public LocalEntityManagerFactoryBean entityManagerFactoryBean() {
LocalEntityManagerFactoryBean emfb
= new LocalEntityManagerFactoryBean();
emfb. setPersistenceU nitN am e("spitterPU");
return emfb;
}
El valor asignado a la propiedad p e r s is t e n c e U n it N a m e hace referencia al nombre
de la unidad de persistencia, tal y como aparece en p e r s i s t e n c e . xm l.
El m o tiv o p o r el qu e la m a y o r p a rte d el có d ig o n e ce sa rio p ara cre a r un
E n t i t y M a n a g e r F a c t o r y gestionado por una aplicación se incluye en el archivo
p e r s i s t e n c e . xm l es porque éste es gestionado por una aplicación. En esta situación (sin
que se vea im plicado Spring), la aplicación es completamente responsable de obtener un
E n t i t y M a n a g e r F a c t o r y m ediante el P e r s i s t e n c e P r o v i d e r de la implementación
JPA. El código de la aplicación puede crecer de forma considerable si tiene que definir la
unidad de persistencia cada vez que solicite un E n tit y M a n a g e r F a c t o r y . Al especificarlo
en p e r s i s t e n c e . xm l, JPA puede buscar en esta ubicación las definiciones de unidades
de persistencia.
Sin embargo, gracias a la compatibilidad de Spring con JPA, nunca vamos a tener que
tratar directamente con P e r s i s t e n c e P r o v i d e r . Por ese motivo, sería una tontería extraer
información de la configuración a p e r s i s t e n c e . xm l. De hecho, si lo hacemos, no vamos
Persistencia de datos con asignación relacional de objetos 345

a poder configurar E n t i t y M a n a g e r F a c t o r y en Spring (de esta forma, por ejemplo,


podemos proporcionar un origen de datos configurado en Spring). Por este motivo, vamos
a centrar nuestra atención en JPA gestionado por un contenedor.

Configurar JPA gestionado por un contenedor


JPA gestionado por un contenedor utiliza un enfoque diferente. Al ejecutarse dentro
de un contenedor, E n t i t y M a n a g e r F a c t o r y puede generarse utilizando información
proporcionada por el contenedor. En nuestro caso, Spring.
En lugar de configurar la información sobre el origen de datos en p e r s i s t e n c e . xml,
podemos hacerlo en el contexto de aplicación de Spring. Por ejemplo, la siguiente decla­
ración de < b e a n > muestra cómo configurar JPA gestionado por un contenedor en Spring
utilizando L o c a lC o n t a in e r E n t it y M a n a g e r F a c t o r y B e a n .
@Bean
public LocalContainerEntityManagerFactoryBean entityManagerFactory{
DataSource dataSource, JpaVendorAdapter jpaVendorAdapter) {
LocalContaínerEntityManagerFactoryBean emfb =
new LocalContainerEntityManagerFactoryBean();
emfb.setDataSource(dataSource);
emfb.setJpaVendorAdapter(jpaVendorAdapter);
return emfb;
}
En este caso, hemos configurado la propiedad d a t a S o u r c e con un origen de datos
configurado en Spring. Cualquier implementación de j a v a x . s q l . D a t a S o u r c e va a ser
adecuada. Aunque un origen de datos puede seguir configurándose en p e r s i s t e n c e .
xml, el especificado a través de esta propiedad va a tener preferencia.
La propiedad jp a V e n d o r A d a p te r puede utilizarse para indicar aspectos especí­
ficos sobre la implementación JPA que utilizar. Spring cuenta con varios adaptadores de
proveedor JPA, entre los que puede elegir:

• E c lip s e L in k Jp a V e n d o r A d a p t e r
• H ib e r n a te Jp a V e n d o r A d a p te r
• O p en Jp a V en d o rA d a p ter
• T o p L in k Jp a V e n d o rA d a p te r

En este caso, vamos a utilizar Hibernate como implementación de JPA, por lo que lo
hemos configurado con H ib e r n a te Jp a V e n d o r A d a p te r :
@Bean
public JpaVendorAdapter jpaVendorAdapter() {
HibernateJpaVendorAdapter adapter = new HibernateJpaVendorAdapter();
adapter.setDatabase("HSQL");
adapter.setShowSql(true);
adapter.setGenerateDdl(false);
adapter.setDatabasePlatf orm (11org.hibernate.dialect.HSQLDialect") ;
return adapter;
}
346 Capítulo 11

En el adaptador del proveedor se configuran varias propiedades, aunque la más impor­


tante es d a ta b a s e , en la que hemos especificado la base de datos Hypersonic que vamos
a utilizar. El resto de valores admitidos por esta propiedad se incluyen en la tabla 11.1.

Tabla 11.1. El adaptador de proveedor JPA de Hibernate admite varias bases de datos
(especificadas con la propiedad database).
■I
Plataforma de base de datos Valor para la propiedad database
J
IBM DB2 DB2
Apache Derby DERBY
H2 H2
Hypersonic HS QL
Informix INFORMIX
MySQL MYSQL
Oracle ORACLE
PostgreSQL POSTGRESQL
Microsoft SQL Server SQLSERVER
Sybase SYBASE

Algunas características dinámicas de persistencia requieren que la clase de los objetos


persistentes se modifique mediante instrumentación para admitir la característica. Los
objetos cuyas propiedades se carguen de forma diferida (es decir, que no se recuperen de
la base de datos hasta que se acceda a ellos) deben tener sus clases instrumentadas con
código que indique cómo recuperar datos no cargados tras el acceso. Algunos marcos de
trabajo utilizan proxy dinámicos para implementar la carga diferida. Otros, como JDO,
llevan a cabo la instrumentación de las clases en tiempo de compilación.
La elección del bean de fábrica de adm inistradores de entidades va a venir determinada
por la form a en que lo utilice, pero existe un truco que puede que le haga decantarse por
L o c a lC o n t a in e r E n t it y M a n a g e r F a c t o r y B e a n .
El principal objetivo del archivo p e r s i s t e n c e . x m l es identificar las clases de
entidad de una unidad de persistencia, pero desde Spring 3.1 puede hacerlo directa­
mente con L o c a lC o n t a in e r E n t it y M a n a g e r F a c t o r y B e a n si establece la propiedad
p ack ag esT oScan :

@Bean
public LocalContainerEntityManagerFactoryBean entityManagerFactory(
DataSource dataSource, JpaVendorAdapter jpaVendorAdapter) {
LocalContainerEntityManagerFactoryBean emfb =
new LocalContainerEntityManagerFactoryBean( );
emfb. setDataSource(dataSource);
emfb. setJpaVendorAdapter(jpaVendorAdapter);
emfb. setPackagesToScan("com.habuma. s p i t t r . domain") ;
return emfb;
í
Persistencia de datos con asignación relacional de objetos 347

Con esta configuración, L o c a lC o n t a in e r E n t it y M a n a g e r F a c t o r y B e a n busca en


el paquete com .habum a. s p i t t r . dom ain clases anotadas con @ E n t i t y . Por lo tanto,
no es necesario declararlas explícitamente en p e r s i s t e n c e .x m l, y como D a t a S o u r c e
también se inyecta en L o c a lC o n t a in e r E n t ity M a n a g e r F a c to r y B e a n , no es necesario
configurar detalles sobre la base de datos en p e r s i s t e n c e . xm l. Por lo tanto, no se nece­
sita p e r s i s t e n c e . xm l para nada. Puede borrarlo y dejar que L o c a lC o n t a in e r E n t i t y
M a n a g e r F a c to r y B e a n se encargue de todo.

Obtener EntityManagerFactory desde JNDI


Merece la pena mencionar que si implementa su aplicación Spring en un servidor de
aplicaciones, puede que ya se haya creado E n t i t y M a n a g e r F a c t o r y y esté esperando en
JNDI para ser recuperado. En ese caso, puede utilizar el elemento < j e e : j n d i - lookup> del
espacio de nombres j e e de Spring para obtener una referencia a E n tity M a n a g e r F a c to r y :
<j e e : jndi-lookup id="emf" jndi-nam e="persistence/spitterPU " />

También puede configurar el bean E n t i t y M a n a g e r F a c t o r y con configuración de


Java si utiliza lo siguiente:
@Bean
public JndiObjectFactoryBean entityManagerFactory() {}
JndiObjectFactoryBean jndiObjectFB = new JndiObjectFactoryBean0 ;
jndiO bjectFB. setJndiName("jd b c/Sp ittrD S") ;
return jndiObjectFB;
}
Aunque este método no devuelve un E n t i t y M a n a g e r F a c t o r y , generará un bean
E n t i t y M a n a g e r F a c t o r y ya que devuelve J n d iO b j e c t F a c t o r y B e a n , una implemen-
tación de la interfaz F a c t o r y B e a n que genera E n t it y M a n a g e r F a c t o r y . Con indepen­
dencia de la forma en que obtenga E n t i t y M a n a g e r F a c t o r y , una vez disponga de uno,
estará listo para comenzar a escribir un repositorio, como veremos a continuación.

Escribir un repositorio basado en JPA


Al igual que el resto de las opciones de integración de persistencia de Spring, la integra­
ción entre Spring y JPA se produce en forma de plantilla con Jp a T e m p la te . En cualquier
caso, el enfoque de JPA basado en plantillas ha sido relegado por uno puramente JPA. Es un
método similar a las sesiones contextúales de Hibernate que hemos utilizado en un apartado
anterior. Como se prefiere utilizar JPA puro, vamos a centrarnos en crear repositorios JPA
sin código Spring. En concreto, J p a S p i t t e r R e p o s i t o r y , en el siguiente código, muestra
cómo desarrollar un repositorio JPA sin recurrir a Jp a T e m p la t e de Spring.

Listado 11.2. Un repositorio JPA puro que no utiliza plantillas de Spring.

package com .habum a.spittr.persistence;


import ja v a .ú t i l . L i s t ;
import ja v a x .p e rsiste n c e . EntityManagerFactory;
348 Capítulo 11

import javax.persistence.PersistenceUnit;
import org.springframework.dao.DataAccessException;
import org.springframework.stereotype.Repository;
import org.springframework.transaction.annotation.Transactional;
import com.habuma.spittr.domain.Spitter;
import com.habuma.spittr.domain.Spittle;

@Repository
@Transactional
public class JpaSpitterRepository implements SpitterRepository {

@PersistenceUnit
private EntityManagerFactory emf; // Inyectar EntityManagerFactory.

public void addSpitter(Spitter spitter) {


emf.createEntityManager().persist(spitter); // Crear y usar EntityManager.
}
public Spitter getSpitterByld(long id) {
return emf.createEntityManager().find(Spitter.class, id)/
}
public void saveSpitter(Spitter spitter) {
emf.createEntityManager().merge(spitter);
}

i
Lo más importante del listado 11.2 es la propiedad E n t i t y M a n a g e r F a c t o r y . Está
anotada con @ P e r s is t e n c e U n it para que Spring pueda inyectar E n tity M a n a g e r F a c to r y
en el repositorio. Ahora que tenemos E n t i t y M a n a g e r F a c t o r y , los métodos de
J p a S p i t t e r R e p o s i t o r y la pueden usar para crear un E n tit y M a n a g e r que después
utilizarán para realizar operaciones en la base de datos.
El único inconveniente de J p a S p i t t e r R e p o s i t o r y es que cada método acaba
invocando c r e a t e E n t i t y M a n a g e r () . Además de propiciar la duplicación de código,
también significa que se crea un nuevo E n tit y M a n a g e r cada vez que se invoca uno de
los métodos del repositorio. Esto complica todo lo relacionado con las transacciones. ¿No
sería más sencillo disponer de E n tit y M a n a g e r con antelación?
El problem a es la incom patibilidad de E n tit y M a n a g e r con los subprocesos y que por
lo general no debe inyectarse como bean de instancia única compartido en su repositorio,
pero eso no significa que no pueda solicitar un E n tit y M a n a g e r . El siguiente código
m uestra cóm o usar @ P e r s i s t e n c e C o n t e x t para asignar a J p a S p i t t e r R e p o s i t o r y
un E n tity M a n a g e r .

Listado 11.3. Inyección de un repositorio con un proxy en EntityManager.

package com.habuma.spittr.persistence;
import java.útil.List;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import org.springframework.dao.DataAccessException;
import org.springframework.stereotype.Repository;
Persistencia de datos con asignación relacional de objetos 349

import org. springfram ew ork.transaction.annotation.Transactional;


import com.habuma. s p i t t r . domain. S p itt e r ;
import com.habuma. s p i t t r . domain. S p i t t le ;

©Repository
©Transact ional
public cla ss JpaSpitterR epository implements SpitterR epository {

@PersistenceContext
private EntityManager em; / / Inyectar EntityManager.

public void ad d Sp itter(Sp itter s p itte r) {


e m .p e rs is t(s p itte r ); / / Usar EntityManager.
}
public S p itte r getSpitterByld(long id) {
return e m .fin d (S p itte r.c la ss, id );
}
public void sa v e S p itte r(S p itte r s p itte r) {
em .m erge(spitter);
}

}
Esta nueva versión de J p a S p i t t e r R e p o s i t o r y recibe ahora directamente un
E n tity M a n a g e r ; no es necesario crearlo desde E n tit y M a n a g e r F a c t o r y en cada uno
de sus métodos. Aunque sea mucho más cómodo, probablemente se pregunte sobre los
problemas de compatibilidad con subprocesos propios del uso de un E n tit y M a n a g e r
inyectado.
Lo cierto es que @ P e r s i s t e n c e C o n t e x t no inyecta exactamente un E n tity M a n a g e r .
En lugar de asignar al repositorio un E n t i t y M a n a g e r real, asigna un proxy a un
E n tit y M a n a g e r real, que está asociado a la transacción actual o, si no existe, crea una
nueva. Por lo tanto, sabe que siempre podrá trabajar con un administrador de enti­
dades de forma compatible a los subprocesos. No olvide que @ P e r s i s t e n c e U n i t y
@ P e r s i s t e n c e C o n t e x t no son anotaciones de Spring; las proporciona la especificación
JPA. Para que Spring las entienda e inyecte E n t it y M a n a g e r F a c t o r y o E n tity M a n a g e r ,
es necesario configurar P e r s i s t e n c e A n n o t a t i o n B e a n P o s t P r o c e s s o r de Spring.
Si ya utiliza c c o n t e x t : a n n o t a t i o n - c o n f i g > o « c o n t e x t : c o m p o n e n t-s c a n > no
necesita nada más, ya que estos elementos de configuración registran automáticamente
un bean P e r s i s t e n c e A n n o t a t i o n B e a n P o s t P r o c e s s o r . En caso contrario tendrá que
registrarlo de forma explícita:
@Bean
public PersistenceAnnotationBeanPostProcessor paPostProcessor() {
return new PersistenceAnnotationBeanPostProcessor();
}
Puede que también se haya dado cuenta de que hemos anotado J p a S p i t t e r R e p o s i t o r y
con © R e p o s ito r y y (» T r a n s a c ti o n a l . Este último indica que los métodos de persistencia
de este repositorio se van a ver implicados en un contexto transaccional.
350 Capítulo 11

@ R e p o s i t o r y cumple la misma función aquí que cuando desarrollamos la versión


de sesión contextual de Hibernate del repositorio. Sin una plantilla para gestionar la
traducción de excepciones, tenemos que anotar nuestro repositorio con @ R e p o s it o r y
para que P e r s i s t e n c e E x c e p t i o n T r a n s l a t i o n P o s t P r o c e s s o r sepa que se trata
de uno de los bean para los que las excepciones deben traducirse a una de las excep­
ciones de acceso a datos unificados de Spring. Y hablando de P e r s i s t e n c e E x c e p t i o n
T r a n s l a t i o n P o s t P r o c e s s o r , no debemos olvidarnos de conectarlo con un bean en
Spring al igual que hicimos en el ejemplo de Hibernate:
@Bean
public BeanPostProcessor p ersisten ceT ran slatio n () {
return new PersistenceExceptionTranslationPostProcessor( );
}
Tenga en cuenta que la traducción de excepciones, ya sea en JPA o en Hibernate, no es obli­
gatoria. Si prefiere que su repositorio genere excepciones específicas de JPA o de Hibernate,
puede omitir sin problema P e r s i s t e n c e E x c e p t i o n T r a n s l a t i o n P o s t P r o c e s s o r .
Sin embargo, si utiliza la traducción de excepciones de Spring, estará unificando todas las
excepciones de acceso a datos bajo la jerarquía de Spring, lo que facilitará el intercambio
de los mecanismos de persistencia más adelante.

Repositorios JPA automáticos con Spring Data


Aunque los métodos de los listados 11.2 y 11.3 sean muy sencillos, siguen interactuando
directamente con el E n tit y M a n a g e r para consultar la base de datos, y si se fija atenta­
mente, estos métodos empiezan a parecer preconfigurados. Por ejemplo, examinemos el
método a d d S p i t t e r () de nuevo:
public void ad d Sp itter(Sp itter s p itte r) {
en tity M a n a g er.p ersist(sp itter);
}
En cualquier aplicación que se precie, es probable que escriba muchas veces el
mismo método de la misma forma. De hecho, menos por tratarse de la persistencia de
un objeto S p i t t e r , seguramente haya escrito antes un método similar, y los demás
métodos de J p a S p i t t e r R e p o s i t o r y tampoco son demasiado innovadores. Los tipos
de dominio serán diferentes pero los métodos son muy habituales entre cualquier tipo de
repositorio. ¿Por qué hay que crear los mismos métodos de persistencia una y otra vez solo
porque tengamos que trabajar con diferentes tipos de dominio? Spring Data JPA acaba con
este desastre. En lugar de escribir repetidamente las mismas implementaciones de reposi­
torio, Spring Data le permite detenerse en la interfaz, sin necesidad de implementaciones.
Fíjese, por ejemplo, en la siguiente interfaz S p i t t e r R e p o s i t o r y .

Listado 11.4. Creación de un repositorio a partir de una definición de interfaz con Spring Data.

public in te rfa ce SpitterR epository


extends JpaR epository<Spitter, Long> {
}
Persistencia de datos con asignación relacional de objetos 351

S p i t t e r R e p o s i t o r y no parece tener demasiada utilidad, pero esconde más de lo


que vemos.
La clave para crear un repositorio JPA de Spring Data consiste en ampliar una de varias
interfaces. En este caso, S p i t t e r R e p o s i t o r y amplía J p a R e p o s i t o r y de Spring Data
JPA (veremos el resto en breve). De esta forma, J p a R e p o s i t o r y recibe parámetros para
que sepa que es un repositorio para la persistencia de objetos S p i t t e r que tienen un ID de
tipo Long. También hereda 18 métodos para realizar operaciones comunes de persistencia,
como las de guardar, eliminar o busca un objeto S p i t t e r por su ID. Seguramente espere
que el siguiente paso sea crear una clase que implemente S p i t t e r R e p o s i t o r y y sus 18
métodos. Si fuera así, el capítulo adoptaría un rumbo demasiado tedioso. Afortunadamente,
no tendrá que escribir ninguna implementación de S p i t t e r R e p o s i t o r y , sino dejar que
Spring Data se encargue de ello. Solo tendrá que pedírselo.
Para pedirle a Spring Data que cree una implementación de S p i t t e r R e p o s i t o r y
tendrá que añadir un único elemento a su configuración de Spring. El siguiente código
muestra la configuración XML necesaria para activar Spring Data JPA.

Listado 11.5. Configuración de Spring Data JPA.

<?xml v e rsio n = "l.0" encoding="UTF-8"?>


cbeans xmlns="h ttp : / /www. springframework. org/schema/beans"
xmlns:x s i= "h ttp : //www.w3. org/2001/XMLSchema-instance"
xmlns: jp a="h ttp : //www.springframework.org/schema/data/jpa"
x s i : schemaLocation="h ttp : //www.springframework. org/schema/data/jpa
h ttp : //www.springframework. org/schem a/d ata/jpa/spring-jpa-1. O.xsd" >

<jp a : rep o sito ries base-package="com.habuma. s p ittr.d b " />

</beans>

El elemento < j p a : r e p o s i t o r i e s > esconde toda la magia de Spring Data JPA. Como
sucede con < c o n t e x t : c o m p o n e n t-s c a n > , < j p a : r e p o s i t o r i e s > recibe un paquete
base para examinar, pero mientras que < c o n t e x t : com ponent - s c a n > examina un paquete
(y sus subpaquetes) en busca de clases anotadas con @Component, < j p a : r e p o s i t o r i e s >
analiza su paquete base en busca de interfaces que amplían la interfaz R e p o s i t o r y de
Spring Data JPA. Si encuentra alguna, genera automáticamente (al iniciar la aplicación) una
implementación de la misma. En lugar de usar el elemento < j p a : r e p o s i t o r i e s > puede
usar @ E n a b le J p a R e p o s i t o r i e s en su clase de configuración de Java. A continuación le
mostramos una clase de configuración de Java anotada con @ E n a b l e J p a R e p o s i t o r i e s
para examinar el paquete com . h abu m a. s p i t t r . db:
@Configuration
@EnableJpaRepositories(basePackages="com. habuma. s p i t t r . db")
public cla ss JpaConfiguration {

}
Volviendo a la interfaz S p i t t e r R e p o s i t o r y , amplía J p a R e p o s i t o r y que, a su vez,
amplía la interfaz R e p o s i t o r y (aunque indirectamente). Por tanto, S p i t t e r R e p o s i t o r y
amplía transitivamente la interfaz R e p o s i t o r y que busca el análisis de repositorios.
352 Capítulo 11

Cuando Spring Data la detecta, crea una implementación de S p i t t e r R e p o s i t o r y ,


incluida la im plem entación de los 18 métodos heredados de J p a R e p o s i t o r y ,
P a g in g A n d S o r t i n g R e p o s it o r y y C r u d R e p o s it o r y . Debe entender que la imple-
mentación del repositorio se genera al iniciar la aplicación, cuando se crea el contexto de
aplicación de Spring. No es producto de la generación de código de tiempo de generación,
ni se crea durante la invocación de los métodos de la interfaz.
Es increíble que Spring Data JPAnos ofrezca 18 métodos para operaciones JPA comunes
para objetos S p i t t e r sin tener que escribir el código de persistencia. ¿Y si necesita más
de lo que ofrecen dichos métodos? Afortunadamente, Spring Data JPA cuenta con varias
formas de añadir métodos personalizados a un repositorio. Veamos primero cómo definir
un método de consulta personalizado con Spring Data JPA.

Definir métodos de consulta


S p i t t e r R e p o s i t o r y necesita una forma de buscar un objeto S p i t t e r cuando se le
proporcione un nombre de usuario. Imagine, por ejemplo, que desea modificar la interfaz
S p i t t e r R e p o s i t o r y de esta forma:
public in te rfa ce SpitterR epository
extends JpaRepository<Spitter, Long> {
S p itte r findByüsername(String username);
i

El nuevo método f indByüsernam e () es muy sencillo y basta para satisfacer el requi­


sito anterior, pero ¿cómo se puede incorporar a Spring Data JPA una implementación de
dicho método?
En realidad, no tiene que hacer nada más para implementar f in dByüsernam e ().
La firma del método indica a Spring Data JPA todo lo que necesita saber para crear una
implementación del método.
Al crear la implementación del repositorio, Spring Data examina los métodos de la
interfaz del repositorio, analiza el nombre del método e intenta comprender su cometido
en el contexto del objeto para persistencia. Básicamente, lo que hace Spring Data es definir
una especie de lenguaje específico del dominio (DSL) en miniatura en el que los detalles
de persistencia se expresan en las firmas de los métodos de repositorio. Spring Data
sabe que este método sirve para buscar objetos S p i t t e r , ya que hemos parametrizado
J p a R e p o s i t o r y con S p i t t e r . El nombre del método, f in d B y ü sern am e, revela que debe
buscar objetos S p i t t e r comparando su propiedad u se rn a m e con el nombre de usuario
pasado como parámetro al método. Es más, como la firma define el método para devolver
un único objeto S p i t t e r y no una colección, sabe que solamente debe buscar un objeto
S p i t t e r que tenga el nombre de usuario que coincida.
El método f in d B y ü s e rn a m e () es muy sencillo pero Spring Data también puede
procesar nombres de método más interesantes. Los métodos de repositorio están formados
por un verbo, un sujeto opcional, la palabra By y un predicado. En el caso de f in d B y ü -
se rn a m e ( ) , el verbo es f in d (buscar) y el predicado es U sernam e (nombre de usuario);
no se especifica el sujeto y se asume que es un objeto S p i t t e r .
Persistencia de datos con asignación relacional de objetos 353

Como ejemplo adicional de nombres de métodos de repositorio, fíjese en cómo se asignan


los distintos componentes al método r e a d S p it t e r B y F i r s t n a m e O r L a s t n a m e ( ) , desa­
rrollado en la figura 11.1.

Verbo de la consulta Predicado

readSpitterByFirstnameOrLastnameOrderByLastname()
I___ I
^ Sujeto
Figura 11.1. Los nombres de métodos de repositorio se ajustan a un patrón que permite
a Spring Data generar consultas de base de datos.

Como puede comprobar, el verbo es re a d (leer) y no f in d como en el ejemplo anterior.


Spring Data permite el uso de cuatro verbos en el nombre de un método: g e t (obtener),
re a d , f in d y co u n t (contar), g e t , re a d y f in d son sinónimos y todos generan métodos
de repositorio que consultan datos y devuelven objetos. El verbo co u n t, por su parte,
devuelve un recuento de los objetos que coincidan, no los objetos propiamente dichos.
En un método de repositorio, el sujeto es opcional. Sirve para brindarle cierta
flexibilidad a la hora de asignar nombres a sus métodos. Por ejemplo, si lo desea
puede usar el nombre r e a d S p i t t e r s B y F i r s t n a m e O r L a s t n a m e () en lugar de
r e a d B y F ir s t n a m e O r L a s tn a m e ( ) .
En muchos casos el sujeto se ignora. r e a d S p it t e r s B y F ir s t n a m e O r L a s t n a m e () no
difiere en nada de r e a d P u p p ie s B y F ir s tn a m e O r L a s tn a m e ( ) , que tampoco es distinto
a re a d T h o se T h in g sW e W a n tB y F irstn a m e O rL a stn a m e ( ) . El tipo de objeto recuperado
se determina en función de los parám etros de la interfaz J p a R e p o s i t o r y , no del sujeto
del nombre del método.
Pero hay una excepción. Si el sujeto empieza por la palabra D i s t i n c t , la consulta
generada se escribe para garantizar un conjunto de resultados exclusivo. El predicado es
la parte más interesante del nombre de un método. Especifica las propiedades que limitan
el conjunto de resultado. En el caso de r e a d B y F ir s tn a m e O r L a s tn a m e ( ) , los resultados
se limitan por el valor de la propiedad f ir s t n a m e o la s tn a m e .
En el predicado encontrará una o más condiciones que limitan los resultados. Cada
condición debe hacer referencia a una propiedad y también puede especificar una opera­
ción de comparación. Si se excluye el operador de comparación, se supone que es una
operación de igualdad, pero puede elegir cualquier otra operación de comparación, como
las mostradas a continuación:

• I s A f t e r , A f t e r , I s G r e a t e r T h a n , G r e a te r T h a n .
• I s G r e a t e r T h a n E q u a l, G r e a te r T h a n E q u a l.
• I s B e f o r e , B e f o r e , I s L e s s T h a n , L e ss T h a n .
• Is L e s s T h a n E q u a l, L e s s T h a n E q u a l.
• Is B e tw e e n , B etw een .
354 Capítulo 11

• I s N u l l , N u ll.
• I s N o tN u ll, N o tN u ll.
• I s In , In .
• I s N o t l n , N o tin .
• I s S t a r t in g W ith , S ta r tin g W ith , S ta r ts W ith .
• Is E n d in g W ith , E n d in g W ith , E n d sW ith .
• I s C o n t a i n i n g , C o n ta in in g , C o n ta in s .

• I s L i k e , L ik e .
• I s N o t L ik e , N o tL ik e .
• I s T r u e , T ru e .
• I s F a ls e , F a ls e .
• I s , E q u a ls .
• Is N o t, N ot.
Los valores con los que se comparan las propiedades son los parámetros del método.
La firma completa del método tendría este aspecto:
L ist< S p itte r> readByFirstnameOrLastname(String f i r s t , Strin g l a s t ) ;

Al trabajar con propiedades S tr in g , la condición también puede incluir Ig n o rin g C ase


o Ig n o r e s C a s e para realizar la comparación sin discriminar entre mayúsculas o minús­
culas. Por ejemplo, para no diferenciar entre mayúsculas o minúsculas en las propiedades
f ir s tn a m e y la s tn a m e , puede escribir la siguiente firma de método:
L ist< S p itte r> readByFirstnamelgnoringCaseOrLastnameIgnoresCase(
String f i r s t , String l a s t ) ;

I g n o r in g C a s e e I g n o r e s C a s e son sinónimos; puede elegir el que más le convenza.


C om o a lte rn a tiv a a I g n o r i n g C a s e / l g n o r e s C a s e ta m b ién p u ed e u sar
A l l I g n o r i n g C a s e o A l l I g n o r e s C a s e después de las condiciones para ignorar las
mayúsculas o m inúsculas de todas ellas:
L ist< S p itter> readByFirstnameOrLastnameAllIgnoresCase(
String f i r s t , String l a s t ) ;

Los nombres de los parámetros son irrelevantes, pero deben ordenarse para que coin­
cidan con los comparadores del nombre del método.
Por último, puede ordenar los resultados si añade O rderBy al final del nombre del
método. Por ejemplo, puede mostrar los resultados en orden ascendente por la propiedad
la stn a m e :
L ist< S p itte r> readByFirstnameOrLastnameOrderByLastnameAsc(
String f i r s t , String l a s t ) ;
Persistencia de datos con asignación relacional de objetos 355

Para ordenar por varias propiedades, añádalas a la clase O rd erB y . El siguiente ejemplo
ordena por la propiedad la s tn a m e en orden ascendente y después por la propiedad
f ir s tn a m e en orden descendente:
L ist< S p itte r> readByFírstnameOrLastnameOrderByLastnameAscFirstnameDesc(
Strin g f i r s t , String l a s t ) ;

Como ya sabe, las partes condicionales se separan mediante And u Or.


Sería imposible (o muy complicado) mostrar la lista completa de todos los métodos que
puede escribir con el sistema de nombres de Spring Data. A continuación le mostramos
varias firmas que emplean dicho sistema:

• L i s t < P e t > f in d P e t s B y B r e e d ln ( L i s t < S t r i n g > b r e e d ) .


• i n t c o u n t P r o d u c t s B y D is c o n t i n u e d T r u e ( ) .
• L is t < O r d e r > f in d B y S h ip p in g D a te B e tw e e n (D a te s t a r t , D a te e n d ) .

Es una pequeña muestra de los tipos de métodos que puede declarar para que Spring
Data JPA los implemente. Basta con prestar atención al crear la firma de un método de
repositorio mediante la combinación de nombres de propiedades y palabras clave para que
Spring Data JPA genere una implementación de un método para consultar prácticamente
todo lo que se le ocurra.
No obstante, el mini DSL de Spring Data tiene sus limitaciones y no siempre se puede
expresar la consulta deseada en el nombre de un método. En ese caso, Spring Data le ofrece
la anotación @Query.

Declarar consultas personalizadas


Imagine que desea crear un método de repositorio que busque todos los objetos S p i t t e r
con una dirección de correo de Gmail. Podría definir un método f in d B y E m a ilL ik e ()
y pasar % g m a il . com para buscar usuarios de Gmail, pero sería más indicado definir un
método f i n d A l l G m a i l S p i t t e r s () en el que no sea necesario pasar la dirección de
correo electrónico parcial:
L ist< S p itte r> findA llG m ailSpitters() ;

Desafortunadamente, este nombre de método no se ajusta a las convenciones de nomen­


clatura de Spring Data. Cuando Spring Data intenta generar una implementación para este
método, no puede comparar los contenidos de su nombre con el metamodelo S p i t t e r y
genera una excepción.
Cuando los datos deseados no se pueden expresar correctamente en el nombre del
método, puede usar la anotación @ Q uery para proporcionar a Spring Data la consulta que
ejecutar. Para el método f i n d A l l G m a i l S p i t t e r s () podría usar @Q uery de esta forma:
@Query("s e le c t s from S p itte r s where s.em ail lik e ' %gmail. com'")
L ist< S p itte r> findA llG m ailSpitters( );
356 Capítulo 11

No tiene que escribir la implementación del método f i n d A H G m a i l S p i t t e r s ( ) ,


solamente proporcionar la consulta para indicar a Spring Data JPA cómo debe implementar
el método. Como hemos visto, @ Q uery es muy útil cuando resulta complicado expresar
la consulta deseada por medio de la convención de nomenclatura para métodos. También
puede usarla si tras aplicar dicha convención el nombre del método es demasiado extenso.
Fíjese en el siguiente método:
List<Order>
findByCustomerAddressZipCodeOrCustomerNameAndCustomerAddressState();

¡Demasiado largo! Es un ejemplo retorcido pero puede darse una situación real en la
que tenga que escribir un método de repositorio para realizar una consulta definida con
un nombre de método muy extenso. En ese caso, podría utilizar uno más corto y recurrir
a @ Q uery para especificar cómo debe consultar la base de datos.
La anotación @ Q uery es muy útil para añadir nombres de consulta personalizados a
una interfaz compatible con Spring Data JPA, pero se limita a una única consulta JPA. ¿Y
si tiene que combinar elementos más complejos?

Combinar funcionalidades personalizadas


Es muy probable que en algún momento necesite funcionalidad para su repositorio que
no se pueda describir con las convenciones de nomenclatura de métodos de Spring Data
o ni siquiera con una consulta con la anotación @Query. A pesar de las increíbles presta­
ciones de Spring Data JPA, tiene sus limitaciones y puede que necesite escribir un método
de repositorio a la antigua usanza: directamente con E n tity M a n a g e r . En ese caso ¿tiene
que prescindir de Spring Data JPA y volver a escribir sus repositorios como hicimos en un
apartado anterior?
Básicamente sí. Cuando tenga que hacer algo que Spring Data JPA no es capaz de hacer,
tendrá que trabajar con JPA a un nivel inferior al que ofrece Spring Data JPA. La buena notica
es que no tendrá que prescindir totalmente de Spring Data JPA. Basta con trabajar en el nivel
inferior con los métodos que lo necesiten y seguir utilizando Spring Data JPA para el resto
de tareas. Cuando Spring Data JPA genera la implementación de una interfaz de repositorio,
también busca una clase que tenga el mismo nombre que la interfaz y con el sufijo Im p l.
Si la clase existe, Spring Data JPA fusiona sus métodos con los generados por Spring Data
JPA. Para la interfaz S p i t t e r R e p o s i t o r y , busca la clase S p i t t e r R e p o s i t o r y l m p l .
Para ilustrarlo, imagine que necesita un método en S p i t t e r R e p o s i t o r y que actua­
lice todos los objetos S p i t t e r que hayan publicado 10.000 o más objetos S p i t t l e y los
establece con el estado E l i t e . No hay forma de declarar ese método con la convención
de nomenclatura de Spring Data JPA ni con @Query. La solución más práctica consiste en
usar el siguiente método e l i t e S w e e p ( ) .

Listado 11.6. Repositorio que asciende a los usuarios Spitter más activos al estado Elite.

public cla ss SpitterRepositorylmpl implements SpitterSweeper {

@PersistenceContext
Persistencia de datos con asignación relational de objetos 357

private EntityManager em;

public in t eliteSweepO {
String update =
"UPDATE S p itte r s p itte r " +
"SET s p i t t e r . statu s = 'E l i t e ' " +
"WHERE s p i t t e r . statu s = 'Newbie' " +
"AND s p itt e r .id IN (" +
"SELECT s FROM S p itte r s WHERE {" +
11 SELECT COUNT (s p ittle s ) FROM s . s p itt le s s p ittle s ) > 10000" +
")" ;
return em.createQuery(update). executeUpdate();
}
}

Como puede apreciar, el método e l i t e S t a t u s () no difiere mucho de otros métodos de


repositorio que hemos visto antes. No hay nada especial sobre S p i t t e r R e p o s i t o r y l m p l .
Usa el E n tit y M a n a g e r inyectado para realizar su labor.
S p i t t e r R e p o s i t o r y l m p l no implementa la interfaz S p i t t e r R e p o s i t o r y ; sigue
siendo responsabilidad de Spring Data JPA. En su lugar, S p i t t e r R e p o s i t o r y l m p l imple-
menta S p i t t e r S w e e p e r , mostrada a continuación (lo único que la vincula al repositorio
de Spring Data es el nombre):
public in te rfa ce SpitterSweeper{
in t eliteSw eepO ;
i

D eb e a se g u ra rs e de d e c la ra r el m éto d o e l i t e S w e e p O en la in te rfa z
S p i t t e r R e p o s i t o r y . La form a más sencilla de hacerlo y de evitar código duplicado
consiste en m odificar S p i t t e r R e p o s i t o r y para que amplíe S p i t t e r S w e e p e r :

public in te rfa c e SpitterR epository


extends JpaRepository<Spitter, Long>,
SpitterSweeper {

Como ya hemos mencionado, Spring Data JPA asocia la clase de implementación


a la interfaz porque el nombre de la implementación se basa en el de la interfaz. El
sufijo Im pl es el predeterminado. Si lo prefiere, puede usar otro distinto, que tendrá
que especificar al configurar @ E n a b l e J p a R e p o s i t o r i e s estableciendo el atributo
r e p o s ito r y Im p le m e n ta tio n P o s tfix :

@EnableJpaRepositories(
basePackages="com.habuma. s p i t t r .db",
repositorylm plem entationPostfix="Helper")

Si configura Spring Data JPA enXM L con < j p a : re p o s i t o r i e s > puede especificar el
sufijo con el atributo r e p o s i t o r y - im p l- p o s t f i x :
<jp a : re p o sito ries base-package="com.habuma. s p i t t r . db"
rep o sitory -im p l-p ostfix="Helper" />
358 Capítulo 11

Al establecer el sufijo en H e lp e r , Spring Data JPA busca una clase con el nombre
S p i t t e r R e p o s i t o r y H e l p e r para compararla con la interfaz S p i t t e r R e p o s i t o r y .

Resumen
Las bases de datos relaciónales han sido el almacén de datos tradicional de muchas
aplicaciones durante años. Al trabajar con datos y asignar objetos a tablas, el proceso es
muy tedioso y opciones ORM como Hibernate y JPA ofrecen un modelo más declarativo
para la persistencia de datos. Aunque Spring no ofrece compatibilidad directa con ORM, se
integra con varias soluciones ORM, como Hibernate y el API de persistencia de Java (JPA).
En este capítulo hemos visto cómo usar las sesiones contextúales de Hibernate en una
aplicación Spring para que sus repositorios apenas contengan código específico de Spring.
Además, hemos aprendido a crear repositorios JPA sin Spring mediante la inyección de
E n tit y M a n a g e r F a c t o r y o E n tity M a n a g e r en las implementaciones de sus repositorios.
También hemos presentado Spring Data para declarar interfaces de repositorios JPA
mientras que Spring Data JPA genera automáticamente implementaciones de las mismas
en tiempo de ejecución, y cuando se necesita más de lo que ofrecen los métodos de repo­
sitorio, se puede recurrir a la anotación @Query y crear implementaciones personalizadas
de métodos de repositorio.
Pero apenas hemos descrito Spring Data. En el siguiente capítulo nos adentraremos en
su DSL de nombres de métodos y veremos que sirve para mucho más que para simples
bases de datos relaciónales. Así es: veremos cómo admite el nuevo contingente de bases
de datos NoSQL, tan populares en los últimos años.
Capítulo

12 Trabajar con b a se s
de d a to s N o S Q L *•

CONCEPTOS FUNDAMENTALES:

• Creación de repositorios en MongoDB y Neo4j.


• Persistencia de datos entre varios almacenes de datos.
• Trabajar con Spring y Redis.
En su autobiografía, Henry Ford pronunció una de sus famosas frases: "El cliente puede
comprar un coche del color que desee, siempre y cuando sea negro". Hay quienes afirman
que es una frase arrogante y pretenciosa, y otros que piensan que ilustra su sentido del
humor. Sin embargo, la realidad es que cuando se publicó su biografía, Henry Ford había
conseguido reducir gastos utilizando una pintura de secado rápido que solamente era de
un color: negra.
Si parafraseamos la famosa cita de Ford y la aplicamos al mundo de las bases de datos,
durante muchos años nos han dicho que podemos usar la base de datos que queramos,
siempre y cuando sea una base de datos relacional. Las bases de datos relaciónales han
monopolizado el desarrollo de aplicaciones durante mucho tiempo.
Pero dicho monopolio empieza a verse cuestionado tras la irrupción de serios contrin­
cantes en el mundo de las bases de datos. Las denominadas bases de datos NoSQL se acercan
a las aplicaciones de producción tras asumir que no existe una base de datos estándar para
todas. Ahora contamos con nuevas opciones y podemos elegir la base de datos óptima para
el problema que tengamos que resolver.
En los últimos capítulos nos hemos centrado en las bases de datos relaciónales, empe­
zando con la compatibilidad de Spring con JDBC y después con la asignación relacional
de objetos. En el capítulo anterior hemos descrito Spring Data JPA, uno de los proyectos
de Spring Data, y cómo facilita el trabajo con JPA gracias a la generación automática de
implementaciones de repositorio en tiempo de ejecución.
Spring Data también admite varias bases de datos NoSQL, como MongoDB, Neo4j
y Redis. No solo admite repositorios automáticos, sino también acceso a datos basado
en plantillas y asignación de anotaciones. En ese capítulo veremos cómo crear repo­
sitorios compatibles con bases de datos NoSQL no relaciónales. Comenzaremos por
Spring Data MongoDB y veremos cómo diseñar repositorios para datos basados en
documentos.

Persistencia de documentos
con MongoDB
Algunos datos se representan mejor en forma de documentos, es decir, en lugar de
distribuirlos entre diferentes tablas, nodos o entidades, tiene más sentido recopilar la
información en estructuras desnormalizadas (documentos). Aunque dos o más de estos
documentos pueden estar relacionados, por lo general son entes independientes. Las bases
de datos configuradas para trabajar de esta forma con documentos se denominan bases
de datos documentales.
Imagine que tiene que crear una aplicación que capture el expediente académico de un
universitario. Necesitará una tabla para recuperar las notas dado el nombre del alumno
o la capacidad de buscar propiedades comunes en las notas, pero como la evaluación de
cada alumno es individual, no es necesario que dos expedientes estén relacionados. Aunque
se podría diseñar un esquema relacional de base de datos (y seguramente ya exista) para
capturar estos datos, puede que una base de datos documental sea más indicada.
362 Capítulo 12

Para qué no sirven las bases de datos documentales


Es muy importante saber cuándo usar una base de datos documental, pero igual
de importante es saber cuándo no hacerlo. Las bases de datos documentales no
son bases de datos de carácter general y solucionan una serie de problemas muy
concretos.
No son las más indicadas para alm acen ar datos con un elevado grado de
relaciones. Por ejemplo, una red social representa la relación de los distintos
usuarios de la aplicación, y la m ejor forma de alm acenarlo no es una base de
datos documental. Aunque no sea im posible alm acenar todas las relaciones,
supondría más un inconveniente que una ventaja.
E l dominio de la aplicación Spittr no es el más adecuado para una base de datos
documental. En este capítulo usaremos MongoDB en e l c o n t e x t o de un sistema
de pedidos.

MongoDB es una de las bases de datos documentales de código abierto más utili­
zadas. Spring Data MongoDB ofrece MongoDB para aplicaciones de Spring mediante tres
elementos:

• Anotaciones para asignación de objetos a documentos.


• Acceso a base de datos basado en plantillas por medio de M ongoTemplate.
• Generación automática de repositorios en tiempo de ejecución.

Ya hemos visto cómo Spring Data JPA permitía la generación automática de repositorios
para acceso a datos basado en JPA, lo mismo que ofrece Spring Data MongoDB para el
acceso a datos basado en MongoDB.
Sin embargo, al contrario de lo que sucede con Spring Data JPA, Spring Data MongoDB
también o f r e c e anotaciones para asignar objetos de Java a documentos (Spring Data JPA no
ofrece dichas anotaciones para JPA ya que la propia especificación JPA define anotaciones de
asignación de objetos). Es más, Spring Data MongoDB posibilita el acceso a datos MongoDB
mediante plantillas para diversas tareas de manipulación de documentos.
No obstante, antes de utilizar sus características tendremos que configurar Spring Data
MongoDB.

Habilitar MongoDB
Para poder trabajar de forma eficaz con Spring Data MongoDB tendrá que crear una serie
de bean básicos en su configuración de Spring. En primer lugar, un bean MongoCl i e n t para
acceder a la base de datos MongoDB. También necesitará un bean M o n go T em p late para
acceder a la base de datos de acuerdo a una plantilla. Si lo desea, también puede habilitar
la generación automática de repositorios de Spring Data MongoDB.
El siguiente listado muestra una sencilla clase de configuración de Spring Data MongoDB
que permite satisfacer estos requisitos.
Trabajar con bases de datos NoSQL 363

Listado 12.1. Configuración esencial para Spring Data MongoDB.

package orders.conf ig;


import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.mongodb.core.MongoFactoryBean;
import org.springframework.data.mongodb.core.MongoOperations;
import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.data.mongodb.repository.conf ig.

import com.mongodb.Mongo;

©Configuration
@EnableMongoRepositories(basePackages="orders.db") // Habilitar repositorios MongoDB.
public class MongoConfig {

@Bean
public MongoFactoryBean mongo() { // Bean MongoClient.
MongoFactoryBean mongo = new MongoFactoryBean();
mongo.setHost("localhost");
return mongo;
}

@Bean
public MongoOperations mongoTemplate(Mongo mongo) { // Bean MongoTemplate.
return new MongoTemplate(mongo, "OrdersDB")/

}
}
Como recordará del capítulo anterior, se habilita la generación automática de reposi­
torios JPA de Spring Data mediante la anotación @ E n a b l e J p a R e p o s i t o r i e s . En el caso
de MongoDB, debe usar @ E n a b le M o n g o R e p o s ito r ie s .
Además de esta anotación, el listado 12.1 incluye dos métodos @Bean. El primero usa
M o n g o F a c to ry B e a n para declarar una instancia Mongo y permite el acceso de Spring
Data MongoDB a la propia base de datos (parecido a la función de D a t a S o u r c e en una
base de datos relacional).
Aunque se podría crear directamente una instancia de Mongo con M o n g o C lie n t,
tendría que procesar la excepción U n k n o w n H o stE x ce p tio n que generaría el constructor
de M o n g o C lie n t. Resulta más sencillo usar M o n g o F a c to ry B e a n de Spring Data Mongo.
Como bean de factoría, M o n g o F a c to ry B e a n crea una instancia de Mongo y no tendrá que
preocuparse de U n k n o w n H o stE x cep tio n .
El otro método @ Bean declara un bean M ongoT em p late. Para crearlo, se le asigna una
referencia a la instancia Mongo creada por el otro método de bean y el nombre de la base de
datos. Más adelante veremos cómo usar M o n go T em p late para consultar la base de datos.
Aunque nunca utilice M ongoT em plate directamente, lo necesitará porque los repositorios
generados automáticamente lo utilizan entre bastidores.
En lugar de declarar los bean directamente, la clase de configuración podría ampliar
A b s tra c tM o n g o C o n f i g u r a t i o n y reemplazar sus m étodos g e tD a ta b a se N a m e () y
mongo ( ) , como se ilustra en el siguiente código.
364 Capítulo 12

Listado 12.2. Habilitación de Spring Data MongoDB con @EnableMongoRepositories.


package o rd ers. conf ig;
import org. springframework. co n text. annotation.Configurâtion;
import o rg . springframework. data.mongodb. config.AbstraetMongoConfiguration;
import org. springframework. data.mongodb. rep o sito ry .co n fig . EnableMongoRepositories;
import com.mongodb.Mongo ;
import com.mongodb.MongoClient;
©Configuration
@EnableMongoRepositories("ord ers. db")
public cla ss MongoConfig extends AbstractMongoConfiguration {
©Override
protected Strin g getDatabaseName() { / / E sp e cifica r nombre de la base de datos,
return "OrdersDB";
}
©Override
public Mongo mongo() throws Exception { / / Crear un c lie n te Mongo,
return new MongoClient( );

}
Esta nueva clase de configuración es similar a la del listado 12.1, pero mucho más
sencilla. La principal diferencia es que no declara directamente un bean M o n go T em p late,
aunque se crea uno implícitamente. En su lugar, reemplazamos g e tD a ta b a se N a m e ()
para proporcionar el nombre de la base de datos. El método mongo () crea una instancia
de M o n g o C lie n t, pero como genera E x c e p t io n , podemos trabajar directamente con
M o n g o C lie n t sin necesidad de trabajar con M o n g o F a cto ry B e a n .
Tanto el listado 12.1 como el 12.2 ofrecen una configuración funcional para Spring Data
MongoDB, es decir, siempre que el servidor MongoDB se ejecute en el host local. Si se ejecuta
en otro servidor diferente, puede especificarlo al crear M on g oC lien t:
public Mongo mongo 0 throws Exception {
return new MongoClient("mongodbserver") ;
}
Puede que su servidor MongoDB escuche en un puerto que no sea el predeterminado
(27017). En ese caso, también puede especificarlo al crear M on g oC lient:
public Mongo mongo() throws Exception {
return new MongoClient("mongodbserver", 37017);
}
Y si su servidor MongoDB se ejecuta en un entorno de producción, espero que haya
habilitado la autenticación. Tendrá que proporcionar sus credenciales para poder acceder
a la base de datos. El acceso a un servidor MongoDB autenticado es más complejo, como
se aprecia en el siguiente listado.

Listado 12.3. Creación de MongoClient para acceder a un servidor MongoDB autenticado.


@Autowired
private Environment env;

©Override
Trabajar con bases de datos NoSQL 365

public Mongo mongo() throws Exception {


MongoCredential cred en tial =
MongoCredential. createMongoCRCredential( / / Crear credencial MongoDB.
env.getProperty("mongo.username") ,
"OrdersDB",
env.getProperty("mongo.password"). toCharArray( ) ) ;

return new MongoClient( / / Crear un c lie n te Mongo,


new ServerAddress("lo c a lh o s t", 37017),
A rra y s.a sL ist(c re d e n tia l)) ;

Para poder acceder a un servidor MongoDB autenticado, hay que crear una instancia
de M o n g o C lie n t con una lista de M o n g o C r e d e n tia l. En el listado 12.3 se crea una única
M o n g o C re d e n tia l. Para excluir los detalles de las credenciales de la clase de configuración
se parte del entorno inyectado (E n v iro n m e n t).
Spring Data MongoDB también se puede configurar en XML. Como ya he mencionado,
prefiero la opción de configuración de Java, pero si en su caso prefiere XML, el siguiente
listado muestra un ejemplo de configuración de Spring Data MongoDB con el espacio de
nombres mongo.

Listado 12.4. Spring Data MongoDB ofrece una opción de configuración XML.
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="h ttp : //www.springframework.org/schema/beans"
xm lns:xsi="h ttp : //www.w3. org/2001/XMLSchema-instance"
xmlns:mongo="h ttp : / /www.springframework. org/schema/data/mongo"
/ / D eclarar espacio de nombres mongo.
x s i : schemaLocation="
http://www.springframework.org/schema/data/mongo
h ttp : / /www. springframework. org/schema/data/mongo/spring-mongo.xsd
h ttp : //www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">

cmongo:repositories base-package="orders.db" />


/ / H ab ilitar generación de rep o sito rio s.

cmongo:mongo /> / / Declarar c lie n te Mongo.

cbean id="mongoTemplate" / / Crear bean MongoTemplate.


elass="org.springframework.data.mongodb. core.MongoTemplate">
<constructor-arg ref="mongo" />
<constructor-arg value="OrdersDB" />
</bean>

</beans>

Una vez configurado Spring Data MongoDB, ya puede empezar a usarlo para guardar
y recuperar documentos, pero antes tendrá que asignar los tipos de dominio de Java para
persistencia de documentos por medio de las anotaciones de asignación de objetos a docu­
mentos de Spring Data MongoDB.
366 Capítulo 12

An star tipos de m íei • jara jersister cía en M ongoDB


Al trabajar con JPA tuvimos que asignar los tipos de entidades Java a tablas y columnas
relaciónales. La especificación JPA ofrece diversas anotaciones para admitir la asignación
de objetos y algunas implementaciones JPA como Hibernate cuentan con sus propias
anotaciones de asignación.
MongoDB, sin embargo, no dispone de anotaciones de asignación propias. Spring Data
MongoDB aprovechó esta oportunidad para incluir varias anotaciones que puede usar para
asignar sus tipos de Java a documentos MongoDB. Se describen en la tabla 12.1.

Tabla 12.1. Anotaciones de Spring Data MongoDB para asignar objetos a documentos.

Anotación Descripción

@Document Identifica un objeto de dominio que asignar a un documento MongoDB.


@Id Indica que un campo es el campo de ID.
@DbRef Indica que un campo debe hacer referencia a otro documento, posiblemente
en otra base de datos.
@Field Define metadatos personalizados para un campo de documento.
@Version Identifica una propiedad que usar como campo de versión.

Las anotaciones @Docum ent e @ Id son equivalentes a © E n t i t y e @ Id de JPA. Las


utilizará a menudo y en todos los tipos de Java que se almacenen como documento en la
base de datos MongoDB. Por ejemplo, el siguiente código muestra cómo podría anotar una
clase O rd e r para su persistencia en MongoDB.

Listado 12.5. Las anotaciones de Spring Data MongoDB asignan tipos de Java a documentos.
package orders;
import ja v a .ú t i l . C olleetion;
import ja v a .ú t i l . LinkedHashSet;
import org. springframework. d ata. annotation.Id;
import org. springframework. data.mongodb. c o re .mapping.Document;
import org.springframework.data.mongodb.core.mapping.Field;

@Document / / Un documento,
public cla ss Order {

@Id
private String id ; / / Designar e l ID:

@ F ie ld {"c lie n t")


private String customer; / / Reemplazar e l nombre predeterminado del campo,
private String type;
private Collection<Item> Ítems = new LinkedHashSet<Item>();

public String getCustomer() {


Trabajar con bases de datos NoSQL 367

return customer;
}
public void setCustomer(String customer) {
t h i s . customer = customer;
}
public String getTypeO {
return type;
}
public void setType(String type) {
th is.ty p e = type;
}
public Collection<Item> getltemsO {
return item s;
}
public void setltem s(C ollection<Item > items) {
th is.ite m s = items;
}
public Strin g g etld () {
return id;
}

Como puede apreciar, O rd e r se anota con @Docum ent, lo que permite su persistencia
con M ongoTem plate, un repositorio generado automáticamente. Su propiedad i d se anota
con @ ld para designarla como ID del documento. Además, la propiedad c u s to m e r se anota
con @ F ie I d para que al guardar el documento, esta propiedad se asigne al campo C lie n t .
No se anotan más propiedades. A menos que se marquen como transitivos, los campos
de objetos de Java se guardan como campos del documento, y a menos que se indique
mediante @ F ie ld , los campos de documento tendrán los mismos nombres que sus corres­
pondientes propiedades de Java.
Fíjese en la propiedad Ítem s. Evidentemente es una colección de productos del pedido.
En una base de datos relacional tradicional, dichos productos se guardarían en una tabla
independiente, a la que se haría referencia mediante una clave secundaria, y el campo
Íte m s se podría anotar con @OneToMany para JPA, pero no en este caso.
Como mencionamos antes, los documentos pueden estar relacionados con otros docu­
mentos, aunque esto no sea la principal ventaja de una base de datos documental. En el
caso de la relación entre una orden de compra y sus productos, estos son simplemente una
parte anidada del mismo documento de pedido (véase la figura 12.1).
Por lo tanto, no necesitamos anotaciones para designar la relación. De hecho, la propia
clase Ite m carece de anotaciones:
package ord ers;

publíc cla ss Item {


368 Capítulo 12

private Long id ;
private Order order;
private Strin g product;
private double p rice ;
p riv ate in t quantity;

public Order getOrderO {


return order;
}
public String getProductO {
return product;
}
public void setProduct(String product) {
this.p rodu ct = product;
}
public double g etP riceO {
return p rice ;
}
public void setPrice(double p rice) {
th is .p r ic e = p rice ;
}
public in t getQ uantity() {
return quantity;
}
public void setQ uantity(int quantity) {
t h i s . quantity = quantity;
}
public Long g etld () {
return id;
}
}

Order
(Pedido)
id
customer
type

Items
(Productos)
id
order
product
price
quantity

Figura 12.1. Los documentos representan datos relacionados pero desnormalizados. Los conceptos
relacionados (como los productos de un pedido) se incrustan en el documento de nivel superior.
Trabajar con bases de datos NoSQL 369

No es necesario anotar Ite m con @Document, ni tampoco anotar ninguno de sus campos
con @ ld, ya que nunca se producirá la persistencia de un elemento Ite m como documento
independiente. Siempre será miembro de la lista Ite m de un documento O rd e r y un
elemento anidado en dicho documento.
Evidentemente, podría anotar una de las propiedades de 11emcon @F i e 1 d par a indicar
que dicho campo se almacene en el documento, aunque en este ejemplo no ha sido necesario.
Ya tenemos un tipo de dominio de Java anotado para su persistencia en MongoDB. A
continuación veremos cómo usar M o ng oT em p late para almacenar varios de estos tipos.

Acceder a MongoDB con MongoTemplate


Ya hemos configurado un bean M o n g o T e m p la te , explícitamente o ampliando
A b s tr a c tM o n g o C o n f i g u r a t i o n en la clase de configuración. Solo falta inyectarlo
donde haya que usarlo:
@Autowired
MongoOperations mongo;

En este caso inyectamos M ongoTem plate en una propiedad de tipo M ongoOperat io n s ,


una interfaz implem entada por M o n g o T em p late y que resulta m uy adecuada para evitar
trabajar directamente con la im plem entación concreta, en especial si se ha inyectado.
M o n g o O p e ra tio n s cuenta con varios métodos muy útiles para trabajar con una base
de datos MongoDB. No tenemos espacio para analizarlos todos, pero veremos algunos de
los más utilizados, por ejemplo para contar el número de documentos de una colección
de documentos. Con M o n g o O p e ra tio n s puede obtener la colección o r d e r y después
invocar c o u n t () para conseguir un recuento:
long orderCount = m ongo.getCollection("order") .count();

Imagine que quiere guardar un nuevo pedido. Para ello, invoque el método s a v e ():
Order order = new Order( );
. . . / / estab le ce r propiedades y añadir productos
mongo. save(order, "order" ) ;

El primer parámetro de sa v e () es el nuevo elemento O rd e r; el segundo es el nombre


del almacén de documentos en el que se guarda. También puede buscar un pedido por su
ID si invoca f in d B y ld ():
String orderld = . . . ;
Order order = mongo. findByld(orderld, Order. c l a s s ) ;

Para realizar consultas más avanzadas tendrá que crear un objeto Q u ery y pasarlo al
método f in d () . Por ejemplo, para buscar todos los pedidos cuyo campo C l i e n t sea
" Chuck W agón" , utilice el siguiente código:
List<Order> chucksOrders = mongo. find(Query. query(
C rite ria .w h e re ("C lie n t"). i s ( "Chuck Wagón")), Order. cla ss) ;
370 Capítulo 12

En este caso, los criterios ( C r i t e r i a ) usados para crear la consulta solamente


comprueban un campo, pero también se puede usar en consultas más interesantes. Por
ejemplo, puede recuperar todos los pedidos de Chuck realizados por la Web:
List<0rd.er> chucksWebOrders = mongo.find (Query.query (
Criteria.where("customer").is("Chuck Wagon")
.and ("type") .is ("WEB")) , Order, class)

Y para eliminar un documento, utilice el método remove ():


mongo.remove(order);

Como hemos mencionado, M ongoO perat io n s dispone de varios métodos para trabajar
con datos de documentos. No dude en consultar su documentación para descubrir todo
lo que puede ofrecerle.
Por lo general, M o n g o O p eration s se inyecta en una clase de repositorio propia y se usan
sus operaciones para implementar los métodos de repositorio, pero si prefiere no escribir
personalmente el repositorio, Spring Data MongoDB puede generarlo automáticamente
en tiempo de ejecución, como veremos a continuación.

Crear repositorio MongoDB


Para entender la creación de repositorios con Spring Data MongoDB, repasamos lo que
vimos en el capítulo anterior. En el listado 11.4 creamos la interfaz S p i t t e r R e p o s i t o r y ,
que amplía J p a R e p o s i t o r y . También habilitamos los repositorios de Spring Data JPA.
Como resultado, Spring Data JPA pudo crear automáticamente una implementación de
dicha interfaz, con varios métodos integrados y con otros que añadimos de acuerdo a una
convención de nomenclatura. Ya hemos habilitado los repositorios Spring Data MongoDB
con @ E n a b le M o n g o R e p o s it o r ie s , de modo que solo falta crear una interfaz desde la
que generar la implementación del repositorio, pero en lugar de ampliar J p a R e p o s i t o r y ,
tendremos que ampliar M o n g o R e p o sito ry . La interfaz O r d e r R e p o s it o r y del siguiente
listado amplía M o n g o R e p o s ito r y para proporcionar operaciones CRUD básicas para
documentos O rd er.

Listado 12.6. Spring Data MongoDB implementa automáticamente interfaces de repositorio.

package ord ers. db;


import ord ers.Order;
import org. springframework. d ata. mongodb. repository.MongoRepository;

public in te rfa c e OrderRepository


extends MongoRepositorycOrder, String> {
}
Como O r d e r R e p o s i t o r y amplía M o n g o R e p o s ito r y , amplía transitivamente la
interfaz de marcadores R ep os i t o r y . Recordará que toda interfaz que amplíe R ep os i t o r y
genera automáticamente una implementación en tiempo de ejecución. Sin embargo, en
este caso en lugar de un repositorio JPA que interactúa con una base de datos relacional,
Trabajar con bases de datos NoSQL 371

O r d e r R e p o s it o r y se implementará para leer y escribir datos en una base de datos docu­


mental MongoDB. La interfaz M o n g o R e p o s ito ry tiene dos parámetros. El primero es el
tipo de objeto anotado con @Docum ent con el que trabaja este repositorio. El segundo es
el tipo de la propiedad anotada con @ld.
Aunque O r d e r R e p o s ito r y no define métodos propios, hereda varios, incluidos los que
permiten realizar operaciones CRUD en documentos O rd e r. En la tabla 12.2 se describen
todos los métodos heredados por O r d e r R e p o s it o r y .

Tabla 12.2. Al ampliar MongoRepository, una interfaz de repositorio hereda varias operaciones
CRUD que Spring Data MongoDB implementa de forma automática.

Método Descripción
Salla
long count () ; Devuelve el número de documentos del tipo de
repositorio.
void delete (Iterable<? extends T) ; Elimina todos los documentos asociados a los objetos
indicados.
void delete (T) ; Elimina el documento asociado al objeto indicado.

void delete (ID) ; Elimina un documento por su ID.


void deleteAll () ; Elimina todos los documentos del tipo de repositorio
indicado.
boolean exists (Object) ; Devuelve true si existe un documento asociado al
objeto indicado.

boolean exists (ID) ; Devuelve true si existe un documento para el ID


indicado.

List<T> findAll () ; Devuelve todos los documentos del tipo de repositorio.

List<T> findAll (Iterable<ID>) ; Devuelve todos los documentos de los ID de


documento indicados.
List<T> findAll (Pageable) ; Devuelve una lista paginada y ordenada de los
documentos del tipo de repositorio.

List<T>findAll(Sort); Devuelve una lista ordenada de todos los documentos


del ID de documento indicado.
T findOne(ID) ; Devuelve un único documento para el ID indicado.

save(Iterable<S>); Guarda todos los documentos en el objeto iterable


indicado.

save (S) ; Guarda un único documento para el objeto indicado.

Los métodos de esta tabla se refieren a los tipos genéricos pasados a los métodos y
devueltos por estos. Como O r d e r R e p o s i t o r y amplía M o n g o R e p o s it o r y < O r d e r ,
S t r i n g > , significa que T se asigna a O rd e r, ID a S t r i n g y S a cualquier otro tipo que
amplíe O rd er.
372 Capítulo 12

Añadir métodos de consulta personalizados


Las operaciones CRUD suelen ser muy útiles, pero puede que necesite que el reposi­
torio proporcione métodos distintos a los predefinidos. En el capítulo anterior vimos que
Spring Data JPA admite una convención de nombres de método que permite a Spring Data
generar automáticamente implementaciones para los métodos que siguen dicha convención,
y la misma convención funciona con Spring Data MongoDB, de modo que puede añadir
métodos personalizados a OrderRepository de esta forma:
public interface OrderRepository
extends MongoRepositorycOrder, String> {
List<Order> findByCustomer(String c);
List<Order> findByCustomerLike(String c);
List<Order> findByCustomerAndType(String c, String t);
List<Order> findByCustomerLikeAndType(String c, String t);
}
Aquí tenemos cuatro métodos nuevos para buscar objetos O rd e r que cumplan deter­
minados criterios. Un método busca una lista de objetos O rd e r en los que la propiedad
c u s to m e r sea igual al valor pasado al método. Otro busca una lista de objetos O rd e r en
los que la propiedad c u s to m e r sea como el valor pasado al método. Un tercer método
busca objetos O rd e r en los que las propiedades c u s to m e r y t y p e sean iguales a los
valores pasados, y el cuarto es similar a los anteriores pero es una comparación l i k e en
lugar de e q u a ls .
El verbo de consulta f in d es muy flexible. Si lo prefiere, puede usar g e t como verbo
de consulta:
List<Order> getByCustomer(String c);

O también rea d :
List<Order> readByCustomer(String c);

Existe un verbo especial para contar los objetos que coincidan:


int countByCustomer(String c);

Como sucede con Spring Data JPA, dispone de gran flexibilidad sobre el contenido
incluido entre el verbo de la consulta y By. Por ejemplo, podría expresar qué busca
exactamente:
List<Order> findOrdersByCustomer(String c);

La palabra O r d e r s no tiene nada de especial y carece de relación alguna con lo que se


recupera. Podría asignar el siguiente nombre al método:
List<Order> findSomeStuf fWeNeedByCustomer (String c)

Tampoco es necesario devolver L is t < O r d e r > . Si solamente necesita un objeto O rd e r,


puede devolverlo:
Order findASingleOrderByCustomer(String c);

Aquí, se devolvería el prim er objeto O rd e r localizado si fuera de tipo L i s t . Si no hay


coincidencias, el m étodo devuelve n u i l .
Trabajar con bases de datos NoSQL 373

Especificar consultas
Como vimos en el capítulo anterior, puede usar la anotación @Q uery para especificar
una consulta personalizada para un método de repositorio. @ Q uery también funciona
con MongoDB; la única diferencia es que para MongoDB, @ Q uery acepta una cadena de
consulta JSON en lugar de una consulta JPA. Imagine por ejemplo que necesita un método
que localice todos los pedidos de un determinado tipo para el cliente Chuck Wagón. Lo
puede conseguir con la declaración del siguiente método en O r d e r R e p o s it o r y :
@Query( " { 'custom er': 'Chuck Wagón', 'typ e7 : ?0}" )
List<Order> findChucksOrders(String t);

La cadena JSON proporcionada a @Query se compara con todos los documentos O rder
y se devuelve el que coincida. Comprobará que la propiedad ty p e se asigna a ? 0, lo que
indica que debe ser igual al parámetro cero del método de consulta. Si hubiera más pará­
metros, se podrían indicar por medio de ?1, ?2, etc.

Combinar comportamientos personalizados de repositorios


En el capítulo anterior aprendimos a combinar métodos personalizados en un reposi­
torio generado automáticamente. Para JPA era necesario crear una interfaz intermediaria
que declarara los métodos personalizados, una clase de implementación para los mismos y
cambiar la interfaz del repositorio automático para que ampliara la interfaz intermediaria.
Para un repositorio de Spring Data MongoDB los pasos son los mismos.
Imagine que necesita un método que localice todos los objetos O rd e r en los que la
propiedad ty p e del documento coincida con un determinado valor. Podría crearlo con la
firma L i s t < O r d e r > f in d B y T y p e ( S t r i n g t ) , pero para nuestro ejemplo, imagine que
si el tipo indicado es NET, tendrá que consultar objetos O rd e r cuyo tipo sea WEB. Sería
complicado de hacer, incluso con la anotación @Q uery, pero no con una implementación
combinada. En primer lugar, se define la interfaz intermediaria:
package ord ers. db;
import ja v a .ú t i l.L is t ;
import orders.Order;

public in te rfa ce OrderOperations {


List<Order> findOrdersByType(String t) ;
}
Muy sencilla. Ya podemos crear la implementación combinada, mostrada en el siguiente
código.

Listado 12.7. Combinación de funcionalidad de repositorio personalizada


en un repositorio automático.

package o rd ers.db;
import ja v a .ú t i l .L i s t ;
import ord ers.Order;
374 Capítulo 12

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.mongodb.core.MongoOperations;
import org.springframework.data.mongodb.core.query.Criteria;
import org.springframework.data.mongodb.core.query.Query;

public class OrderRepositorylmpl implements OrderOperations {


@Autowired
private MongoOperations mongo; // Inyectar MongoOperations.

public List<Order> findOrdersByType(String t) {


String type = t .equals("NET”) ? "WEB" : t;

Criteria where = Criteria.where("type").is(t); // Crear consulta.


Query query = Query.query(where);

return mongo.find(query, Order.class); // Ejecutar consulta.


}
}

Como puede apreciar, la implementación combinada se inyecta con M o n g o O p eratio n s


(la interfaz implementada por M ongoT em p late). El método fin d O r d e r s B y T y p e () usa
M o n g o O p e ra tio n s para consultar en la base de datos los documentos que coincidan
con la consulta. Solo falta por cambiar O r d e r R e p o s it o r y para que amplíe la interfaz
O r d e r O p e r a t io n s :
public interface OrderRepository
extends MongoRepositorycOrder, Strings-, OrderOperations {

La base de todo es que el nombre de la clase de implementación es O r d e r R e p o s it o r y


Im p l, el mismo de la interfaz O r d e r R e p o s it o r y pero con el sufijo Im p l. Cuando Spring
Data MongoDB genera la implementación del repositorio, busca esta clase y la combina en
la implementación generada de forma automática.
También puede configurar Spring Data MongoDB para que busque una clase con otro
sufijo diferente. Para ello, basta con establecer el atributo r e p o s i t o r y l m p l e m e n t a t i o n
P o s t f i x de @ E n a b le M o n g o R e p o s ito r ie s (en la clase de configuración de Spring).
@Conf igurat ion
@EnableMongoRepositories(basePackages="orders.db",
repositorylmplementationPostfix="Stuff")
public class MongoConfig extends AbstractMongoConfiguration {

O, si usa configuración XML, puede establecer el atributo r e p o s it o r y - im pl - p o s t f i x


de «mongo: r e p o s i t o r i e s > :
cmongo:repositories base-package="orders.db"
repository-impl-postfix="Stuff" />

En cualquier caso, si configura Spring Data M ongoDB de esta forma, buscará


O r d e r R e p o s i t o r y S t u f f en lugar de O r d e r R e p o s it o r y lm p l.
Trabajar con bases de datos NoSQL 375

Las bases de datos documentales como MongoDB permiten solucionar determinados


problemas pero, como sucede con las bases de datos relaciónales, no sirven para todos
los casos y hay determinados problemas que no resuelven. Afortunadamente, no son las
únicas opciones disponibles.
A continuación presentaremos la compatibilidad de Spring Data con Neo4j, una cono­
cida base de datos gráfica.

Trabajar con datos gráficos en Neo4j


Mientras que las bases de datos documentales almacenan datos en documentos, las
gráficas lo hacen en nodos conectados entre sí por medio de relaciones. En una base de
datos gráfica, un nodo suele representar un concepto de la base de datos, con propiedades
que describen el estado del nodo. Las relaciones conectan dos nodos y pueden tener
propiedades propias.
En su forma más sencilla, las bases de datos gráficas suelen tener un propósito más
general que las documentales, y pueden ser una alternativa sin esquema a las bases de
datos relaciónales, pero al estructurar los datos como gráficos, se pueden recorrer relaciones
para descubrir aspectos sobre los datos que con otro tipo de base de datos sería complicado
e incluso imposible. Spring Data Neo4j ofrece muchas de las prestaciones de Spring Data
JPA y Spring Data MongoDB, pero orientadas a la base de datos gráfica Neo4j. Ofrece
anotaciones para asignar tipos de Java a nodos y relaciones, acceso orientado a plantillas
y generación automática de implementaciones de repositorio.
Veremos cómo usar estas funciones para trabajar con Neo4j, pero antes tendremos que
configurar Spring Data Neo4j.

Configurar Spring Data Neo4j


La clave para configurar Spring Data Neo4j consiste en declarar un bean
G r a p h D a ta b a s e S e r v ic e y en habilitar la generación automática de repositorios Neo4j. El
siguiente listado muestra la configuración básica de Java necesaria para Spring Data Neo4j.

Listado 12.8. Configuración de Spring Data Neo4j con @EnableNeo4jRepositories.

package ord ers. conf ig ;


import org. n eo 4j. graphdb. GraphDatabaseService;
import org.n eo 4 j. graphdb. fa cto ry . GraphDatabaseFactory;
import org. springframework. co n tex t. annotation. Bean;
import org.springframework.context. annotation.Configuration;
import org. springframework.d ata.n eo 4 j. co n fig . EnableNeo4jRepositories;
import org. springframework. d ata.neo4j . co n fig .Neo4jConfiguration;

©Configuration
@EnableNeo4jRepositories(basePackages=,,ord ers.d b") / / H a b ilita r rep o sitorio s automáticos,
public cla ss Neo4jConfig extends Neo4jConfiguration {

public Neo4jConfig() {
376 Capítulo 12

setBasePackage("orders") ; / / D efinir paquete base del modelo.


}
@Bean(destroyMethod="shutdown")
public GraphDatabaseService graphDatabaseService() {
return new GraphDatabaseFactory()
.newEmbeddedDatabase("/tmp/graphdb"} ; / / Configurar paquete de base de
/ / datos incrustada.
}
}
La anotación @ E n a b l e N e o 4 jR e p o s i t o r i e s permite a Spring Data Neo4j generar
automáticamente implementaciones de repositorios Neo4j. Se establece b a s e P a c k a g e s
para que examine el paquete o r d e r s . db en busca de interfaces que amplíen (directa o
indirectamente) la interfaz R e p o s i t o r y .
Neo4 jC o n f i g amplía N eo 4 j C o n f i g u r a t i o n , que proporciona métodos para confi­
gurar S p r in g D a ta Neo4 j . Entre ellos se encuentra s e t B a s e P a c k a g e ( ) , que se invoca
desde el constructor de Neo4 j Conf i g para indicar a Spring Data Neo4j que puede encontrar
clases del modelo en el paquete o r d e r s .
La última pieza del puzzle consiste en definir un bean G r a p h D a t a b a s e S e r v ic e . En
este caso, el método g r a p h D a t a b a s e S e r v ic e () usa G r a p h D a ta b a s e F a c to r y para
crear una base de datos Neo4j incrustada. Con Neo4j, una base de datos incrustada no
debe confundirse con una base de datos en memoria. "Incrustada" significa que el motor
de base de datos se ejecuta en la misma MVJ como parte de la aplicación, no como servidor
independiente. Los datos se siguen guardando en el sistema de archivos (en este caso en
/ tm p /g ra p h d b ).
También podría configurar G r a p h D a t a b a s e S e r v ic e para que haga referencia a un
servidor Neo4j remoto. Si tiene la biblioteca s p r i n g - d a t a - n e o 4 j - r e s t en la ruta de
clases de su aplicación, puede configurar S p r in g R e s tG r a p h D a ta b a s e , que accede a una
base de datos Neo4j remota sobre un API RESTful:
@Bean(destroyMethod="shutdown")
public GraphDatabaseService graphDatabaseService() {
return new SpringRestGraphDatabase(
"h ttp : / /graphdbserver: 7474/db/data/" ) ;
}
En este caso, se configura S p r in g R e s tG r a p h D a t a b a s e para asumir que la base de
datos remota no requiere autenticación. Sin embargo, en un entorno de producción es
probable que desee proteger el servidor de base de datos. En ese caso, tendrá que propor­
cionar las credenciales de su aplicación al crear S p r in g R e s tG r a p h D a t a b a s e :
@Bean(destroyMethod="shutdown")
public GraphDatabaseService graphDatabaseService(Environment env) {
return new SpringRestGraphDatabase(
"http://graphd bserver: 7474/db/data/" ,
env.getProperty("db.username"), env.getProperty("db.password") ) ;
}
Aquí, las credenciales se obtienen a través del entorno inyectado para evitar mostrarlas
en el código de la clase de configuración.
Trabajar con bases de datos NoSQL 377

Spring Data Neo4j también ofrece un espacio de nombres de configuración XML. Si


prefiere configurar Spring Data Neo4j en XML, puede usar los elementos <neo4 j : co n f ig >
y <neo4 j : r e p o s i t o r i e s > de dicho espacio. En el listado 12.9 puede ver una configu­
ración XML equivalente a la de Java mostrada en el listado 12.8.

Listado 12.9. Spring Data Neo4j también se puede configurar en XML.

<?xml v e rsio n = "l.0" encoding=,'UTF-8"?>


cbeans xmlns="h ttp : / /www. springframework.org/schema/beans"
xralns: x s i= "h ttp : //www.w3. org/2001/XMLSchema-in stan ce"
xmlns:neo4j = "h ttp : //www. springframework. org/schema/data/neo4j"
x s i : schemaLocation="
h ttp : //www. springframework. org/schema/beans
h ttp : //www. springframework. org/schema/beans/spring-beans.xsd
h ttp : / /www.springframework. o rg /schéma/data/neo4j
h ttp : / /www. springframework. o rg /s chema/data/neo4 j / spring-neo4 j .xsd">
<neo4j:config
storeDirectory="/tmp/graphdb"
base-package="orders" />

<neo4j: rep o sito ries base-package="orders.db" />


</beans>

El elemento < n eo 4 j : c o n f ig > configura los detalles de acceso a la base de datos. En


este caso, configura Spring Data Neo4j para que trabaje con una base de datos incrustada;
en concreto, el atributo s t o r e D i r e c t o r y especifica la ruta del sistema de archivos para
la persistencia de los datos. El atributo b a s e -p a c k a g e establece el paquete en el que se
definen las clases del modelo.
En cuanto a < n e o 4 j : r e p o s i t o r i e s > , permite a Spring Data Neo4j generar automá­
ticamente implementaciones de repositorio examinando el paquete o r d e r s . db en busca
de interfaces que amplíen la interfaz R ep o s i t o r y .
Para configurar Spring Neo4j para acceder a un servidor Neo4j remoto, basta con declarar
unbean S p rin g R e stG ra p h D a ta b a se y establecer el atributo g r a p h D a ta b a s e S e r v ic e
de < n e o 4 j:c o n f i g > :
<n eo4j:config base-package="orders"
graphDatabaseService="graphDatabaseService" />

<bean id="graphDatabaseService" class=


"org. springframework. d ata. neo4j . r e s t . SpringRestGraphDatabase">
<constructor-arg value="http://graphdbserver : 7474/db/data/" />
<constructor-arg value="db.username" />
<constructor-arg value="db.password" />
</bean>

Independientemente de que configure Spring Data Neo4j con Java o XML, asegúrese
de que los tipos de dominio se encuentran bajo un paquete especificado como base (el atri­
buto b a s e P a c k a g e s de @ E n ab leN eo 4 j R e p o s i t o r i e s o el atributo b a s e p a c k a g e de
< n eo 4 j : c o n f ig > ) . También tendrá que anotarlos como entidades de nodo o de relación,
como veremos a continuación.
378 Capítulo 12

Anotar entidades gráficas


Neo4j define dos tipos de entidades: nodos y relaciones. Los nodos suelen representar
los elementos de la aplicación, mientras que las relaciones definen cómo se relacionan entre
ellos. Spring Data Neo4j cuenta con varias anotaciones, recogidas en la tabla 12.3, que puede
aplicar a tipos de dominio y a sus campos para la persistencia en Neo4j.

Tabla 12.3. Anotaciones de Spring Data Neo4j para asignar tipos de dominio a nodos
y relaciones en un gráfico.

Descripción
¡k m * ....
@NodeEntity Declara un tipo de Java como entidad nodo.
@RelationshipEntity Declara un tipo de Java como entidad relación.
@StartNode Declara una propiedad como nodo inicial de una entidad relación.
@EndNode Declara una propiedad como nodo final de una entidad relación.
@Fetch Declara una propiedad de una entidad para su carga recomendada.
@GraphId Declara una propiedad como campo de ID de una entidad (el campo
debe ser de tipo Long).
@GraphProperty Declara explícitamente una propiedad.
@GraphTraversal Declara una propiedad para proporcionar automáticamente un
elemento iterable creado siguiendo el recorrido de un gráfico.
@Indexed Declara una propiedad para indexar.
@Labels Declara las etiquetas de @NodeEntity.
@Query Declara una propiedad para proporcionar automáticamente un
elemento iterable creado mediante la ejecución de una consulta
Cypher.
@QueryResult Declara una clase o interfaz de Java capaz de almacenar los
resultados de una consulta.
@RelatedTo Declara una relación sencilla entre la @NodeEntity actual y otra
@NodeEntity a través de una propiedad.
@RelatedToVia Declara un campo de una @NodeEntity como referencia a una
@RelationshipEntity a la que pertenece el nodo.
@Re1ationshipType Declara un campo como tipo de una entidad relación.
@ResultColumn Declara una propiedad de un tipo anotado con @QueryResult para
capturar un campo concreto del resultado de una consulta.

Para ilustrar el uso de estas anotaciones, las aplicaremos a nuestro ejemplo o r d e r /ite m .
Una forma de modelar los datos sería designar un pedido como nodo relacionado con
uno o varios productos. En la figura 12.2 se ilustra dicho modelo como gráfico.
Trabajar con bases de datos NoSQL

T ie n e p ro d u c to s

Figura 12.2. Una relación sencilla conecta dos nodos pero carece de propiedades propias.

Para designar nodos como pedidos tiene que anotar la clase O rd e r con @ N o d e E n tity .
El siguiente código muestra la clase O rd e r anotada con © N o d e E n tity , además de otras
anotaciones de la tabla 12.3.

Listado 12.10. Order se anota para ser un nodo de la base de datos gráfica.

package orders ;
import ja v a .u t i l .LinkedHashSet;
import j ava.ut i 1 . Set ;
import org.springframework.data.neo4j.annotation.Graphld;
import org .sp rin gframew ork.data.neo4j. annotation.NodeEntity;
import o rg .sp ringframework.data .n e o 4 j. annotation.RelatedTo;

@NodeEntity / / Los pedidos son nodos,


public cla ss Order {

@GraphId / / ID del g rá fico ,


private Long id;
private String customer;
private String type;

@RelatedTo(type="HAS_ITEMS") / / Relación con productos,


private Set<Item> items = new LinkedHashSet<Item >();

Además de @ N o d e E n t i t y en el nivel de la clase, la propiedad i d se anota con


@GraphId. En Neo4j todas las entidades deben tener un ID de gráfico, similar a las propie­
dades anotadas co n @ Id d e @ E n t i t y de JPAo de @Document de MongoDB. Es obligatorio
que la propiedad anotada con @GraphId sea de tipo Long. Las propiedades c u s to m e r y
t y p e no tienen anotaciones; serán propiedades del nodo en la base de datos.
La propiedad ít e m s se anota con @ R e la te d T o para indicar que un objeto O rd e r está
relacionado con un conjunto de elementos Ite m . El atributo ty p e básicamente etiqueta la
relación. Puede recibir cualquier valor pero es habitual asignar un texto legible que describa
brevemente la naturaleza de la relación. Más adelante usaremos esta etiqueta en consultas
entre relaciones. En cuanto a la propia clase Ite m , el siguiente listado muestra cómo se
anota para la persistencia de gráficos.

Listado 12.11. Los objetos también se representan como nodos en la base de datos gráfica.

package orders;
import o rg . springfraraework. d ata. n eo 4 j. annotation.Graphld;
import o rg .sp rin gframework.data.neo4j.annotation.N odeEntity;
380 Capítulo 12

@NodeEntity / / Los productos son nodos,


public cla ss Item {

@GraphId / / ID del g rá fico .


private Long id ;
private String product;
private double p rice ;
private in t quantity;

}
Como sucede con O rd er, Ite m se anota con @ N o d e E n tity para designarlo como nodo.
También tiene una propiedad Long anotada con @ G rap h Id para ser el ID del gráfico del
nodo. La persistencia de las propiedades p r o d u c t , p r i c e y q u a n t i t y se realiza como
propiedades de nodo en la base de datos. La relación entre O rd e r e I te m es muy sencilla
y carece de datos propios. Por ello, basta con la anotación @ R e la te d T o para definir la
relación, pero no todas las relaciones son tan sencillas.
Repasemos el modelo de datos para ver cómo enfrentarnos a situaciones más complejas.
En el modelo de datos actual hemos combinado los conceptos de artículo de línea y producto
en la clase 11 em. Si lo piensa, un pedido está relacionado con uno o varios productos y esta
relación constituye un artículo de línea del pedido. En la figura 12.3 se ilustra un modelo
de datos alternativo.

Lineltem
(con artículos de línea para)

Cantidad

Figura 12.3. Una entidad relación es una relación que tiene propiedades propias.

En este nuevo modelo, la cantidad de productos del pedido es una propiedad del artí­
culo de línea, y un producto es un concepto diferente. Como antes, los pedidos son nodos
y también los productos. Los artículos de línea son relaciones, pero ahora que un artículo
de línea tiene que incluir un valor de cantidad, la relación no puede ser sencilla. Tendrá
que definir una clase que represente un artículo de línea, como L in e lte m mostrada a
continuación.

Listado 12.12. Lineltem conecta un nodo Order con un nodo Product.

package orders;
import org. springframework. d ata.n eo 4 j. annotation.EndNode;
import org .sp ringframework.data .n e o 4 j. annotation.Graphld;
import org.springframework. d ata.n eo 4j. annotation.R elationshipE ntity;
import org. springframework. d ata.n e o 4 j. annotat io n . StartNode;

@RelationshipEntity(type="HAS_LINE_ITEM_FOR") / / Lineltem es una re la ció n ,


public cla ss Lineltem {

@Graphid // ID del gráfico.


Trabajar con bases de datos NoSQL 381

private Long id;

@StartNode / / Nodo i n i c i a l .
private Order order;

@EndNode / / Nodo f in a l,
private Product product;

private in t quantity;

}
Mientras que O rd er se anotó con© N oaem clLy paia drágT.MW t>orí o , T,-i -ne*T1“Rm
se anota con @ R e l a t i o n s h i p E n t i t y y también tiene una propiedad i d anotada con
@ G rap h Id . De nuevo, todas las entidades, tanto nodos como relaciones, deben tener un ID
gráfico de tipo Long. Lo que hace que las relaciones sean especiales es que conectan dos
entidades de nodo. Se aplican las anotaciones @ S ta r tN o d e y @EndNode a propiedades
que definen ambos extremos de la relación. En este caso, O rd e r es el extremo inicial y
P r o d u c t el final. Por último, L in e l t e m tiene una propiedad q u a n t i t y que se guarda
en la base de datos al crear la relación.
Una vez anotado el dominio, ya puede empezar a guardar y leer nodos y relaciones.
Veremos primero cómo usar el acceso a datos orientado a plantillas de Spring Data Neo4j
con N eo 4 j T é m p la te .

Trabajar con Neo4jTempíate


Al igual que Spring Data MongoDB cuenta con M o n go T em p late para la persistencia
en MongoDB basada en plantillas, Spring Data Neo4j nos ofrece N eo 4 j T é m p la te para
trabajar con nodos y relaciones en la base de datos gráfica Neo4j. Si ha configurado Spring
Data Neo4j como indicamos antes, ya dispone de un bean N eo 4 j T é m p la te en el contexto
de la aplicación de Spring. Solo tendrá que inyectarlo donde lo necesite. Por ejemplo, puede
conectarlo de forma automática directamente a una propiedad de bean:
@Autowired
prívate Neo4jOperations n eo 4j;

Neo4 j T é m p la te define varios métodos, incluidos los que le permiten guardar nodos,
eliminarlos y crear relaciones entre métodos. No tenemos espacio suficiente para descri­
birlos todos pero nos detendremos en algunos de los más utilizados. Una de las primeras
operaciones básicas que puede hacer con Neo4 j T é m p la te es guardar un objeto como nodo.
Si el objeto está anotado con @N odeEnt i t y , puede usar el método s a v e () de esta forma:
Order order = . . . ;
Order savedOrder = n eo 4 j. save(ord er);

Si conoce el ID de gráfico del objeto, puede recuperarlo por medio del método
f in d O n e ( ) :
Order order = n eo 4 j. findOne(42, Order. c l a s s ) ;
382 Capítulo 12

Si no existe un nodo con el ID indicado, f indone () genera NotFoundException.


Si prefiere recuperar todos los objetos de un determinado tipo, puede usar el método
findAll ():
EndResult<Order> allOrders = n eo 4 j. findAll(Order. c l a s s ) ;

Aquí, E n d R e s u lt es I t e r a d l e , lo que permite usarlo en bucles f o r - e a c h y siempre


que pueda usar I t e r a b l e . Si los nodos no existen, f i n d A l l () devuelve un elemento
I t e r a b l e vacío. Si solamente necesita saber la cantidad de objetos de un determinado
tipo en la base de datos Neo4j, puede invocar el método c o u n t () :
long orderCount = cou nt(O rd er.class);

El método delete () le permite eliminar un objeto:


n eo 4 j. d e lete(o rd e r);

Uno de los métodos más interesantes que proporciona N e o 4 jT e m p la te es c r e a t e


R e l a t i o n s h i p B e t w e e n () . Crea una relación entre dos nodos. Por ejemplo, podría crear
una relación L in e l t e m entre un nodo O rd e r y un nodo P r o d u c t:
Order order = . . . ;
Product prod = . . . ;
Lineltem lineltem = n eo 4 j. createRelationshipBetw een(
order, prod, L in eltem .class, "HAS_LINE_ITEM_FORM, f a l s e ) /
lin eltem .setQ u an tity (5 );
n eo 4 j.sa v e(lin e ltem );

Los dos primeros parámetros de c r e a t e R e l a t i o n s h i p B e t w e e n () son los objetos


cuyos nodos constituyen los extremos de la relación. El siguiente parámetro especifica el
tipo anotado c o n @ R e l a t i o n s h ip E n t it y que representa la relación. Después, se especifica
un valor S t r in g que describe la naturaleza de la relación. El último parámetro es un valor
Booleano que indica si se permiten relaciones duplicadas entre las dos entidades de nodo.
c r e a t e R e l a t i o n s h i p B e t w e e n () devuelve una instancia de la clase de relación,
desde la que puede establecer las propiedades que desee. En el ejemplo anterior se esta­
blece la propiedad q u a n t i t y . Cuando termine, invoque s ave () para guardar la relación
en la base de datos.
N eo4j T é m p la te ofrece una forma muy sencilla de trabajar con nodos y relaciones de
una base de datos Neo4j, pero tendrá que crear sus propias implementaciones de reposi­
torio que deleguen en N eo4j T é m p la te . Acontinuación veremos cómo puede Spring Data
Neo4j generar automáticamente implementaciones de repositorio.

Crear repositorios Neo4j automáticos


Una de las mayores ventajas de muchos proyectos Spring Data es que generan automá­
ticamente implementaciones de una interfaz de repositorio. Ya lo hemos visto con Spring
Data JPA y Spring Data MongoDB. Para no ser menos, Spring Data Neo4j también admite
la generación automática de repositorios.
Trabajar con bases de datos NoSQL 383

Ya hemos añadido @ E n ab leN eo 4 j R e p o s i t o r i e s a la configuración, de modo que


Spring Data Neo4j ya puede generar repositorios, solo faltan las interfaces. La siguiente
interfaz O r d e r R e p o s it o r y es un buen punto de partida:
package ord ers. db;
import ord ers. Order;
import org.springfram ework.data.neo4j. repository.GraphRepository;

public in te rfa ce OrderRepository extends GraphRepository<Order> {}

Como sucede en otros proyectos de Spring Data, Spring Data Neo4j habilita la gene­
ración de repositorios para interfaces que amplían la interfaz R e p o s i t o r y . En este caso,
O r d e r R e p o s it o r y amplía G r a p h R e p o s ito r y , que indirectamente amplía R e p o s it o r y .
Por lo tanto, Spring Data Neo4j generará una implementación de O r d e r R e p o s it o r y en
tiempo de ejecución. G r a p h R e p o s it o r y tiene como parámetro O rd e r, el tipo de entidad
con el que trabaja el repositorio. Como Neo4j requiere que los ID de gráfico sean de tipo
Long, no es necesario especificar el tipo del ID al ampliar G r a p h R e p o s ito r y .
De forma predeterminada dispondrá de operaciones CRUD comunes similares a las que
ofrecen J p a R e p o s ito r y y M on g oR ep osito ry. En la tabla 12.4 se describen los métodos
obtenidos al ampliar G ra p h R e p o sito ry .

Tabla 12.4. Al ampliar GraphRepository, una interfaz de repositorio hereda varias operaciones
CRUD que Spring Data Neo4j implementa de forma automática.

Método Descripción

long count () ; Devuelve la cantidad de entidades del tipo de


destino que hay en la base de datos.
void delete (Iterable<? extends T>) ; Elimina varias entidades.
void delete (Long id) ; Elimina una única entidad dado su ID.
void delete (T) ; Elimina una única entidad.
void deleteAll () ; Elimina todas las entidades del tipo de destino.
boolean exists (Long id) ; Comprueba la existencia de una entidad dado
su ID.
EndResult<T> findAll () ; Recupera todas las entidades del tipo de destino.
Iterable<T> findAll (Iterable<Long>) ; Recupera todas las entidades del tipo de destino
para los ID proporcionados.
Page<T> findAll (Pageable) ; Recupera una lista paginada y ordenada de todas
las entidades del tipo de destino.
EndResult<T> findAll (Sort) ; Recupera una lista ordenada de todas las
entidades del tipo de destino.
EndResult<T> Recupera todas las entidades en las que la
findAHBySchemaPropertyValue propiedad indicada coincida con el valor indicado.
(String, Object) ;
384 Capítulo 12

' ‘ '
' ■
Método Descripción l >J - - ' -*j <"r j! §

a&¡-¿ mm imMÉm
Iterable<T> f indAllByTraversal Recupera todas las entidades obtenidas tras
(N, TraversalDescription); recorrer un gráfico empezando desde el nodo indicado.
T findBySchemaPropertyValue Busca una entidad en la que la propiedad indicada
(String, Object); coincida con el valor indicado.
T findOne(Long) ; Busca una única entidad dado su ID.
EndResult<T> query (String,
Map<string,Object>); Busca todas las entidades que coincidan con una
consulta Cypher indicada.
Iterable<T> save(Iterable<T>) ; Guarda varias entidades.
S save(S); Guarda una única entidad.

No tenemos espacio suficiente para describir todos estos métodos pero utilizará perió­
dicamente algunos de ellos. La siguiente línea permite guardar una entidad O rder:
Order savedOrder = orderRepository .save (order)

Al guardarla, el método sa v e () devuelve la entidad guardada, que ahora debe tener


completada su propiedad anotada con @GraphId si antes era n u il .
Para buscar una entidad concreta, invoque f indO ne ( ) . El siguiente ejemplo busca una
entidad O rd e r con el ID de gráfico 4:
Order order = orderRepository. findOne(4L);

También puede buscar todos los pedidos:


EndResult<Order> allOrders = orderRepository. fin d A ll0 ;

Evidentemente, puede eliminar una entidad si lo desea, por medio de d e l e t e ():


d ele te (o rd e r);

Se elimina el nodo O rd er indicado de la base de datos. Si solo tiene el ID de gráfico,


puede pasarlo a d e l e t e () en lugar del propio tipo de nodo:
d elete(o rd e rld );

Para realizar consultas personalizadas puede usar el método q u e r y () para ejecutar


una consulta Cypher arbitraria en el gráfico. Es un funcionamiento similar al del método
q u e r y () de N eo 4 j T é m p la te . En su lugar, puede añadir métodos de consulta propios a
O r d e r R e p o s it o r y .

Añadir métodos de consulta


Ya hemos visto cómo añadir métodos de consulta que sigan una convención de nomen­
clatura en Spring Data JPA y Spring Data MongoDB. Sería muy decepcionante que Spring
Data Neo4j no ofreciera la misma posibilidad. Como ilustra el siguiente código, no hay
motivo de alarma.
Trabajar con bases de datos NoSQL 385

Listado 12.13. Definición de métodos de consulta con una convención de nomenclatura.

package orders.db;
import ja v a .ú t i l . L i s t ;
import ord ers.Order;
import org. springframework. d ata.n eo 4j. repository.GraphRepository;

public in te rfa ce OrderRepository extends GraphRepository<Order> {

List<Order> findByCustomer(String customer); / / Métodos de consulta.


List<Order> findByCustomerAndType(String customer, Strin g ty p e );
}
Aquí añadimos dos métodos de consulta. Uno busca todos los nodos O rd e r en los
que la propiedad cu sto m e r es igual al valor S t r i n g proporcionado. El otro método es
similar pero además de comparar la propiedad cu sto m er, los nodos O rd e r deben tener
una propiedad t y p e igual al tipo proporcionado. Ya hemos visto la convención de nomen­
clatura para métodos de consulta, de modo que no la describiremos más. Encontrará más
información sobre cómo crear estos métodos en el capítulo anterior.

Especificar consultas personalizadas


Si la convención de nomenclatura no se ajusta a sus necesidades, puede anotar un
método con @Query para especificar su propia consulta. Ya hemos visto @Q uery antes. Con
Spring Data JPA, se usa para especificar una consulta JPA de un método de repositorio y
con Spring Data MongoDB se utiliza para especificar una consulta JSON de comparación.
Sin embargo, en el caso de Spring Data Neo4j, debe especificar una consulta Cypher:
BQuery("match (o :Order)- [ :HAS_ITEMS]- > ( i : Item) " +
"where i.product='Spring in Action' return o")
List<Order> findSiAOrders();

Aquí, se anota f in d S iA O r d e r s () con @ Q uery y se asigna una consulta Cypher para


buscar todos los nodos O rd e r relacionados con un I te m cuya propiedad p r o d u c t sea
igual a S p r in g i n A c t io n .

Combinar comportamientos personalizados de repositorios


Si para sus necesidades no le sirve ni la convención de nomenclatura ni los métodos
@Query, siempre puede combinar lógica personalizada de repositorios.
Imagine, por ejemplo, que desea escribir la implementación de f in d S iA O r d e r s ()
personalmente y no recurrir a la anotación @Query. Podría empezar con la definición de
una interfaz intermediaria que definiera el método f in d S iA O r d e r s () :
package o rd ers. db;
import ja v a .ú t i l.L is t ;
import ord ers.Order;

public in te rfa c e OrderOperations {


List<Order> findSiAOrders{);
i
386 Capítulo 12

Tras ello, puede cambiar O r d e r R e p o s it o r y para que amplíe O r d e r O p e r a t io n s


además de G r a p h R e p o s ito r y :
public in te rfa c e OrderRepository
extends GraphRepository<Order>, OrderOperations {

}
Por último, escriba la implementación propiamente dicha. Como sucede con Spring
Data JPA y Spring Data MongoDB, Spring Data Neo4j busca una clase de implementa­
ción cuyo nombre sea el mismo de la interfaz de repositorio y tenga el sufijo Im p l. Así
pues, tendrá que crear una clase O r d e r R e p o s it o r y lm p l. El siguiente código muestra
O r d e r R e p o s it o r y Im p l, que implementa f in d S iA O r d e r s ( ) .

Listado 12.14. Combinación de funcionalidad personalizada en OrderRepository.

package ord ers. db;


import ja v a .u t i l . C o lle ctio n s;
import ja v a .u t i l.L is t ;
import j ava.util.M ap;
import ord ers. Order;
import o rg .n eo 4 j.h e lp e r s .c o lle c tio n .Ite r a to r U til;
import org. springframework.beans. fa c to ry . annotation.Autowired;
import org.springframework.data.neo4j.conversion.EndResult;
import org.springfram ework.data.neo4j. conversion.Result;
import org.springfram ework.data.neo4j. template.Neo4jOperations;

public cla ss OrderRepositorylmpl implements OrderOperations {


/ / Implementar in terfa z interm ed iaria.

private f in a l Neo4jOperations n eo 4j;

@Autowired
public OrderRepositorylmpl(Neo4jOperations neo4j) { / / Inyectar Neo4jOperations.
th is.n e o 4 j = n eo 4 j;
}
public List<Order> findSiAOrders() {
Result<Map<String, Object>> re su lt = n eo 4j. query( / / E jecu tar consulta.
"match (o:Order)- [ ;HAS_ITEMS]- > ( i : Item) " +
"where i.prod uct= 'Spring in Action' return o",

EndResult<Order> endResult = r e s u lt. to(O rder. c l a s s ) ; / / Convertir a EndResult<Order>.

return It e r a t o r U til. asL ist(en d R esu lt); / / Convertir a List<Order>.


}
}
Se inyecta N e o 4 j O p e r a t i o n s en O r d e r R e p o s i t o r y l m p l (en concreto una
instancia de N e o 4 jT e m p la t e ) , que utiliza para consultar la base de datos. Como el
método q u e r y () devuelve R e s u l t < M a p < S t r i n g , Obj e c t > > , tendrá que convertirlo a
L is t < O r d e r > . El prim er paso consiste en invocar el método t o () en R e s u l t para generar
Trabajar con bases de datos NoSQL 387

E n d R e s u lt< O rd e r> . Después, se usa I t e r a t o r U t i l . a s L i s t () de x\reo4jpaia luiuci iu -


E n d R e s u lt< O r d e r > en L i s t < O r d e r > , que es lo que se devuelve. Las bases de datos
gráficas como Neo4j son magníficas para capturar datos que se representan mediante nodos
y relaciones. Si piensa que el mundo que nos rodea está repleto de elementos relacionados
entre sí, verá que las bases de datos gráficas tienen múltiples aplicaciones. Personalmente
me confieso un gran seguidor de Neo4j.
Pero en ocasiones sus necesidades de datos serán más sencillas y solo tendrá que alma­
cenar un valor para poder recuperarlo después por medio de una clave. A continuación
veremos cómo permite Spring Data la persistencia de datos de clave-valor por medio del
almacén de claves y valores Redis.

Trabajar con datos de clave-valor en Redis


Redis es un tipo especial de base de datos denominado almacén de clave-valor. Como
su nombre indica, almacena pares de claves y valores. De hecho, tiene mucho en común
con los mapas de hash. Denominarlos mapas de hash persistentes no estaría demasiado
desencaminado.
Si lo piensa, no hay demasiadas consultas que se puedan realizar sobre un mapa de
hash... o un almacén de claves y valores. Puede almacenar un valor en una clave concreta
y obtener el valor de una clave determinada, y poco más. Por ello, la compatibilidad con
repositorios automáticos de Spring Data no tiene demasiado sentido al aplicarse a Redis.
Por otra parte, la función de acceso a datos orientada a plantillas de Spring Data puede ser
muy útil a la hora de trabajar con Redis.
Spring Data Redis incluye varias implementaciones de plantilla para almacenar datos
y recuperarlos de una base de datos Redis, como veremos en breve, pero para crear una
de las plantillas de Spring Data Redis primero necesita una factoría de conexión Redis.
Afortunadamente, Spring Data Redis le permite elegir entre cuatro diferentes.

Conectarse a Redis ________ ________________________


Una factoría de conexiones Redis genera conexiones a un servidor de base de datos
Redis. Spring Data Redis incluye cuatro factorías de conexión para cuatro implementa­
ciones cliente de Redis:

• Je d is C o n n e c tio n F a c to r y
• Jre d is C o n n e c tio n F a c to ry
• L e ttu c e C o n n e c tio n F a c to r y
• S r p C o n n e c t io n F a c t o r y

Puede elegir la que prefiera. Le sugiero que realice sus propias pruebas para determinar
cuál es la más indicada. Desde la perspectiva de Spring Data Redis, las cuatro son igual
de completas.
388 Capítulo 12

Tras elegir una, puede configurarla como bean en Spring. El siguiente ejemplo muestra
la configuración del bean J e d i s C o n n e c t i o n F a c t o r y :
@Bean
public RedisConnectionFactory redisCFO {
return new JedisConnectionFactory();
}

Al crear una instancia de la factoría de conexión a través de su constructor predetermi­


nado se genera una factoría de conexión que crea sus conexiones para el host local, en el
puerto 6379 y sin contraseña.
Si ejecuta su servidor Redis en otro host o puerto, puede establecer dichas propiedades
al crear la factoría de conexión:
@Bean
public RedisConnectionFactory redisCFO {
JedisConnectionFactory cf = new JedisConnectionFactory();
cf.setHostName("redis-server");
cf.setPort(7379);
return cf;
}

Del mismo modo, si su servidor Redis está configurado para exigir la autorización de
los clientes, puede establecer la contraseña si invoca s e tP a s s w o r d () :
@Bean
public RedisConnectionFactory redisCFO {
JedisConnectionFactory cf = new JedisConnectionFactory0;
cf.setHostName("redis-server");
cf.setPort(7379);
cf.setPassword("foobared");
return cf;

En estos ejemplos se asume el uso de J e d i s C o n n e c t i o n F a c t o r y . Si ha elegido


otra opción, puede sustituir los elementos correspondientes. Por ejemplo, si prefiere usar
L e t t u c e C o n n e c t i o n F a c t o r y , puede configurarla de esta forma:
@Bean
public RedisConnectionFactory redisCFO {
JedisConnectionFactory cf = new LettuceConnectionFactory();
cf.setHostName("redis-server");
cf .setPort(7379);
cf.setPassword("foobared");
return cf;
}

Todas las factorías de conexión Redis cuentan con los métodos se tH o s tN a m e ( ) ,


s e t P o r t () y s e tP a s s w o r d ( ) , de modo que prácticamente son idénticas en lo que a
configuración se refiere.
Después de configurar la factoría de conexión Redis ya puede empezar a trabajar con
las plantillas de Spring Data Redis.
Trabajar con bases de datos NoSQL 389

Trabajar con RedisTemplate


Como su propio nombre indica, las factorías de conexiones Redis generan conexiones
(como R e d is C o n n e c tio n ) a un almacén Redis de clave-valor. Con R e d is C o n n e c t io n
puede almacenar y leer datos. Por ejemplo, podría obtener una conexión y usarla para
almacenar un saludo de esta forma:
RedisConnectionFactory c f = . . . ;
RedisConnection conn = c f .getConnectionO ;
co n n .se t("g re e tin g ". getB y tes(), "Helio W orld".getBytes0 ) ;

Del mismo modo, podría recuperar ese saludo por medio de R e d is C o n n e c t io n de


esta forma:

byte[] greetingBytes = co n n .g et("g reetin g ".g etB y tes( ) ) ;


String greeting = new Strin g (g reetin g B y tes);

Funciona sin problemas ¿pero realmente quiere trabajar con matrices de bytes? Como
sucede con otros proyectos de Spring Data, Spring Data Redis le ofrece una opción de
acceso a datos de nivel superior a través de plantillas, en concreto dos:

• R e d is T e m p la te
• S t r i n g R e d is T e m p l a t e

R e d is T e m p la te es una clase que simplifica considerablemente el acceso a datos en


Redis y que permite la persistencia de claves y valores de cualquier tipo, no solo matrices
de bytes. Como los valores y las claves suelen ser de tipo S t r i n g , S t r in g R e d is T e m p la t e
amplía R e d is T e m p la t e para centrarse en cadenas.
Si ya dispone de R e d i s C o n n e c t i o n F a c t o r y , puede crear R e d is T e m p la t e de esta
forma:

RedisConnectionFactory c f = . . . ;
RedisTemplate<String, Product> redis =
new RedisTemplate<String/ Product>();
r e d is . setConnectionFactory(cf) ;

R e d is T e m p la t e tiene como parám etros dos tipos. El primero es el de la clave y el


segundo el del valor. En este caso, los objetos P r o d u c t se almacenan como valores asig­
nados a claves S t r i n g .
Si sabe que va a trabajar con valores y claves de tipo S t r in g , utilice S trin g R e d is T e m p la te
en lugar de R e d is T e m p la te :

RedisConnectionFactory c f = . . . ;
StringRedisTemplate redis = new StringRedisTem plate(cf);

Al contrario de lo que sucede con R e d is T e m p la t e , S t r i n g R e d i s T e m p l a t e tiene


un constructor que acepta una R e d i s C o n n e c t i o n F a c t o r y , por lo que no es necesario
invocar s e t C o n n e c t i o n F a c t o r y () tras la construcción.
390 Capítulo 12

Aunque no sea obligatorio, si utiliza frecuentem ente R e d i s T e m p l a t e o


S t r i n g R e d is T e m p l a t e , podría configurarlas como bean para su inyección cuando las
necesite. El siguiente método @ B ean declara un bean R e d is T e m p la te :
@Bean
public RedisTemplate<String, Product>
redisTemplate(RedisConnectionFactory cf) {
RedisTemplate<String/ Product> redis =
new RedisTemplate<String/ Product>();
r e d is . setConnectionFactory(cf) ;
return re d is;
}
El siguiente método de bean declara un bean S tr in g R e d is T e m p la te :
@Bean
public StringRedisTemplate
StringRedisTemplate(RedisConnectionFactory cf) {
return new StringRedisTem plate(cf);
}
Cuando ya disponga de R e d is T e m p la te (o S t r in g R e d is T e m p la te ) , puede empezar
a guardar, recuperar y eliminar entradas de clave-valor. Muchas de las operaciones propor­
cionadas por R e d is T e m p la t e están disponibles a través de las sub API enumeradas en
la tabla 12.5.

Tabla 12.5. RedisTemplate ofrece gran parte de su funcionalidad a través de sub API, que
distinguen entre valores individuales y de colección.

Método Interfaz sub API Descripción


X CM m h m i m •-

opsForValue() ValueOperations<K, V> Operaciones para trabajar con entradas


que tengan valores sencillos.
opsForList() ListOperations<K, V> Operaciones para trabajar con entradas
que tengan valores de lista.
opsForSet() SetOperationscK, V> Operaciones para trabajar con entradas
que tengan valores de conjunto.
opsForZSet() ZSetOperationscK, V> Operaciones para trabajar con entradas
que tengan valores ZSet (de conjunto
ordenado).
opsForHash() HashOperationscK, HK, HV> Operaciones para trabajar con entradas
que tengan valores hash.
boundValueOps ( K ) BoundValueOperations<K, v > Operaciones para trabajar con valores
sencillos vinculados a la clave indicada.
boundListOps(K) BoundListOperations<K,V> Operaciones para trabajar con valores
de lista vinculados a la clave indicada.

boundSetOps(K) BoundSetOperations<K,V> Operaciones para trabajar con valores


de conjunto vinculados a la clave
indicada.
Trabajar con bases de datos NoSQL 391

HMétodo Interfaz sub API Descripción


É¿
boundZSet(K) BoundZSetOperations<K,V> O p eracio ne s para tra b a ja r con
valores ZSet (de conjunto ordenado)
vinculados a la clave indicada.
boundHashOps(K) BoundHashOperations<K,V> Operaciones para trabajar con valores
hash vinculados a la clave indicada.

Como puede apreciar, las sub API de la tabla 12.5 están disponibles a través de métodos
de R e d is T e m p la t e (y S t r i n g R e d is T e m p l a t e ) . Cada una proporciona operaciones
que trabajan con entradas en función de si se trata de un valor sencillo o de una colección
de valores.
En todas estas sub API hay varios métodos para guardar y recuperar datos en Redis.
No los veremos todos, solo los necesarios para las operaciones más habituales.

Trabajar con valores sencillos


Imagine que quiere guardar P ro d u c t e n R e d is T e m p la t e < S t r in g , P r o d u c t> , donde
la clave es el valor de la propiedad sku . En el siguiente fragmento de código puede hacerlo
a través de o p s F o r V a lu e () :
r e d is . opsForValue( ) . set(prod uct. getSku(), product) ;

Si quisiera obtener un producto cuyo sk u fuera 1 2 3 4 5 6 , podría usar lo siguiente:


Product product = redis.opsForValue( ) .g e t ("123456");

Si no hay entradas con la clave indicada, se devuelve n u il .

Trabajar con listas


El uso de valores de lista es igual de sencillo gracias a o p s F o r L i s t () . Por ejemplo,
puede añadir un valor al final de una entrada de lista de esta forma:
r e d is . o psForList( ) . rightPush("c a r t " , product);

Se añade un elemento P ro d u c t al final de la lista almacenada en la clave c a r t . Si en


esa clave no existe una lista, se crea una.
Mientras que el método r i g h t P u s h () añade un elemento al final de una entrada de
lista, l e f tP u s h () lo añade al principio:
red is.o p sF o rL ist( ) . le ftP u sh (" c a r t", product);

Existen varias formas de recuperar un elemento de una lista. Puede usar l e f t Pop () o
r ig h t P o p () para recuperar una entrada de cualquiera de los extremos.

Product f i r s t = r e d is . opsForList( ) . le ftP o p (" c a r t" ) ;


Product la s t = red is.o p sF o rL ist( ) .rig h tP o p ("c a rt");
392 Capítulo 12

Además de recuperar un valor de la lista, estos dos métodos tienen como efecto secun­
dario la eliminación de la lista de los elementos recuperados. Si solo quiere recuperar el
valor (por ejemplo de la parte central de la lista), puede usar el método ra n g e ():
List<Product> products = re d is . o psForList( ) . range(" c a r t ", 2 , 12);
El método ra n g e () no elimina valores de la entrada de lista; recupera uno o varios
valores dada la clave y un intervalo de índices. En el ejemplo anterior se recuperan once
entradas empezando con la situada en el índice 2, hasta el 12 (inclusive). Si el intervalo
supera los límites de la lista, solo se devuelven las entradas comprendidas entre dichos
límites. Si no hay entradas comprendidas entre los índices, se devuelve una lista vacía.

Realizar opere cic nes en conjuntos


Además de listas, puede trabajar con conjuntos a través del método o p s F o r S e t ( ). La
operación más básica que puede realizar es añadir un elemento a una entrada de conjunto:
red is.o p sF o rSet( ) . add("c a r t", product);

Después de crear varias entradas y de completarlas con valores, puede realizar intere­
santes operaciones en esos conjuntos, como las de diferencia, intersección o unión.
List<Product> d if f = redis .opsForSet ( ) . d ifference (" c a r tl" , " c a r t2 " );
List<Product> unión = red is.o p sF o rSet( ) .unión(" c a r t l " , " c a r t2 " );
List<Product> is e c t = r e d is . opsForSet( ) . i s e c t ( " c a r t l " , " c a r t2 " );

Evidentemente también puede eliminar elementos:


r e d is . opsForSet( ) . remove(product);

E incluso recuperar un elemento aleatorio del conjunto:


Product random = r e d is . opsForSet( ) . randomMember{ " c a r t" ) ;

Como los conjuntos carecen de índices o d e u n orden implícito, no puede elegir y recu­
perar un elemento concreto.

Vincular a. una clave


La tabla 12.5 incluye cinco sub API para trabajar con operaciones vinculadas a una
determinada clave. Son similares a las demás, pero se centran en una clave concreta.
Como ejemplo de su uso, imagine que tiene que almacenar objetos P r o d u c t en una
entrada de lista con la clave c a r t . Imagine ahora que desea extraer un objeto del extremo
derecho de la lista y después añadir tres nuevos objetos al final de la misma. Podría hacerlo
con B o u n d L is t O p e r a t io n s , que se devuelve tras invocar b o u n d L is tO p s () :
BoundListOperations<String, Product> ca rt =
redis.boundListOps(" c a r t" ) ;
Product popped = c a r t . rightPop();
c a r t . rightPush(productl);
c a r t . rightPush(product2);
c a r t . rightPush(product3);
Trabajar con bases de datos NoSQL 393

Solamente se menciona la clave de la entrada al invocar b o u n d L is tO p s () . Las demás


operaciones realizadas sobre B o u n d L is t O p e r a t io n s se aplicarán a esa entrada.

Establecer señalizadores de clave y valor


Al guardar una entrada en el almacén Redis de claves y valores, tanto la clave como el
valor se señalizan con un serializador Redis. Spring Data Redis le ofrece varios, descritos
a continuación:

• G e n e r i c T o S t r i n g S e r i a l i z e r : Señaliza mediante un servicio de conversión de


Spring.
• J a c k s o n J s o n R e d i s S e r i a l i z e r : Señaliza objetos en JSON mediante Jackson 1.
• J a c k s o n 2 J s o n R e d i s S e r i a l i z e r : Señaliza objetos en JSON mediante Jackson 2.
• J d k S e r i a l i z a t i o n R e d i s S e r i a l i z e r : Utiliza señalización de Java.
• OxmSer i a l i z e r: Señaliza mediante conversores y desconversores de la asignación
O /X de Spring, para señalización XML.
• S t r i n g R e d i s S e r i a l i z e r : Señaliza claves y valores de tipo S t r i n g .

Todos estos señalizadores implementan la interfaz R e d i s S e r i a l i z e r , de modo que


si ninguno se ajusta a sus necesidades, siempre puede crear el suyo propio.
R e d is T e m p la te usa J d k S e r i a l i z a t i o n R e d i s S e r i a l i z e r , de modo que claves
y valores se señalizan con Java. Como habrá imaginado, S t r i n g R e d is T e m p l a t e utiliza
S t r i n g R e d i s S e r i a l i z e r de forma predeterminada para convertir valores S t r i n g en y
desde matrices de bytes. Estas opciones predeterminadas son muy útiles en muchos casos,
pero puede que le interese recurrir a un serializador diferente.
Imagine por ejemplo que con R e d is T e m p la t e quiere señalizar valores P r o d u c t
en JSON con claves S t r i n g . Para ello necesitará los métodos s e t K e y S e r i a l i z e r () y
s e t V a l u e S e r i a l i z e r () de R e d is T e m p la te :

@Bean
public RedisTemplate<String/ Product>
redisTemplate(RedisConnectionFactory cf) {
RedisTemplate<String, Product> redis =
new RedisTemplate<String, Product>();
redis.setConnectionFactory(cf);
redis.setKeySerializer(new StringRedisSerializer());
redis.setValueSerializer(
new Jackson2JsonRedisSerializer<Product>(Product.class));
return redis;

En este caso se establece R e d is T e m p la t e para que siempre utilice S t r i n g R e d i s


S e r i a l i z e r al señalizar valores de claves. También puede especificar que utilice
J a c k s o n 2 J s o n R e d i s S e r i a l i z e r solamente para señalizar valores P r o d u c t.
394 Capítulo 12

Resumen
La época en la que la única opción para la persistencia de datos era una base de datos
relacional está ahora muy lejana. En la actualidad contamos con distintos tipos de bases de
datos y cada una representa los datos de forma diferente y ofrece prestaciones para modelos
de dominio de todo tipo. El proyecto Spring Data permite a los programadores usar estas
bases de datos en sus aplicaciones de Spring y recurrir a abstracciones razonablemente
coherentes entre las distintas alternativas.
Partiendo de nuestros conocimientos sobre Spring Data del capítulo anterior, los hemos
aplicado a las bases de datos MongoDB y Neo4j. Como sucede con JPA, los proyectos Spring
Data MongoDB y Spring Data Neo4j ofrecen generación automática de repositorios basada
en definiciones de interfaces. También hemos aprendido a usar las anotaciones proporcio­
nadas por ambos proyectos para asignar tipos de dominio a documentos, nodos y relaciones.
Spring Data también permite la persistencia de datos en el almacén de claves y valores
Redis. Es mucho más sencillo y no requiere compatibilidad con repositorios automáticos
ni anotaciones de asignación. No obstante, Spring Data Redis ofrece dos clases de plantilla
diferentes para trabajar con el almacén de claves y valores Redis.
Independientemente del tipo de base de datos que elija, la recuperación de datos es una
operación costosa. De hecho, las consultas de base de datos suelen ser los principales cuellos
de botella de cualquier aplicación. Después de ver cómo almacenar y recuperar datos de
distintos orígenes de datos, aprenderemos a evitar ese cuello de botella. En el siguiente
capítulo veremos cómo aplicar el almacenamiento en caché declarativo para evitar accesos
innecesarios a la base de datos.
Capítulo

13 de d a to s en caché

CONCEPTOS FUNDAMENTALES:

• Habilitar el almacenamiento declarativo en caché.


• Almacenamiento en caché con Ehcache, Redis y
GemFire.
• Almacenamiento en caché orientado a anotaciones.
¿Alguna vez le han preguntado algo e instantes después de responder le vuelven a
preguntar lo mismo? Me pasa continuamente con mis hijos: "¿Puedo comer un caramelo?”,
"¿Ya hemos llegado?", "¿Qué hora es?", "¿Ya hemos llegado?".
En muchos aspectos, los componentes de nuestras aplicaciones son iguales. Los compo­
nentes sin estado suelen escalarse mejor, pero realizan la misma pregunta una y otra vez.
Al carecer de estado, descartan cualquier respuesta que les demos una vez completada
su tarea actual y tienen que formular la misma pregunta la siguiente vez que necesitan la
misma respuesta.
En ocasiones se tarda en obtener o calcular la respuesta a la pregunta formulada. Puede
que tenga que recuperar datos de una base de datos, invocar un servicio remoto o realizar
un complejo cálculo. Se malgasta tiempo y recursos para conseguir la respuesta.
Si la respuesta no varía con demasiada frecuencia (o no lo hace), no compensa realizar
el mismo proceso para volverla a obtener. Es más, la repetición innecesaria puede tener
un efecto negativo en el rendimiento de la aplicación. En lugar de formular repetidamente
la misma pregunta para llegar siempre a la misma respuesta, tiene más sentido preguntar
una vez y recordar la respuesta para cuando se necesite después.
El almacenamiento en caché es una forma de guardar información que se necesita
frecuentemente para que esté siempre disponible. En este capítulo veremos la abstracción
de caché de Spring. Aunque Spring no implementa una solución de caché, ofrece compatibi­
lidad declarativa con el almacenamiento en caché y se integra con varias implementaciones
de almacenamiento en caché muy conocidas.

Habilitar la compatibilidad con caché


Spring ofrece dos formas de abstracción de caché:

• Almacenamiento en caché controlado por anotaciones.


• Almacenamiento en caché declarado en XML.

La forma más habitual de usar la abstracción de caché de Spring consiste en anotar


métodos con anotaciones como @ C a c h e a b le y @ C a c h e E v ic t. En gran parte de este capí­
tulo nos centraremos en esta forma de almacenamiento en caché. Después, en un apartado
posterior, veremos cómo declarar límites de caché en XML.
Antes de empezar a aplicar anotaciones de almacenamiento en caché a sus bean, debe
habilitar la compatibilidad de Spring con almacenamiento en caché controlado por anota­
ciones. Si usa configuración de Java, añada @ E n a b le C a c h in g a una de sus clases de
configuración, como se ilustra en el siguiente código.

Listado 13.1. Habilitación de almacenamiento en caché controlado por anotaciones


con @EnableCaching.

package com.habuma. cachefun;


import org. springframework. cache. CacheManager;
import org. springframework. cache. annotation.EnableCaching;
398 Capítulo 13

import org. springframework. cache. concurrent. ConcurrentMapCacheManager;


import org. springframework. co n tex t.annotation. Bean;
import org.springframework. co n tex t. annotation.Configuration;

@Configuration
@EnableCaching / / H a b ilita r almacenamiento en caché,
public cla ss CachingConfig {

@Bean
public CacheManager cacheManager() { / / D eclarar un administrador de caché,
return new ConcurrentMapCacheManager();
}
}

Si configura su aplicación con XML, puede habilitar el almacenamiento en caché contro­


lado por anotaciones con el elemento < c a c h e : a n n o t a t io n - d r iv e n > del espacio de
nombres c a c h e de Spring.

Listado 13.2. Habilitación de almacenamiento en caché con <cache:annotation-driven>.


<?xml v e rsio n = "l. 0" encoding="UTF-8" ?>
cbeans xmlns="http://www. springframework.org/schema/beans"
xm lns:xsi="h ttp : / /www.w3. org/200l/XMLSchema-in stan ce"
xmlns: cachea"h ttp : //www.springframework. o rg /schema/cache"
x s i : schemaLocation="
h ttp : //www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
h ttp : / /www. springframework. org/schema/cache
h ttp : / /www.springframework.org/schema/cache/spring-cache.xsd" >

<cache: annotation-driven /> / / H ab ilitar almacenamiento en caché,

cbean id="cacheManager" class=


"org. springframework. cache. concurrent. ConcurrentMapCacheManager" />
/ / D eclarar un administrador de caché.

</beans>

Entrebastidores, @ E n a b le C a c h in g y c c a c h e : a n n o t a t i o n - d r i v e n > funcionan de


forma similar. Crean un aspecto con puntos de corte que desencadenan las anotaciones
de almacenamiento en caché Spring. En función de la anotación usada y del estado de la
caché, el aspecto recupera un valor de la caché, lo añade o lo elimina.
Habrá comprobado que los listados 13.1 y 13.2 hacen algo más que habilitar el almace­
namiento en caché controlado por anotaciones. También declaran un bean administrador
de caché. Los administradores de caché son el corazón de la abstracción de caché de Spring
y permiten su integración con diferentes implementaciones de almacenamiento en caché.
En este caso, se declara C o n cu rre n tM a p C a ch eM a n a g er. Este sencillo administrador
de caché usa j a v a . ú t i l . c o n c u r r e n t . C o n cu rren tH ash M ap como almacén de caché.
Su simplicidad lo convierte en una tentadora opción para aplicaciones de desarrollo,
pruebas o básicas. Como su almacenamiento en caché se basa en memoria y, por lo tanto,
Almacenamiento de datos en caché 399

está vinculado al ciclo de vida de la aplicación, seguramente no sea la opción perfecta para
aplicaciones de producción de gran tamaño. Afortunadamente, existen otras opciones de
administrador de caché. A continuación veremos algunas de las más habituales.

Configurar un administrador de caché


De forma predefinida, Spring 3.1 incluye cinco implementaciones de administrador
de caché:

• S im p le C a ch e M a n a g e r
• NoOpCacheM anager
• C o n cu rren tM a p C a ch eM a n a g er
• C o m p o siteC a ch eM a n a g e r
• E h C ach eC ach eM an ag er

Spring 3.2 presentó otro administrador de caché para trabajar con proveedores de
caché basados en JCache (JSR-107). Fuera del núcleo de Spring, Spring Data ofrece otras
dos opciones:

• R e d isC a c h e M a n a g e r (de Spring Data Redis).


• Gemf ire C a c h e M a n a g e r (de Spring Data GemFire).

Como puede apreciar, dispone de múltiples opciones a la hora de seleccionar un


administrador de caché para la abstracción de caché de Spring. Su elección dependerá del
proveedor de caché subyacente que desee usar. Cada uno proporciona a su aplicación una
variante de almacenamiento en caché distinta y algunos son más útiles para producción
que otros. Aunque su elección afecte a la forma de almacenar sus datos en caché, no lo hará
a la forma en que declare las reglas de almacenamiento en caché en Spring.
Tendrá que seleccionar y configurar un administrador de caché como bean en el contexto
de su aplicación de Spring. Ya hemos visto cómo configurar C oncurrentM apC acheM anager
y que puede no ser la mejor opción para aplicaciones del mundo real. A continuación apren­
deremos a configurar algunos de los administradores de caché de Spring, empezando por
E h C ach eC ach eM an ag er.

Almacenamiento en caché con Ehcache


Ehcache es uno de los proveedores de caché más conocidos. En su sitio Web afirma
que es la caché de Java más utilizada. Debido a su generalizada implantación, tiene
sentido que Spring ofrezca un administrador de caché que se integre con Ehcache:
E h C ach eC ach eM an ag er.
Su configuración en Spring es muy sencilla. En el siguiente código se muestra cómo
hacerlo en Java.
400 Capítulo 13

Listado 13.3. Configuración de EhCacheCacheManager en Java.


package com.habuma. cachefun;
import n e t. s f . ehcache. CacheManager;
import o rg . springframework. cache. annotation.EnableCaching;
import org. springframework. cache. ehcache. EhCacheCacheManager;
import org. springframework. cache. ehcache. EhCacheManagerFactoryBean;
import org. springframework. co n tex t. annotation.Bean;
import org. springframework. co n tex t. annotation. Configuration;
import org. springframework. co re. i o . ClassPathResource;

@Conf iguration
@EnableCaching
public d a s s CachingConfig {

@Bean
public EhCacheCacheManager cacheManager(CacheManager cm) { / / Configurar
/ / EhCacheCacheManager.
return new EhCacheCacheManager(cm);
}
@Bean
public EhCacheManagerFactoryBean ehcache() { / / EhCacheManagerFactoryBean.
EhCacheManagerFactoryBean ehCacheFactoryBean =
new EhCacheManagerFactoryBean();
ehCacheFactoryBean.setConfigLocation(
new ClassPathResource("com/habuma/spittr/cache/ehcache.xml") ) ;
return ehCacheFactoryBean;
}
}
El método cacheM anager () del listado 13.3 crea una instancia de EhCacheCacheM anager
pasando una instancia de un C a ch eM a n a g er de Ehcache. Esta inyección puede resultar
confusa ya que tanto Spring como Ehcache definen un tipo C ach eM an ag er. Para dejarlo
claro, C a ch eM a n a g er de Ehcache se inyecta en E h C ach eC ach eM an ag er de Spring (que
implementa la implementación C a c h e M a n a g e r de Spring). Después de conseguir el
C ach eM a n a g er de Ehcache que inyectar, tendrá que declarar un bean C ach eM an ager.
Para facilitar el proceso, Spring le ofrece E h C a c h e M a n a g e rF a c to ry B e a n , que genera
un C a ch eM a n a g er de Ehcache. El método e h c a c h e () crea y devuelve una instancia
de E h C a c h e M a n a g e rF a c to ry B e a n . Al ser un bean de factoría (implementa la interfaz
F a c t o r y B e a n de Spring), el bean que se registra en el contexto de la aplicación de Spring
no es una instancia de E h C a c h e M a n a g e rF a c to ry B e a n sino de C ach eM an ager, de modo
que se puede inyectar en E h C ach eC ach eM an ag er.
La configuración de Ehcache va más allá de los bean configurados en Spring. Ehcache
define su propio esquema de configuración para XML y puede configurar detalles concretos
de almacenamiento en caché en un archivo XML compatible con dicho esquema. Al crear
E h C a c h e M a n a g e rF a c to ry B e a n , tendrá que indicarle dónde se encuentra la configura­
ción XML de Ehcache.
En este caso se invoca el método s e t C o n f i g L o c a t i o n () y se le pasa C l a s s P a t h
Re s o u r c e para especificar la ubicación de la configuración XML de Ehcache relativa a la
raíz de la ruta de clases.
Almacenamiento de datos en caché 401

El contenido del archivo ehcache.xml depende de cada aplicación pero tendrá que
declarar una caché mínima. Por ejemplo, la siguiente configuración de Ehcache declara la
caché spittleCache con 50 MB de almacenamiento máximo en pila y una duración de
100 segundos.
<ehcache>
<cache name="spittleCache"
maxBytesLocalHeap="50m"
timeToLiveSeconds="100">
< /cache>
< /ehcache>

Evidentemente, es una configuración muy básica de Ehcache. En sus aplicaciones, es


probable que prefiera las opciones avanzadas de configuración de Ehcache. En su docu­
mentación, h t t p : / /e h c a c h e . o rg /d o c u m e n ta tio n /c o n f ig u r a t io n , puede consultar
más detalles al respecto.

Utilizar Redís para almacenamiento en caché


Si lo piensa, una entrada de caché no es más que un par de clave-valor en el que la clave
describe la operación y los parámetros para los que se ha generado el valor. Por ello, no le
debería sorprender saber que Redis, un almacén de claves y valores, es la opción perfecta
para almacén de caché.
Para poder usar Redis y almacenar entradas de caché para la abstracción del almacena­
miento en caché de Spring, Spring Data Redis ofrece RedisCacheManager, una imple-
mentación de CacheManager. RedisCacheManager funciona con un servidor Redis a
través de RedisTemplate para almacenar entradas de caché en Redis.
Para usar RedisCacheManager necesita unbean RedisTemplate y otro que sea una
implementación de RedisConnectionFactory (como JedisConnectionFactory). En
el capítulo anterior ya vimos cómo configurar estos bean. Con RedisTemplate, resulta
muy sencillo configurar RedisCacheManager, como se muestra a continuación.

Listado 13.4. Configuración de un administrador de caché que almacena entradas de caché


en un servidor Redis.

package com.Tnyapp-,
import o rg . springframework. cache. CacheManager;
import org. springframework. cache. annotation.EnableCaching;
import org. springframework.co n text. annotation.Bean;
import org. springframework. d ata. r e d is . cache.RedisCacheManager;
import org. springframework. d ata. r e d is . connection. je d i s . JedisConnectionFactory;

import o rg . springframework. d ata. r e d is . co re.RedisTemplate;

©Configuration
@EnableCaching
public cla ss CachingConfig {

@Bean
public CacheManager CacheManager(RedisTemplate redisTemplate) {
402 Capítulo 13

return new RedisCacheManager(redisTemplate); / / Bean de administrador de caché


/ / de Redis.
}
@Bean
public JedisConnectionFactory redisConnectionFactory() { / / Bean de fa c to ría de
/ / conexión de Redis.
JedisConnectionFactory jedisConnectionFactory =
new JedisConnectionFactory( );
jed isC onnectionF actory.afterP rop ertiesSet( );
return jedisConnectionFactory;
}
@Bean
public RedisTemplate<String, String> redisTemplate( / / Bean RedisTemplate.
RedisConnectionFactory redisCF) {
RedisTemplate<String, String> redisTemplate =
new RedisTemplate<String, S trin g > ();
redisTemplate. setConnectionFactory(redisCF);
redisTemplate. a fte rP ro p e rtie sS e t( );
return redisTemplate;
}

Como puede apreciar, se crea RedisCacheManager pasando una instancia de


RedisTemplate como argumento a su constructor.

Trabajar con varios administradores de caché


No tiene por qué elegir un único administrador de caché. Si le cuesta decidirse entre uno
concreto o si por motivos técnicos desea seleccionar varios, puede probar con Compos ite
CacheManager de Spring. CompositeCacheManager se configura con uno o varios admi­
nistradores de caché e itera por todos ellos en busca de un valor previamente almacenado
en caché. El siguiente código muestra cómo crear un bean CompositeCacheManager que
itera sobre JCacheCacheManager, EhCacheCacheManager y RedisCacheManager.

Listado 13.5. CompositeCacheManager itera por una lista de administradores de caché.


@Bean
public CacheManager cacheManager(
n e t. s f . ehcache. CacheManager cm,
ja v a x . cache. CacheManager jcm) {

/ / Crear CompositeCacheManager.
CompositeCacheManager cacheManager = new CompositeCacheManager();
List<CacheManager> managers = new ArrayList<CacheManager>();
managers. add(new JCacheCacheManager(jcm)) ;
managers. add(new EhCacheCacheManager(cm))
managers. add(new RedisCacheManager(redisTemplate( ) ) ) ;
cacheManager. setCacheManagers(managers); / / Añadir administradores de caché,
return cacheManager;
Almacenamiento de datos en caché 403

A la hora de buscar una entrada en caché, CompositeCacheManager empieza


por JCacheCacheManager para comprobar la implementación de JCache, después
se fija en Ehcache y comprueba EhCacheCacheManager, y por último consulta a
RedisCacheManager para comprobar Redis.
Después de configurar un administrador de caché y de habilitar el almacenamiento
en caché, ya puede empezar a aplicar reglas de almacenamiento en caché a sus métodos
de bean. Veamos cómo usar las anotaciones de almacenamiento en caché de Spring para
definir límites de caché.

Anotar métodos para almacenamiento en caché


Como ya hemos mencionado, la abstracción del almacenamiento en caché de Spring se
basa en aspectos. Al habilitar el almacenamiento en caché en Spring se crea un aspecto que
desencadena una o varias anotaciones de almacenamiento en caché de Spring, descritas
en la tabla 13.1.

Tabla 13.1. Spring cuenta con cuatro anotaciones para declarar reglas de almacenamiento en caché

Anotación Descripción
lias;:
@Cacheable Indica que Spring debe buscar en una caché el valor devuelto por
el método antes de invocarlo. Si se encuentra el valor, se devuelve
el valor en caché. En caso contrario, se invoca el método y el valor
devuelto se añade a la caché.
@CachePut Indica que Spring debe añadir a una caché el valor devuelto por el
método. No se comprueba la caché antes de invocar el método y
el método siempre se invoca.
@CacheEvict Indica que Spring debe expulsar a una o varias entradas de una
caché.
@Caching Una anotación de agrupación para aplicar simultáneamente múltiplos
de las demás anotaciones de almacenamiento en caché.

Todas las anotaciones de la tabla 13.1 se pueden incluir en un método o una clase. Si se
incluyen en un único método, el comportamiento de almacenamiento en caché indicado por
la anotación solo se aplica a ese método. Si la anotación se incluye en el nivel de la clase, el
comportamiento del almacenamiento en caché se aplica a todos los métodos de esa clase.

Completar la caché
Como puede apreciar, las anotaciones @Cacheable y @CachePut sirven para completar
una caché, aunque su funcionamiento sea ligeramente distinto. @Cacheable busca primero
una entrada en la caché y evita la invocación del método si encuentra una entrada que
404 Capítulo 13

coincida. Si no coincide ninguna entrada, se invoca el método y el valor devuelto se añade a


la caché. @ C a ch eP u t, por su parte, nunca comprueba el valor en la caché, siempre permite
la invocación del m étodo de destino y añade el valor devuelto a la caché.
@ C a c h e a b le y @ C a ch eP u t com parten un conjunto común de atributos, enumerados
en la tabla 13.2.

Tabla 13.2. @Cacheable y @CachePut comparten un conjunto común de atributos.


«lisais
Atributo Tipo Descripción

value String[] El nombre (s) de la caché(s) que usar.


condition String Una expresión SpEL que, si evalúa a f a l s e , no aplica el
almacenamiento en caché a la invocación del método.
key String Una expresión SpEL para calcular una clave de caché personalizada.
unless String Una expresión SpEL que, si evalúa a true, impide que el valor
devuelto se añada a la caché.

En su versión más sencilla, los atributos @Cacheable y @CachePut solo especi­


fican una o varias cachés con el atributo valué. Fíjese en el método f indOne () de
SpittleRepository. Tras guardarse inicialmente, es improbable que un objeto Spittle
cambie. Siun determinado objeto Spittle es popular y se solicita frecuentemente, es una
pérdida de tiempo y recursos tener que recuperarlo repetidamente de la base de datos. Al
anotar el método f indOne () con @Cacheable, como se ilustra en el siguiente código,
se asegura de que el objeto Spittle se almacene en caché y se eviten viajes innecesarios
a la base de datos.

Listado 13.6. Uso de @Cacheable para almacenar y recuperar valores de una caché.
@Cacheable("sp ittleC ach e") / / Almacenar en caché lo s resultados de e ste método,
public S p ittle findOne(long id) {
try {
return j dbcTemplate. queryForObj e c t (
SELECT_SPITTLE_BY_ID,
new SpittleRowMapper(),
id) ;
} catch (EmptyResultDataAccessException e) {
return n u il;
i

Al invocar f indOne (), el aspecto de almacenamiento en caché intercepta la invoca­


ción y busca en la caché un valor previamente devuelto con el nombre spittleCache.
La clave de la caché es el parámetro id pasado al método f indOne ( ). Si se encuentra un
valor para dicha clave, el valor localizado se devuelve y el método no se invoca. Por otra
parte, si no se encuentra ningún valor, el método se invoca y el valor devuelto se añade a
la caché, listo para la siguiente invocación de f indOne ( ) .
Almacenamiento de datos en caché 405

En el listado 13.6, la anotación @ C a c h e a b le se incluye en la implementación de


f indO ne () en J d b c S p i t t l e R e p o s i t o r y . Funcionará pero el almacenamiento en caché se
limita a la implementación J d b c S p i t t l e R e p o s i t o r y . Cualquier otra implementación de
S p i t t l e R e p o s i t o r y no tendrá reglas de almacenamiento en caché a menos que también
se anote con @ C a c h e a b le . Por lo tanto, podría añadir la anotación en la declaración del
método en S p i t t l e R e p o s i t o r y en lugar de en la implementación:
@Cacheable{"sp ittleC ach e")
S p íttle findOne(long id ) ;

Al anotar elmétodo de interfaz, la anotación @Cacheable se hereda en todas las imple-


mentaciones de SpittleRepository y se aplican las mismas reglas de almacenamiento
en caché.

Añadir valores a la caché


Mientras que @ C a c h e a b le invoca condicionalmente un método en función de si el
valor deseado ya se encuentra o no en la caché, @ C ach eP u t aplica un flujo más lineal a
los métodos que anota. Un método anotado con @ C a ch eP u t siempre se invoca y el valor
que devuelve siempre se añade a la caché, una forma muy útil de precargar la caché antes
de cualquier solicitud.
Por ejemplo, cuando se guarda un nuevo objeto S p i t t l e a través del método s a v e ()
en S p i t t l e R e p o s i t o r y , es muy probable que se solicite en breve. Tiene sentido añadir el
objeto Sp i 1 1 1 e a la caché al invocar s ave ( ) , para que esté listo cuando alguien lo solicite
mediante la invocación de f in d O ne () . Para ello, puede anotar el método sa v e () con @
C a ch e P u t de esta forma:
@CachePut("sp ittleC ach e")
S p ittle sa v e {S p ittle s p i t t l e ) ;

Al invocar s a v e ( ) , hace todo lo posible por guardar el objeto S p i t t l e . Tras ello, el


objeto S p i t t l e se añade a la caché s p i t t l e C a c h e . Solo hay un problema: la clave de
la caché. Como ya hemos mencionado, la clave predeterminada de la caché se basa en los
parámetros del método. Como el único parámetro de s a v e () es un objeto S p i t t l e , se
utiliza como clave de la caché. ¿No le parece un tanto extraño añadir un objeto S p i t t l e
a una caché en la que la clave es el mismo objeto S p i t t l e ?
Evidentemente, la clave de caché predeterminada no es lo que queremos en este caso.
Necesitamos que la clave de la caché sea el ID del nuevo objeto S p i t t l e guardado, no el
propio objeto S p i t t l e . Por ello, tendrá que especificar una clave distinta a la predeter­
minada, como veremos a continuación.

Personalizar la clave de caché


Tanto @Cacheable como @CachePut tienen un atributo key que nos permite sustituir
la clave predeterminada por otra derivada de una expresión SpEL. Puede usar cualquier
expresión SpEL válida, pero seguramente prefiera usar una expresión que evalúe a una
clave relevante para el valor que se almacene en la caché.
406 Capítulo 13

En este caso concreto, necesitamos que la clave sea el ID del objeto S p i t t l e guardado.
El objeto S p i t t l e que se pasa como parámetro a sa v e () todavía no se ha guardado y, por
tanto, carece de ID. Necesitará la propiedad i d del objeto S p i t t l e devuelto por s a v e ().
Afortunadamente, Spring muestra diferentes metadatos muy útiles para crear expre­
siones SpEL para almacenamiento en caché, enumerados en la tabla 13.3.

Tabla 13.3. Spring ofrece diversas extensiones SpEL concretas para definir reglas de
almacenamiento en caché.

D escripción
. .. ...■ ...... ii.. . • . it .■.íü., .. .:.W ¡s££Ét¿
#root.args Los argumentos pasados al método en caché, como matriz.

#root.caches Las cachés sobre las que se ejecuta este método, como matriz.
#root.target El objeto de destino.
#root.targetClass La clase del objeto de destino; una abreviatura de #root.target.class.
#root.method El método en caché.
#root.methodName El nombre del método en caché; una abreviatura de #root .method.
ñame.
#result El valor devuelto de la invocación del método (no está disponible con
@Cacheable).
#Argument El nombre del argumento de un método (como #argName) o del índice
de un argumento (como #a0 o #p0).

Para el método sa v e () necesita que la clave sea la propiedad i d del objeto S p i t t l e


devuelto, proporcionado por la expresión # r e s u l t . Desde ahí, puede hacer referencia a
la propiedad i d si establece el atributo key en # r e s u l t . id :
@CachePut(value=nsp ittleC ach e", k ey ="#resu lt. id")
S p ittle sa v e (S p ittle s p i t t l e ) ;

Al especificar @CachePut de esta forma, la caché no accede al método save ( ) ,pero el


objeto Spittle devuelto se añade a la caché con una clave que coincide con la propiedad
id del objeto Spittle.

Almacenamiento condiciona! en caché


Al anotar un método con una de las anotaciones de almacenamiento en caché de Spring,
indicamos que queremos crear un aspecto de almacenamiento en caché alrededor de ese
método, pero en otros casos le interesará desactivar el almacenamiento en caché.
@ C a c h e a b le y @ C a ch eP u t ofrecen dos atributos para el almacenamiento condicional
en caché: u n l e s s y c o n d i t i o n . Ambos reciben una expresión SpEL. Si la del atributo
u n l e s s evalúa a t r u e , los datos devueltos del método en caché no se añaden a la propia
caché. Del mismo modo, si la expresión SpEL del atributo c o n d i t i o n evalúe a f a l s e , se
desactiva el almacenamiento en caché para ese método.
Almacenamiento de datos en caché 407

En la superficie, puede parecer que tanto u n le s s como c o n d it io n realizan la misma


función, pero hay una sutil diferencia. El atributo u n le s s solo puede evitar que un objeto
se añada a la caché, pero se sigue buscando en la caché cuando se invoca el método y si se
encuentra una coincidencia, se devuelve.
Por otra parte, si la expresión de c o n d it io n evalúa a f a l s e , se desactiva el almace­
namiento en caché mientras dure la invocación del método. No se busca en la caché, ni el
valor devuelto se añade a la misma.
Como ejemplo, un tanto enrevesado, imagine que no quiere almacenar en caché ningún
objeto S p i t t l e cuya propiedad m e ss a g e contenga el texto N oCache. Para evitar que
estos objetos S p i t t l e se almacenen en la caché, puede establecer el atributo u n l e s s de
esta forma:
@Cacheable(value="spittleCache"
unless="# r e s u lt.message. co n tain s( ' NoCache') " )
S p ittle findOne(long id );

La expresión SpEL asignada a u n le s s tiene en cuenta la propiedad m essag e del


objeto S p i t t l e devuelto (identificada en la expresión como # r e s u l t ) . Si contiene el
texto NoCache, la expresión evalúa a t r u e y el objeto S p i t t l e no se añade a la caché. En
caso contrario, la expresión evalúa a f a l s e , la cláusula u n le s s no se satisface y el objeto
S p i t t l e se almacena en caché.
El atributo u n le s s impide que se escriban los valores en la caché, pero puede optar por
desactivar directamente el almacenamiento en caché, es decir, que los valores ni se añadan
ni se recuperen de la caché en determinadas circunstancias.
Imagine por ejemplo que no quiere aplicar el almacenamiento en caché a ningún objeto
S p i t t l e cuyo ID sea inferior a 10. En este caso, dichos objetos S p i t t l e son entradas de
prueba para tareas de depuración y no tiene sentido almacenarlos en caché. Para desactivar
el almacenamiento en caché puede usar el atributo c o n d i t i o n de @ C a c h e a b le de esta
forma:
@Cacheable(value=”sp ittleC ach e"
unless="#result.m essage. co n tain s( 'NoCache1)"
condition=M#id >= 10”)
S p ittle findOne(long id );

Si se invoca f indOne () con cualquier valor inferior a 10 como parámetro, no se realizan


búsquedas en la caché ni el objeto S p i t t l e devuelto se añade a la misma. Es como si el
método careciera de anotación @ C ach eab le.
Como habrá comprobado en los ejemplos, la expresión del atributo u n le s s puede hacer
referencia al valor devuelto si hace referencia a # r e s u l t . Es muy útil ya que u n le s s no
inicia su labor hasta que se devuelve un valor desde el método en caché. Por otra parte,
c o n d it io n se encarga de desactivar el almacenamiento en caché en el método, por lo
que no puede esperar hasta que se termine para decidir si desactiva el almacenamiento en
caché. Esto significa que su expresión debe evaluarse de camino al método y que no puede
hacer referencia al valor devuelto con # r e s u l t .
Hemos añadido contenidos a la caché pero ¿cómo se eliminan? Veamos cómo usar la
anotación @ C a c h e E v ic t.
408 Capítulo 13

Eliminar entradas de la. caché


@CacheEvict no añade nada a la caché. Si se invoca un método anotado con @Cache
Evict, se eliminan una o varias entradas de la caché.
¿En qué casos querría eliminar contenidos de la caché? Siempre que un valor en caché
deje de ser válido tendrá que asegurarse de eliminarlo para que las futuras consultas a la
caché no devuelvan datos caducados o inexistentes.
U n ejemplo sería la eliminación de datos, lo que convierte al método remove () de
SpittleRepository en un candidato perfecto para @CacheEvict:
@CacheEvict (" sp ittleC ache11)
void remove(long s p i t t le ld ) ;

1 . . . . ........................................................................................................................................... .................
Al contrario de lo que sucede con ©cacheabie y ©cachePut, @CacheEvict se puede
usar en métodos void. @Cacheable y @CachePut requieren que el valor devuelto
no sea void, que será el elemento que añadir a la caché, pero como @cacheEvict
solamente elim ina elementos de la caché, se puede añadir a cualquier método,
incluso a los de tipo void.

Como hemos visto, se elimina una única entrada de la caché s p i t t l e C a c h e al invocar


rem ov e () . La entrada eliminada es la que tiene una clave igual al valor pasado en el pará­
metro s p i t t l e l d . @ C a c h e E v ic t cuenta con varios atributos, enumerados en la tabla
13.4, que afectan a su comportamiento.

Tabla 13.4. Atributos de la anotación @CacheEvict para especificar entradas de caché que eliminar.

A trib uto Tipo D escripción .;

value String [] El nombre(s) de la caché(s) que usar.


key String Una expresión SpEL para calcular una clave de caché
persuiTdilk-dvris'.

condition String Una expresión SpEL que si evalúa a false no se aplica el


almacenamiento en caché a la invocación del método.
allEntries boolean Si es true, deben eliminarse todas las entradas de la caché(s)
especificada.
beforelnvocation boolean Si es true, se eliminan las entradas de la caché antes de
invocar el método. Si es false (el valor predeterminado), las
entradas se eliminan tras la correcta invocación del método.

Como puede apreciar, @ C a c h e E v ic t comparte algunos de los atributos de @ C a ch e a b le


y @ C a ch eP u t, pero a diferencia de estas anotaciones, @ C a c h e E v ic t no cuenta con un
atributo u n l e s s .
Almacenamiento de datos en caché 409

Las anotaciones de almacenamiento en caché de Spring constituyen una forma elegante


de especificar reglas de almacenamiento en caché en el código de su aplicación, pero Spring
también le ofrece un espacio de nombres XMLpara almacenamiento en caché. Para terminar
nuestro análisis sobre el almacenamiento en caché veremos cómo configurar reglas de
almacenamiento en caché en XML.

Declarar almacenamiento en caché en XML*•


Seguramente se pregunte por qué querría declarar almacenamiento en caché en XML.
Después de todo, las anotaciones de almacenamiento en caché que hemos visto a lo largo
del capítulo son mucho más elegantes. Se me ocurren dos motivos:

• No se siente cómodo con la inclusión de anotaciones especificas de Spring en su


código fuente.
• Quiere aplicar almacenamiento en caché a bean de los que no dispone del código
fuente.

En ambos casos, es más recomendable (o necesario) separar la configuración de alma­


cenamiento en caché del código cuyos datos se guardan en caché. El espacio de nombres
c a c h e de Spring le ofrece una forma de declarar reglas de almacenamiento en caché en
XML como alternativa al almacenamiento en caché orientado a anotaciones. Como el alma­
cenamiento en caché es una actividad orientada a aspectos, el espacio de nombres c a c h e
se vincula al espacio de nombres aop de Spring para declarar los puntos de corte en los
que aplicar el almacenamiento en caché.
Para empezar con el almacenamiento en caché declarado en XML debe crear un archivo
de configuración XML de Spring que incluya los espacios de nombres c a c h e y aop:
<?xml version=" 1 .0 11 encoding="UTF-8"?>
cbeans xmlns="http://www.springframework.org/schema/beans"
xm lns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns: cache="http://www.springframework.org/schema/cache"
xmlns: aop="h ttp : //www.springframework. org/schema/aop"
x s i : schemaLocation="h ttp : //www.springframework.org/schema/aop
h ttp : / /www. springframework. org/schema/aop/spring-aop.xsd
http -. / /www. springframework. org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
h ttp : //www.springframework. org/schema/cache
h ttp : //www.springframework.org/schema/cache/spring-cache.xsd" >

< !- - Añadir aquí la configuración de almacenamiento en caché -->

</beans>

El espacio de nombres c a c h e define los elementos de configuración necesarios para


declarar almacenamiento en caché en el archivo de configuración XML de Spring. En la
tabla 13.5 se enumeran todos los elementos que ofrece el espacio de nombres ca c h e .
410 Capítulo 13

Tabla 13.5. El espacio de nombres cache de Spring ofrece elementos para configurar reglas
de almacenamiento en caché en XML.

I Elemento D escripción

<cache :annotation-driven> Habilita almacenamiento en caché controlado por anotaciones.


Es equivalente a @EnableCaching en configuración de
Java.
<cache: advice> Define un consejo de almacenamiento en caché. Se combina
con <aop -.advisor> para aplicar el consejo a un punto de
corte.
ccache:caching> Define un conjunto concreto de reglas de almacenamiento
en caché dentro del consejo de almacenamiento en caché.
ccache:cacheable> Designa un método como candidato a ser almacenado en
caché. Es equivalente a la anotación @Cacheable.
ccache:cache-put> Designa un método que completa la caché (pero que no la
tiene en cuenta). Es equivalente a la anotación @cachePut.
ccache:cache-evict> Designa un método que elimina una o varias entradas de la
caché. Es equivalente a la anotación @CacheEvict.

El elemento c c a c h e : a n n o t a t i o n - d r i v e n > , como su homólogo de Java @ E n a b le


C a c h in g , activa el almacenamiento en caché orientado a anotaciones. Ya hemos visto este
estilo de almacenamiento en caché, por lo que no lo detallaremos más.
Los demás elementos de la tabla 13.5 sirven para la configuración de almacenamiento
en caché basado en XML. El siguiente código ilustra cómo usarlos para configurar alma­
cenamiento en caché en el bean S p i t t l e R e p o s i t o r y , similar a lo que hicimos en un
apartado anterior con el uso de anotaciones de almacenamiento en caché.

Listado 13.7. Declaración de reglas de almacenamiento en caché en SpittleRepository


con elementos XML.

<?xml v e rsio n = "l.0" encoding="UTF-8"?>


cbeans xmlns="http://www.springframework.org/schema/beans"
xm lns:xsi="http://www.w3.org/2001/XMLSchema-in stan ce"
xmlns: cache="h ttp : / /www. springframework. org/schema/cache"
xmlns: aop="h ttp : //www.springframework. o rg /schema/aop"
x s i : schemaLocation="h ttp : / /www. springframework. org/schema/aop
h ttp : //www.springframework.org/schema/aop/spring-aop.xsd
http://www.springframework.org/schema/beans
h ttp : / /www.springframework. org/schema/beans/spring-beans.xsd
h ttp : / /www.springframework. org /schema/cache
h ttp : //www. springframework. o rg /schema/cache/spring-cache.xsd”>

<aop:config> / / Vincular consejo de caché a un punto de co rte.


<aop: advisor a d v ice-ref=ncacheAdvice"
pointcut=
"execu tion(* com.habuma. s p ittr.d b .S p ittle R e p o s ito ry .* ( . . ) ) " / >
Almacenamiento de datos en caché 411

</aop: config>

<cache: advice id="cacheAdvice">


<cache: caching>
ccache: cacheable / / Hacer que se guarde en caché.
cache="sp ittleC ache"
method="f indRecent" />

<cache: cacheable / / Hacer que se guarde en caché.


cache="spittleCache" method="findOne" />
ccache: cacheable / / Hacer que se guarde en caché.
cache=" s p itt leCache11
method="findBySpitterld" />

<cache: cache-put / / Completar la caché a l guardar.


cache="spittleC ache"
method="save"
k ey ="#resu lt. id" />

<cache: cache-evict / / Eliminar de la caché.


cache="sp ittleC ache"
method="remove" />

</cach e: caching>
</cache: advice>

<bean id="cacheManager" class=


"org. springframework. cache. concurrent. ConcurrentMapCacheManager"
/>
</beans>

Lo primero que declaramos es < a o p : a d v i s o r >, que hace referencia al consejo con el ID
c a c h e A d v ic e . Este elemento compara el consejo con un punto de corte, lo que establece
un aspecto completo. En este caso, el punto de corte del aspecto se desencadena al ejecutar
cualquier método de la interfaz S p i t t l e R e p o s i t o r y . Si se invoca uno de estos métodos
en cualquier bean del contexto de la aplicación de Spring, se invoca el consejo del aspecto.
El consejo se declara con el elemento c c a c h e : a d v ic e > , en el que puede tener todos los
elementos c c a c h e : c a c h in g > que necesite para definir las reglas de almacenamiento en
caché de su aplicación. En este caso solo hay un elemento c c a c h e : c a c h in g > que contiene
tres elementos c c a c h e : c a c h e a b l e > y un elemento c c a c h e : c a c h e - p u t >.
Los elementos c c a c h e : c a c h e a b le > declaran un método del punto de corte para
su almacenamiento en caché. Es el equivalente XML a la anotación @ C a c h e a b le . En
concreto, se declaran los métodos f in d R e c e n t ( ) , f indO ne () y f i n d B y S p i t t e r l d ()
para su almacenamiento en caché, y los valores que devuelven se almacenan en la caché
s p ittle C a c h e .
c c a c h e : c a c h e - p u t > es el equivalente XML de Spring a la anotación @ C ach eP u t.
Designa un método cuyo valor devuelto completará una caché pero el método nunca obtiene
su valor devuelto de la misma. En este caso, se usa el método s a v e () para completar la
caché y, como sucede con las anotaciones, tendrá que reemplazar la clave predeterminada
para que sea la propiedad i d del objeto S p i t t l e devuelto.
412 Capítulo 13

Por último, el elemento < c a c h e : c a c h e - e v i c t > es la alternativa XML de Spring a


la anotación @ C a c h e E v ic t. Elimina un elemento de la caché para que no se localice la
siguiente vez que alguien lo busque. En este caso, al eliminar un objeto S p i t t l e de la
caché mediante la invocación de rem ove ( ) , se elimina la entrada cuya clave sea igual al
ID pasado a rem ove ( ) .
Conviene mencionar que el elemento < c a c h e : a d v ic e > tiene un atributo c a c h e -
m anager para especificar un bean que actúa como administrador de caché. De forma
predeterminada es cacheM an ager, que coincide con el <bean > declarado al final del
listado 13.7, por lo que no es necesario establecerlo de forma explícita, pero si su bean de
administrador de caché tiene un ID diferente (por ejemplo si ha declarado varios admi­
nistradores de caché), puede especificar el que desea usar si establece el atributo c a c h e -
m anager.
Además, los elementos c c a c h e : c a c h e a b l e > , < c a c h e : c a c h e - p u t > y < c a c h e :
c a c h e e v i c t > hacen referencia a la misma caché: s p i t t l e C a c h e . Para eliminar esta
duplicación, puede especificar el nombre de la caché en < c a c h e : c a c h in g > :
ccache: advice id="cacheAdvice">
ccache: caching cache="sp ittleC ache">

ccache: cacheable method="findRecent” />

ccache: cacheable method="findOne" />

ccache: cacheable method="findBySpitterld" />

ccache: cache-put
method="save"
k ey ="#resu lt. id" />

ccache: cache-evict method="remove" />

c/cach e: caching>
c/cach e: advice>

c c a c h e : c a c h in g > comparte varios atributos con c c a c h e : c a c h e a b le > , c c a c h e :


c a c h e p u t> y c c a c h e : c a c h e - e v i c t > , incluidos los siguientes:

• ca c h e : Especifica la caché en la que almacenar valores y de la que recuperarlos.


• c o n d itio n : Una expresión SpEL que si evalúa a f a l s e deshabilita el almacena­
miento en caché para el método.
• key: Una expresión SpEL utilizada para deducir la clave de caché (de forma prede­
terminada son los parámetros del método).
• method: El nombre del método que almacenar en caché.

Además, c c a c h e -. c a c h e a b le > y c c a c h e : c a c h e - p u t > cuentan con un atributo


u n le s s opcional que puede recibir una expresión SpEL que, si evalúa a t r u e , impide que
el valor se guarde en caché. El elemento c c a c h e : c a c h e - e v i c t > ofrece varios atributos
exclusivos:
Almacenamiento de datos en caché 413

• a l l - e n t r i e s : Si es tr u e , se eliminan todas las entradas de la caché. Si es f a l s e ,


solo se elimina la clave que coincida.
• b e f o r e - in v o c a tio n : Si es t r u e , se elimina la entrada en caché antes de invocar
el método. Si es f a l s e , se elimina después de la invocación.

El valor predeterminado de a l l - e n t r i e s y b e f o r e - i n v o c a t i o n es f a l s e , de
modo que si usa < c a c h e : c a c h e - e v i c t > sin uno de ellos solo se elimina una entrada de
la caché después de la invocación del método. El elemento que eliminar se identifica por
medio de la clave predeterminada (basada en el parámetro del método) o por una clave
especificada con una expresión SpEL asignada al atributo key.

Resumen
El almacenamiento en caché es una forma perfecta de evitar que el código de su apli­
cación tenga que deducir, calcular o recuperar repetidamente las mismas respuestas para
la misma pregunta. Al invocar un método con una serie concreta de parámetros, el valor
que devuelve se puede almacenar en una caché y se puede recuperar después de la misma
al invocar el mismo método con los mismos parámetros. En muchos casos, la búsqueda de
un valor en una caché es una operación menos costosa que si se hiciera de otra forma (por
ejemplo con una consulta de base de datos). Por ello, el almacenamiento en caché puede
tener un impacto positivo en el rendimiento de una aplicación.
En este capítulo hemos visto cómo declarar almacenamiento en caché en una apli­
cación de Spring. En primer lugar, hemos visto cómo declarar uno o varios de los
administradores de caché de Spring. Después, aplicamos almacenamiento en caché a la
aplicación Spittr añadiendo anotaciones como @ C a c h e a b le , @ C ach eP u t y @ C a c h e E v ic t
a S p ittle R e p o s ito r y .
También hemos aprendido a configurar reglas de almacenamiento en caché inde­
pendientes al código de la aplicación en XML. Los elementos < c a c h e : c a c h e a b le > ,
c c a c h e : c a c h e - p u t> y « c a c h e : c a c h e - e v i c t > son equivalentes a las anotaciones
empleadas al inicio del capítulo.
Por el camino, hemos descrito el almacenamiento en caché como actividad orientada
a aspectos. De hecho, Spring implementa el almacenamiento en caché como aspecto, algo
evidente al declarar reglas de almacenamiento en caché en XML, ya que tuvimos que
vincular el consejo de almacenamiento en caché a un punto de corte.
Spring también usa aspectos al aplicar reglas de seguridad a métodos. En el siguiente
capítulo aprenderemos a usar Spring Security para aplicar seguridad a métodos de bean.
Capítulo

14 Proteger m é to d o s

CONCEPTOS FUNDAMENTALES:

• Protección de invocaciones de métodos.


• Definición de reglas de seguridad con expresiones.
• Creación de evaluadores de expresiones de seguridad.
Antes de salir de casa o de irme a la cama, lo último que hago es comprobar que la
puerta de casa esté cerrada, pero justo antes, activo la alarma. ¿Por qué? Porque aunque la
puerta sea una buena medida de seguridad, el sistema de alarma constituye una segunda
línea de defensa, en caso de que algún intruso consiga burlar la primera.
En un capítulo anterior vimos cómo usar Spring Security para proteger el nivel Web de
una aplicación. La seguridad Web es muy importante, ya que evita que los usuarios accedan
a contenidos para los que no tienen autorización. ¿Y si la seguridad de la capa Web de su
aplicación tiene una fuga? ¿Y si un usuario logra solicitar contenido que supuestamente
no debe ver?
Aunque es improbable que un usuario pueda burlar la seguridad de su aplicación, una
fuga puede producirse en cualquier momento. Imagine por ejemplo que un usuario solicita
una página que puede ver pero que por culpa del programador, el controlador que procesa
esa solicitud invoca un método que recupera datos que el usuario no debería ver. Es un
error comprensible, pero las fugas de seguridad surgen tanto por errores comprensibles
como por ataques a gran escala. Si protege tanto la capa Web de su aplicación como los
métodos entre bastidores, se asegurará de que no se ejecute ninguna lógica a menos que
el usuario tenga la debida autorización.
En este capítulo veremos cómo proteger métodos de bean con Spring Security.
Declararemos reglas de seguridad que impidan la ejecución de un método a menos que el
usuario para que el se ejecute tenga autorización para ejecutarlo. Comenzaremos con una
serie de sencillas anotaciones que puede añadir a sus métodos para protegerlos de accesos
no autorizados.

Proteger métodos con anotaciones*•


El enfoque más utilizado de seguridad de métodos con Spring Security consiste en
aplicar anotaciones de seguridad especiales a los métodos que desee proteger. Esto le ofrece
diversas ventajas, como por ejemplo que las reglas de seguridad de cualquier método sean
visibles al examinarlo en un editor.
Spring Security le ofrece tres tipos diferentes de anotaciones de seguridad:

• @ S e c u re d de Spring Security.
• @ R o le s A llo w e d de JSR-250.
• Anotaciones controladas por expresiones, como @ P reA u th o ri ze, @ P o stA u th o ri ze,
@ P r e F ilte r y @ P o s tF ilte r .

Las anotaciones @ S e c u r e d y @ R o le s A l lo w ed son las opciones más sencillas y limitan


el acceso en función de las autoridades concedidas al usuario. Si necesita mayor flexibilidad
para definir reglas de seguridad para métodos, Spring Security le ofrece @ P r e A u th o r iz e
y @ P o s t A u t h o r iz e , y @ P r e F i l t e r / @ P o s t F i l t e r filtran elementos de colecciones
devueltas o pasadas a un método. Antes de finalizar el capítulo habremos visto todas estas
anotaciones en acción. Para empezar, nos detendremos en la anotación @ S e c u re d , la más
sencilla de todas las que ofrece Spring Security.
416 Capítulo 14

Restringir el acceso a métodos con @Secured


La clave para habilitar la seguridad de métodos basada en anotaciones de Spring
consiste en anotar una clase de configuración con @ E n a b le G lo b a lM e t h o d S e c u r ity ,
de esta forma:
@Conf igurat ion
@EnableGlobalMethodSecurity(securedEnablecUtrue)
public cla ss MethodSecurityConfig
extends GlobalMethodSecurityConfiguration {
}
Además de anotarse con @ E n a b le G lo b a lM e t h o d S e c u r ity , comprobará que la clase
de configuración amplía G lo b a lM e t h o d S e c u r ity C o n f i g u r a t io n . Como sucedía con
la clase W e b S e cu ri ty C o n f ig u r e r A d a p t e r que en un capítulo anterior ampliaba la clase
de configuración de seguridad Web, esta clase le permite configurar puntos concretos de
seguridad en el nivel de los métodos. Por ejemplo, si todavía no ha configurado la auten­
ticación en la seguridad de la capa Web, puede hacerlo ahora si reemplaza el método
c o n f i g u r e () de G l o b a lM e t h o d S e c u r i t y C o n f i g u r a t io n :
@Override
protected void configure(AuthenticationManagerBuilder auth)
throws Exception {

auth
. inMemoryAuthentication()
.w ithüser("u ser") .password("password") . r o le s ("USER") ;
}
En un apartado posterior verem os cómo reemplazar el método c r e a t e E x p r e s s i o n
H a n d le r () de G lo b a lM e th o d S e c u r ity C o n f i g u r a t io n para proporcionar un compor­
tamiento predeterm inado de procesam iento de expresiones de seguridad.
Volviendo a la anotación @ E n a b le G lo b a lM e t h o d S e c u r ity , verá que su atributo
s e c u r e d E n a b le d se establece en t r u e , para crear un punto de corte de forma que los
aspectos de Spring Security envuelvan los métodos de bean anotados con @Se c u re d . Fíjese
en el siguiente método a d d S p i t t l e () anotado con @ S e c u re d :
©Secured( "ROLE_SPITTER")
public void ad d S p ittle(S p ittle s p ittle ) {
// . . .

}
La anotación @ S e c u r e d acepta una matriz de elementos S t r i n g como argumento.
Cada valor S t r i n g es una autorización, necesaria para invocar el método. Al proporcionar
ROLE_S PITTER, indicamos a Spring Security que no permita que se invoque el método
a d d S p i t t l e (), a menos que el usuario autenticado cuente con ROLE_SPITTER como una
de sus autoridades. Si se proporciona más de un valor a © S e c u re d , entonces el usuario
autenticado debe contar con, al menos, una de esas autoridades para obtener acceso al
método. En el siguiente ejemplo con @ S e c u r e d se indica que el usuario debe contar con
el privilegio RO LE_SPITTER o ROLE_ADMIN para invocar el método.
Proteger métodos 417

©Secured ({ "ROLE_SPITTER11, "ROLE_ADMIN"})


public void a d d S p ittle (S p ittle s p ittle ) {
// ...
}
Cuando un usuario no autenticado o uno que no cuente con los privilegios necesarios
intenta invocar el método, el aspecto que contiene al método genera una de las excepciones
de seguridad de Spring Security (probablemente, una subclase de A u t h e n t i c a t i o n
E x c e p t i o n o A c c e s s D e n i e d E x c e p t i o n ) . Son excepciones sin comprobar pero en
última instancia se tendrá que capturar y procesar la excepción. Si el método protegido se
invoca en el curso de una solicitud Web, la excepción será gestionada de forma automática
por uno de los filtros de Spring Security. De lo contrario, tendrá que crear el código para
gestionar la excepción.
La desventaja de © S e c u re d es que se trata de una anotación específica de Spring. Si se
siente más cómodo utilizando anotaciones estándar definidas en estándares de Java, quizás
debería considerar el uso de @ R o le s A llow ed.

Utilizar @ RolesAflov /ed d JSR-250 con Spring Security


La anotación @ R o le s A llo w e d es prácticamente equivalente a @ S e c u r e d en todos los
aspectos. La única diferencia importante es que @ R o le sA llo w e d es una de las anotaciones
estándar de Java, definida en la JSR-250.
Esta diferencia tiene un efecto más político que técnico, pero el uso de la anotación
@ R o le s A llo w e d puede tener implicaciones si se utiliza en el contexto de otros marcos de
trabajo o API que procesen esta anotación.
En cualquier caso, si utiliza @ R o le s A llo w e d , tendrá que activarla configurando el
atributo j s r 2 5 0 E n a b le d de @ E n a b le G lo b a lM e t h o d S e c u r ity en t r u e :

@Conf igurat ion


@EnableGlobalMethodSecurity(j sr25OEnabled=true)
public cla ss MethodSecurityConfig
extends GlobalMethodSecurityConfiguration {
}
Aunque, en este caso, solo hemos activado j s r 2 5 0 E n a b le d , debe saber que su uso no
es incompatible con s e c u r e d E n a b le d . Estos dos estilos de anotación se pueden activar
al mismo tiempo.
Al establecer j s r 2 5 0 E n a b le d en t r u e , se crea un punto de corte para que todos los
métodos anotados con @ R o le s A llo w e d se envuelvan con aspectos de Spring Security.
De este modo puede usar @ R o le s A l lo w ed en sus métodos de la misma forma que usaría
@ S e c u re d . El siguiente ejemplo muestra el mismo método a d d S p i t t l e () pero ahora
anotado con @ R o le s A llo w e d en lugar de con @ S e c u re d :
©RolesAllowed("ROLE_SPITTER")
public void ad d S p ittle (S p ittle s p ittle ) {
// . . .
418 Capítulo 14

Aunque @ R o le s A llo w e d tenga una ligera ventaja política sobre @ S e c u r e d por ser
una anotación basada en estándares para la seguridad de métodos, ambas anotaciones
comparten un defecto común. Solamente pueden restringir la invocación de un método
en función de si el usuario tiene o no un determinado privilegio. No se tienen en cuenta
otros factores a la hora de decidir si el método se ejecuta o no. Sin embargo, en un capítulo
anterior vimos que se podían usar expresiones SpEL para solucionar un problema similar
de protección de URL. Veamos cómo usar SpEL junto a las anotaciones previas y poste­
riores a la invocación de un método de Spring Security para aplicar seguridad de métodos
basada en expresiones.

Utilizar expresiones para proteger métodos


Aunque S S e c u r e d y @ R o le s A llo w e d nos permiten evitar el acceso de usuarios no
autorizados, eso es todo lo que pueden hacer. En algunos casos, las restricciones de segu­
ridad dependen de algo más que los privilegios concretos de un usuario. Spring Security
3.0 presentó una serie de nuevas anotaciones que utilizan SpEL para permitir restricciones
de seguridad más interesantes para métodos. Se describen en la tabla 14.1.

Tabla 14.1. Spring Security 3.0 incluye cuatro nuevas anotaciones que pueden utilizarse
para proteger métodos utilizando expresiones SpEL

@PreAuthorize Restringe el acceso a un método antes de su invocación en función


del resultado de evaluar una expresión.
@PostAuthorize Permite la invocación de un método, aunque genera una excepción
de seguridad si la expresión evalúa a false.
@PostFilter Permite la invocación de un método, aunque filtra sus resultados
en función de una expresión.
@PreFilter Permite la invocación de un método, pero filtra la información de
entrada antes de acceder al método.

Todas estas anotaciones aceptan una expresión SpEL para su parámetro v a lu é . La


expresión puede ser cualquier expresión SpEL válida y puede incluir cualquiera de las
extensiones de Spring Security para SpEL enumeradas en la tabla 9.5. Si la expresión evalúa
a t r u e , la regla de seguridad pasa; en caso contrario falla. Las implicaciones del resultado
de la prueba difieren en función de la anotación empleada. Veremos ejemplos específicos de
cada uno de estos elementos, aunque antes tendrá que habilitarlos y establecer el atributo
p r e P o s t E n a b le d de @ E n a b le G lo b a lM e t h o d S e c u r ity en t r u e :

@Configuration
public cla ss MethodSecurityConfig
extends GlobalMethodSecurityConfiguration {
}
Proteger métodos 419

Una vez activadas las anotaciones, puede comenzar a usarlas. Empezaremos por
restringir el acceso a un método por medio de @ P r e a u t h o r i z e y @ P o s t A u t h o r iz e .

Expresar reglas de acceso a métodos


Hasta el momento hemos visto cómo @ S e c u re d y @ R o lesA llo w ed impiden la ejecución
de un método a menos que el usuario tenga la autoridad necesaria, pero su punto débil es
que solo pueden tomar una decisión en función de las autoridades concedidas al usuario.
Spring Security ofrece otras dos anotaciones, @ P re A u th o riz e y @ P o s tA u th o r iz e , que
restringen el acceso a métodos en función de la evaluación de expresiones. Las expresiones
brindan una elevada flexibilidad a la hora de definir limitaciones de seguridad. Por medio
de una expresión puede permitir o impedir el acceso a un método utilizando prácticamente
cualquier condición que imagine.
La principal diferencia entre @ P r e A u th o r iz e y @ P o s t A u t h o r iz e radica en cuándo
se evalúan sus expresiones. @ P r e A u th o r iz e se evalúa antes de la ejecución del método
e impide que se ejecute a menos que la expresión evalúe a t r u e . Por el contrario, @
P o s t A u t h o r i z e espera a que el método devuelva un valor antes de decidir si genera o
no una excepción de seguridad.
Nos centraremos primero en la autorización previa, por ser la más utilizada de las
anotaciones de seguridad controladas por expresiones. Después veremos cómo proteger
el acceso a métodos una vez ejecutados.

Autorización previa de acceso a métodos


A prim era vista, @ P r e A u th o r iz e puede parecer equivalente a @ S e c u r e d y @ R o le s
A llo w e d en SpEL. De hecho, puede utilizarla para lim itar el acceso en función de las
funciones asignadas al usuario autenticado:
@PreAuthorize("hasRole( ' ROLE_SPITTER')")
public void a d d S p ittle (S p ittle s p ittle ) {
II ...
}

Si se usa de esta forma, @ P r e A u th o r i z e no parece ofrecer más ventajas que @ S e c u re d


o @ R o le s A l low ed. Si el usuario tiene la función ROLE_SPITTER, se permitirá la ejecución
del método. En caso contrario, se genera una excepción de seguridad y el método no se
ejecuta.
Pero @ P re A u th o riz e es capaz de mucho más. El argumento S t r i n g de @ P reA u th o ri ze
es una expresión SpEL. Si utilizamos expresiones SpEL para controlar el acceso, podemos
crear restricciones de seguridad más avanzadas. Por ejemplo, imagine que el usuario medio
de Spittr solo puede crear spittles de 140 caracteres pero que los de los usuarios premium no
tienen limitación de caracteres. En este caso, ni @ S e c u r e d ni @ R o lesA llo w ed nos serían
de utilidad, aunque sí @ P r e A u th o r iz e :
@PreAuthorize(
" (hasRole( 'ROLE_SPITTER' ) and # s p i t t l e . t e x t . len gth () <= 140)"
+"or hasRole( ' ROLE_PREMIUM') " )
420 Capítulo 14

public void a d d S p ittle (S p ittle s p ittle ) {


// . . .

}
La parte # s p i t t l e de la expresión hace referencia directamente al parámetro del método
del mismo nombre. Este permite que Spring Security examine los parámetros proporcio­
nados al método y los utilice en el proceso de autorización. En este caso, trabajamos con
el texto de S p i t t l e para asegurarnos de que no supera la longitud permitida para los
usuarios estándar de Spittr. Si el usuario es premium, la longitud no importa.

Autorización posterior de acceso a métodos


Una forma menos obvia de autorizar un método es hacerlo mediante una autorización
posterior. Esto suele implicar tomar decisiones de seguridad en función del objeto devuelto
por el método protegido. Obviamente, significa que el método debe invocarse y permitir
que genere un valor y lo devuelva.
Imagine por ejemplo que quiere proteger el método g e t S p i t t l e B y l d () para que solo
autorice el acceso si el objeto S p i t t l e devuelto pertenece al usuario autenticado. No hay
forma de saber si un objeto S p i t t l e pertenece al usuario actual hasta que lo recuperamos.
Por lo tanto, primero hay que ejecutar g e t S p i t t l e B y l d (). Si después de recuperar el
objeto S p i t t l e comprobamos que no pertenece al usuario actual, habrá que generar una
excepción de seguridad. @ P o s t A u t h o r iz e de Spring Security funciona de forma similar
a @ P r e A u th o r i z e , pero espera a que el método se haya ejecutado para aplicar la regla de
seguridad. En ese momento puede tener en cuenta el valor devuelto para tomar su decisión.
Por ejemplo, para proteger el método g e t S p i t t l e B y l d () como acabamos de describir,
puede usar @ P o s t A u t h o r iz e de esta forma:
OPostAuthorize{"retu rnO bject. spitter.usernam e == principal.usernam e")
public S p ittle g etSpittleB yld (long id) {
// . . .

Para disponer de un acceso sencillo al objeto devuelto por el método protegido, Spring
Security proporciona la variable r e t u r n O b je c t en SpEL. En este caso sabemos que el
objeto devuelto es un S p i t t l e , por lo que la expresión accede a su propiedad s p i t t e r
y obtiene la propiedad u se rn a m e a partir de ésta.
En el otro lado de esta comparación de doble igualdad, la expresión accede al objeto
p r i n c i p a l para obtener su propiedad u sern a m e. p r i n c i p a l es otro de los nombres
especiales de Spring Security y representa el elemento principal del usuario actualmente
autenticado. Si el objeto S p i t t l e cuenta con un S p i t t e r cuya propiedad u se rn a m e es
la misma que la propiedad u sern a m e de p r i n c i p a l , el objeto S p i t t l e se devuelve al
invocador. En caso contrario, se generará una excepción A c c e s s D e n ie d E x c e p t io n y el
invocador no podrá ver el objeto S p i t t l e .
Es im portante tener en cuenta que, a diferencia de los métodos anotados con
@ P r e A u th o r iz e , con @ P o s t A u t h o r iz e los métodos primero se ejecutan y después se
interceptan. Por ello debe tomar las precauciones adecuadas para que el método no provoque
ningún efecto no deseado en caso de que falle la autorización.
Proteger métodos 421

Filtrar entradas y salidas de métodos


@ P r e A u th o r iz e y @ P o s t A u t h o r iz e resultan perfectas si usamos expresiones para
proteger un método, pero en ocasiones, impedir el acceso a un método es demasiado
complicado y se acaban protegiendo los datos pasados o devueltos por el método, y no el
propio método. Imagine que tiene el método g e t o f f e n s i v e S p i t t l e s () que devuelve
una lista de objetos S p i t t l e marcados como ofensivos. Es un método para que el adminis­
trador modere el contenido de la aplicación Spittr, pero también lo podría usar un usuario
concreto para ver si alguno de sus objetos S p i t t l e se ha marcado como ofensivo. La firma
del método podría ser similar a la siguiente:
public L is t< S p ittle > g e tO ffe n siv e S p ittles() { . . . }

Al método g e t O f f e n s i v e S p i t t l e s () no le preocupa ningún usuario concreto.


Simplemente devuelve una lista de objetos S p i t t l e ofensivos, independientemente de a
quién pertenezcan. Es perfecto para el uso administrativo del método pero se queda corto
a la hora de limitar la lista de objetos S p i t t l e que pertenecen al usuario actual.
Evidentemente, podría sobrecargar g e t o f f e n s i v e S p i t t l e s () con otra versión que
acepte un ID de usuario como parámetro y lo utilice para recuperar solamente los objetos
S p i t t l e ofensivos de un determinado usuario, pero como mencionamos al inicio del
capítulo, siempre existe la posibilidad de usar la versión menos restrictiva en zonas en las
que se necesite cierta restricción1.
Lo que necesitamos es una forma de filtrar la colección de objetos S p i t t l e devuelta
por g e t o f f e n s i v e S p i t t l e s () y reducirla a la lista que el usuario actual pueda ver,
precisamente lo que hace @ P o s t F i l t e r d e Spring Security, como veremos a continuación.

Postfíltrado de valores devueltos por métodos


Al igual que sucede con @ P re A u th o riz e y @ P o s tA u th o riz e , @ P o s t F i l t e r acepta
una expresión SpEL como valor, pero en lugar de usarla para limitar el acceso a un método,
@ P o s t F i l t e r la evalúa sobre cada miembro de una colección devuelta por el método y
elimina los miembros de la misma para los que la expresión evalúa a f a l s e .
Para ilustrarlo, aplicaremos @ P o s t F i l t e r al método g e t O f f e n s i v e S p i t t l e s ():
@PreAuthorize("hasAnyRole( { ' ROLE_SPITTER', ' ROLE_ADMIN'} ) " >
@ P ostP ilter( "hasRole( 'ROLE_ADMIN') || "
+ " f ilte r O b je c t. spitter.usernam e == p rin c ip a l.ñame")
public L ist< S p ittle > g etO ffe n siv e S p ittles() {

}
En este caso, la anotación @ P r e A u t h o r i z e solo permite ejecutar el método a los
usuarios con la autoridad RO LE_SPITTER o ROLE_ADMIN. Si el usuario pasa la compro­
bación, el método se ejecuta y se devuelve una lista de objetos S p i t t l e , pero la anotación

1. Además, s¡ sobrecargara g e to f f e n s iv e S p ittle s () tendría que pensar en otro ejemplo para mostrarle
cómo filtrar la salida del método con SpEL)
422 Capítulo 14

@ P o s t F i l t e r filtra esa lista para asegurarse de que el usuario solo ve los objetos S p i t t l e
que tiene permiso para ver. En concreto, los administradores pueden ver todos los objetos
S p i t t l e ofensivos y los demás usuarios solo los que les pertenezcan.
En la expresión se hace referencia a f i l t e r O b j e c t , correspondiente a un elemento
concreto (que sabemos que es un objeto S p i t t l e ) de la lista devuelta por el método. Si
el S p i t t e r de ese objeto S p i t t l e tiene un nom bre de usuario que coincide con el del
usuario autenticado ( p r i n c i p a l . ñame en la expresión) o si el usuario tiene la función
ROLE_ADMIN, el elem ento se conserva en la lista filtrada. En caso contrario se excluye.

Filtrado previo de parámetros de método


Además del filtrado posterior del valor devuelto por un método, también puede realizar
un filtrado previo de los valores pasados al mismo. Es una técnica menos habitual pero
puede resultarle útil en alguna ocasión.
Imagine que tiene una lista de objetos S p i t t l e que desea eliminar en bloque. Para ello,
puede escribir un método con una firma similar a la siguiente:
public void d e le te S p ittle s (L is t< S p ittle > s p ittle s ) { ... }

Parece sencilla, ¿no? Pero imagine que desea aplicarle reglas de seguridad para que los
objetos S p i t t l e solo los pueda borrar su propietario o un administrador. En ese caso,
podría escribir lógica en el método d e l e t e S p i t t l e s () para recorrer todos los objetos
S p i t t l e de la lista y eliminar únicamente los que pertenezcan al usuario actual (o todos
si el usuario actual es un administrador).
Aunque podría funcionar, tendría que incrustar lógica de seguridad directamente en la
del método, y dicha lógica representa una preocupación adicional a la de la eliminación de
objetos S p i t t l e . Sería más indicado que la lista solamente incluyera los objetos S p i t t l e
que se van a eliminar. De esa forma se simplificaría la lógica de d e l e t e S p i t t l e s ()
y se centraría en la tarea de eliminar objetos S p i t t l e . La anotación @ P r e F i l t e r de
Spring Security parece la opción perfecta para este problema. Al igual que @ P o s t F i l t e r ,
@ P r e F i l t e r usa SpEL para filtrar una colección a los elementos que satisfagan la expre­
sión SpEL, pero en lugar de filtrar el valor devuelto por un método, filtra los miembros de
una colección que acceden al mismo. El uso de @ P r e F i l t e r es muy sencillo. Veamos el
método d e l e t e S p i t t l e s ( ), anotado ahora con @ P r e F i l t e r :
@PreAuthorize("hasAnyRole( { 'ROLEjSPITTER', ' ROLE_ADMIN'} ) " )
® P reFilter( "hasRole( ' ROLE_ADMIN') || "
+ "targ e tO b ject. spitter.usernam e == principal.ñam e")
public void d e le te S p ittle s (L is t< S p ittle > s p ittle s ) { . . . }

Como antes, @ P r e A u th o r iz e impide la invocación de este método si el usuario no


tiene una autoridad R 0L E _SPIT T E R o ROLE_ADMIN, pero también se asegura de que la
lista pasada a d e l e t e S p i t t l e s () solo contenga objetos S p i t t l e que el usuario actual
tenga permiso para borrar. La expresión se evalúa sobre todos los elementos de la colec­
ción y solo se conservan en la lista aquellos para los que la expresión evalúe a t r u e . La
variable t a r g e t O b j e c t es otro valor proporcionado por Spring Security que representa
el elemento de la lista sobre el que se evalúa.
Proteger métodos 423

Ya hemos visto las cuatro anotaciones controladas por expresiones de Spring Security.
Las expresiones constituyen una forma mucho más completa de definir restricciones de
seguridad que la especificación de una autoridad que conceder a un usuario.
Sin embargo, no debe usarlas en exceso. Evite crear expresiones de seguridad demasiado
complejas o incrustar demasiada lógica empresarial no relacionada con la seguridad en sus
expresiones. En última instancia, las expresiones son simples valores S t r i n g asignados a
las anotaciones y, como tales, resultan difíciles de probar y depurar.
Si cree que sus expresiones de seguridad empiezan a descontrolarse, puede recurrir a un
evaluador de permisos personalizado para simplificar sus expresiones SpEL. A continua­
ción, veremos cómo crear y usar un evaluador de permisos para simplificar las expresiones
utilizadas en tareas de filtrado.

Definir un evaluador de permisos


La expresión que hemos usado con @ P r e F i l t e r y @ P o s t F i l t e r no es tan compleja,
pero tampoco es trivial y seguramente ya haya pensado en formas de ampliarla para
acomodar otras reglas de seguridad. Con el tiempo, la expresión será demasiado compleja
y difícil de probar.
Podría sustituir toda la expresión por otra más sencilla similar a la mostrada a
continuación:
«PreAuthorize("hasAnyRole( { ' ROLE_SPITTER', ' ROLE_ADMIN'} ) " )
@ P re F ilte r( "hasPerm ission(targetO bject, 'd e le t e ')")
public void d e le te S p ittle s (L is t< S p ittle > s p ittle s ) { . . . }

Ahora la expresión asignada a @ P r e F i l t e r es mucho más concisa. Simplemente


pregunta si el usuario tiene permiso para borrar el objeto de destino. En caso afirmativo,
la expresión evalúa a t r u e y el objeto S p i t t l e se conserva en la lista pasada a d e l e t e
S p i t t l e s () . En caso contrario se descarta.
Pero ¿de dónde proviene h a s P e r m is s io n () ? ¿Qué significa? Y sobre todo ¿cómo
sabe si el usuario tiene o no permiso para eliminar el objeto S p i t t l e en t a r g e t O b j e c t ?
La función h a s P e r m is s io n () es una extensión de Spring Security para SpEL y le
permite, como programador, añadir la lógica que desee ejecutar cuando se evalúe. Solo
tiene que crear y registrar un evaluador de permisos personalizado. En el siguiente código
se reproduce S p i t t l e P e r m i s s i o n E v a l u a t o r , un evaluador de permisos personalizado
que contiene la lógica de la expresión.

Listado 14.1. Un evaluador de permisos que proporciona la lógica subyacente a hasPermission().

package s p i t t r . secu rity ;


import ja v a .io .S e r ia liz a b le ;
import org. springframework. se c u rity . a cc e ss. PermissionEvaluator;
import org.springfram ework.security. core.A uthentication;
import s p i t t r . S p i t t le ;

public cla ss SpittlePerm issionEvaluator implements PermissionEvaluator {


private s t a t ic f in a l GrantedAuthority ADMIN_AUTHORITY =
new GrantedAuthoritylmpl("ROLE_ADMIN") ;
424 Capítulo 14

public boolean hasPermission(Authentication authentication,


Object ta rg e t, Object permission) {
i f (targ et instanceof S p ittle ) {
S p ittle s p it t le = (S p ittle ) ta rg e t;
Strin g username = s p i t t l e . g e tS p itte r ( ) .getUsername();
i f ("d e le te ". equals(perm ission)) {
return isAdmin(authentication) ||
username. equals(authentication.getNam e( ) ) ;
}
}
throw new UnsupportedOperationException(
"hasPermission not supported fo r o b je ct <" + targ et
+ "> and permission <" + permission + ;
}
public boolean hasPermission(Authentication authentication,
S e ria liz a b le ta rg e tld , String targetType, Object permission) {
throw new UnsupportedOperationException();
}
private boolean isAdmin(Authentication authentication) {
return au th en ticatio n .g etA u th o rities( ) . contains(ADMIN_AUTHORITY);
}

}
S p i t t l e P e r m i s s i o n E v a l u a t o r implementa la interfaz P e r m i s s i o n E v a l u a t o r
de Spring Security, que exige la implementación de dos métodos h a s P e r m i s s i o n ()
diferentes. Uno acepta O b je c t como objeto sobre el que evaluar el segundo parámetro.
El segundo método h a s P e r m is s io n () solo es útil si está disponible el ID del objeto de
destino, que acepta como S e r i a l i z a b l e en su segundo parámetro.
Para nuestros objetivos, asumiremos que siempre contará con el objeto S p i t t l e
sobre el que evaluar los permisos, de modo que el otro método simplemente genera
U n s u p p o r t e d O p e r a t io n E x c e p tio n .
En cuanto al prim er m étodo h a s P e r m is s io n ( ) , comprueba que el objeto evaluado
sea un objeto S p i t t l e y que se esté comprobando el permiso de eliminación. En caso
afirmativo, comprueba que el nom bre de usuario de S p i t t e r coincida con el del usuario
autenticado o que la autenticación actual tenga la autoridad ROLE_ADMIN.
Una vez esté listo el evaluador de perm isos, tendrá que registrarlo con Spring
Security para que respalde la operación h a s P e r m is s io n () en la expresión asignada a
@ P o s t F i l t e r . Para ello, debe reemplazar el controlador de expresiones por otro confi­
gurado para usar su evaluador de permisos personalizado. De forma predeterminada
Spring Security se configura con D e f a u l t M e t h o d S e c u r i t y E x p r e s s i o n H a n d l e r ,
que recibe una instancia de D e n y A l l P e r m i s s i o n E v a l u a t o r . Como su nombre
indica, D e n y A l l P e r m i s s i o n E v a l u a t o r siempre devuelve f a l s e de sus métodos
P e r m is s io n () y deniega cualquier acceso a los métodos, pero puede proporcionar a
Spring Security un controlador D e f a u lt M e t h o d S e c u r it y E x p r e s s i o n H a n d le r confi­
gurado con su S p i t t l e P e r m i s s i o n E v a l u a t o r personalizado si reemplaza el método
c r e a t e E x p r e s s i o n H a n d l e r de G lo b a lM e t h o d S e c u r ity C o n f i g u r a t i o n :
Proteger métodos 425

@Override
protected MethodSecurityExpressionHandler createExpressionHandler() {
DefaultMethodSecurityExpressionHandler expressionHandler =
new DefaultMethodSecurityExpressionHandler( );
expressionHandler. setPerm issionEvaluator(
new SpittlePerm issionEvaluator( ) ) ;
return expressionHandler;

Ahora, siempre que proteja un método con una expresión que utilice h a s P e r m is s io n ( ) ,
se invocará S p i t t l e P e r m i s s i o n E v a l u a t o r y decidirá si el usuario tiene o no permiso
para invocar el método.

Resumen
La seguridad de los métodos es un importante complemento a la seguridad del nivel
Web de Spring Security, que vimos en un capítulo anterior. En el caso de aplicaciones que
no estén orientadas a la Web, la seguridad en el nivel de los métodos es la primera línea
de defensa. Si se aplica a una aplicación Web, respalda las reglas de seguridad declaradas
para proteger las solicitudes Web.
En este capítulo hemos visto seis anotaciones que se pueden añadir a los métodos para
declarar restricciones de seguridad. Para contar con una seguridad sencilla orientada a auto­
ridades, son muy útiles las anotaciones @ S e c u r e d de Spring Security o @ R o le s A llo w e d ,
basada en estándares. Para disponer de reglas de seguridad más interesantes, puede recu­
rrir a @ P r e A u th o r iz e , @ P o s t A u t h o r iz e y SpEL. También hemos visto cómo filtrar las
entradas y salidas de un método por medio de expresiones SpEL asignadas a @ P r e F i l t e r
y @ P o s tF ilte r .
Por último, hemos aprendido a simplificar el mantenimiento, pruebas y depuración de
las reglas de seguridad mediante la definición de un evaluador de expresiones personali­
zado que, entre bastidores, trabaja con la función h a s P e r m is s io n () de SpEL.
A partir del siguiente capítulo pasaremos del desarrollo en el servidor al uso de Spring
para integrarlo con otras aplicaciones. Describiremos todo tipo de técnicas de integración,
como acceso remoto, mensajería asincrona, REST e incluso el envío de correos electrónicos.
La primera técnica de integración será el uso remoto de Spring, que veremos en el siguiente
capítulo.
Parte IV.
Integración de Spring

Ninguna aplicación es una isla. En la actualidad, las aplicaciones empresariales deben


coordinarse con otros sistemas para desempeñar sus funciones. En la parte final del libro
veremos cómo superar los límites de una aplicación y aprenderemos a integrarla con otras
aplicaciones y con servicios empresariales.
En el capítulo 15 va a aprender a exponer los objetos de su aplicación como servicios
remotos. También aprenderá a acceder a estos de forma transparente, como si se encontraran
en cualquier otro objeto de su aplicación. Veremos diferentes tecnologías de acceso remoto,
incluyendo RMI, Hessian/Burlap y servicios Web SOAP con JAS-WS.
En comparación con los servicios remotos de estilo RPC mostrados en el capítulo 15, en
el 16 veremos cómo crear servicios REST orientados a recursos con Spring MVC.
En el capítulo 17 vamos a ver un enfoque diferente para la integración de aplicaciones,
mostrándole como utilizar Spring con JMS y AMQP para establecer comunicaciones asin­
cronas entre aplicaciones.
Cada vez más, se espera que las aplicaciones Web tengan una mayor capacidad de
respuesta y muestren datos en tiempo real. En el capítulo 18 se describe la nueva compa­
tibilidad de Spring para establecer comunicaciones asincronas entre un servidor y sus
clientes Web.
Otra forma de comunicación asincrona no es necesariamente la que se realiza entre
aplicaciones. En el capítulo 19 veremos cómo enviar mensajes asincronos a usuarios en
forma de correo electrónico por medio de Spring.
En el capítulo 20 vamos a hablar sobre la gestión y la supervisión de los bean de Spring.
Aquí aprenderá cómo Spring puede exponer, de forma automática, bean configurados en
Spring como MBean de JMX.
Para finalizar el libro, en el capítulo 21 presentamos una apasionante novedad para el
desarrollo con Spring. Veremos cómo Spring Boot simplifica al máximo la configuración
necesaria en muchas aplicaciones de Spring para que pueda centrarse en implementar
funciones empresariales.
Capítulo

15 Trabajar con
se rv ic io s rem otos

CONCEPTOS FUNDAMENTALES:

• Acceso y exposición de servicios RMI.


• Uso de servicios Hessian y Burlap.
• Trabajar con el invocador HTTP de Spring.
• Uso de Spring con servicios Web
Imagine, por un momento que se encuentra en una isla desierta. Quizá le parezca un
sueño hecho realidad porque, después de todo, ¿a quién no le gustaría estar solo en una
isla, aislado del ruidoso mundo?
Sin embargo, en una isla desierta, no todo son piñas coladas y tomar el sol. Llegará un
momento en que tendrá hambre, estará aburrido y se sentirá solo. No puede vivir de cocos
y de pescado durante mucho tiempo y acabará por necesitar comida, ropa limpia y otros
suministros, y si no habla pronto con otra persona, lo más probable es que acabe hablando
con un balón de fútbol.
Muchas de las aplicaciones que va a desarrollar son como náufragos en una isla. Desde
fuera pueden parecer autosuficientes pero, en su interior, probablemente colaboren con
otros sistemas, tanto internos como externos a su organización.
Por ejemplo, piense en un sistema de adquisiciones que tiene que comunicarse con un
sistema de cadena de suministro de proveedores. Quizás el sistema de recursos humanos de
su empresa tenga que integrarse con el de pagos, o que este último tenga que comunicarse
con otro sistema externo para imprimir y enviar por correo las nóminas. Con independencia
del contexto, su aplicación va a tener que comunicarse con otros sistemas para acceder a
servicios de forma remota.
Como desarrollador de Java, dispone de varias tecnologías para este fin, entre las que
se incluyen:

• RMI (R em óte M ethod Invocation, Invocación de métodos remotos).


• Hessian y Burlap (de Caucho).
• El propio servicio HTTP de acceso remoto de Spring.
• Servicios Web con JAX-RPC y JAX-WS.

Con independencia de la tecnología de acceso remoto que elija, Spring le proporciona


una amplia compatibilidad para acceder y crear servicios remotos con diferentes tecnolo­
gías. En este capítulo va a aprender cómo Spring simplifica y complementa estos servicios,
pero antes, veamos cómo funciona el acceso remoto en Spring.

Acceso remoto en Spring


El acceso remoto consiste en una conversación entre una aplicación cliente y un
servicio. En el lado del cliente, necesitamos cierta funcionalidad que no se encuentra en el
ámbito de la aplicación, por lo que ésta se conecta a otro sistema que pueda proporcionar
esa funcionalidad. La aplicación remota expone la funcionalidad mediante un servicio
remoto.
Supongamos que quiere hacer disponible alguna de las funcionalidades de la aplicación
Spittr en forma de servicios remotos para que otras aplicaciones puedan utilizarlas. Además
de la interfaz de usuario existente basada en el navegador, tendría que crear una interfaz
de escritorio o móvil para Spittr (véase la figura 15.1). Para ello, tendría que exponer las
funciones básicas de la interfaz S p i t t e r S e r v i c e como servicio remoto.
430 Capítulo 15

Figura 15.1. Un cliente de terceros puede interactuar con la aplicación Spittr realizando
invocaciones remotas a un servicio expuesto por Spittr.

La conversación entre las otras aplicaciones y Spittr comienza con una llamada de
procedimiento remoto (RPC) desde las aplicaciones cliente. A simple vista, una RPC es
similar a la invocación de un método en un objeto local. Ambas son operaciones síncronas
que bloquean la ejecución en el código de llamada hasta que el procedimiento solicitado se
completa. La diferencia se encontraría en la proximidad. Podemos hacer una comparación
con la comunicación humana. Si está hablando en el trabajo sobre el resultado del partido
del domingo, está realizando una conversación local (que tiene lugar entre dos personas
en la misma habitación). Del mismo modo, la invocación de un método local tiene lugar
cuando el flujo de ejecución se intercambia entre dos fragmentos de código dentro de la
misma aplicación.
Por otro lado, si coge el teléfono para llamar a un cliente que se encuentra en otra ciudad,
su conversación va a realizarse de forma remota, a través de la red telefónica. Del mismo
modo, la RPC tiene lugar cuando el flujo de ejecución pasa de una aplicación a otra, en
teoría en un equipo diferente en una ubicación remota y a través de la red.
Spring permite el acceso remoto para diferentes modelos RPC, incluyendo RMI, Hessian
y Burlap de Caucho, y el propio invocador HTTP de Spring. En la tabla 15.1 se recogen
estos modelos y se menciona su utilidad en diferentes situaciones.

Tabla 15.1. Spring admite RPC mediante diversas tecnologías de acceso remoto.

Modelo RPCI Utilidad


I..s .... L.
RMI Acceso y exposición de servicios basados en Java cuando las restricciones
de red (como los cortafuegos) no son un factor que tener en cuenta.
Hessian o Burlap Acceso y exposición de servicios a través de HTTP cuando las restricciones
de red deben tenerse en cuenta. Hesian es un protocolo binario, mientras
que Burlap se basa en XML.
Invocador HTTP Acceso y exposición de servicios basados en Spring cuando las restricciones
de red son un factor y desea contar con señalización Java sobre XML o
señalización propietaria.
JAX-RPX y JAX-WS Acceso y exposición de servicios Web basados en SOAP, con independencia
de la plataforma utilizada.

Con independencia del modelo de acceso remoto que seleccione, verá que existen
elementos comunes respecto a la compatibilidad de Spring con cada uno de ellos, de modo
que una vez sepa configurar Spring para que funcione con uno de estos modelos, lo tendrá
Trabajar con servicios remotos 431

muy fácil si decide utilizar otro. En todos estos modelos, los servicios pueden configurarse
en su aplicación como bean gestionados por Spring. Esto se lleva a cabo utilizando un bean
de fábrica de proxy que le permite conectar servicios remotos a propiedades de otros bean
como si fueran objetos locales. La figura 15.2 ilustra el funcionamiento de este mecanismo:

Figura 15.2. En Spring, los servicios remotos se conectan mediante proxy para así poder
conectarse al código cliente como si fueran cualquier otro bean de Spring.

El cliente realiza llamadas al proxy com o si fuera el proveedor de la funcionalidad del


servicio. A continuación, el proxy se comunica con el servicio remoto en nombre del cliente
y le entrega los detalles de la conexión y de la realización de llamadas remotas.
Es más, si la llamada al servicio remoto genera una excepción ja v a .r m i .R em ó te
E x c e p t io n , el proxy la controla y la vuelve a generar como R e m o te A c c e s s E x c e p t io n no
comprobada. Las excepciones remotas suelen indicar problemas de red o de configuración
que no pueden solucionarse. Como un cliente puede hacer poco para recuperarse de una
excepción remota, volver a generar R e m o te A c c e s s E x c e p t io n hace que sea opcional para
el cliente gestionar la excepción. En el lado del servicio, puede exponer la funcionalidad
de cualquier bean gestionado por Spring como servicio remoto utilizando cualquiera de
los módulos de la tabla 15.1. La figura 15.3 ilustra cómo los exportadores remotos exponen
métodos de bean como servicios remotos.

Figura 15.3. Los bean gestionados por Spring se pueden exportar como servicios remotos
utilizando exportadores remotos.
432 Capítulo 15

Si está desarrollando código que utiliza servicios remotos, implementado estos servicios
o ambas cosas, tenga en cuenta que trabajar con servicios remotos en Spring es una mera
cuestión de configuración. No tiene que crear código de Java para permitir el acceso remoto,
y los bean de su servicio no tienen que saber que están implicados en una RPC (aunque
cualquier bean transmitido o devuelto desde una llamada remota puede que tenga que
implementar j a v a . i o . S e r i a l i z a b l e ) .
Empezaremos nuestro análisis de la compatibilidad con servicios remotos de Spring
con RMI, la tecnología de acceso remoto original de Java.

Trabajar con RMI


Si ha trabajado con anterioridad con Java, seguro que ha oído hablar o incluso ya ha
utilizado RMI. Apareció por primera vez en la plataforma Java en el JDK 1.1 y proporciona
a los programadores de esta plataforma un método para establecer la comunicación entre
programas de Java. Antes de RMI, la única opción de acceso remoto para los programadores
de Java era CORBA (que, en su momento, requería la compra de un agente de solicitud de
objetos u ORB de terceros) o el uso de código personalizado.
Sin embargo, el desarrollo y el acceso a los servicios RMI es aburrido y además requiere
varios pasos, tanto programáticos como manuales. Spring simplifica el modelo RMI al
proporcionar un bean de fábrica de proxy que le permite conectar servicios RMI a su aplica­
ción de Spring como si fueran JavaBean locales. Spring también proporciona un exportador
remoto que facilita la tarea de convertir sus bean gestionados por Spring a servicios RMI.
Para la aplicación Spittr, voy a mostrarle cómo conectar un servicio RMI al contexto
de aplicación de Spring de una aplicación cliente, pero antes, veamos cómo utilizar el
exportador RMI para publicar la implementación S p i t t e r S e r v i c e como servicio RMI.

Exportar un servicio RMI*1


Si ha creado un servicio RMI antes, sabrá que este proceso consta de los siguientes pasos:

1. Crear la clase de implementación del servicio con métodos que generan la excepción
j a v a . r m i . R e m o te E x c e p tio n .
2. Crear la interfaz de servicio para ampliar j a v a . rm i . Rem óte.
3. Ejecutar el compilador RMI (rm ic) para generar las clases esqueleto del servidor y
el código cliente.
4. Iniciar un registro RMI para alojar los servicios.
5. Registrar el servicio en el registro RMI.

Vaya, parece bastante trabajo solo para un servicio RMI. Probablemente lo peor de todos
estos pasos es que las excepciones R e m o te E x c e p tio n y M a lf o rm ed U R L E x ce p tio n se
generan con demasiada frecuencia. Estas excepciones suelen indicar un error fatal del que
Trabajar con servicios remotos 433

no es posible recuperarse utilizando un bloque c a t c h . Sin embargo, aún se espera que


escriba código reutilizable para capturar y controlar estas excepciones, incluso aunque no
se puede hacer mucho para solucionarlas.
Queda claro que se requiere bastante código y trabajo manual para publicar un servicio
RMI. ¿No hay nada que Spring pueda hacer para ayudarnos en esta situación?

Configurar un servicio RMI en Spring


Por fortuna, Spring proporciona una forma más sencilla de publicar servicios RMI. En
lugar de escribir clases específicas de RMI con métodos que generen R e m o te E x c e p tio n ,
basta con escribir un POJO que cuente con la funcionalidad de su servicio. Spring se hará
cargo del resto.
El servicio RMI que vamos a crear expone los métodos de la interfaz S p i t t e r S e r v i c e .
A modo de recordatorio, el listado 15.1 le muestra el aspecto de la interfaz.

Listado 15.1. SpitterService define la capa de servicio de la aplicación Spittr.

package com.habuma.spittr.service;
import java.util.List;
import com.habuma.spitter.domain.Spitter;
import com.habuma.spitter.domain.Spittle;
public interfaceSpitterService{
List<Spittle>getRecentSpittles(int count);
void saveSpittle(Spittle spittle);
void saveSpitter(Spitter spitter);
Spitter getSpitter(long id);
void startFollowing(Spitter follower,Spitter followee);
List<Spittle>getSpittlesForSpitter(Spitter spitter);
List<Spittle>getSpittlesForSpitter(String username);
Spitter getSpitter(String username);
Spittle getSpittleByld(long id);
void deleteSpittle(long id);
List<Spitter>getAllSpitters();
}
Si utilizáramos el método RMI tradicional para exponer el servicio, todos los métodos
de S p i t t e r S e r v i c e y S p i t t e r S e r v i c e l m p l tendrían que generar la excepción j a v a .
rm i .R e m o te E x c e p tio n . Sin embargo, vamos a recurrir a un servicio RMI utilizando
R m i S e r v ic e E x p o r t e r de Spring, por lo que las implementaciones existentes servirán.
R m i S e r v ic e E x p o r t e r exporta cualquier bean gestionado por Spring como servicio
RMI. Como ilustra la figura 15.4, R m i S e r v ic e E x p o r t e r se encarga de incluir el bean en
una clase de adaptador. Ésta, a su vez, se vincula al registro RMI y redirige las solicitudes
a la clase de servicio (en este caso, S p i t t e r S e r v i c e l m p l ) . La forma más sencilla de
utilizar R m i S e r v ic e E x p o r t e r para exponer S p i t t e r S e r v i c e l m p l como servicio RMI
es configurarlo en Spring con el siguiente código XML:
@Bean
public RmiServiceExporter rmiExporter(SpitterService SpitterService) {
RmiServiceExporter rmiExporter = new RmiServiceExporter();
434 Capítulo 15

rmiExporter. se tS e rv ic e (s p itte rS e rv ic e );
rmiExporter. setServiceN am e("SpitterService");
rm iE x p o rter.setS erv iceln terface(S p itterS erv ice. c l a s s ) ;
return rmiExporter;

Figura 15.4. RmiServiceExporter convierte los POJO en servicios RMI, incluyéndolos en un


adaptador de servicios y vinculándolo al registro RMI.

En este caso, elb ean s p i t t e r S e r v i c e se conecta a la propiedad S e r v i c e para indicar


que R m i S e r v i c e E x p o r t e r va a exportar el bean como servicio RMI. La propiedad
s e rv ic e N a m e indica el nombre del servicio RMI y la propiedad s e r v i c e l n t e r f a c e la
interfaz que el servicio implementa.
De forma predeterminada, R m iS e r v ic e E x p o r t e r intenta vincularse a un registro RMI
a través del puerto 1099 del equipo local. Si no se encuentra ningún registro en ese puerto,
R m i S e r v ic e E x p o r t e r inicia uno. Si prefiere vincular un registro RMI a un puerto o a un
host diferente, puede especificarlo con las propiedades r e g i s t r y P o r t y r e g i s t r y H o s t ,
respectivamente.
Por ejemplo, el siguiente R m i S e r v ic e E x p o r t e r va a intentar vincularse a un registro
RMI en el puerto 1 1 9 9 del host rm i . s p i t t e r . com:
@Bean
public RmiServiceExporter rm iE xporter(SpitterService sp itterS e rv ice) {
RmiServiceExporter rmiExporter = new RmiServiceExporter();
rmiExporter. s e tS e rv ic e (s p itte rS e rv ic e );
rmiExporter. setServiceName("S p itte rS e rv ice ") ;
rmiExporter. se tS e rv ic e ln te rfa c e (S p itte rS e rv ic e . c l a s s ) ;
rmiExporter. setRegistryH ost("rm i. s p i t t e r . com") ;
rmiExporter. setR eg istry P o rt(1199);
return rmiExporter;

Es todo lo que necesita para que Spring convierta un bean en un servicio RMI. Ahora
que el servicio Spitter se ha expuesto como uno de RMI, podemos crear interfaces de
usuario alternativas o invitar a terceros a que creen clientes para Spittr mediante el
servicio RMI.
Los desarrolladores de esos clientes lo van a tener fácil para conectarse al servicio RMI
de Spitter si utilizan Spring. Ahora que ya hemos configurado el servicio, veamos cómo
crear un cliente para el servicio RMI Spitter.
Trabajar con servicios remotos 435

Conectar un servicio RMI


Tradicionalmente, los clientes RMI deben utilizar la clase Naming del API RMI para
buscar un servicio en el registro RMI. Por ejemplo, el siguiente fragmento de código puede
utilizarse para obtener el servicio RMI Spitter:
try {
String serviceU rl="rmi: / s p itte r/S p itte rS e rv ic e ";
Sp itterS erv ice sp itte rS erv ice=
(Sp itterService) Naming.lookup(serviceUrl);

}
catch (RemoteException e ) { . . . }
catch (NotBoundException e ) { . . . }
catch (MalformedURLException e ) { . . . }

Aunque este fragmento de código nos permitiría obtener una referencia al servicio RMI
Spitter, presenta dos problemas:

• Las búsquedas RMI convencionales podrían generar una de las tres siguientes
excepciones comprobadas ( R e m o t e E x c e p t i o n , N o t B o u n d E x c e p t i o n o
M a lfo rm ed U R L E x cep tio n ), que deben capturarse o generarse de nuevo.
• Cualquier código que necesite el servicio Spitter es responsable de recuperar el servicio
por sí mismo. Este código no mantiene una cohesión directa con la funcionalidad del
cliente.

Las excepciones que se generan durante una búsqueda RMI suelen marcar una condición
fatal y no recuperable en la aplicación. Por ejemplo, M a lfo rm ed U R L E x cep tio n indica que
la dirección proporcionada para el servicio no es válida. Para recuperarse de esta excep­
ción, la aplicación tiene que, como mínimo, configurarse de nuevo y volver a compilarse.
Ningún bloque t r y / c a t c h va a permitir la recuperación desde esta excepción. Por tanto
¿por qué nuestro código tiene que estar obligado a capturar la excepción y a controlarla?
Sin embargo, lo más siniestro puede que sea que este código es completamente contrario
a los principios de la inyección de dependencias. Como el código cliente es responsable de
buscar el servicio Spitter y éste es de tipo RMI, no hay oportunidad de proporcionar una
implementación diferente de S p i t t e r S e r v i c e desde otra fuente. Lo ideal sería poder
inyectar un objeto S p i t t e r S e r v i c e en cualquier bean que lo necesite en lugar de hacer
que el bean busque el servicio por sí mismo. Mediante el uso de la DI, cualquier cliente de
S p i t t e r S e r v i c e puede ignorar la procedencia del servicio.
R m iP r o x y F a c to r y B e a n es un bean de fábrica que crea un proxy a un servicio RMI.
Utilizar R m iP r o x y F a c to r y B e a n para establecer una referencia a un S p i t t e r S e r v i c e
RMI es tan sencillo como añadir el siguiente método @ Bean al archivo de configuración
de Spring:
@Bean
public RmiProxyFactoryBean S p itte rS e rv ic e () {
RmiProxyFactoryBean rmiProxy = new RmiProxyFactoryBean();
rmiProxy. se tS e rv ic e ü rl("rm i: //lo c a lh o s t/S p itte rS e rv ic e ") ;
436 Capítulo 15

rmiProxy. se tS e rv ic e ln te rfa c e (S p itte rS e rv ic e . c l a s s ) ;


return rmiProxy;

La URL del servicio se configura m ediante la propiedad s e r v i c e ü r l de


R m iP ro x y F a c to ry B e a n . En este caso, al servicio se le asigna el nombre S p i t t e r S e r v i c e
y se aloja en el equipo local. Mientras tanto, la interfaz que el servicio proporciona se
especifica mediante la propiedad s e r v i c e l n t e r f a c e . La interacción entre el cliente y el
proxy RMI se muestra en la figura 15.5:

Figura 15.5. RmiProxyFactoryBean genera un objeto de proxy que se comunica con los servicios
remotos RMI en nombre del cliente. El cliente se comunica con el proxy a través de la interfaz del
servicio como si el servicio remoto fuera un POJO local.

Ahora que ya hemos declarado el servicio RMI como bean gestionado por Spring,
podemos conectarlo como dependencia en otro bean al igual que haríamos con un bean
normal. Por ejemplo, supongamos que el cliente tiene que utilizar el servicio Spitter para
obtener una lista de objetos S p i t t l e de un usuario concreto. Puede utilizar @ A u to w ired
para conectar el proxy del servicio al cliente:
@Autowired
S p itterS erv ice S p itterS erv ice ;
A continuación, puede invocar métodos sobre este como si se tratase de un bean local:
public L ist< S p ittle > g e tS p ittle s(S tr in g userName) {
S p itte r s p itte r = S p itte rS e rv ice . getSpitter(userNam e);
return S p itte rS e rv ice .g e tS p ittle s F o r S p itte r (s p itte r );
}
Lo mejor de acceder a un servicio RMI de esta forma es que el código cliente ni
siquiera sabe que está tratando con un servicio RMI. Solo se le proporciona un objeto
S p i t t e r S e r v i c e mediante inyección, sin que importe su procedencia. De hecho ¿quién
podría afirmar que el cliente recibe una implementación basada en RMI?
Asimismo, el proxy captura cualquier excepción R e m o te E x c e p tio n del servicio y la
vuelve a generar como excepción sin comprobar qué puede ignorar. Esto facilita cambiar
el bean del servicio remoto por otra implementación de éste (quizás un servicio remoto
diferente o una implementación de prueba).
Trabajar con servicios remotos 437

Aunque el código cliente no sepa que S p i t t e r S e r v i c e es un servicio remoto, puede


que quiera prestar atención al diseño de la interfaz del servicio. Tenga en cuenta que el
cliente tiene que hacer dos llamadas al servicio: una para buscar el S p i t t e r por su nombre
de usuario y otra para recuperar la lista de objetos S p i t t l e . Se trata de dos llamadas
remotas que se ven afectadas por la latencia de la red y que van a afectar al rendimiento
del cliente. Si sabemos que es la forma en que se va a utilizar el cliente, puede merecer la
pena acceder de nuevo a la interfaz para consolidar esas dos llamadas en un único método
aunque, por ahora, vamos a aceptar el servicio tal cual.
RMI es una forma excelente de comunicarse con servicios remotos, aunque cuenta con
ciertas limitaciones. En primer lugar, RMI tiene dificultades con los cortafuegos, ya que
utiliza puertos aleatorios para realizar la comunicación, algo que los cortafuegos no suelen
permitir. En un entorno de intranet, no supone ningún problema. Sin embargo, si trabaja a
través de Internet, tendrá problemas con RMI. Aunque admita la tunelización a través de
HTTP (admitida por los cortafuegos), la configuración puede ser complicada.
Otro aspecto que tener en cuenta es que RMI se basa en Java, por lo que tanto el cliente
como el servicio deben estar escritos en este lenguaje, y como RMI utiliza serialización
Java, los tipos de los objetos enviados a través de la red deben contar con exactamente la
misma versión en ambos lados de la llamada. Todo esto puede ser (o no) un problema para
su aplicación. Sea como sea, debe tenerlo en cuenta al elegir RMI para el acceso remoto.
Caucho Technology (la misma empresa que desarrolló el servidor de aplicaciones Resin)
ha desarrollado una solución de acceso remoto que hace frente a las limitaciones de RMI.
De hecho, han creado dos: Hessian y Burlap. Veamos cómo funcionan y cómo usarlas con
servicios remotos en Spring.

Exponer servicios remotos con Hessian y Burlap


Hessian y Burlap son dos soluciones desarrolladas por Caucho Technology que permiten
el uso de servicios remotos ligeros a través de HTTP. Su objetivo es simplificar los servicios
Web al mantener tanto sus API como sus protocolos de comunicación tan sencillos como
sea posible. Puede que se esté preguntando por qué Caucho ha desarrollado dos soluciones
para el mismo problema. Hessian y Burlap son dos caras de una misma moneda, aunque
cada una cuenta con funciones ligeramente diferentes.
Hessian, al igual que RMI, utiliza mensajes binarios para comunicarse entre el cliente y
el servicio aunque, a diferencia de otras tecnologías de acceso remoto binario (como RMI),
el mensaje binario puede llevarse a otros lenguajes que no son Java, como por ejemplo
PHP, Python, C ++ y C#. Burlap es una tecnología de acceso remoto basada en XML, lo que
permite trasladarla a cualquier lenguaje que pueda analizar XML. Al estar escrita en este
lenguaje, su código es mucho más legible que el formato binario de Hessian. A diferencia de
otras tecnologías basadas en XML (como SOAP o XML-RPC), la estructura de mensajes de
Burlap es muy sencilla y no requiere un lenguaje de definición externo (como WSDL o IDL).
¿Cuál de las dos opciones elegir? En la mayor parte de los casos son idénticas. Solo
se diferencian en que los mensajes de Hessian son binarios, mientras que los de Burlap
son código XML. Los mensajes de Hessian son binarios y, por tanto, requieren un menor
438 Capítulo 15

consumo de ancho de banda, mientras que los de Burlap son legibles, lo cual es importante
para la resolución de fallos o para aquellos casos en los que la aplicación se comunique con
un lenguaje para el que no exista una implementación de Hessian.
Para mostrar el funcionamiento de los servicios Hessian y Burlap en Spring, vamos a
utilizar el ejemplo del servicio Spitter de la sección anterior. En esta ocasión abordaremos
la resolución del problema mediante estos dos modelos.

Exponer funcionalidad de bean con Hessian o Burlap


Como antes, supongamos que queremos exponer como servicio la funcionalidad de la
clase S p i t t e r S e r v i c e l m p l , en este caso, un servicio Hessian. Incluso sin Spring, sería
una tarea bastante sencilla. Tendríamos que crear una clase de servicio que ampliase com .
c a u c h o . h e s s i a n . s e r v e r . H e s s i a n S e r v l e t y asegurarnos de que todos los métodos
de servicio son p u b l i c (todos los métodos públicos se consideran métodos de servicio de
Hessian). Como los servicios Hessian son fáciles de implementar, Spring no se preocupa
de simplificar ese modelo. Sin embargo, cuando se utiliza junto con Spring, un servicio
Hessian puede sacar partido al marco de trabajo de formas que un servicio Hessian por sí
solo no puede. Esto incluye el uso de la AOP de Spring para aconsejar un servicio Hessian
con servicios a nivel de sistema, como por ejemplo transacciones declarativas.

Exportar un servicio Hessian


El proceso para exportar un servicio Hessian en Spring es bastante parecido a la
implementación de un servicio RMI en Spring. Para exponer el bean del servicio Spitter
como servicio RMI, teníamos que configurar un bean R m i S e r v i c e E x p o r t e r en el
archivo de configuración de Spring. De forma similar, para exponer el servicio Spitter
como servicio Hessian, vamos a tener que configurar otro bean exportador. En este caso,
H e s s i a n S e r v i c e E x p o r t e r . H e s s i a n S e r v i c e E x p o r t e r va a llevar a cabo la misma
función para un servicio Hessian que R m i S e r v ic e E x p o r t e r para uno RMI: exponer los
métodos públicos de un POJO como métodos de un servicio Hessian. Sin embargo, como
ilustra la figura 15.6, la forma de realizar esta tarea es diferente al método empleado por
R m i S e r v ic e E x p o r t e r .

Figura 15.6. HessianServiceExporter es un controlador de Spring MVC que exporta un POJO


como servicio Hessian recibiendo solicitudes de éste y convirtiéndolas a llamadas al POJO.
Trabajar con servicios remotos 439

H e s s i a n S e r v i c e E x p o r t e r es un controlador de Spring MVC que recibe solicitudes


Hessian y las convierte en invocaciones de método en el POJO exportado. La siguiente
declaración de H e s s i a n S e r v i c e E x p o r t e r en Spring exporta el bean s p i t t e r S e r v i c e
como servicio Hessian:
@Bean
public HessianServiceExporter
hessianE xp orted SpitterService(SpitterService service) {
HessianServiceExporter exporter = new HessianServiceExporter();
exp o rter. s e tS e rv ic e (s e rv ic e );
exp orter. se tS e rv ic e ln te rfa c e (S p itte rS e rv ic e . c l a s s ) ;
return exporter;
}
Al igual que sucede con Rm iS e r v i c e E x p o r t e r , la propiedad s e r v i c e está conec­
tada a una referencia al bean que im plem enta el servicio. En este caso, se trata de una
referencia al bean s p i t t e r S e r v i c e . s e r v i c e l n t e r f a c e se configura para indicar que
S p i t t e r S e r v i c e es la interfaz que el servicio implementa.
Adiferencia de R m iS e r v ic e E x p o r te r , no tenemos que obtener urna propiedad s e r v i c e
Name. Con RMI, la propiedad s e rv ic e N a m e se utiliza para registrar un servicio en el
registro RMI. Sin embargo, Hessian no cuenta con un registro, por lo que no hay necesidad
de obtener un servicio Hessian.

Configurar el controlador Hessian


Otra gran diferencia entre R m i S e r v ic e E x p o r t e r y H e s s i a n S e r v i c e E x p o r t e r es
que, como Hessian se basa en HTTP, H e s s i a n S e r v i c e E x p l o r e r se implementa como
controlador de Spring MVC. Esto quiere decir que, para poder utilizar servicios Hessian
exportados, tendrá que llevar a cabo dos pasos de configuración adicionales:

• Configurar un D i s p a t c h e r S e r v l e t de Spring en w eb . xm l e implementar su apli­


cación como aplicación Web.
• Configurar un gestor de URL en su archivo de configuración de Spring para dirigir
las URL de servicios Hessian al bean de servicio Hessian adecuado.

Ya hemos visto cómo configurar D is p a tc h e r U R L y gestores de URL, por lo que estos


pasos deberían serle familiares. En primer lugar, necesitamos un D i s p a t c h e r S e r v l e t .
Por fortuna, contamos con uno configurado en el archivo w eb. xm l de la aplicación Spittr.
Sin embargo, para gestionar servicios Hessian, ese D i s p a t c h e r S e r v l e t va a necesitar
una asignación de servlet que capture URL * . s e r v i c e :
<s e r v le t-mapping>
<servlet-nam e>spitter</servlet-nam e>
<u rl-p a ttern > *. serv ice</u rl-p attern >
</servlet-mapping>

Si configura D i s p a t c h e r S e r v l e t en Java m ediante la im plem entación de WebA


p p l i c a t i on I n i t i a l i z e r , querrá añadir el patrón de URL como asignación al S e r v l e t
R e g i s t r a t i o n . D ynam ic obtenido al añadir D i s p a t c h e r S e r v l e t al contenedor:
440 Capítulo 15

ServletRegistration.Dynamic dispatcher = co n tain er. addServlet(


"appServlet", new D ispatcherServlet(dispatcherServletC ontext)) ;
d isp atcher. setLoadOnStartup(1);
d isp atch er. addMapping( " / " ) ;
d isp atcher. addMapping (" * . se rv ic e 11) ;

Por el contrario, si configura D i s p a t c h e r S e r v l e t ampliando A b s t r a e t D i s p a t c h e r


S e r v l e t l n i t i a l i z e r o A b s tr a c tA n n o ta tio n C o n fig D is p a tc h e r S e r v le t
I n i t i a l i z e r , tendrá que incluir la asignación al reemplazar g e t S e r v le t M a p p i n g s ( ) :

@Override
protected S trin g !] getServletMappings() {
return new Strin g !] { " * . service" };
}
Con esta configuración, cualquier solicitud cuya URL termine en . s e r v i c e va a
contar con un D i s p a t c h e r S e r v l e t , que se va a encargar de entregar la solicitud al
C o n t r o l l e r asignado a la URL. Por tanto, las solicitudes a / s p i t t e r . s e r v i c e serán
gestionadas al final por el bean h e s s i a n S p i t t e r S e r v i c e (que es simplemente un proxy
de S p i t t e r S e r v i c e l m p l ) .
¿Cómo sabemos que la solicitud va a ir a h e s s i a n S p i t t e r S e r v i c e ? Porque también
vamos a configurar una asignación de URL para que D i s p a t c h e r S e r v l e t la envíe a
h e s s i a n S p i t t e r S e r v i c e . Para ello, utilizaremos S im p le U rlH a n d le rM a p p in g :

@Bean
public HandlerMapping hessianMapping() {
SimpleUrlHandlerMapping mapping = new SimpleUrlHandlerMapping();
Properties mappings = new P ro p ertie s();
mappings. setP roperty(" / s p i t t e r . se r v ic e ",
"hessianExportedSpitterService") ;
mapping.setMappings(mappings);
return mapping;

Una alternativa al protocolo binario Hessian es Burlap, un protocolo basado en XML.


Veamos cómo exportar un servicio de Burlap.

Exportar un servicio cíe Burlap


B u r l a p S e r v i c e E x o r t e r es prácticamente idéntico a H e s s i a n S e r v i c e E x p o r t e r .
La única diferencia es que utiliza un protocolo basado en XML en lugar de uno binario. La
siguiente definición de bean muestra cómo exponer el servicio Spitter como uno de Burlap
utilizando B u r l a p S e r v i c e E x p o r t e r :
@Bean
public BurlapServiceExporter
burlapExportedSpitterService(SpitterService service) {
BurlapServiceExporter exporter = new BurlapServiceExporter( );
exp orter. s e tS e rv ic e (s e rv ic e );
exp orter. se tS e rv ic e ln te rfa c e (S p itte rS e rv ic e . c l a s s ) ;
return exporter;
}
Trabajar con servicios remotos 441

Como puede ver, la única diferencia entre este bean y su equivalente en Hessian es
el método del bean y la clase del exportador. El resto del proceso de configuración del
servicio Burlap es exactamente igual, incluida la necesidad de configurar un gestor URL
y un D i s p a t c h e r S e r v l e t .
Ahora, echemos un vistazo al otro lado de la conversación y utilicemos el servicio que
hemos publicado con Hessian (o Burlap).

Acceder a servicios Hessian/Burlap


Como hemos mencionado con anterioridad, el código cliente que utilizaba el servicio
Spitter mediante R m iP r o x y F a c to r y B e a n no sabía que era un servicio RMI. De hecho,
ni siquiera sabía que se trataba de un servicio remoto. Solo trataba con la interfaz
S p i t t e r S e r v i c e , ya que todos los detalles sobre RMI se incluían en la configuración de
los bean, en el archivo de configuración de Spring.
La buena noticia es que, como el cliente ignora la implementación del servicio, pasar
del cliente RMI a uno Hessian es muy sencillo, ya que no es necesario realizar cambios en
el código Java del cliente.
La mala noticia es que, si le gusta escribir código Java, esta sección le resultará una
decepción. Se debe a que la única diferencia entre conectar el lado del cliente de un
servicio basado en RMI y hacer lo mismo en un servicio basado en Hessian es el uso de
H e s s ia n P r o x y F a c t o r y B e a n en lugar de R m iP r o x y F a c to r y B e a n . Un servicio Spitter
basado en Hessian puede declararse en el código cliente de la siguiente manera:
@Bean
public HessianProxyFactoryBean s p itte rS e rv ic e 0 {
HessianProxyFactoryBean proxy = new HessianProxyFactoryBean();
proxy. setS erv iceU rl("h ttp : //lo c a lh o s t: 8 0 8 0 /S p itte r /s p itte r . S erv ice" ) ;
proxy. se tS e rv ic e ln te rfa c e (S p itte rS e rv ic e . c l a s s ) ;
return proxy;
}
Al igual que en un servicio basado en RMI, la propiedad s e r v i c e l n t e r f a c e especi­
fica la interfaz que implementa el servicio y, como sucede con R m iP r o x y F a c to r y B e a n ,
s e r v i c e U r l indica la URL del servicio. Como Hessian se basa en HTTP, aquí se ha confi­
gurado con una URL HTTP (determinada, en parte, por la asignación de URL definida con
anterioridad). La figura 15.7 muestra la interacción entre un cliente y un proxy producida
por H e s s ia n P r o x y F a c t o r y B e a n .
La conexión de un servicio Burlap al cliente es igual de aburrida. La única diferencia es que
vamos a utilizar B u r la p P r o x y F a c t o r y B e a n en lugar de H e s s ia n P r o x y F a c t o r y B e a n :
@Bean
public BurlapProxyFactoryBean s p itte rS e rv ic e () {
BurlapProxyFactoryBean proxy = new BurlapProxyFactoryBean();
proxy. setS erv iceU rl("h ttp : //lo c a lh o s t: 8 0 8 0 /S p ítte r /s p itte r . Serv ice" );
proxy. se tS e rv ic e ln te rfa c e (S p itte rS e rv ic e . c l a s s ) ;
return proxy;
}
442 Capítulo 15

Figura 15.7. HessianProxyFactoryBean y BurlapProxyFactoryBean generan objetos de proxy que


se comunican con un servicio remoto a través de HTTP (Hessian en binario y Burlap en XML).

Aunque he dejado claro lo poco interesantes que son las diferencias de configuración
entre RMI, Hessian y Burlap, este aburrimiento es una ventaja, ya que le demuestra que
puede cambiar entre las distintas tecnologías de acceso remoto compatibles con Spring
sin tener que aprender a utilizar un nuevo modelo completamente diferente. Una vez ha
configurado una referencia a un servicio RMI, es fácil volver a configurarla como servicio
Hessian o Burlap.
Puesto que tanto Hessian como Burlap están configurados en HTTP, no sufren los
mismos problemas con los cortafuegos que padece RMI. Sin embargo, RMI supera a Hessian
y Burlap en lo relativo a la serialización de objetos enviados en mensajes RPC. Mientras
que Hessian y Burlap utilizan un mecanismo de serialización con tecnología de propiedad
exclusiva, RMI utiliza el de Java. Si su modelo de datos es complejo, puede que el modelo
de serialización de Hessian y Burlap no sea suficiente.
No se preocupe, porque hay una solución óptima para ambos casos. Veamos el invocador
HTTP de Spring, que ofrece RPC a través de HTTP (como Hessian y Burlap), al tiempo que
utiliza serialización de objetos de Java (como RMI).

Utilizar Httplnvoker de Spring


El equipo de desarrollo de Spring detectó que faltaba algo entre los servicios RMI y
los basados en HTTP como Hessian y Burlap. Por un lado, RMI utiliza la serialización de
objetos estándar de Java, que puede ser difícil de utilizar cuando hay cortafuegos. Por otro,
Hessian y Burlap trabajan sin problemas en presencia de cortafuegos, aunque utilizan un
mecanismo de serialización de objetos de propiedad exclusiva.
Por ese motivo se creó el invocador HTTP de Spring. Es un nuevo modelo de acceso
remoto creado como parte del marco de trabajo Spring para llevar a cabo accesos remotos a
través de HTTP (para evitar los problemas con los cortafuegos) utilizando serialización de
Java (para hacer felices a los programadores). Trabajar con servicios basados en un invocador
Trabajar con servicios remotos 443

HTTP es parecido a hacerlo con los basados en Hessian o Burlap. Para familiarizarnos con
el invocador HTTP, veamos de nuevo el servicio Spitter, en esta ocasión implementado
como servicio invocador de HTTP.

Exponer bean com o servicios HTTP


Para exportar un bean como servicio RMI, hemos utilizado R m i S e r v ic e E x p o r t e r .
Para exportarlo como servicio Hessian, hemos utilizado H e s s i a n S e r v i c e E x p o r t e r y,
para exportarlo como servicio Burlap, B u r l a p S e r v i c e E x p o r t e r . Para continuar con
esta monotonía, no debería sorprenderle que, para exportarlo como servicio del invocador
HTTP vayamos a utilizar H t t p I n v o k e r S e r v i c e E x p o r t e r .
Para exportar el servicio Spitter como servicio basado en el invocador HTTP, tenemos
que configurarlo utilizando un bean H t t p I n v o k e r S e r v i c e E x p o r t e r como este:
@Bean
public HttpInvokerServiceExporter
h ttpE xportedSpitterService(SpitterService service) {
HttpInvokerServiceExporter exporter =
new HttpInvokerServiceExporter( );
exp orter. s e tS e rv ic e (s e rv ic e );
exporter. se tS e rv ic e ln te rfa c e (S p itte rS e rv ic e . c l a s s ) ;
return exporter;
}

¿Le suena este fragmento de código? Lo cierto es que va a resultarle difícil encontrar las
diferencias entre esta declaración de bean y las que hemos visto en apartados anteriores.
La única diferencia es el nombre de la clase: H t t p In v o k e r S e r v ic e E x p o r te r . El resto
es idéntico a los otros exportadores de servicios remotos.
Como ilustra la figura 15.8, H t t p I n v o k e r S e r v i c e E x p o r t e r funciona de forma muy
similar a H e s s ia n S e r v i c e E x p o r t e r y B u r la p S e r v ic e E x p o r t e r . Se trata de un contro­
lador de Spring MVC que recibe solicitudes de un cliente a través de D i s p a t c h e r S e r v l e t
y las convierte en invocaciones de métodos sobre el POJO de implementación del servicio.

Figura 15.8. El funcionamiento de HttpInvokerServiceExporter es muy similar


al de sus equivalentes en Hessian y Burlap. Recibe solicitudes de un elemento DispatcherServlet
de Spring MVC y las convierte en invocaciones de métodos sobre un bean gestionado por Spring.
444 Capitulo 15

Como H t t p I n v o k e r S e r v i c e E x p o r t e r es un controlador de Spring MVC, tendrá


que configurar un gestor URL para asignar una URL HTTP al servicio, al igual que en los
exportadores de Hessian y de Burlap:
@Bean
public HandlerMapping h ttp InvokerMapping() {
SimpleUrlHandlerMapping mapping = new SimpleUrlHandlerMapping();
Properties mappings = new Properties ();
mappings. setP roperty(" / s p it t e r .s e r v ic e " ,
"httpExportedSpitterService") ;
mapping. setMappings(mappings);
return mapping;

Como antes, tendrá que asegurarse de asignar D i s p a t c h e r S e r v l e t para que procese


solicitudes con la extensión . S e r v i c e , como vimos en un apartado anterior.
Ya hemos visto cómo consumir servicios remotos a través de RMI, Hessian y Burlap.
Ahora, vamos a modificar el cliente Spitter para utilizar el servicio que acabamos de exponer
con el invocador HTTR

Acceder a servicios mediante HTTP


Aunque pueda parecer un disco rayado, tengo que decir que el uso de un servicio basado
en el invocador HTTP es muy similar a lo que ya hemos visto con los proxy de servicios
remotos. Es prácticamente idéntico. Como puede ver en la figura 15.9, H t t p l n v o k e r
P r o x y F a c t o r y B e a n cumple la misma función que los otros bean de fábrica de proxy de
servicio remoto que ya hemos visto en este capítulo.

Figura 15.9. HttpInvokerProxyFactoryBean es un bean de fábrica de proxy que genera


uno para el acceso remoto con un protocolo basado en HTTP y específico para Spring.

Para conectar el servicio basado en el invocador HTTP al contexto de la aplicación de


Spring de nuestro cliente, tenemos que configurar un bean como proxy utilizandoH ttp-
In v o k e r P r o x y F a c to ry B e a n de la siguiente manera:
Trabajar con servicios remotos 445

@Bean
public HttpInvokerProxyFactoryBean s p itte rS e rv ic e () {
HttpInvokerProxyFactoryBean proxy = new HttpInvokerProxyFactoryBean( );
p ro x y .setS erv iceü rl( "h ttp : //lo c a lh o s t: 8 0 8 0 /S p itte r /s p itte r . Serv ice" ) ;
p ro x y .setS e rv iceln terfa ce(S p itte rS e rv ice. c l a s s ) ;
return proxy;
}
Si compara esta definición de bean con la de apartados anteriores, podrá ver que han
variado muy pocos elementos. La propiedad s e r v i c e l n t e r f a c e sigue utilizándose para
indicar la interfaz implementada por el servicio Spitter. Asimismo, la propiedad S e r v i c e
U r l sigue utilizándose para indicar la ubicación del servicio remoto. Como el invocador
HTTP se basa en HTTP, al igual que Hessian y Burlap, s e r v i c e ü r l puede incluir la misma
URL que las versiones de Hessian y Burlap. ¿No le apasiona esta simetría?
El invocador HTTP de Spring es la solución de acceso remoto óptima, al combinar la
simplicidad de la comunicación HTTP con la serialización incluida de serie de Java. Esto
hace que los servicios del invocador HTTP sean una alternativa muy interesante tanto a
RMI como a Hessian/Burlap.
La única limitación significativa de H t t p ln v o k e r es ser una solución de acceso remoto
que solo ofrece el marco de trabajo Spring. Esto quiere decir que tanto el cliente como el
servicio deben ser aplicaciones de Spring. Asimismo, y por el momento, tanto el cliente
como el servicio tienen que estar basados en Java, y como utilizamos serialización de Java,
ambos lados deben contar con la misma versión de las clases (como en RMI).
RMI, Hessian, Burlap y el invocador HTTP son grandes opciones de acceso remoto. Sin
embargo, si lo que necesita es acceso remoto en cualquier ubicación, nada puede superar
a los servicios Web.

Publicar y consumir servidos Web ____


Uno de los acrónimos más utilizados en los últimos años es SOA (S erv ice O riented
A rchitecture, Arquitectura orientada a servicios). SOA significa diferentes cosas para muchas
personas pero en el fondo de este concepto se encuentra la idea de que las aplicaciones
pueden y deben diseñarse para basarse en un conjunto común de servicios básicos, en lugar
de implementar la misma funcionalidad para cada aplicación.
Por ejemplo, una institución financiera puede contar con varias aplicaciones, y algunas
de ellas tendrán que acceder a información sobre cuentas. En lugar de crear una lógica de
acceso a cuentas en cada aplicación (lo que supondría la duplicación de elementos), todas
las aplicaciones podrían basarse en un servicio común para recuperar la información de
las cuentas.
Los servicios Web y Java tienen un largo historial de colaboración y existen varias
opciones para trabajar con servicios Web en Java. Muchas de estas opciones se integran con
Spring de alguna forma. Aunque sería imposible hablar en este libro de todos los marcos
de trabajo y conjuntos de herramientas disponibles para Spring, Spring cuenta con cierta
compatibilidad para la publicación y uso de servicios Web SOAP utilizando el API de Java
para servicios Web XML (JAX-WS).
446 Capítulo 15

En este apartado volveremos al ejemplo del servicio Spitter. En esta ocasión, vamos
a exponer y utilizar Spitter como servicio Web utilizando la compatibilidad JAX-WS
de Spring. Primero veremos qué necesitamos para crear un servicio Web JAX-WS
en Spring.

Crear pontos finales JAX-WS habilitados para Spring


Con anterioridad, en este capítulo, hemos creado servicios remotos utilizando expor­
tadores de servicios de Spring, que convertían, como por arte de magia, POJO confi­
gurados con Spring en servicios remotos. Hemos visto cómo crear servicios RMI con
R m i S e r v i c e E x p o r t e r , servicios Hessian con H e s s i a n S e r v i c e E x p o r t e r , servi­
cios Burlap con B u r l a p S e r v i c e E x p o r t e r y servicios del invocador HTTP con
H t t p I n v o k e r S e r v ic e E x p o r t e r . Ahora, probablemente estará esperando que le muestre
cómo crear servicios Web utilizando un exportador de servicios JAX-WS.
Spring incluye un exportador de servicios JAX-WS, S im p le J a x W s S e r v ic e E x p o r t e r ,
cuyo uso veremos en breve. Sin embargo, antes de pasar a ver su funcionamiento,
debe saber que puede que no sea la m ejor opción para todas las situaciones.
S im p le J a x W s S e r v ic e E x p o r t e r requiere que el tiempo de ejecución de JAX-WS admita
la publicación de puntos finales en una dirección especificada. El tiempo de ejecución de
JAX-WS, incluido en el JD K 1.6 de Sun, cumple estos requisitos pero otras implementaciones
de JAX-WS, incluyendo la de referencia, pueden no hacerlo.
Si va a implementar en un tiempo de ejecución JAX-WS que no admita la publicación
de una dirección especificada, tendrá que crear sus puntos finales JAX-WS de forma más
convencional. Esto significa que el ciclo de vida de los puntos finales va a ser gestionado
por el tiempo de ejecución JAX-WS y no por Spring, lo que no quiere decir que no puedan
conectarse a bean de un contexto de aplicación de Spring.

Conexión automática de puntos finales JAX-WS en Spring


El modelo de programación de JAX-WS implica el uso de anotaciones para declarar
una clase y sus métodos como operaciones del servicio Web. Una clase anotada con
@ W e b S e r v ic e se considera un punto final de servicio Web. Asimismo, sus métodos,
anotados con @WebMethod, son las operaciones.
Al igual que cualquier otro objeto de una aplicación ajustable, un punto final JAX-WS
probablemente va a depender de otros objetos para llevar a cabo su trabajo. Esto quiere
decir que los puntos finales JAX-WS pueden beneficiarse de la inyección de dependencias.
Sin embargo, el ciclo de vida de los puntos finales es gestionado por el tiempo de ejecución
de JAX-WS, no por Spring, lo que puede hacer que parezca imposible conectar los bean
gestionados por Spring a una instancia de punto final gestionada por JAX-WS.
El secreto p ara co n ectar pu n tos fin a les de JAX-W S es am p liar S p r i n g B e a n
A u to w ir in g S u p p o r t. Al hacerlo, puede anotar las propiedades de un punto final con
@ A u tow ired . De esta form a, se cum plirán sus dependencias. En el listado 15.2 podemos
ver un ejem plo de este funcionamiento.
Trabajar con servicios remotos 447

Listado 15.2. SpringBeanAutowiringSupport en puntos finales JAX-WS.

package com.habuma.spitter.remoting.jaxws;
import java.útil.List;
import javax.jw s .WebMethod;
import javax.jws.WebService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.context.support.SpringBeanAutowiringSupport;
import com.habuma.spitter.domain.Spitter;
import com.habuma.spitter.domain.Spittle;
import com.habuma.spitter.Service.SpitterService;
@WebService(serviceName="SpitterServiceu)
public classSpitterServiceEndpoint
extendsSpringBeanAutowiringSupport{ // Activar conexión automática.
@Autowired
SpitterService spitterService? // Conectar SpitterService de forma automática
@WebMethod
public void addSpittle(Spittle spittle){
spitterService.saveSpittle(spittle); // Delegar en SpitterService.
}
@WebMethod
public void deleteSpittle(long spittleld){
spitterService.deleteSpittle(spittleld); // Delegar en SpitterService.
}
@WebMethod
public List<Spittle>getRecentSpittles(int spittleCount){
return spitterService.getRecentSpittles (spittleCount) ; // Delegar en SpitterService.
}
@WebMethod
public List<Spittle>getSpittlesForSpitter(Spitter spitter){
return spitterService.getSpittlesForSpitter(spitter); // Delegar en SpitterService.
}
}
Hemos anotado la propiedad s p i t t e r S e r v i c e con @ A u to w ired para indicar que
debe inyectársele de forma automática un bean del contexto de aplicación de Spring.
Desde ahí, ese punto final delega en el S p i t t e r S e r v i c e inyectado para que lleve a cabo
el trabajo en cuestión.

Exportar puntos finales JAX-WS independientes


Como hemos visto, S p r in g B e a n A u to w ir in g S u p p o r t es útil cuando el objeto cuyas
propiedades se están inyectando no cuenta con un ciclo de vida gestionado por Spring.
Sin embargo, en las circunstancias adecuadas, es posible exportar un bean gestionado por
Spring como punto final JAX-WS.
S i m p le J a x W s S e r v i c e E x p o r t e r de Spring funciona de forma similar a los otros
exportadores de servicios que hemos visto en este capítulo, ya que publica bean gestionados
por Spring como puntos finales de servicio en un tiempo de ejecución de JAX-WS. A dife­
rencia de esos exportadores de servicios, S i m p le J a x W s S e r v i c e E x p o r t e r no necesita
una referencia al bean que va a exportar. En su lugar, publica todos los bean que cuenten
con anotaciones JAX-WS como servicios de este tipo.
448 Capítulo 15

Puede configurar S i m p le J a x W s S e r v i c e E x p o r t e r con el siguiente método @Bean:


@Bean
public SimpleJaxWsServiceExporter jaxWsExporter() {
return new SimpleJaxWsServiceExporter( );
}

Como puede ver, S i m p le J a x W s S e r v i c e E x p o r t e r no necesita nada más para llevar


a cabo su trabajo. Cuando se inicia, busca en el contexto de aplicación de Spring los
bean anotados con @ W e b S e rv ic e . Cuando encuentra uno, lo publica como punto final
JAX-WS con la dirección base h t t p : / / l o c a l h o s t ¡ 8 0 8 0 / . Un bean de este tipo sería
S p i t t e r S e r v i c e E n d p o i n t , como se muestra en el listado 15.3.

Listado 15.3. SimpleJaxWsServiceExporter convierte bean en puntos finales de JAX-WS.

package com. habuma. s p i t t e r . remoting. j axws;


import ja v a .ú t i l .L i s t ;
import j avax. j ws. WebMethod;
import jav ax .jw s. WebService;
import org.springframework.beans. fa c to ry .annotation.Autowired;
import org. springframework. stereotyp e. Component;
import com.habuma.spitter.domain.Spitter;
import com.habuma. s p i t t e r . domain. S p i t t le ;
import com.habuma.spitter. S e rv ice. S p itterS erv ice ;
@Component
@WebService(serviceN am e="SpitterService")
public cla ss SpitterServiceEndpoint {
@Autowired
S p itterS erv ice S p itterS erv ice ; / / Conexión automática de S p itterS erv ice.
@WebMethod
public void a d d S p ittle (S p ittle s p ittle ) {
S p itte rS e rv ice . s a v e S p it tle ( s p ittle ) ; / / Delegar en Sp itterS erv ice
i
@WebMethod
public void d eleteS p ittle (lo n g s p ittle ld ) {
S p itte r S e r v ic e .d e le te S p ittle (s p ittle ld ); / / Delegar en S p itterS erv ice .
}
@WebMethod
public L ist< S p ittle > g etR ecen tS p ittles(in t spittleCount) {
return Sp itterS erv ice . getR ecentSp ittles (spittleCount) ; / / Delegar en SpitterService.
}
@WebMethod
public L ist< S p ittle > g e tS p ittle sF o rS p itte r(S p itte r s p itte r) {
return S p itte rS e rv ice . g e tS p ittle s F o r S p itte r (s p itte r ); / / Delegar en S p itterS erv ice .
}
)
Se habrá dado cuenta de que esta nueva implementación de S p it t e r S e r v i c e E n d p o in t
ya no amplía S p r in g B e a n A u t o w ir in g S u p p o r t . Al tratarse de un bean de Spring
completo, puede conectarse de forma automática sin tener que ampliar ninguna clase
de apoyo especial. Como la dirección base de S im p le J a x W s S e r v ic e E n d p o in t es, por
defecto, h t t p : / / l o c a l h o s t : 8 0 8 0 / , y S p i t t e r S e r v i c e E n d p o i n t está anotado con
@ W e b S e rv ic e ( s e r v i c e N a m e = " S p i t t e r S e r v i c e " ), la coincidencia entre estos dos
Trabajar con servicios remotos 449

bean va a generar un servicio Web en h t t p : / / l o c a l h o s t : 8 0 8 0 / S p i t t e r S e r v í c e .


Sin embargo, usted tiene el control completo de la URL del servicio, por lo que, si lo
prefiere, puede configurarla con otro valor. Por ejemplo, la siguiente configuración de
S im p le J a x W s S e r v ic e E n d p o in t publica el mismo punto final de servicio en h t t p : / /
lo c a lh o s t : 8 8 8 8 /s e r v ic e s /S p itte r S e r v ic e .
@Bean
public SimpleJaxWsServiceExporter jaxWsExporter() {
SimpleJaxWsServiceExporter exporter =
new SimpleJaxWsServiceExporter();
exporter.setBaseAddress("h ttp : / /lo c a lh o s t: 8 8 8 8 /s erv ices /" ) ;
}
Aunque S im p le J a x W s S e r v ic e E n d p o in t parece muy sencillo, debe tener en cuenta
que solo funciona con un tiempo de ejecución JAX-WS que admita la publicación de puntos
finales con una dirección, como el tiempo de ejecución que incluye el JDK 1.6 de Sun.
Otros tiempos de ejecución JAX-WS, como la implementación de referencia JAX-WS 2.1,
no admiten este tipo de publicación de puntos finales y, por tanto, no pueden utilizarse
con S im p le J a x W s S e r v ic e E n d p o in t .

Proxy de servicios JAX-WS en el lado cliente


Hemos podido ver que la publicación de servicios Web con Spring era bastante diferente
a la forma en que se hacía en RMI, Hessian, Burlap o con el invocador HTTR Sin embargo,
como vamos a ver en breve, el uso de servicios Web con Spring implica el uso de proxy en
el lado diente de forma similar a como los dientes basados en Spring utilizan otras tecno­
logías de acceso remoto. Mediante J a x W s P o r t P r o x y F a c t o r y B e a n , podemos conectar el
servicio Web de Spitter en Spring como si fuera un bean. J a x W s P o r t P r o x y F a c t o r y B e a n
es un bean de fábrica de Spring que genera un proxy que sabe cómo comunicarse con un
servicio Web SOAR El propio proxy se crea para implementar la interfaz del servicio (véase
la figura 15.10). Por tanto, J a x W s P o r t P r o x y F a c t o r y B e a n hace posible la conexión y el
uso de un servicio Web remoto como si fuera cualquier otro POJO local.

Figura 15.10. JaxWsPortProxyFactoryBean genera proxy que se comunican con servicios Web
remotos. Pueden conectarse a otros bean como si fueran POJO locales.
450 Capítulo 15

Vamos a configurar J a x W s P o r t P r o x y F a c t o r y B e a n para que haga referencia al


servicio Web Spitter:
@Bean
public JaxWsPortProxyFactoryBean s p itte rS e rv ic e () {
JaxWsPortProxyFactoryBean proxy = new JaxWsPortProxyFactoryBean();
proxy. setWsdlDocument(
"h ttp : //lo c a lh o s t:8080/services/Sp itterServ ice?w sd l" ) ;
proxy. setServiceName("s p itte rS e rv ic e ") ;
proxy. setPortName{"sp itterServiceH ttp P ort" ) ;
p ro x y .se tS e rv ice ln te rfa ce (S p itte rS e rv ice . c l a s s ) ;
proxy. setNamespaceUri("h ttp :/ / sp itter.co m " ) ;
return proxy;
}
Como puede ver, es necesario configurar varias propiedades para que Ja x W s
P o r t P r o x y F a c t o r y B e a n funcione. La propiedad w sd lD ocu m en tU rl identifica la ubica­
ción del archivo de definición del servicio Web remoto. Ja x W s P o r tP r o x y F a c to r y B e a n va
a utilizar el WSDL disponible en esa URL para crear un proxy para el servicio. El proxy gene­
rado por Ja x W s P o r tP r o x y F a c to r y B e a n va a implementar la interfaz S p i t t e r S e r v i c e ,
tal y como se especifica en la propiedad S e r v i c e l n t e r f a c e . Los valores de las tres
propiedades restantes suelen determinarse examinando el WSDL del servicio. A modo de
ejemplo, supongamos que el WSDL del servicio Spitter fuera el siguiente:
<wsdl: d efin itio n s targetNamespace="h ttp : / / s p i t t e r . com" >

<wsdl: Service name="s p itte rS e rv ic e ">


<wsdl:port name=" sp itterServiceH ttpP ort"
binding="tn s : spitterServiceH ttpBinding">

</wsdl:port>
</wsdl: service>
</wsdl: d efin itio n s>

Aunque no es habitual, es posible definir varios servicios o puertos en el WSDL del


servicio. Por ese motivo, J a x W s P o r t P r o x y F a c t o r y B e a n requiere que especifiquemos
los nombres del puerto y del servicio en las propiedades portN am e y s e rv ic e N a m e ,
respectivamente. Un rápido vistazo a los atributos ñame de los elementos < w s d l: p o r t >
y < w s d l: s e r v i c e > del WSDL le ayudará a saber cómo configurar estas propiedades.
Por último, la propiedad n a m e s p a c e ü r i especifica el espacio de nombres del servicio.
Entre otras cosas, el espacio de nombres ayuda a J a x W s P o r t P r o x y F a c t o r y B e a n a
encontrar la definición del servicio en el WSDL. Respecto a los nombres del puerto y del
servicio, puede encontrar el valor correcto para esta propiedad examinando el WSDL. Suele
estar disponible en el atributo ta r g e tN a m e s p a c e del elemento < w s d l: d e f i n i t i o n s >.

Resumen
Trabajar con servicios remotos suele ser una tarea aburrida. Para evitarlo, Spring hace
que resulte tan sencillo como trabajar con cualquier otro bean de Java.
Trabajar con servicios remotos 451

En el lado del cliente, Spring cuenta con bean de fábrica de proxy que le permiten confi­
gurar servicios remotos en su aplicación de Spring. Con independencia de que utilice RMI,
Hessian, Burlap, el propio invocador de Spring o servicios Web para el acceso remoto, podrá
conectar servicios remotos a su aplicación como si fueran POJO. Spring incluso captura
cualquier excepción R e m o te E x c e p tio n generada y en su lugar vuelve a generar excep­
ciones de tiempo de ejecución R e m o te A c c e s s E x c e p t io n s , lo que evita que su código
tenga que gestionar excepciones de las que, probablemente, no va a poder recuperarse.
Aunque Spring oculta muchos de los detalles de los servicios remotos, haciendo que
aparezcan como si fueran bean de Java locales, debe tener en cuenta sus consecuencias.
Por su naturaleza, suelen ser menos eficientes que los locales. Debe tenerlo en cuenta a la
hora de crear código que acceda a los servicios remotos, limitando las invocaciones remotas
para evitar cuellos de botella en el rendimiento.
En este capítulo, ha visto cómo utilizar Spring para exponer y consumir servicios basados
en distintas tecnologías básicas de acceso remoto. Aunque estas opciones de acceso remoto
son útiles en la distribución de aplicaciones, solo ha sido una introducción a lo que supone
trabajar con una arquitectura orientada a servicios (SOA).
También hemos visto cómo exportar bean como servicios Web basados en SOAP. Aunque
sea una forma sencilla de desarrollar servicios Web, puede que no sea la mejor opción
desde el punto de vista de la arquitectura utilizada. En el siguiente capítulo, presentaremos
diferentes enfoques para crear aplicaciones distribuidas exponiendo fragmentos de una
aplicación como recursos REST.
Capítulo

16 Crear A P I R E S T
con S p rin g M V C

CONCEPTOS FUNDAMENTALES:

• Creación de controladores para la entrega de recursos


REST.
• Representación de recursos en XML, JSON y otros
formatos.
• Consumo de recursos REST.
Es indudable que los datos son el elemento más importante de una aplicación. Como
desarrolladores, a menudo nos centramos en crear grandes programas para solucionar
problemas de negocio. Los datos son solo los materiales con los que los procesos de software
llevan a cabo su función. Sin embargo, si pregunta a los usuarios qué es lo más valioso
para ellos, si los datos o el software, lo más probable es que respondan que prefieren los
primeros. Los datos son la savia de muchos negocios y mientras que el software puede
sustituirse, los datos recopilados a lo largo de varios años nunca pueden sustituirse.
Si los datos son tan importantes, ¿no cree que la forma en que desarrollamos el software
los trata como algo secundario? Piense por ejemplo en los servicios remotos del capítulo
anterior. Estos servicios estaban centrados en acciones y procesos, no en información y
recursos.
En los últimos años, la Transferencia de estados representacionales (también conocida
por sus siglas en inglés, REST) se ha convertido en una alternativa muy popular centrada
en la información y opuesta a los servicios Web basados en SOAP. Mientras que SOAP suele
centrarse en acciones y tareas de procesamiento, REST se preocupa de los datos procesados.
Desde Spring 3.0, Spring incluye compatibilidad para crear API REST, y la implemen-
tación REST de Spring ha seguido evolucionando en Spring 3.1,3.2 y ahora 4.0.
La buena noticia es que la compatibilidad de Spring con REST se basa en Spring MVC,
por lo que ya hemos visto gran parte de lo que necesita para poder trabajar con REST en
Spring. En este capítulo, vamos a basarnos en lo que ya sabemos sobre Spring MVC para
desarrollar controladores que gestionen solicitudes de recursos REST, pero antes de avanzar,
veamos en qué consiste REST.

Acerca de REST
Estoy seguro de que no es la primera vez que oye hablar de REST. En los últimos años se
ha hablado mucho sobre REST. De hecho, se ha puesto de moda en los círculos de desarrollo
de software hablar mal sobre los servicios Web basados en SOAP y promover REST como
alternativa. Sin duda, SOAP puede resultar demasiado pesado para muchas aplicaciones
y REST ofrece una alternativa más sencilla. Muchas aplicaciones modernas cuentan con
clientes móviles y JavaScript que consumen API REST ejecutadas en un servidor.
Sin embargo, no todo el mundo conoce a fondo este modelo. En consecuencia, tenemos
muchos usuarios desinformados. Antes de hablar sobre la compatibilidad de Spring con
este modelo, tenemos que entender realmente en qué consiste REST.

Aspectos básicos de REST


Un error habitual a la hora de hablar sobre REST es considerarlo como ''servicios Web
con URL". Es decir, como otro mecanismo RPC como SOAP, aunque mediante el uso de
URL HTTP y omitiendo los espacios de nombres XML.
En realidad, REST tiene muy poco en común con RPC. Mientras que RPC está orientado
a servicios y se centra en acciones y verbos, REST se orienta hacia recursos, enfatizando los
elementos y nombres que describen una aplicación.
454 Capítulo 16

Para comprender en qué consiste REST en realidad, podemos analizar cada uno
de los elementos que forman parte del concepto "Transferencia de estado representa-
cional".

• Transferencia: REST implica la transferencia de datos de recursos, en forma de repre­


sentación, de una aplicación a otra.
• Estado: Cuando trabajamos con REST, nos preocupa más el estado de un recurso que
las acciones que podemos realizar con ellos.
• Representacional: Los recursos REST pueden representarse prácticamente en cual­
quier formato, como XML, JSON (JavaScript O bject N otation, Notación de objetos de
JavaScript) o incluso HTML.

En resumen, podemos decir que REST consiste en transferir el estado de los recursos
en el formato que consideremos más adecuado, de un servidor a un cliente y viceversa.
En REST, los recursos se identifican y localizan mediante URL. No hay reglas estrictas
sobre la estructura de las URL de REST, pero deben identificar un recurso, no devolver un
comando al servidor. Como ya hemos mencionado, el proceso se centra en las cosas, no
en las acciones.
Dicho esto, en REST hay acciones y se definen mediante métodos HTTP. En concreto,
GET, POST, PUT, DELETE, PATCH y otros métodos HTTP que constituyen los verbos de
REST. Estos métodos HTTP suelen asignarse a verbos CRUD como se muestra a conti­
nuación:

• Crear: POST
• Leer: GET
• Actualizar: PUT o PATCH
• Eliminar: DELETE
Aunque sea la asignación habitual de métodos HTTP a verbos CRUD, no es un requisito
estricto. Hay casos en los que se puede usar PUT para crear un nuevo recurso o POST para
actualizarlo. De hecho, la naturaleza no idempotente de POST le permite realizar opera­
ciones que no encajan con la semántica de los demás métodos HTTP.
Teniendo en cuenta esta visión de REST, prefiero evitar términos como "servicio REST",
"servicio Web REST" o cualquier denominación similar que, de forma incorrecta, dé más
importancia a las acciones. En su lugar, prefiero destacar la naturaleza orientada a los
recursos de REST y hablar de recursos REST.

Compatibilidad de Spring con REST


Durante mucho tiempo Spring ha contado con todos los ingredientes necesarios para
exponer recursos REST. Sin embargo, desde Spring 3.0 introdujo varias mejoras en Spring
MVC, para ofrecer una compatibilidad de primera clase con REST. Ahora, en la versión
4.0, Spring admite la creación de recursos REST de las siguientes formas:
Crear API REST con Spring MVC 455

• Los controladores pueden gestionar solicitudes de todos los métodos HTTP, inclu­
yendo los cuatro métodos REST principales: GET, PUT, DELETE y POST. Spring 3.2
y superior también admite el método PATCH.
• La anotación @ P a t h V a r i a b l e permite que los controladores puedan gestionar
solicitudes de URL con parámetros (las que incluyen entradas variables como parte
de su ruta).
• Los recursos se pueden representar de diferentes maneras con vistas de Spring y
solucionadores de vistas, incluyendo implementaciones V i ew para representar datos
de modelo como XML, JSON, Atom y RSS.
• Se puede seleccionar la representación más adecuada para el cliente utilizando
C o n te n t N e g o t ia t in g V ie w R e s o lv e r .
• La rep resentación basada en vistas se puede ignorar u tilizando la anotación
@ R esp o n seB o d y y varias implementaciones de H ttp M e th o d C o n v e rte r.
• De forma similar, la anotación @ R eq u estB o d y , junto con las implementaciones de
H ttp M e th o d C o n v e rte r, puede convertir datos de entrada HTTP en objetos de Java
proporcionados a los m étodos de un controlador.
• Las aplicaciones de Spring pueden consumir recursos REST por medio R estT em p la te.

A lo largo de este capítulo vamos a explorar todas estas características, que permiten
sacar un mayor partido a REST mediante Spring, empezando por la creación de recursos
REST con Spring MVC. En un apartado posterior cambiaremos al lado cliente y aprende­
remos a consumir estos recursos. Empezaremos con el análisis de un controlador REST de
Spring MVC.

Crear el primer punto final REST_____ _________


Una de las principales ventajas de la compatibilidad de Spring con REST es que ya
sabemos lo necesario para crear controladores REST. En capítulos anteriores aprendimos
a crear aplicaciones Web que ahora podemos usar para exponer recursos en un API
REST. Para empezar crearemos el primer punto final REST en un nuevo controlador:
S p i t t le A p i C o n t r o l le r . El siguiente código muestra la estructura de un nuevo contro­
lador REST que sirve recursos S p i t t l e . Es un inicio humilde pero lo desarrollaremos a lo
largo del capítulo con detalles del modelo de programación REST de Spring.

Listado 16.1. Controlador REST de Spring MVC.

package s p itt r .a p i;

import ja v a .ú t i l .L i s t ;
import org. springframework.beans. factory.annotation.Autow ired;
import o rg . springframework. stereotype. C ontroller;
import org. springframework.web.bind. annotation.RequestMapping;
456 Capítulo 16

import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import s p i t t r . S p i t t le ;
import s p i t t r . d ata. Sp ittleR ep ository;

©Controller
@RequestMapping{" / s p i t t l e s " )
public cla ss S p ittle C o n tro lle r {

private s t a t ic f in a l Strin g MAX_LONG_AS_STRING="9223372036854775807";

private SpittleR epository Sp ittleR epository;

@Autowired
public S p ittleC o n tro ller(S p ittleR ep o sito ry SpittleRepository) {
t h i s . SpittleR epository = Sp ittleR ep ository;
}
@RequestMapping(method=RequestMethod. GET)
public L is t< S p ittle > s p i t t l e s (
@RequestParam(value="max",
defaultValue=MAX_LONG_AS_STRING) long max,
@RequestParam(value="count", defaultValue="20") in t count) {

return S p ittleR ep o sitory . findSpittles(m ax, count);


}
}

Fíjese atentamente en este código. ¿Puede ver cómo sirve un recurso REST en lugar de
una página Web? Seguramente no. No hay nada en el controlador que lo convierta en contro­
lador REST para servir recursos. De hecho, puede que reconozca el método s p i t t l e s ()
que ya vimos en un capítulo anterior.
Recordará que cuando se recibe una solicitud GET de / s p i t t l e s , se invoca el método
s p i t t l e s ( ) . Busca y devuelve una lista de objetos S p i t t l e recuperada del repositorio
S p i t t l e R e p o s i t o r y inyectado, lista que se añade al modelo para representar una vista.
En una aplicación Web basada en el navegador, seguramente signifique que los datos del
modelo se representen en una página HTML. Pero estamos hablando de crear un API REST.
En ese caso, HTML no sería la representación adecuada de los datos.
La representación es una faceta importante de REST. Indica cómo se comunica un recurso
entre un cliente y un servidor. Cualquier recurso se puede representar prácticamente de
cualquier forma. Si el consumidor del recurso prefiere JSON, el recurso se puede presentar
en formato JSON. O si el consumidor tiene preferencia por las comillas angulosas, el
mismo recurso se puede representar en XML. Mientras tanto, un usuario humano que vea
el recurso en un navegador Web seguramente prefiera verlo en HTML (o en PDF, Excel o
cualquier otro formato legible para los humanos). El recurso no cambia, lo hace la forma
de representarlo.
Evidentemente, si va a presentar contenido para el consumo de usuarios humanos,
tendrá que admitir recursos con formato HTML. En función de la naturaleza del recurso y
de los requisitos de la aplicación, podría presentar el recurso como documento PDF o una
hoja de cálculo de Excel.
Crear API REST con Spring MVC 457

Nota
Aunque Spring admite diversos formatos para representar recursos, no está
obligado a usarlos todos en la definición de sus API REST. Suele bastar con JSON
y XML para la mayoría de los clientes.

Si los consumidores no son humanos, como por ejemplo otras aplicaciones o código que
invoque sus puntos REST, las principales opciones de representación son XML y JSON. En
Spring resulta muy sencillo admitir estas opciones.
Dicho esto, le recomiendo una compatibilidad mínima de JSON. JSON es tan fácil de
usar como XML (y habrá quien afirme que incluso más), y si el cliente es de JavaScript (algo
cada vez más habitual en la actualidad), JSON es un claro ganador ya que no se necesita
serialización/deserialización para usarlo en JavaScript.
Sepa que a los controladores no les preocupa la forma de representar los recursos. Los
consideran en términos de los objetos de Java que los definen, pero el recurso no se trans­
forma en el formato más adecuado para el cliente hasta que el controlador no termina su
trabajo.
Spring le ofrece dos opciones para transformar la representación Java de un recurso a
la representación más adecuada para el cliente:

• Negociación de contenido: Se selecciona una vista que pueda representar el modelo


en una representación que entregar al cliente.
• Conversión de mensajes: Un conversor de mensajes transforma un objeto devuelto
por el controlador en una representación que entregar al cliente.
Como en capítulos anteriores ya analizamos los solucionadores de vistas y ya que conoce
la representación basada en vistas, veremos cómo usar la negociación de contenido para
seleccionar una vista o un solucionador de vistas que pueda representar un recurso en un
formato aceptable para el cliente.

Negociar ia representación cíe recursos


Como probablemente recuerde de un capítulo anterior, cuando un método gestor de
controlador finaliza, se suele devolver un nombre de vista lógica. Incluso si el método no lo
hace así (si, por ejemplo, devuelve v o id ), el nombre de la vista lógica se deriva de la URL
de la solicitud. A continuación, D i s p a t c h e r S e r v l e t transmite el nombre de la vista a
un solucionador de vistas y le pide que le ayude a determinar qué vista debe representar
los resultados de la solicitud.
En una aplicación Web para usuarios humanos, la vista seleccionada casi siempre se
representa en formato HTML. La resolución de vistas es una actividad unidimensional.
Si el nombre de la vista coincide con una, ésta va a ser la vista que vamos a utilizar. En la
resolución de nombres de vista en vistas que puedan generar representaciones de recursos
hay una dimensión adicional que tener en cuenta. No solo la vista tiene que coincidir con
458 Capítulo 16

su nombre, sino que tiene que seleccionarse para ajustarse al cliente. Si el cliente quiere
datos JSON, no podremos utilizar una representación de vista en HTML, incluso aunque
el nombre de vista coincida. C o n t e n t N e g o t ia t in g V i e w R e s o lv e r es un solucionador
de vistas especial de Spring que acepta el tipo de contenido que el cliente desea. En su
versión más sencilla, se puede configurar de esta forma:
@Bean
public ViewResolver cnViewResolver() {
return new ContentNegotiatingViewResolver();
}
En esta sencilla declaración de bean suceden muchas cosas. Para comprender el funcio­
namiento de C o n t e n t N e g o t ia t in g V i e w R e s o lv e r tenemos que conocer el proceso de
negociación de contenido:

1. Determinar el tipo de contenido solicitado.


2. Encontrar la mejor vista para el tipo de contenido solicitado.

Vamos a profundizar en cada uno de estos pasos para ver cómo funciona C o n ten t
N e g o t ia t in g V i e w R e s o lv e r . Primero hay que saber qué tipo de contenido quiere el
cliente.

Determinar el tipo de contenido solicitado


El primer paso del proceso de negociación de contenido consiste en determinar el tipo
de representación de recursos que desea el cliente. En principio parece un trabajo sencillo,
ya que el encabezado A c c e p t de la solicitud nos daría una indicación clara de la repre­
sentación que se debe enviar al cliente. Lamentablemente, este encabezado no siempre es
fiable. Si el cliente en cuestión es un navegador Web, no hay ninguna garantía de que lo que
el cliente quiere es lo que el navegador envía en el encabezado A c c e p t. Los navegadores
Web solo aceptan contenidos que los usuarios pueden leer (como t e x t / h t m l ) y no hay
forma de especificar un tipo de contenido diferente.
C o n t e n t N e g o t i a t i n g V i e w R e s o l v e r tiene en cuenta el encabezado A c c e p t
y utiliza cualquier tipo de contenido que solicite, aunque solo después de examinar
la extensión de archivo de la URL. Si la URL incluye una extensión al final, C o n te n t
N eg o t i a t in g V ie w R e s o lv e r intenta determinar el tipo correcto en función de la misma.
Si la extensiones . j so n , el tipo de contenido deseado debe ser a p p l i c a t i ó n / j so n . Si es
.x m l, significa que el cliente solicita a p p l i c a t i o n / x m l . Evidentemente, una extensión
. h tm l indica que el cliente quiere que el recurso se represente como HTML ( t e x t / h t m l ) .
Si la extensión de archivo no genera ningún tipo de contenido con el que podamos
trabajar, tendremos que tener en cuenta el encabezado A cce p t de la solicitud. En este caso,
el valor del encabezado A cce p t indica el tipo MIME que quiere el cliente; no es necesario
buscarlo.
Al final, si no hay encabezado Accept y la extensión no sirve de nada, Content
NegotiatingViewResolver retoma / como tipo de contenido predeterminado, de modo
que el cliente tendrá que aceptar la representación que el servidor le envíe.
Crear API REST con Spring MVC 459

Una vez determinado el tipo de contenido, C o n te n t N e g o t ia t in g V i e w R e s o lv e r


debe resolver el nombre lógico de la vista en un elemento V iew para representar el
modelo. Al contrario de lo que sucede con otros solucionadores de vistas de Spring,
C o n t e n t N e g o t ia t in g V i e w R e s o lv e r no resuelve vistas por sí mismo. En su lugar,
delega en los demás solucionadores de vistas y les pide que resuelvan la vista.
C o n t e n t N e g o t i a t i n g V i e w R e s o l v e r pide a los demás solucionadores
de vistas que resuelvan el nombre lógico de la vista en una vista. Todas las vistas
resueltas se añaden a una lista de vistas candidatas. Una vez confeccionada esta lista,
C o n t e n t N e g o t ia t in g V ie w R e s o lv e r itera por todos los tipos de contenidos solicitados
e intenta buscar una vista entre las candidatas para generar un tipo de contenido que coin­
cida. La primera coincidencia es la que se usa para representar el modelo.

Influir en la forma ele seleccionar tipos de contenido


El proceso de selección de tipos de contenido descrito hasta ahora recoge la estrategia
predeterminada para determinar los tipos de contenido solicitados, pero se puede modi­
ficar el comportamiento si se le asigna un ContentNegotiationManager, con el que
podemos hacer lo siguiente:
• Especificar un tipo de contenido predeterminado que usar si no se puede derivar el
tipo de contenido de la solicitud.
• Especificar un tipo de contenido a través de un parámetro de consulta.
• Ignorar el encabezado A cce p t de la solicitud.
• Asignar extensiones de solicitud a tipos de contenido concretos.
• Usar JAF {Java A ctivation Fram ew ork, Estructura de activación de Java) como opción
de respaldo para buscar tipos de contenidos en extensiones.
Hay tres formas de configurar ContentNegotiationManager:
• Declarar directamente unbean de tipo C o n te n tN e g o tia tio n M a n a g e r .
• Crear indirectamente el bean a través de C o n t e n t N e g o t i a t i o n M a n a g e r
F a c to r y B e a n .
• Reemplazar el método c o n f i g u r e C o n t e n t N e g o t i a t i o n () de WebMvc
C onf ig u r e r A d a p te r .
La creación directa de C o n te n t N e g o t ia t io n M a n a g e r es más complicada y segu­
ramente no la necesite. Las otras dos opciones existen para facilitar la creación de
C o n te n t N e g o t ia t io n M a n a g e r .

ContentNegotiationManager añadido en Spring 3.2


ContentNegotiationManager es relativam ente nuevo en Spring y apareció en
Spring 3.2. Antes, gran parte de su comportamiento se configuraba estableciendo
sus p ropias p ropied ad es. Desde Spring 3.2, m uchos de los m étodos de
460 Capítulo 16

establecim iento de ContentNegotiatíngViewResolver han quedado obsoletos


y se recom ienda su configuración a través de ContentNegotiationManager.
Aunque en este capítulo no veremos la técn ica de configuración antigua de
ContentNegotiatíngViewResolver, muchas de las propiedades que se establecen
al crear un ContentNegotiationManager cuentan con propiedades equivalentes en
ContentNegotiatíngViewResolver. Podrá asignar el nuevo estilo de configuración
al antiguo si todavía trabaja con una versión anterior de Spring.

Por lo general, C o n t e n t N e g o t i a t i o n M a n a g e r F a c t o r y B e a n resulta de mayor


utilidad cuando se configura C o n t e n t N e g o t ia t io n M a n a g e r en XML. Por ejemplo,
podría configurar C o n te n t N e g o t ia t io n M a n a g e r con el tipo de contenido predetermi­
nado a p p l i c a t i o n / j so n en XML de esta forma:
cbean id="ContentNegotiationManager"
cla ss= "o rg . springframework. h ttp . ContentNegotiationManagerFactoryBean"
p:defaultContentType="application/j son">

Com o C o n t e n t N e g o t i a t i o n M a n a g e r F a c t o r y B e a n es una im plem entación de


F a c t o r y B e a n , se crea un bean C o n te n t N e g o t ia t io n M a n a g e r como resultado. Tras
ello, se puede inyectar C o n t e n t N e g o t i a t i o n M a n a g e r en la propiedad c o n t e n t
N e g o t ia t io n M a n a g e r de C o n t e n t N e g o t ia t in g V i e w R e s o lv e r . Para configuración
de Java, la form a más sencilla de obtener C o n te n t N e g o t ia t io n M a n a g e r consiste en
am pliar W e b M v c C o n fig u re rA d a p te r y reemplazar el método c o n f i g u r e C o n t e n t
N e g o t i a t i o n ( ) . Es m uy probable que ya haya ampliado WebMvcConf ig u r e r A d a p t e r
al em pezar a crear su aplicación de Spring MVC. En la aplicación Spittr, por ejemplo, ya
cuenta con una extensión de WebMvcConf i g u r e r A d a p t e r con el nombre WebConf ig ,
por lo que solo tiene que reemplazar c o n f ig u r e C o n t e n t N e g o t i a t io n ( ) . La siguiente
im plem entación de c o n f i g u r e C o n t e n t N e g o t i a t i o n () define el tipo de contenido
predeterminado:
@Override
public void configureContentNegotiation(
ContentNegotiationConfigurer configurer) {
configu rer. defaultContentType(MediaType.APPLICATION_JSON);
}
Com o puede apreciar, c o n f i g u r e C o n t e n t N e g o t i a t i o n () recibe un C o n te n t
N e g o t i a t i o n C o n f i g u r e r con el que trabajar. C o n t e n t N e g o t ia t io n C o n f i g u r e r
d isp o n e de v a rio s m é to d o s s im ila re s a lo s m é to d o s de e s ta b le c im ie n to de
C o n te n t N e g o t ia t io n M a n a g e r y le perm iten establecer el comportamiento de nego­
ciación de contenidos que desee en el C o n t e n t N e g o t i a t i o n M a n a g e r que se va a
crear. En este caso se invoca el m étodo d e f a u l t C o n t e n t T y p e () para establecer el
tipo de contenido predeterm inado en a p p l i c a t i o n / j s o n . Una vez conseguido el
bean C o n te n t N e g o t ia t io n M a n a g e r , basta con inyectarlo en la propiedad c o n t e n t
N e g o t ia t io n M a n a g e r de C o n t e n t N e g o t ia t in g V ie w R e s o lv e r . Para ello tendrá que
cam biar el m étodo @ B ean en el que se declara C o n t e n t N e g o t ia t in g V ie w R e s o lv e r :
Crear API REST con Spring MVC 461

@Bean
public ViewResolver cnViewResolver(ContentNegotiationManager cnm) {
ContentNegotiatingViewResolver cnvr =
new ContentNegotiatingViewResolver( );
cnvr. setContentNegotiationManager(cnm);
return cnvr;
}
C o n te n tN e g o tia tio n M a n a g e r se inyecta en el método @Bean y se invoca s e tC o n t e n t
N e g o tia tio n M a n a g e r ( ). Como resultado, ahora C o n te n tN e g o tia tin g V ie w R e s o lv e r
acepta el comportamiento definido en C o n te n t N e g o t ia t io n M a n a g e r .
Existen tantas variantes de configuración de C o n t e n t N e g o t ia t io n M a n a g e r que
sería imposible describirlas todas. El siguiente código muestra un ejemplo de una sencilla
configuración que suelo emplear al usar C o n te n t N e g o t ia t in g V i e w R e s o lv e r : escoge
vistas HTML de forma predeterminada pero representa contenidos JSON para determi­
nados nombres de vista.

Listado 16.2. Configuración de ContentNegotiationManager.


@Bean
public ViewResolver cnViewResolver(ContentNegotiationManager cnm) {
ContentNegotiatingViewResolver cnvr =
new ContentNegotiatingViewResolver( );
cnvr. setContentNegotiationManager(cnm);
return cnvr;
>
@Override
public void configureContentNegotiation(
ContentNegotiationConfigurer configurer) {
configu rer. defaultContentType(MediaType.TEXT_HTML); / / HTML de forma predeterminada.
}
@Bean
public ViewResolver beanNameViewResolver() { / / Buscar v is ta s como bean,
return new BeanNameViewResolver( );
}
@Bean
public View s p i t t l e s () {
return new MappingJackson2JsonView( ) ; / / V ista " s p ittle s " en formato JSON.
}

A dem ás de lo que m uestra el código anterior, tam bién existe u n solu cion ad or
de v istas com p atib le con HTML (com o I n t e r n a l R e s o u r c e V i e w R e s o l v e r o
T ile s V ie w R e s o lv e r ) . En la mayoría de los casos, C o n te n tN e g o tia tin g V ie w R e s o lv e r
asume que el cliente quiere HTML, como se configura en C o n te n tN e g o tia tio n M a n a g e r ,
pero si el chente indica que prefiere JSON (ya sea con una extensión . j so n en la ruta de
la solicitud o a través del encabezado A c c e p t), C o n te n t N e g o t ia t in g V i e w R e s o lv e r
intenta buscar un solucionador de vistas que pueda servir una vista JSON. Si el nombre
lógico de la vista es s p i t t l e s , el B ean N a m e V iew R eso lv e r configurado resuelve la vista
462 Capítulo 16

(v i ew) declarada en el método sp i 1 1 1 e s (). Se debe a que el nombre del bean coincide con
el nombre lógico de la vista. En caso contrario, a menos que haya otra vista que coincida,
C o n te n tN e g o tia tin g V ie w R e s o lv e r recurre a la predeterminada y entrega HTML.
Cuando C o n t e n t N e g o t ia t in g V i e w R e s o lv e r sabe qué tipos de contenido quiere
el cliente, llega el momento de buscar una vista que pueda representar dichos tipos de
contenido.

Ventajas y ¡imitaciones de ContentNegotiatingViewResolver


La principal ventaja de C o n te n tN e g o tia tin g V ie w R e s o lv e r es que ubica la represen­
tación de recursos REST sobre Spring MVC sin modificar el código de los controladores. El
mismo método de controlador que entrega contenido HTML para humanos también puede
entregar JSON o XML a un cliente no humano. La negociación de contenido es una opción
muy útil cuando se produce un marcado solapamiento entre las interfaces humanas y no
h u m a n a s . Sin e m b a r g o , en Ja práctica,, las vistaspara humanos no suelen trabajar en el mismo
nivel de detalle que un API REST. La ventaja de C o n te n t N e g o t ia t in g V i e w R e s o lv e r
no se aprecia cuando no se produce tanto solapamiento.
C o n te n tN e g o tia tin g V ie w R e s o lv e r también sufre una seria limitación. Como imple-
mentación de V ie w R e so lv e r, solo tiene una oportunidad para determinar la representación
de un recurso para el cliente. No decide qué representaciones puede consumir un contro­
lador del cliente. Si el cliente envía JSON o XML, C o n te n tN e g o tia tin g V ie w R e s o lv e r no
sirve de mucho. Y hay otro detalle más asociado a C o n te n t N e g o t ia t in g V ie w R e s o lv e r .
La vista seleccionada es la que representa el modelo al cliente, no el recurso. Es una sutil
diferencia, pero importante. Cuando un cliente solicita una lista de objetos S p i t t l e en
JSON, el cliente probablemente espera una respuesta similar a la siguiente:
[
{
"id ": 42,
"la titu d e ": 28.419489,
"longitude": -81.581184,
"message": "Helio World!",
"tim e": 1400389200000
}.
{
"id ": 43,
"la titu d e ": 28.419136,
"longitude": -81.577225,
"message": "B last o f f ! " ,
"tim e": 1400475600000
}
]
Pero como el modelo es un mapa de pares de clave y valor, la respuesta se parece más
a la siguiente:

{
" s p i t t le L i s t ": [
{
"id": 42,
Crear API REST con Spring MVC 463

"la titu d e ": 28.419489,


"longitude": -81.581184,
"message": "Hello World!",
"tim e": 1400389200000
b
{
"id ": 43,
"la titu d e ": 28.41913S,
"longitude": -81.577225,
"message": "B last o ff !" ,
"tim e": 1400475600000
}

}
Aunque no sea nada trascendente, puede que no sea lo esperado por el cliente.
Debido a estas limitaciones, prefiero no usar C o n t e n t N e g o t ia t in g V ie w R e s o lv e r .
En su lugar, me decanto por los conversores de mensajes para crear representaciones de
recursos. A continuación veremos cómo utilizarlos en métodos de controlador.

Trabajar con conversores de mensajes HTTP


La conversión de mensajes es una forma más directa de transformar datos generados
por un controlador en una representación que se ofrece al cliente. Al usar la conversión
de mensajes, D i s p a t c h e r S e r v l e t no se preocupa de plasmar los datos del modelo en
una vista. De hecho, no hay modelo y no hay vista. Solamente hay datos generados por el
controlador y una representación de recursos generada cuando un conversor de mensajes
transforma esos datos. Spring incluye diversos conversores de mensajes, enumerados en la
tabla 16.1, para las necesidades de conversión de objetos a representaciones más habituales.

Tabla 16.1. Spring ofrece diversos conversores de mensajes HTTP que permiten convertir
representaciones de recursos a distintos tipos Java.
-- --- :
Conversor de mensajes Descripción
M..ü .. ■
■ Ü---.................i.. üi

AtomFeedHt tpMes sageConvert er Convierte objetos Feedde Rome en hilos Atom y


a la inversa (tipo de contenido application/
atom+xmi). Se registra si la biblioteca Rome
se encuentra en la ruta de clases.
BufferedlmageHttpMessageConverter Convierte Buf feredimage en datos binarios
de imagen y a la inversa.
ByteArrayHttpMessageConverter Lee y escribe matrices de bytes. Lee todos
los tipos de contenido ( * / * ) y escribe como
application/octet-stream.

FormHt tpMe ssage Convert e r Lee contenido como application/x-www-


for-urlenconded en un MultiValueMap
<string, string>. También escribe Mui ti
V a l u e M a p < S t r i n g , S t r i n g > como
464 Capítulo 16

f Conversor de mensajes Descripción


■.....s í ......Síí ..................................... ■....................................

application/x-www-form-urlencodedy
MultiValueMap<String, Object> como
multipart/form-data.
Jaxb2RootElementHttpMessageConverter LeeyescribeXML(text/xmlOapplication/
xm l) desde objetos anotados JAXB2 y a la
inversa. Se registra si las bibliotecas JAXB
v2 se encuentran en la ruta de clases.
MappingJacksonHttpMessageConverter Lee y escribe JSON desde objetos con tipo o
HashMap sin tipo. Se registra si la biblioteca
JSON Jackson se encuentra en la ruta de
clases.
MappingJackson2HttpMessageConverter Lee y escribe JSON desde objetos con tipo o
HashMap sin tipo. Se registra si la biblioteca
JSON Jackson 2 se encuentra en la ruta de
clases.
MarshallingHttpMessageConverter Lee y escribe XML utilizando un agente de
señalización y deserialización inyectado. Entre
los agentes admitidos se incluyen Castor,
JAXB2, JIBX, XMLBeans y XStream.
ResourceHttpMessageConverter Lee y escribe org.springframework.core.
io.Resource.
RssChannelHttpMessageConverter Lee y escribe hilos RSS desde y en objetos
channel de Rome. Se registra si la biblioteca
Rome se encuentra en la ruta de clases.
SourceHttpMessageConverter Lee y escribe XML desde y en objetos javax.
xml.transform.Source.
StringHttpMessageConverter Lee todos los tipos de contenido ( * / * ) en un
objeto string. Escribe cadenas como text/
plain.
XmlAwareFormHttpMessageConverter Una extensión de FormHttpMessage
Converter que añade compatibilidad para
fragmentos basados en XML utilizando un
SoureHttpMessageConverter.

Por ejemplo, supongamos que el cliente ha indicado, mediante el encabezado A c c e p t


de la solicitud, que puede aceptar a p p l i c a t i o n / j so n . Si suponemos que la biblioteca
ÍSON Jarksnn se encuentra e n la ruta de clases de la aplicación, el objeto devuelto por el
método de controlador se va a proporcionar a M a p p in g Ja c k so n H ttp M e ss a g e C o n v e rte r
para convertirlo en una representación JSON que devolver al cliente. Por otro lado,
si el encabezado de la solicitud indica que el cliente prefiere t e x t / x m l , J a x b 2 R o o t
E le m e n tH ttp M e s s a g e C o n v e r te r se hace cargo de generar una respuesta XML para el
cliente.
Crear API REST con Spring MVC 465

Todos los conversores de mensajes HTTP de la tabla 16.1 menos cinco se registran de
forma predeterminada. Por tanto, no se necesita configuración de Spring para utilizarlos.
Por ejemplo, si quiere utilizar M a p p in g Ja c k s o n H ttp M e s s a g e C o n v e r te r para convertir
mensajes JSON a objetos Java (y a la inversa), tendrá que añadir la biblioteca JSON Jackson
Processor a la ruta de clases.
Del mismo modo, se necesita la biblioteca JAXB para que ja x M R o o t E l e m e i i t
H ttpM es s a g e C o n v e r te r convierta mensajes entre XML y objetos de Java, y se necesita la
biblioteca Romepara A to m F e e d H ttp M e s sa g e C o n v e rte r y R s sC h a n n e lH ttp M e ss a g e
C o n v e r t e r cuando el mensaje tiene formato Atom o RSS.
Como habrá imaginado, hay que modificar el modelo de programación del MVC de
Spring para admitir la conversión de mensajes. Modificaremos el controlador del listado
16.1 para que la utilice.

Devolver el estado de un recurso en el cuerpo de la respuesta


Normalmente, cuando un método de controlador devuelve un objeto Java (que no sea
S t r i n g o una implementación de V iew ), ese objeto termina en el modelo para su repre­
sentación en la vista, pero si piensa usar conversión de mensajes, tendrá que indicarle a
Spring que ignore el flujo modelo/vista habitual y que en su lugar use un conversor de
mensajes. Existen varias formas de hacerlo pero la más sencilla es anotar el método de
controlador con @ R esp o n seB o d y . Si volvemos al método s p i t t l e s () del listado 16.1,
podemos añadir @ R esp on seB o d y para que Spring convierta el elemento L i s t < S p i t t l e >
devuelto en el cuerpo de la respuesta:
@RequestMapping(method=RequestMethod.GET,
produces="a p p lica tio n /jso n ")
public @ResponseBody L ist< S p ittle > s p i t t l e s (
@RequestParam(value="raaxM,
defaultValue=MAX_LONG_AS_STRING) long max,
@RequestParam(value="count", defaultValue="20") in t count) {

return sp ittle R ep o sito ry . findSpittles(m ax, count);


}
La anotación @ R esp o n seB o d y indica a Spring que queremos enviar el objeto devuelto
como recurso al cliente, convertido en un formato representacional que el cliente puede
aceptar. En concreto, D i s p a t c h e r S e r v l e t tiene en cuenta el encabezado A c c e p t de
la solicitud y busca un conversor de mensajes que devuelva al cliente la representación
que desea. Para ilustrarlo, si el encabezado A c c e p t del cliente especifica que acepta
a p p l i c a t i o n / j s o n y la biblioteca Jackson JSON se encuentra en la ruta de clases
de la aplicación, no se selecciona ni M a p p in g J a c k s o n H t t p M e s s a g e C o n v e r t e r ni
M a p p in g Ja c k s o n 2 H ttp M e s s a g e C o n v e r te r (en función de la versión de Jackson en la
ruta de clases). El conversor de mensajes convierte la lista devuelta por el controlador en
un documento JSON que se escribirá en el cuerpo de la respuesta, similar a la siguiente:

[
{
"id": 42,
466 Capítulo 16

" la titu d e " : 28.419489,


"longitude": -81.581184,
"message": "Helio World!",
"tim e": 1400389200000
}.
{
"id ": 43,
"la titu d e ": 28.419136,
"longitude": -81.577225,
"message": "B last o f f ! " ,
"tim e": 1400475600000
}

Jackson utiliza reflexión de forma predeterminada


Debe saber que, de forma predeterminada, las bibliotecas JSON Jackson utilizan
la reflexión para generar la representación de recursos JSON del objeto devuelto.
En representaciones sencillas puede ser lo necesario, pero si refactoriza el tipo
de Java añadiendo, eliminando o renombrando propiedades, el código JSON
generado tam bién cambia (lo que puede afectar a los clientes que dependan de
dichas propiedades).
Sin embargo, puede m odificar la generación de JSON si asigna anotaciones de
Jackson al tipo de Java. De ese modo dispone de más control sobre el aspecto
del código JSON resultante y se evitan cambios que puedan afectar al API y a
sus clientes.
Las anotaciones de asignación de Jackson se escapan a los objetivos de este libro,
pero si necesita más información al respecto puede consultar la documentación
en http://wiki.fasterxml.com/JacksonAnnotations.

Hablando del encabezado A c c e p t , fíjese en @ R eq u estM ap p in g de s p i t t l e () . He


añadido un atributo p r o d u c e s para declarar que este método solo procesa solicitudes
para las que se espera un resultado JSON. Es decir, solicitudes cuyo encabezado A c c e p t
incluya a p p l i c a t i o n / j son. Cualquier otro tipo de solicitud, aunque sea de tipo GET y
su URL coincida con la ruta especificada, no será procesada por este método. Se procesará
con otro método de controlador (si existe el adecuado) o el cliente recibirá una respuesta
HTTP 406 (Inaceptable).

Recibir el estado de un recurso en el cuerpo de la solicitud


Hasta el momento nos hemos centrado en puntos finales REST que entregan recursos
al cliente, pero REST no es de solo lectura. Un API REST también puede recibir representa­
ciones de recursos del cliente. No sería conveniente que el controlador tuviera que convertir
una representación JSON o XML enviada desde un cliente en un objeto que pudiera usar. Los
conversores de mensajes de Spring podían convertir objetos en representaciones de salida
para los controladores pero ¿pueden hacer lo contrario con representaciones entrantes?
Crear API REST con Spring MVC 467

Al igual que @ R esp o n seB o d y indica a Spring que utilice un conversor de mensajes al
enviar datos al cliente, @ R e q u e stB o d y le indica que busque un conversor de mensajes
para convertir en objeto una representación recibida del cliente. Imagine por ejemplo que
necesita una forma para que el cliente envíe un nuevo objeto S p i t t l e para guardar. Podría
escribir un método de controlador para procesar una solicitud como la siguiente:
@RequestMapping(
method=RequestMethod. POST
consum es="application/json")
public @ResponseBody
S p ittle saveSpittle(@RequestBody S p ittle s p ittle ) {
return sp ittle R ep o sito ry . s a v e (s p ittle ) ;
}
Obviando las anotaciones, el método s a v e S p i t t l e () es muy sencillo. Acepta un
único objeto S p i t t l e como parámetro, lo guarda con S p i t t l e R e p o s i t o r y y después
devuelve el objeto S p i t t l e devuelto de la invocación de s p i t t l e R e p o s i t o r y . s a v e () .
Pero si aplicamos las anotaciones resulta más interesante y potente. @ R eq u estM ap p in g
indica que solo procesará solicitudes POST de / s p i t t l e s (según la declaración de
@ R eq u estM a p p in g en el nivel de clases). Se espera que el cuerpo de la solicitud POST
incluya una representación de recurso para un S p i t t l e . Como el parámetro S p i t t l e
está anotado con @ R e q u e stB o d y , Spring examina el encabezado C o n te n tT y p e de la
solicitud e intenta buscar un conversor de mensajes que pueda convertir el cuerpo de la
solicitud en un S p i t t l e .
Por ejemplo, si el cliente envía los datos Spittle en una representación JSON,
el encabezado ContentType se puede establecer en application/j son. En ese
caso, DispatcherServlet busca un conversor de mensajes que convierta JSON
en objetos de Java. Si la biblioteca Jackson 2 se encuentra en la ruta de clases,
MappingJackson2HttpMessageConverter se encarga de convertir la representación
JSON en un Spittle que se pasa almétodo saveSpittle () . El método también se anota
con @ResponseBody para que el objeto Spittle devuelto se convierta en una represen­
tación del recurso que devolver al cliente.
@ R eq u estM a p p in g cuenta con un atributo con su m es establecido en a p p l i c a t i o n /
j son. Su funcionamiento es similar al del atributo p r o d u c e s , pero se limita al encabezado
C o n te n tT y p e de la solicitud. Esto indica a Spring que el método solo procesará solicitudes
POST de / s p i t t l e s si el encabezado C o n te n tT y p e de la solicitud es a p p l i c a t i o n /
j son. En caso contrario, si existe otro método adecuado se ocupará de procesar la solicitud.

Controladores predeterminados para la conversión de mensajes


Las anotaciones @ R esp o n seB o d y y @ R e q u e stB o d y constituyen una forma sucinta
pero muy potente de emplear conversores de mensajes de Spring para procesar solicitudes,
pero si tiene que crear un controlador que tenga varios métodos, y todos tienen que usar la
conversión de mensajes, las anotaciones resultan un tanto repetitivas. Spring 4.0 presentó
la anotación @ R e s t C o n t r o l l e r para ayudarle. Si anota su clase de controlador con
@ R e s t C o n t r o l l e r en lugar de © C o n t r o l l e r , Spring aplica la conversión de mensajes
468 Capítulo 16

a todos los métodos de control del controlador. No tiene que anotar cada método con
@ResponseBody. S p i t t l e C o n t r o l l e r , tal y como se ha definido hasta el momento, es
similar al siguiente código.

Listado 16.3. Uso de la anotación @RestController.

package s p itt r .a p i;
import ja v a . u t i l .L i s t ;
import o rg .sp rin gframework.beans. fa c to ry . annotation.Autowired;
import org. springframework. web. bind. annotation. R estC ontroller;
import org. springframework.web.bind. annotation. RequestMapping;
import org. springframework.web.bind. annotation. RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import s p i t t r . S p itt le ;
import s p i t t r . d ata. Sp ittleR ep o sitory ;

@RestController / / Conversion de mensajes de forma predeterminada.


@RequestMapping(" / s p i t t l e s ”)
public cla ss S p ittle C o n tro lle r {

private s t a t ic fin a l String MAX_L0NG_ASJ3TRING="9223372036854775807" ;

private SpittleR epository sp ittleR ep osito ry ;

@Autowired
public S p ittleC o n tro ller(S p ittleR ep o sito ry sp ittleR ep ository) {
t h i s . sp ittleR ep osito ry = sp ittleR ep osito ry ;
}
@RequestMapping(method=RequestMethod. GET)
public L is t< S p ittle > s p itt le s (
@RequestParam(value="max",
defaultValue=MAX_LONG_AS_STRING) long max,
©RequestParam(value="count", defaultV alue="20") in t count) {

return sp ittle R ep o sito ry . findSpittles(m ax, count);


}
(©RequestMapping (
method=RequestMethod.POST
consum es="application/json")
public S p ittle saveSpittle(@RequestBody S p ittle s p ittle ) {
return sp ittle R ep o sito ry . s a v e (s p itt le ) ;

}
En el listado 16.3 el aspecto principal no aparece en el código, y los métodos de contro­
lador tampoco están anotados con @ResponseBody, pero como el controlador está anotado
con @ R e stC o n tro 1 1 e r, los objetos devueltos por esos métodos seguirán realizando la
conversión de mensajes para crear una representación de recursos para el cliente.
Hasta el momento, hemos visto cómo usar el modelo de programación de Spring MVC
para publicar recursos REST en el cuerpo de las respuestas, pero en una respuesta hay
mucho más que la carga de trabajo. Hay encabezados y códigos de estado que también
Crear API REST con Spring MVC 469

pueden proporcionar información muy útil sobre la respuesta para el cliente. A continua­
ción veremos cómo completar encabezados de respuesta y establecer códigos de estado al
entregar recursos.

Entregar más que recursos . ___


La anotación @ R esp o n seB o d y es muy útil para transformar un objeto Java devuelto
por un controlador en una representación de recurso que enviar al cliente, pero el envío al
cliente es solo una parte del proceso. Un API REST de calidad hace algo más que transferir
recursos entre el cliente y el servidor. También proporciona al cliente metadatos adicionales
para que comprenda el recurso o sepa lo que sucede en la solicitud.

Comunicar errores al cliente


Por ejemplo, primero añadiremos un nuevo método de controlador a S p i t t le C o n t r o l 1 e r
para entregar un objeto S p i t t l e :
@RequestMapping(value="/ { id } ", methocURequestMethod.GET)
public @ResponseBody S p ittle s p ittle B y ld (@PathVariable long id) {
return sp ittle R ep o sito ry . findO ne(id);
}
Ese ID se pasa al parámetro i d y se usa para buscar un objeto S p i t t l e en el repositorio
mediante la invocación de f ind O ne ( ) . El objeto S p i t t l e devuelto por f indO ne ( ) se
devuelve desde el método de controlador y la conversión de mensajes se encarga de crear
una representación del recurso para que el cliente la consuma.
Muy sencillo, ¿no? No podría mejorarse. ¿O sí? ¿Qué pasaría si no hubiera un objeto
S p i t t l e cuyo ID coincide con el proporcionado y f indO ne () devolviera n u i l ?
Lo sorprendente es que si s p i t t l e B y l d () devuelve n u i l , el cuerpo de la respuesta
estará vacío. No se devuelven datos de utilidad al cliente. Mientras tanto, el código de estado
HTTP predeterminado incluido en la respuesta es 200, lo que significa que todo es correcto.
Pero no es cierto. El cliente solicita un objeto S p i t t l e pero no recibe nada. No recibe
ni un objeto S p i t t l e ni una indicación de que hay un problema. Básicamente el servidor
le entrega una respuesta inútil pero le recuerda que todo es correcto.
Imagine qué debería suceder en ese caso. Como mínimo, el código de estado no debería
ser 200, sino 404 (Página no encontrada) para indicar al cliente que lo que solicita no se
encuentra, y sería recomendable que el cuerpo de la respuesta incluyera un mensaje de
error en lugar de estar vacío. Spring le ofrece diversas opciones para estas situaciones:

• Códigos de estado que especificar con la anotación @ R e s p o n s e S ta tu s .


• Métodos de controlador que pueden devolver una R e s p o n s e E n t i t y con más
metadatos relacionados con la respuesta.
• Un controlador de excepciones que procese los casos de error para que los métodos
de controlador se centren en su tarea.
470 Capítulo 16

Es otro ámbito en el que Spring ofrece una gran flexibilidad y no hay un único enfoque
correcto. En lugar de limitarnos a una única estrategia o intentar abarcar todas las posi­
bilidades, veremos un par de formas de cambiar s p i t t l e B y l d () cuando no se pueda
encontrar un objeto S p i t t l e .

Trabajar con ResponseEntity


Como alternativa a @ R esp o n seB o d y , los métodos de controlador pueden devolver
R e s p o n s e E n t it y , un objeto que contiene metadatos (como encabezados y códigos de
estado) sobre una respuesta además del objeto que convertir en una representación de
recurso.
Como R e s p o n s e E n t i t y le permite especificar el código de estado de la respuesta,
parece la opción adecuada para comunicar un error HTTP 404 cuando el objeto S p i t t l e
no se encuentre. A continuación le mostramos una nueva versión de sp i 1 1 1 eB y I d ( ) que
devuelve una R e s p o n s e E n t it y :
@RequestMapping(value="/{id}", method=RequestMethod.GET)
public ResponseEntity<Spittle> spittleByld{@PathV ariable long id) {
S p ittle s p it t le = sp ittle R ep o sito ry . findOne(id);
HttpStatus statu s = s p it t le != nuil ?
HttpStatus.OK : HttpStatus.NOT_FOUND;
return new R esp o n seE n tity <Sp ittle>(sp ittle, s ta tu s );
}

Como antes, se usa el ID de la ruta para recuperar un objeto S p i t t l e del repositorio. Si se


encuentra uno, el estado se establece en H t t p S t a t u s . OK (que antes era el predeterminado),
pero si el repositorio devuelve n u i l , el estado se establece en H t t p S t a t u s . NOT_FOUND,
que se traduce en un error HTTP 404. Por último, se crea una nueva R e s p o n s e E n t i t y
para transmitir el objeto S p i t t l e y el código de estado al cliente.
Comprobará que s p i t t l e B y l d () no se anota con @ R esponseBody. Además de incluir
encabezados de respuesta, un código de estado y una carga de trabajo, R e s p o n s e E n t i t y
implica la semántica de @ R esp o n seB o d y , de modo que la carga se representa en el cuerpo
de la respuesta como si el método estuviera anotado con O R esponseBody. No es necesario
anotar el método con @ R esp o n seB o d y si devuelve R e s p o n s e E n t it y .
Sin duda es un paso en la dirección correcta. Ahora el cliente recibe un código de estado
correcto si no se encuentra el objeto S p i t t l e que solicita, pero el cuerpo de la respuesta
sigue vacío y queremos que incluya información adicional sobre el error.
Volvamos a intentarlo. En primer lugar se define un objeto E r r o r para incluir la infor­
mación del error:
public cla ss Error {
prívate in t code;
prívate String message;

public E rro r(in t code, Strin g message) {


th is.co d e = code;
this.m essage = message;
}
Crear API REST con Spring MVC 471

public in t getCodeO {
return code;
}
public String getMessageO {
return message;
}
}
Tras ello, se cambia s p i t t l e B y l d () para devolver el error:
@RequestMapping(value="/{id}", method=RequestMethod.GET)
public ResponseEntity<?> spittleByld(@PathVariable long id) {
S p ittle s p it t le = sp ittle R ep o sito ry . findO ne(id);
i f ( s p ittle == nuil) {
Error erro r = new E rror(4, "S p ittle [" + id + "] not found") ;
return new ResponseEntity<Error>(error, HttpStatus.NOT_FOUND);
}
return new R esp o n seE n tity <Sp ittle>(sp ittle, H ttpStatus. OK);
}
Ahora este método de controlador se comporta de la forma esperada. Si se encuentra un
objeto S p i t t l e , se devuelve envuelto en una R e s p o n s e E n t i t y con el código de estado
200. Por otra parte, si f ind O ne () devuelve n u i l , se crea un objeto E r r o r y se devuelve
envuelto en una R e s p o n s e E n t i t y con el código de estado 404 (Página no encontrada).
Pararemos aquí. Después de todo, los métodos funcionan como deseamos, pero hay
detalles que me preocupan.
Por un lado, es más complicado de lo que teníamos al principio. Hay más lógica, incluida
una instrucción condicional, y que el método devuelva R e s p o n s e E n t i t y < ? > no parece
correcto. El uso genérico de R e s p o n s e E n t it y está expuesto a numerosas interpretaciones
o errores. Afortunadamente lo podemos corregir con un controlador de errores.

Controlar errores
El bloque i f de s p i t t l e B y l d () controla un error, pero esa es la labor de los contro­
ladores de errores, que se encargan de los problemas mientras que dejan que los métodos
de controlador convencionales se ocupen de las tareas más felices.
Modificaremos parte del código para usar un controlador de errores. Para empezar, definimos
un controlador de errores que reaccione a una excepción S p ittle N o tF o u n d E x c e p t io n :
@Except i onHandler(Spi111eNotFoundExcept io n . cla s s )
public ResponseEntity<Error> spittleNotFound(
SpittleNotFoundException e) {
long s p ittle ld = e .g e tS p ittle ld O ;
Error error = new E rror(4, "S p ittle [" + s p itt le ld + "] not found"};
return new ResponseEntity<Error>(error, HttpStatus.NOT_FOUND);
}
La anotación @ E x c e p tio n H a n d le r se puede aplicar a métodos de controlador para
procesar excepciones concretas. En este caso, indica que si se genera una excepción
S p it t l e N o t F o u n d E x c e p t i o n desde cualquiera de los m étodos de control del mismo
controlador, debe invocarse el m étodo s p i t t l e N o t F o u n d () para procesarla.
472 Capítulo 16

En cuanto a S p ittle N o tF o u n d E x c e p tio n , es una clase de excepción bastante básica:


public cla ss SpittleNotFoundException extends RuntimeException {
private long s p itt le ld ;
public SpittleNotFoundException(long s p ittle ld ) {
t h i s . s p itt le ld = s p itt le ld ;
}
public long g e tS p ittle ld () {
return s p ittle ld ;
}
}
Ahora podemos eliminar gran parte del control de errores del método s p i t t le B y ld ():
@RequestMapping(value="/{ id } ", method=RequestMethod.GET)
public ResponseEntity<Spittle> s p ittle B y ld (@PathVariable long id) {
S p ittle s p it t le = sp ittle R ep o sito ry . findOne( id ) ;
i f ( s p ittle == null) { throw new SpittleNotFoundException(id); }
return new R esp o n seE n tity <Sp ittle>(sp ittle, H ttpStatus. OK);

Con esto limpiamos ligeramente s p i t t l e B y l d (). Aparte de comprobar si el valor


devuelto es n u l 1, se centra totalmente en el caso de encontrar un objeto S p i t t l e , y hemos
podido deshacernos del extraño uso de los genéricos en el tipo devuelto.
Podemos seguir con las tareas de limpieza. Como ya sabemos que s p i t t l e B y l d ()
devuelve un objeto S p i t t l e y que el estado HTTP siempre va a ser 200, ya no necesitamos
usar R e s p o n s e E n t i t y y podemos sustituirlo por @ R esp o n seB o d y :
@RequestMapping(value="/{id}", method=RequestMethod.GET)
public @ResponseBody S p ittle spittleByld(@PathV ariable long id) {
S p ittle s p it t le = sp ittle R ep o sito ry . findOne(id);
i f ( s p it tle == nuil) { throw new SpittleNotFoundException(id); }
return s p i t t le ;
}
Evidentemente, si la clase de controlador está anotada con @ R e s t C o n t r o l l e r , ni
siquiera necesitamos @ R esp o n seB o d y :
@RequestMapping(value=" / { id } ", raethod=RequestMethod.GET)
public S p ittle spittleByld(@PathVariable long id) {
S p ittle s p i t t le = sp ittle R ep o sito ry . findO ne(id);
i f ( s p it tle == nuil) { throw new SpittleNotFoundException(id); }
return s p i t t le ;

Como sabemos que el método de control de errores siempre devuelve E r r o r y que


siempre responde con el código HTTP 404 (Página no encontrada), podemos aplicar una
limpieza similar a s p i t t l e N o t F o u n d ( ) :
@Except i onHandler(Spi 111eNot FoundException.class)
@ResponseStatus(HttpStatus.NOT_FOUND)
public @ResponseBody Error spittleNotFound(SpittleNotFoundException e) {
long s p itt le ld = e .g e tS p it tle ld ();
return new E rro r(4, "S p ittle [" + s p itt le ld + "] not found");
}
Crear API REST con Spring MVC 473

Como s p i t t l e N o t F o u n d () siempre devuelve un objeto E r r o r , el único motivo por


el que m antener R e s p o n s e E n t i t y es para establecer el código de estado, pero al anotar
s p i t t l e N o t F o u n d () c o n @ R e s p o n s e S ta tu s ( H t t p S t a t u s .NOT_FOUND) se consigue
el m ism o efecto y nos deshacemos de R e s p o n s e E n t it y .
Como antes, si la clase de controlador está anotada con @ R e s t C o n t r o l l e r , puede
eliminar la anotación @ R esp o n seB o d y y limpiar el código:
@ExceptionHandler(SpittleNotFoundException.class)
@ResponseStatus(HttpStatus.NOT_FOUND)
public Error spittleNotFound(SpittleNotFoundException e) {
long s p itt le ld = e .g e tS p ittle ld O ;
return new E rro r(4, "S p ittle [" + s p itt le ld + "] not found");
}
En cierto modo hemos completado el círculo. Para establecer el código de estado
de la respuesta comenzamos usando R e s p o n s e E n t i t y , pero después pudimos usar
un controlador de excepciones y @ R e s p o n s e S t a t u s para eliminar la necesidad de
R e s p o n s e E n t i t y y reforzar el código.
Parece que nunca vamos a necesitar R e s p o n s e E n t it y , pero hay algo más que hace y
que no podemos conseguir con otras anotaciones o controladores de excepciones. Veamos
ahora cómo establecer encabezados en la respuesta.

Establecer encabezados en la respuesta


En el caso del método s ave Spittle (), creamos un nuevo recurso Spittle durante
elprocesamiento de una solicitud POST, pero taly como está escrito (véase el listado 16.3),
no se lo podemos comunicar bien al cliente.
Después de que el método s a v e S p i t t l e () procese la solicitud, el servidor responde
al cliente con una representación de S p i t t l e en el cuerpo y un código de estado HTTP
200 (Correcto). No está mal pero no es del todo preciso.
Evidentemente, si la solicitud crea el recurso, podemos pensar que el estado es el correcto,
pero es algo más que correcto. Se ha creado algo y un código de estado HTTP debe indicár­
selo al cliente. El código HTTP 201 indica que la solicitud se ha completado correctamente
pero también que se ha creado algo. Si quiere comunicarse de forma completa y precisa
con el cliente, la respuesta debería ser 201 (Creado) y no solo 200 (Correcto).
Si aplicamos lo visto hasta el momento, es fácil de corregir. Basta con anotar s a v e
S p i t t l e () con @ R e s p o n s e S t a tu s de esta forma:

@RequestMapping(
method=RequestMethod. POST
consum es="application/json")
@ResponseStatus(HttpStatus. CREATED)
public S p ittle saveSpittle(@RequestBody S p ittle s p ittle ) {
return s p ittle R e p o s ito ry .s a v e (s p ittle );
}
Debería bastar con esto. Ahora el código de estado refleja con precisión lo sucedido.
Indica al cliente que se ha creado un recurso. Problema resuelto.
474 Capítulo 16

Pero falta un detalle. El cliente sabe que se ha creado algo pero podría estar interesado
en saber dónde se ha creado el recurso. Después de todo es un nuevo recurso con una
nueva URL asociada. ¿Tiene que adivinarla el cliente? ¿O debería comunicársela de alguna
forma?
Al crear un nuevo recurso, se considera recomendable comunicar la URL del recurso
al cliente en el encabezado L o c a t i o n de la respuesta. Por lo tanto, necesita una forma
de com pletar los encabezados de respuesta. Su viejo amigo R e s p o n s e E n t i t y llega al
rescate.
El siguiente código m uestra una nueva versión de s a v e S p i t t l e () que devuelve
R e s p o n s e E n t i t y para com unicar que se ha creado un nuevo recurso.

Listado 16.4. Establecimiento de encabezados en la respuesta al devolver ResponseEntity.


@RequestMapping{
method=RequestMethod.POST
consumes="application/j son")
public ResponseEntity<Spittle> sa v e S p ittle (
@RequestBody S p ittle s p ittle ) {

S p ittle s p it t le = sp ittle R ep o sito ry .sav e( s p i t t l e ) ; / / Recuperar s p it t le .

HttpHeaders headers = new HttpHeaders()/ / / E stablecer encabezado de ubicación.


URI lo catio n ü ri = URI.c r e a te (
"h ttp : / /lo c a lh o s t:8080/s p i t t r / s p i t t l e s / " + s p i t t le .g e t l d ( ) ) ;
headers. se tL o ca tio n (lo ca tio n ü ri);

ResponseEntity<Spittle> responseEntity = / / Crear ResponseEntity.


new R esponseEntity<Spittle>(
s p it t le , headers, H ttpStatus. CREATED)
return responseEntity;

En esta nueva versión, se crea una instancia de H ttp H e a d e rs para incluir los valores
de encabezado que desee en la respuesta. H ttp H e a d e rs es una implementación especial
de M u lt iV a lu e M a p < S t r in g , S t r i n g > con varios métodos de establecimiento (como
s e t L o c a t i o n () ) para establecer encabezados HTTP comunes. Tras calcular la URL del
nuevo recurso S p i t t l e , se usan los encabezados para crear R e s p o n s e E n t it y .
¡Vaya! El sencillo método s a v e S p i t t l e () ha dado el estirón. Lo más preocupante, sin
embargo, es que calcula el valor del encabezado L o c a tio n mediante valores predefinidos.
La parte l o c a l h o s t y 8 0 8 0 del URI son especialmente preocupantes, ya que no se pueden
aplicar si la aplicación se implementa en otro sistema distinto al local.
En lugar de crear manualmente el URI, Spring le ofrece U r iC o m p o n e n ts B u ild e r , una
clase que le permite generar una instancia de U riC o m p o n en ts especificando los distintos
componentes del URI (como el host, el puerto, la ruta y la consulta) de forma individual.
A partir del objeto U riC o m p o n en ts generado por U r iC o m p o n e n ts B u ild e r podemos
obtener una URI adecuada para establecer el encabezado L o c a t io n .
Para usar U r iC o m p o n e n ts B u ild e r basta con solicitarlo como parámetro del método
de controlador, como se muestra a continuación.
Crear API REST con Spring MVC 475

Listado 16.5. Uso de UriComponentsBuilder para crear el URI de ubicación.

@RequestMapping(
method=RequestMethod. POST
consumes="ap p licatio n /j son")
public ResponseEntity<Spittle> sav e S p ittle(
@RequestBody S p ittle s p it t le ,
UriComponentsBuilder ucb) { / / Dado un UriComponentsBuilder.. .

S p ittle s p it t le = s p ittle R e p o s ito ry .s a v e (s p ittle );

HttpHeaders headers = new HttpHeaders(); / / ca lcu la r e l URI de la ubicación.


URI locationU ri =
ucb.path("/ s p i t t l e s / ")
.p a th (S trin g .v a lu e O f(sp ittle .g e tld ()))
.b u ild ()
. to U ri( );
headers. setL o catio n (lo catio n U ri);

ResponseEntity<Spittle> responseEntity =
new R esponseEntity<Spittle>(
s p i t t le , headers, H ttpStatus. CREATED)
return responseEntity;

El U r iC o m p o n e n ts B u ild e r asignado al método de controlador está preconfigurado


con información conocida como el host, el puerto o el contenido del servlet. Obtiene estos
datos de la solicitud que sirve el método de controlador. Tras ello, el código genera el resto
de U riC o m p o n en ts estableciendo la ruta.
Comprobará que la ruta se crea en dos pasos. En el primero se invoca p a t h () para
establecerla en / s p i t t l e s / , la ruta base que procesa el controlador. Tras ello, el ID del
objeto S p i t t l e guardado se proporciona en una segunda invocación de p a t h ( ) . Como
habrá imaginado, cada invocación de p a t h () se basa en las anteriores.
Una vez establecida la ruta, se invoca el método b u i l d () para construir un objeto
U riC o m p o n e n ts. Después, la invocación de t o U r i () proporciona el URI del nuevo
recurso S p i t t l e .
La exposición de recursos en un API REST es solo un extremo de la conversación. No
tiene sentido publicar un API si nadie la puede utilizar. Por lo general, los clientes de un
API REST son aplicaciones móviles y de JavaScript, pero nada impide a una aplicación de
Spring consumir estos recursos. A continuación veremos cómo crear código de Spring que
funcione en el lado cliente de una interacción con REST.

Consumir recursos REST


El diseño de código que interactúe con un recurso REST como cliente puede resultar
tedioso. Imagine por ejemplo que tiene que crear un método para obtener el perfil de
Facebook de un usuario del API Graph de Facebook. El código para obtener los datos del
perfil es más complicado, como se muestra a continuación.
476 Capítulo 16

Listado 16.6. Obtención de un perfil de Facebook con el diente HTTP de Apache.

public Profile fetchFacebookProfile(String id) {


try {
HttpClient client = HttpClients.createDefault()? // Crear el cliente.

HttpGet request = new HttpGet("http://graph.facebook.com/" + id);


// Crear solicitud.
request.setHeader("Accept", "application/json");

HttpResponse response = client.execute(request); // Ejecutar solicitud.

HttpEntity entity = response.getEntityO;


ObjectMapper mapper = new ObjectMapper(); // Asignar respuesta a objeto,
return mapper.readValue(entity.getContent(), Profile.class);
} catch (IOException e) {
throw new RuntimeException(e);

}
}

Como puede apreciar, el consumo de un recurso REST es complejo. Incluso haciendo


trampas utilizando el cliente HTTP de Apache para realizar la solicitud y el procesador
JSON Jackson para analizar la respuesta.
Si se fija en el método f e t c h F a c e b o o k P r o f i l e ( ) , comprobará que no hay mucho
que sea específico de la tarea de recuperación de un perfil de Facebook, la mayor parte es
código predefinido. Si escribiera otro método para consumir un recurso REST diferente,
seguramente compartiría gran parte del código de f e t c h F a c e b o o k P r o f i l e ( ) .
Es más, en algunos puntos se puede generar una excepción IO E x ce p t io n . Al tratarse de
una excepción comprobada, tendrá que capturarla o generarla. En este caso hem os optado
por capturarla y generar una excepción R u n tim e E x c e p t io n sin comprobar en su lugar.
Con tanto código predefinido en el consumo del recurso, pensará que sería recomen­
dable encapsular el código común y añadir las variantes como parámetros, precisamente
lo que hace R e s tT e m p la t e de Spring. Al igual que Jd b c T e m p la t e se encarga del trabajo
sucio con el acceso a datos JDBC, R e s tT e m p la t e le libera del tedio asociado al consumo
de recursos REST.
A continuación verem os cóm o m odificar el método f e t c h F a c e b o o k P r o f i l e () con
R e s tT e m p la t e para sim plificarlo y elim inar el código predefinido, pero antes veremos
las demás operaciones REST de nivel superior que ofrece R e s tT e m p la t e .

Operaciones de .RestTemplate
R e s tT e m p la t e define 36 métodos para interactuar con recursos REST y muchos de
ellos se asignan a métodos HTTR Lamentablemente, no dispongo de espacio suficiente para
examinarlos todos. Además, solo hay 11 operaciones exclusivas. 10 de ellas se sobrecargan
en tres variantes de método, mientras que la decimoprimera se sobrecarga seis veces para
conseguir un total de 36 métodos. La tabla 16.2 describe estas 11 operaciones exclusivas,
proporcionadas por R e s tT e m p la te ^ ,.
Crear API REST con Spring MVC 477

Tabla 16.2. RestTemplate define 11 operaciones exclusivas y cada una se puede sobrecargar para
conseguir un total de 36 métodos.

Metodo D escripción

delete() Ejecuta una solicitud delete de HTTP sobre un recurso en una URL
especificada.
exchange() Ejecuta un método HTTP especificado sobre una URL, devolviendo un
ResponseEntity que contiene un objeto asignado del cuerpo de la
respuesta.
execute() Ejecuta un método HTTP especificado sobre una URL, devolviendo un
objeto asignado con el cuerpo de la respuesta.
getForEntity() Envía una solicitud g e t de HTTP, devolviendo un ResponseEntity que
contiene un objeto asignado del cuerpo de la respuesta.
getForObject() Envía una solicitud g e t de HTTP, devolviendo un objeto asignado con el
cuerpo de la respuesta.
headForHeaders Envía una solicitud h e a d de HTTP y devuelve los encabezados HTTP de
la URL especificada.
optionsForAllow() Envía una solicitud o p t i o n s de HTTP, devolviendo el encabezado Allow
para la URL especificada.
postForEntity() Publica datos mediante p o s t en una URL, devolviendo un ResponseEntity
que contiene un objeto asignado del cuerpo de la respuesta.
postForLocation() Publica datos mediante POST en una URL, devolviendo la URL del nuevo
recurso.
postForObj e c t () Publica datos mediante p o s t ,devolviendo un objeto asignado del cuerpo
de la respuesta.
put () Incluye datos de recurso en la URL especificada mediante put.

Aexcepción de TRACE, R e s tT e m p la t e abarca todos los verbos HTTP con sus métodos.
Además, e x e c u t e () y e x c h a n g e () ofrecen métodos de propósito general de nivel inferior
para utilizar cualquiera de los métodos HTTP.
Cada una de las operaciones de la tabla 16.2 se divide en tres formas de método:
• La que acepta j a v a . n e t . URI como especificación de URL sin compatibilidad con
URL con parámetros.
• La que acepta la especificación de URL S t r in g con parámetros de URL especificados
como Map.
• La que acepta la especificación de URL S t r i n g con parámetros URL especificados
como lista de argumentos de variable.

Ahora que ya conoce las 11 operaciones que proporciona R e s tT e m p la t e y la forma


en que funciona cada una de sus variantes, estará preparado para crear clientes REST que
utilicen recursos.
478 Capítulo 16

Vamos a examinar las operaciones de R e s t T e m p la t e fijándonos en aquellas que


admiten los cuatro métodos HTTP principales: GET, PUT, DELETE y POST. Empezaremos
por g e t F o r O b j e t c () y g e t F o r E n t i t y ( ) , los métodos GET.

Obtener recursos con GET


Comprobará que en la tabla 16.2 se incluyen dos tipos de métodos para realizar soli­
citudes GET: getForObj ect () y getForEntity (). Como se ha mencionado con ante­
rioridad, cada uno de estos métodos se sobrecarga en tres formas. Las firmas de los tres
métodos getForObj ect () son las siguientes:
<T> T getForObject(URI url, Class<T> responseType)
throws RestClientException;
<T> T getForObject(String url, Class<T> responseType,
Object... uriVariables) throws RestClientException,-
<T> T getForObject(String url, Class<T> responseType,
Map<String, ?> uriVariables) throws RestClientException;

De forma similar, las firmas de los métodos g e t F o r E n t i t y () son las siguientes:


<T> ResponseEntity<T> getForEntity(URI url, Class<T> responseType)
throws RestClientException;
<T> ResponseEntity<T> getForEntity(String url, Class<T> responseType,
Object... uriVariables) throws RestClientException,-
<T> ResponseEntity<T> getForEntity(String url, Class<T> responseType,
Map<String, ?> uriVariables) throws RestClientException,-

Exceptuando el tipo de devolución, los métodos g e tF o r O b j e c t s () son copias exactas


de los métodos g e t F o r E n t i t y ( ) , y funcionan de forma muy similar. Ambos realizan
una solicitud GET, obteniendo un recurso si se proporciona una URL, y ambos asignan ese
recurso a una instancia de un tipo especificado por el parámetro r e s p o n s e T y p e . La única
diferencia es que g e t F o r O b j e c t () se limita a devolver un objeto para el tipo solicitado,
mientas que g e t F o r E n t i t y () devuelve ese objeto junto con información adicional sobre
la respuesta.
Nos centraremos primero en el método g e t F o r O b j e c t () para después ver cómo
obtener más información de una respuesta GET por medio de g e t F o r E n t i t y ( ) .

Recuperar recursos
El método g e t F o r O b j e c t () es una opción básica para recuperar un recurso. Basta
con solicitar un recurso y, a cambio, se recibe uno asignado a un tipo Java de su elección.
A modo de ejemplo sencillo de lo que g e t F o r O b j e c t () puede hacer, demos otro paso
en la implementación de f e t c h F a c e b o o k P r o f i l e ():
public Profile fetchFacebookProfile(String id) {
KestTemplate rest = new RestTemplate();
return rest.getForObject("http://graph.facebook.com/{spitter}",
Profile.class, id);
i
Crear API REST con Spring MVC 479

En el listado 16.6, f e t c h F a c e b o o k P r o f i l e () contaba con más de una docena de


líneas de código. Al utilizar R e s tT e m p la t e , hemos podido reducir su número (e, incluso,
podrían ser m enos si no tuviera que ajustarlas a los márgenes del libro).
f e t c h F a c e b o o k P r o f i l e () comienza creando una instancia de R e s tT e m p la t e (una
implementación alternativa podría utilizar, en su lugar, una instancia inyectada). A conti­
nuación, invoca el método g e t F o r O b j e c t () para recuperar un perfil de Facebook. De
esa forma solicita los resultados como objeto P r o f i l e . Después de recibirlo, lo devuelve
al invocador.
Tenga en cuenta que en esta nueva versión de f e t c h F a c e b o o k P r o f i l e () no utili­
zamos la concatenación de elementos S t r i n g para generar la URL. En su lugar, sacamos
partido al hecho de que R e s t T e m p la t e acepta URL con parámetros. El marcador de
posición { i d } de la URL va a incluir el parámetro id del método. El último argumento
de g e t F o r O b j e c t () es una lista de variables, donde cada argumento se inserta en un
marcador de posición en la URL especificada en el orden en que aparece.
De form a alternativa, podríam os haber incluido el parámetro i d en un Map con la clave
id , y proporcionar ese Map como último parámetro de g e t F o r O b j e c t ():

public Spittlef] fetchFacebookProfile(String id) {


Map<String, String> urlVariables = new HashMap<String, String();
urlVariables.put("id", id)?
RestTemplate rest = new RestTemplate();
return rest.getForObject("http://graph.facebook.com/{spitter}",
Profile.class, urlVariables);
}
En este código falta algún tipo de análisis JSON o de asignación de objetos. A nivel
interno, g e t F o r O b j e c t () convierte el cuerpo de la respuesta en un objeto para nosotros.
Para ello, se basa en el mismo conjunto de conversores de mensaje HTTP de la tabla 16.1,
que Spring MVC utiliza para los métodos de controlador anotados con @ R esp o n seB o d y .
Lo que también falta en este método es algún tipo de gestión de excepciones. No se debe
a que g e t F o r O b j e c t () no pueda generar una excepción, sino a que cualquier excepción
que genera no está comprobada. Si se produce algún problema con g e t F o r O b j e c t ( ) ,
se genera una excepción R e s t C l i e n t E x c e p t i o n (o una subclase de la misma). Puede
capturarla si lo desea, aunque el compilador no le obliga a hacerlo.

Extraer metadatos de la respuesta


Como alternativa a g e t F o r O b j e c t ( ) , R e s tT e m p la t e también cuenta con g e t F o -
r E n t i t y () . Estos métodos son muy similares a g e tF o r O b j e c t (). Sin embargo, mientras
que g e t F o r O b j e c t () solo devuelve el recurso (convertido en un objeto de Java mediante
un conversor de mensajes HTTP), g e t F o r E n t i t y () devuelve ese mismo objeto dentro de
R e s p o n s e E n t it y , que también incluye información adicional sobre la respuesta, como
por ejemplo el código de estado HTTP y encabezados de respuesta.
Una de las cosas que puede querer hacer con R e s p o n s e E n t i t y es recuperar el valor
de uno de los encabezados de respuesta. Por ejemplo, suponga que además de recuperar
el recurso, quiere saber cuándo se modificó por última vez.
480 Capítulo 16

Si asu m im os que el serv id o r p ro p o rcio n a esa in fo rm ació n en el en cabezad o


L a s tM o d if ie d , puede utilizar el m étodo g e tH e a d e r s () de la siguiente manera:

Date lastM odified = new Date(response.getHeaders( ) .getLastM odified( ) ) ;

El m étodo g e t H e a d e r s () devuelve un objeto H ttp H e a d e rs que proporciona varios


m étodos adecuados para recuperar encabezados de respuesta, incluido g e t L a s t M o d i-
f i e d ( ) , que devuelve el número de milisegundos transcurrido desde el 1 de enero de 1970.
Además de g e t L a s t M o d if i e d ( ) , H ttp H e a d e rs incluye los siguientes métodos para
recuperar información de encabezado:
public List<MediaType> getAcceptO { . . . }
public List<Charset> getAcceptCharset() { . . . }
public Set<HttpMethod> getAllowO { . . . }
public Strin g getCacheControl() { . . . }
public L ist<Strin g > getConnection() { . . . }
public long getContentLength() { . . . }
public MediaType getContentType() { . . . }
public long getDateO { . . . }
public Strin g getETagO { . . . }
public long getExpiresí) { . . . }
public long getlfNotM odifiedSince() { . . . }
public L ist<Strin g > getlfNoneMatch() { . . . }
public long getLastM odified() { . . . }
public URI getLocation() { . . . }
public Strin g getOriginO { . . . }
public String getPragmaO { . . . }
public String getüpgradeO { . . . }

Para el acceso a encabezados HTTP de propósito general, H ttp H e a d e rs incluye los


métodos g e t () y g e t F i r s t ( ) . Ambos aceptan un argumento S t r i n g que identifica la
clave del encabezado deseado. El método g e t () devuelve una lista de valores S t r i n g ,
uno para cada valor asignado al encabezado. El método g e t F i r s t () solo devuelve el
primer valor del encabezado. Si le interesa el código de estado HTTP de la respuesta, tendrá
que invocar el método g e t S t a t u s C o d e () . Por ejemplo, fíjese en el siguiente método que
recupera un objeto S p i t t l e :
public S p ittle fe tc h S p ittle (lo n g id) {
RestTemplate r e s t = new RestTemplate();
ResponseEntity<Spittle> response = re st.g e tF o rE n tity (
"h ttp ://lo c a lh o s t: 8 0 8 0 /s p it tr - a p i/ s p itt le s / { id } ",
S p i t t le . c la s s , id );
if(response.getStatusC od e() == HttpStatus.NOT_MODIFIED) {
throw new NotModifiedException();
}
return response. getBody();
i
En este caso, si el servidor responde con el código de estado 3 04, indica que el contenido
del servidor no se ha m odificado desde la solicitud anterior del cliente. Así pues, se genera
una excepción N o tM o d if i e d E x c e p t i o n para indicar que el cliente debe comprobar si el
objeto S p i t t l e está en la caché.
Crear API REST con Spring MVC 481

Añadir recursos coo PUT


Para realizar operaciones PUT sobre un recurso, R e s tT e m p la t e ofrece un conjunto
sencillo de tres métodos p u t ( ). Al igual que el resto de sus métodos, el método p u t ()
cuenta con tres variantes:
void p u t(URI u rl, Object request) throws RestClientException;
void p u t(String u rl, Object request, O b je c t... uriV ariables)
throws RestClientException;
void pu t(String u rl, Object request, Map<String, ?> uriV ariables)
throws RestClientException;

En su forma más sencilla, el método p u t () acepta un j a v a . n e t . URI que identifica


(y localiza) el recurso que se está enviando al servidor, así como un objeto que es la repre­
sentación Java de ese recurso. Por ejemplo, esta es la forma en que utilizaríamos la versión
basada en URI de p u t () para actualizar un recurso S p i t t l e en el servidor:
public void u p d a teS p ittle(S p ittle s p ittle ) throws SpitterException {
RestTemplate re st = new RestTemplate();
Strin g u rl = "h ttp : //lo c a lh o s t: 8 0 8 0 /s p it tr - a p i/ s p itt le s /"
+ s p i t t le .g e t l d ();
rest.p u t(U R I. c r e a te (u r l), s p i t t l e ) ;
}

En este caso, aunque la firma del método es sencilla, la implicación de utilizar un


argumento j a v a . n e t . URI es evidente. Para crear la URL para que el objeto S p i t t l e se
actualice, es necesaria una concatenación de elementos S t r i n g .
Como ya hem os visto con g e t F o r O b j e c t () y g e t F o r E n t i t y ( ) , el uso de uno de
los otros métodos p u t () basados en S t r i n g nos evita la mayor parte de las molestias
asociadas a la creación de un URI. Estos métodos nos permiten especificar el URI como
plantilla y conectar los valores para las partes variables. Aquí podemos ver un nuevo método
u p d a t e S p i t t l e () reescrito para utilizar uno de los m étodos p u t () basado en S t r i n g :
public void u p d a teS p ittle(S p ittle s p ittle ) throws SpitterException {
RestTemplate re s t = new RestTemplate();
r e s t.p u t("h ttp : / /lo c a lh o s t: 8 0 8 0 /s p it tr - a p i/ s p itt le s / { id } ",
s p it t le , s p i t t le .g e t l d ( ) ) ;
}

Ahora, el URI se expresa como una sencilla plantilla S t r i n g . Cuando R e s tT e m p la t e


envía la solicitud PUT, la plantilla de URI se amplía para sustituir el fragmento { i d }
por el valor devuelto por s p i t t l e . g e t l d () . Como sucedía con g e t F o r O b je c t () y
g e t F o r E n t i t y ( ) , el último argumento de esta versión de p u t () es una lista variable de
argumentos, cada uno de los cuales se asigna a las variables de marcador de posición en
el orden en que aparecen.
De forma opcional, podría proporcionar las variables de plantilla como un Map:
public void u p d a teS p ittle(S p ittle s p ittle ) throws SpitterException {
RestTemplate re s t = new RestTemplate( );
Map<String, String> params = new HashMap<String, S trin g > ();
482 Capítulo 16

params.put("id" , s p it t le .g e t I d ( ) ) ;
r e s t.p u t("h ttp : //lo c a lh o s t: 8 0 8 0 /s p it tr - a p i/ s p itt le s / { id }",
s p i t t le , params);

Al utilizar Map para enviar las variables de plantilla, la clave de cada entrada de Map
se corresponde a la variable de marcador de posición con el mismo nombre en la plantilla
de URL
En todas las versiones de p u t ( ) , el segundo argumento es el objeto Java que representa
el recurso que se incluye en el servidor mediante PUT en el URI dado. En este caso, se trata
de un objeto S p i t t l e . R e s tT e m p la t e utiliza uno de los conversores de mensajes de la
tabla 16.1 para convertir el S p i t t l e en una representación que se envía al servidor en el
cuerpo de la solicitud.
El tipo de contenido al que se va a convertir el objeto depende en gran parte del tipo propor­
cionado a p u t ( ) . Si se proporciona un valor S t r i n g , S trin g H ttp M e s s a g e C o n v e rte r
entra en acción y el valor se escribe directamente en el cuerpo de la solicitud y el
tipo de contenido se configura como t e x t / p l a i n . Cuando se proporciona
M u lt iV a lu e M a p < S tr in g , S t r i n g > , los valores de la asignación se escriben en el
cuerpo de la solicitud con el formato a p p l ic a t i o n / x - f o r m - u r le n c o d e d por parte de
H ttp M e ssa g e C o n v erter.
Como estamos transmitiendo un objeto S p i t t l e , vamos a necesitar un conversor de
mensajes que pueda trabajar con objetos arbitrarios. Si la biblioteca Jackson 2 se encuentra en
la ruta de clases, M a p p in g Ja c k s o n 2 H ttp M e s s a g e C o n v e r te r escribe el objeto S p i t t l e
en la solicitud como a p p l i c a t i o n / j son.

Eliminar recursos con DELETE


Si queremos eliminar un recurso del servidor, vamos a utilizar los métodos d e l e t e ()
de R estT em p la te. Al igual que sucede con los métodos p u t ( ) , cuentan con tres versiones,
cuyas firmas se muestran a continuación:
void d e le te (String u rl, O b je c t... uriV ariables)
throws RestClientException;
void d e le te (String u rl, Map<String, ?> uriV ariables)
throws RestClientException;
void d e le te (URI u rl) throws RestClientException;

Los métodos d e l e t e () son los más sencillos de todos los de R e s tT e m p la te . Lo


único que debe proporcionarles es el URI del recurso que se va a eliminar. Por ejemplo,
para eliminar un objeto S p i t t l e cuyo ID conocemos, puede invocar d e l e t e () de la
siguiente manera:
public void d eleteS p ittle (lo n g id) {
RestTemplate r e s t = new RestTemplate( );
r e s t . d e le te (
URI. c r e a te ("h tt p :/ /lo c a lh o s t: 8 0 8 0 /s p it tr - a p i/s p itt le s /" + id) ) ;
}
Crear API REST con Spring MVC 483

Muy sencillo, pero de nuevo nos hemos basado en la concatenación de elementos S t r i n g


para crear un objeto URI. Por tanto, vamos a recurrir a una de las versiones más sencillas
de d e l e t e () para evitar este problema:
public void d ele te S p ittle (lo n g id) {
RestTemplate r e s t = new RestTemplate();
r e s t . d e le te ("h ttp : //lo c a lh o s t: 8 0 8 0 /s p it tr - a p i/ s p itt le s / { id } ”, id ) ) ;
}
Mucho mejor ¿no cree?
Después de ver los métodos más sencillos de R e s tT e m p la t e , nos centraremos en los
más diversos, los que admiten solicitudes POST de HTTP.

Publicar datos de recursos con POST


Si echamos un vistazo de nuevo a la tabla 16.2, podemos ver que R e s tT e m p la t e cuenta
con tres tipos de métodos diferentes para enviar solicitudes POST. Si los multiplicam os
por las tres variantes de cada uno, eso hace un total de nueve métodos para publicar datos
en el servidor.
Dos de estos métodos tienen nombres que le resultarán familiares. p o s tF o r O b j e c t ()
y p o s t F o r E n t i t y () trabajan con solicitudes POST de forma similar a los métodos
g e tF o r O b j e c t () y g e t F o r E n t i t y () para el envío de solicitudes GET. El otro método,
g e t F o r L o c a t i o n ( ) , es exclusivo para solicitudes POST.

Recibir respuestas de objeto desde solicitudes POST


Digamos que está utilizando R e s tT e m p la t e para publicar, con un método POST, un
nuevo objeto Sp i t t e r en el API de REST de la aplicación Spittr. Como se trata de un objeto
S p i t t e r completamente nuevo, el servidor aún no sabe nada sobre él. Por tanto, todavía
no es un recurso REST y no cuenta con una URL. Asimismo, el cliente no va a saber el ID
de S p i t t e r hasta que se cree en el servidor.
U na form a de publicar un recurso en el servidor es u tilizar el m étodo p o s t F o r
Ob j e c t () de R e s tT e m p la t e . Las tres variantes de p o s tF o r O b j e c t () cuentan con las
siguientes firmas:
<T> T postForObject(URI u rl, Object request, Class<T> responseType)
throws RestClientException;
<T> T postForO bject(String u r l, Object request, Class<T> responseType,
O b je c t... uriV ariables) throws RestClientException;
<T> T postForO bject(String u r l, Object request, Class<T> responseType,
Map<String, ?> u riV ariables) throws RestClientException;

En todos los casos, el primer parámetro es la URL en la que debe publicarse el recurso,
el segundo es el objeto que se va a publicar y el tercero es el tipo Java que se espera recibir.
En el caso de las dos versiones que aceptan la URL como S t r i n g , tenemos un cuarto pará­
metro que identifica las variables de URL (como lista de argumentos variable o como Map).
484 Capítulo 16

Al publicar nuevos recursos S p i t t e r en el API REST de Spitter con POST, deben


publicarse en la URL h t t p : / / l o c a l h o s t : 8 0 8 0 / s p i t t r - a p i / s p i t t e r s , donde un
controlador de gestión POST va a guardar el objeto. Como esta URL no requiere variables
de URL, podríamos utilizar cualquier versión de p o s tF o r O b j e c t (). Sin embargo, para
mantener la sencillez del ejemplo, lo invocaremos de esta forma:
public S p itte r p o stSp itterF o rO b ject(Sp itter s p itte r) {
RestTemplate re s t = new RestTemplate();
return r e s t .postForO bject("h tt p ://lo c a lh o s t: 8 0 8 0 /s p ittr -a p i/s p itte r s " ,
s p itte r , S p itt e r . c l a s s ) ;
i
Al método p o s t S p i t t e r F o r O b j e c t () se le proporciona un nuevo objeto S p i t t e r
y se utiliza p o s tF o r O b j e c t () para enviarlo al servidor. Como respuesta, se recibe un
objeto S p i t t e r , que se devuelve al invocador.
Como sucede con los métodos g e tF o r O b j e c t ( ) , puede que queramos examinar parte
de los metadatos que incluye la solicitud. En ese caso, el mejor método para esta tarea es
p o s t F o r E n t i t y () . Incluye una serie de firmas similares a las de p o s tF o r O b j e c t ():
<T> ResponseEntity<T> postForEntity(URI u rl, Object request,
Class<T> responseType) throws RestClientException,-
<T> ResponseEntity<T> postForE ntity(String u rl, Object request,
Class<T> responseType, O b je c t... uriV ariables)
throws RestClientException;
<T> ResponseEntity<T> postForE ntity(String u rl, Object request,
Class<T> responseType, Map<String, ?> uriV ariables)
throws RestClientException;

Imagine que además de recibir el recurso S p i t t e r , también queremos ver el valor del
encabezado L o c a t i o n en la respuesta. En este caso, podemos invocar p o s t F o r E n t i t y ()
de la siguiente manera:
RestTemplate re s t = new RestTemplate();
ResponseEntity<Spitter> response = re st.p o stF o rE n tity (
"h ttp : //lo c a lh o s t: 8 0 8 0 /s p ittr -a p i/s p itte r s " ,
s p itte r , S p itt e r . c l a s s ) ;
S p itte r s p itte r = response.getBody();
URI u rl = response .getHeaders () .getLocationO ;

Al igual que el método g e t F o r E n t i t y ( ) , p o s t F o r E n t i t y () devuelve un objeto


R e s p o n s e E n t i t y < T > . A partir de este objeto, podemos invocar g e tB o d y () para obtener
el objeto de recurso (en este caso, S p i t t e r ) . Asimismo, el método g e tH e a d e r s () propor­
ciona un elemento H ttp H e a d e r s con el que se puede acceder a los diferentes encabezados
HTTP devueltos en la respuesta. En este caso, invocamos g e t L o c a t i o n () para recuperar
el encabezado L o c a t i o n como j a v a . n e t .U R I.

Recibir la ubicación de un recurso tras una solicitud POST


El método p o s t F o r E n t i t y () es muy útil para recibir tanto el recurso publicado como
cualquier encabezado de respuesta. Sin embargo, a menudo, no va a necesitar que el recurso
se envíe de vuelta (después de todo, se ha encargado de enviarlo al servidor). Si el valor
Crear API REST con Spring MVC 485

del encabezado L o c a t i o n es todo lo que necesita, es mucho más fácil utilizar el método
p o s t F o r L o c a t i o n () de R e s tT e m p la t e . Como sucede con el resto de métodos POST,
p o s t F o r L o c a t i o n () envía un recurso al servidor en el cuerpo de una solicitud POST. Sin
embargo, en lugar de responder con ese mismo objeto de recurso, p o s t F o r L o c a t i o n ()
responde con la ubicación del recurso recién creado. Cuenta con las tres siguientes firmas
de método:
URI postForLocation(String u rl, Object request, O b je c t... uriV ariables)
throws RestClientException;
URI postForLocation(
String u rl, Object request, Map<String, ?> uriV ariables)
throws RestClientException;
URI postForLocation(URI u rl, Object request) throws RestClientException;

Para mostrar el funcionamiento de p o s t F o r L o c a t i o n ( ) , volveremos a publicar un


objeto S p i t t e r . En esta ocasión, queremos obtener la URL del recurso:
public String p o stS p itte r(S p itte r s p itte r) {
RestTemplate re s t = new RestTemplate () ,-
return rest.p o stF orL o cation (
"h ttp : //lo c a lh o s t: 8 0 8 0 /s p ittr -a p i/s p itte r s " ,
s p itte r) .to Strin g O ;
}
En este caso, proporcionam os la URL de destino como elemento S t r i n g junto al objeto
S p i t t e r que se va a publicar (no hay variables de URL en este caso). Si después de crear
el recurso, el servidor responde con su nueva URL en el encabezado L o c a t i o n de la
respuesta, p o s t F o r L o c a t i o n () devolverá esa URL como elemento S t r i n g .

Intercambiar recursos
Hasta el momento hemos visto diferentes métodos para obtener, incluir, eliminar y publicar
recursos. Entre ellos, hemos visto dos métodos especiales, g e t F o r E n t i t y () y p o s t F o r
E n t i t y ( ) , que nos proporcionan el recurso resultante dentro de un R e q u e s t E n t i t y del
que podíamos obtener encabezados de respuesta y códigos de estado.
La posibilidad de leer los encabezados de la respuesta es útil. Sin embargo, ¿qué ocurre
si queremos configurarlos en la solicitud enviada al servidor? Es la función de los métodos
e x c h a n g e () de R e s tT e m p la t e .
Al igual que el resto de métodos de R e s tT e m p la te , e x ch a n g e () cuenta con tres firmas:
<T> ResponseEntity<T> exchange(URI u rl, HttpMethod method,
HttpEntity<?> requestE ntity, Class<T> responseType)
throws RestClientException;
<T> ResponseEnfcity<T> exchange(String u rl, HttpMethod method,
HttpEntity<?> requestEntity, Class<T> responseType,
O b je c t... u riV ariables) throws RestClientException;
<T> ResponseEntity<T> exchange(String u rl, HttpMethod method,
HttpEntity<?> requestEntity, Class<T> responseType,
Map<String, ?> u riV ariables) throws RestClientException;
486 Capítulo 16

El método ex ch an g e () también acepta un parámetro H ttp M eth o d para indicar el


verbo HTTP que debe utilizarse. En función del valor asignado a este parámetro, el método
e x ch a n g e () puede realizar las mismas tareas que cualquiera de los demás métodos de
R e s tT e m p la t e . Por ejemplo, una forma de recuperar un recurso S p i t t e r del servidor
es utilizar el método g e t F o r E n t i t y () de R e s tT e m p la t e de la siguiente manera:
ResponseEntity<Spitter> response = re st.g e tF o rE n tity (
"h tt p ://lo c a lh o s t: 8080/s p it t r - a p i/ s p i t t e r s / { s p i t t e r } ",
S p itt e r . c l a s s , s p itt e r ld ) ;
S p itte r s p itte r = response.getBodyO;

Como puede ver a continuación, ex ch an g e () también está a la altura de la tarea:


ResponseEntity<Spitter> response = r e s t . exchange(
"h ttp : //lo c a lh o s t: 8 0 8 0 /s p it tr - a p i/ s p itt e r s / {s p itt e r } ",
HttpMethod.GET, n u il, S p itt e r . c la s s , s p itte r ld );
S p itte r s p itte r = response.getBody();
Al proporcionar H ttp M e th o d . GET como verbo HTTP, solicitamos a exch an g e () que
envíe una solicitud GET. El tercer argumento sirve para enviar un recurso en la solicitud
pero, al tratarse de una solicitud GET, su valor puede ser n u i l . El siguiente argumento
indica que queremos convertir la respuesta en un objeto S p i t t e r . Por último, el argumento
final es el valor que vamos a incluir en el marcador de posición { s p i t t e r } de la plantilla
de URL especificada. Si se utiliza de esta forma, el método ex ch an g e () es prácticamente
idéntico a g e t F o r E n t i t y ( ) . Sin embargo, y a diferencia de éste (o de g e t F o r O b je c t ()),
e x c h a n g e () nos va a permitir configurar encabezados en la solicitud enviada. En lugar
de proporcionar un valor n u i l a ex ch an g e ( ) , vamos a proporcionar un H t t p E n t i t y
creado con los encabezados de solicitud que queramos. Si no se especifican los encabezados,
e x c h a n g e () envía la solicitud GET de un objeto S p i t t e r con los siguientes encabezados:
GET / Spitter/spitters/habum a HTTP/1.1
Accept: application/xm l, text/xm l, application/*+xm l, a p p lication /jso n
Content-Length:0
User-Agent: Ja v a /1 . 6 . 0_20
Host: lo c a lh o st:8080
Connection: keep-alive
Fíjese en el encabezado A c c e p t. Indica que puede aceptar diferentes tipos de conte­
nido XML, así como a p p l i c a t i o n / J s o n . Esto deja bastante espacio para que el servidor
decida en qué formato va a enviar de vuelta el recurso. Supongamos que queremos solicitar
al servidor que envíe la respuesta en formato JSON. En ese caso, tenemos que especificar
a p p l i c a t i o n / j so n como único valor del encabezado A c c e p t.
Para configurar encabezados de solicitud solo tenemos que crear el H t t p E n t i t y
enviado a e x c h a n g e ( ) , con un M u ltiV a lu e M a p cargado con los encabezados deseados:
MultiValueMap<String/ String> headers=
new LinkedMultiValueMap<String, S trin g > ();
headers. add("Accept", "a p p lica tio n /j son");
HttpEntity<Object> requestEntity = new H ttpEntity<O bject>(headers);
En este caso hemos creado L in k e d M u lt iV alu eM ap y añadido un encabezado A c c e p t
configurado como a p p l i c a t i o n / j son. A continuación, creamos un H t t p E n t i t y (con un
tipo genérico Ob j e c t ) , proporcionando M u ltiV alu eM ap como argumento de constructor.
Crear API REST con Spring MVC 487

Si fuera una solicitud PUT o POST, también incluiríamos en H t t p E n t i t y un objeto para


enviarlo con el cuerpo de la solicitud (para una solicitud GET no es necesario). Ahora
podemos invocar e x c h a n g e () proporcionándolo en H t t p E n t i t y :
ResponseEntity<Spitter> response = r e s t . exchange(
"h ttp : //lo c a lh o s t: 8 0 8 0 /s p it tr - a p i/ s p itt e r s / {s p itt e r }",
HttpMethod.GET, requestE ntity, S p itt e r . c la s s , s p itt e r ld ) ;
S p itte r s p itte r = response.getBody();

Asimple vista, el resultado debería ser el mismo. Vamos a recibir el objeto S p i t t e r que
hemos solicitado. Sin embargo, a nivel interno, la solicitud se va a enviar con los siguientes
encabezados:
GET /Spitter/sp itters/habum a HTTP/1.1
Accept: ap p lication /jso n
Content- Length: 0
User-Agent: Ja v a /1 . 6 . 0_20
Host: lo c a lh o st:8080
Connection: keep-alive

Asimismo, asumiendo que el servidor pueda señalizar la respuesta S p i t t e r en JSON,


el cuerpo de la respuesta debería representarse en formato JSON.

Resumen
La arquitectura REST recurre a los estándares Web para integrar aplicaciones, haciendo
que sean sencillas y naturales. Los recursos de un sistema se identifican mediante URL, se
manipulan con métodos HTTP y se representan en uno o más formatos adecuados para
el cliente.
En este capítulo, hemos aprendido a crear controladores de Spring MVC que responden
a solicitudes para manipular recursos REST. Mediante el uso de patrones de URL con pará­
metros y asociando métodos de controlador a métodos HTTP específicos, los controladores
pueden responder a solicitudes GET, POST, PUT y DELETE de los recursos de una aplicación.
En respuesta a estas solicitudes, Spring puede representar los datos de estos
recursos en un formato adecuado para el cliente. Para las respuestas basadas en vistas,
C o n t e n t N e g o t i a t i n g V i e w R e s o l v e r puede seleccionar la mejor vista generada a
partir de distintos solucionadores de vistas para satisfacer el tipo de contenido deseado del
cliente. Otra posibilidad es anotar un método gestor de controlador con @ R esp o n seB o d y
para omitir por completo la resolución de vistas y contar con uno o varios conversores de
mensajes para transformar el valor devuelto en una respuesta para el cliente.
Los API REST exponen la funcionalidad de una aplicación al cliente, de formas que puede
que los diseñadores originales de API nunca imaginaran. En muchos casos, los clientes de un
API REST son aplicaciones móviles o de JavaScript ejecutadas en un servidor Web, pero las
aplicaciones de Spring también pueden consumir estos API por medio de R e s tT e m p la te .
Los recursos REST definidos en este capítulo forman parte de un API pública. Es decir, si
los implementara en una aplicación de Internet, no podría impedir que se creara un cliente
para utilizarlos. En el siguiente capítulo aprenderemos a bloquearlos y veremos formas de
proteger recursos REST para que solamente puedan ser consumidos por clientes autorizados.
Capítulo

17 M ensajería
en S p rin g

CONCEPTOS FUNDAMENTALES:

• Introducción a la mensajería asincrona.


• Mensajería con JMS.
• Envío de mensajes con Spring y AMQP.
• POJO controlados por mensajes.
Son las 16:55 del viernes. Está a unos minutos de comenzar sus vacaciones y tiene el
tiempo justo para llegar al aeropuerto y coger su vuelo. Sin embargo, antes de salir, tiene
que asegurarse de que su jefe y sus compañeros están informados sobre el estado de su
trabajo para que puedan seguir gestionándolo a partir del lunes. Lamentablemente, algunos
de sus compañeros ya han salido del trabajo y su jefe se encuentra en una reunión. ¿Qué
puede hacer?
Podría llamar a su jefe, aunque quizá no sea buena idea interrumpir una reunión impor­
tante solo para ponerle al día. Quizá podría esperarle hasta que volviese de la reunión, pero
nadie sabe cuánto va a durar. O podría dejar una nota adhesiva sobre el monitor... junto a
otras 100 más que tampoco verá.
La fo r m a m á s práctica de notificar su estado y de coger su avión a tiempo es enviar un
mensaje de correo electrónico a su jefe y a sus compañeros, indicando los detalles de su
trabajo y prometiendo enviar una postal. Usted no sabe dónde van a encontrarse o en qué
momento van a leer su mensaje. Lo que sí sabe es que volverán en algún momento a su
puesto y lo leerán. Quizá cuando se encuentre camino del aeropuerto.
En ocasiones es necesario hablar con alguien directamente. Si tiene un accidente y nece­
sita una ambulancia, probablemente lla m a r á p o r teléfono para pedir una (en estos casos,
enviar un correo electrónico quizá no sea la mejor idea). Sin embargo, en otros casos basta
con enviar un mensaje y plantea ciertas ventajas sobre la comunicación directa, como por
ejemplo permitirle coger su vuelo para irse de vacaciones.
Ya hemos visto cómo utilizar RMI, Hessian, Burlap, el invocador HTTP y los servicios
Web para permitir la comunicación entre aplicaciones. Todos estos mecanismos utilizan
comunicación síncrona, en la que una aplicación cliente contacta directamente con un
servicio remoto y espera a que el procedimiento remoto se complete antes de continuar.
La comunicación síncrona cumple un papel importante, aunque no es el único estilo de
comunicación disponible para los desarrolladores. La mensajería asincrona es un método
para enviar mensajes de forma indirecta desde una aplicación a otra sin tener que esperar
una respuesta. La mensajería asincrona ofrece varias ventajas sobre la síncrona, como
vamos a ver en breve. JMS (Java M essage Service, Servicio de mensajes de Java) es un API
estándar para mensajería asincrona. En este capítulo, vamos a ver cómo Spring simplifica
el envío y recepción de mensajes con este API y AMQP (A dvan ced M essag e Q ueuing P rotocol,
Protocolo avanzado de cola de mensajes). Además del envío y la recepción de mensajes,
vamos a examinar la compatibilidad de Spring con POJO basados en mensajes, una forma
de recibir mensajes similar a los Bean basados en mensajes (MUB) de EJB.

Breve introducción a Sa mensajería asincrona


De forma similar a los mecanismos remotos y a las interfaces REST que hemos visto
hasta ahora, JMS permite la comunicación entre aplicaciones, aunque se diferencia de estos
elementos en la forma en que la información se transfiere entre sistemas. Las opciones de
acceso remoto como RMI y Hessian/Burlap permiten una comunicación síncrona. Como
podemos ver en la figura 17.1, cuando el cliente invoca un método remoto, debe esperar a
que el método finalice antes de continuar. Incluso aunque el método remoto no devuelva
ningún elemento al cliente, éste va a estar a la espera hasta que el servicio finalice.
490 Capítulo 17

Flujo del
programa

Invocación
El cliente
espera -
er
<D
I o
Resultado

Figura 17.1. En la comunicación síncrona, el cliente debe esperar a que la operación finalice.

Por otro lado, cuando los mensajes se envían de forma asincrona (véase la figura 17.2),
el cliente no tiene que esperar a que el servicio procese el mensaje o, incluso, a que se
entregue. El cliente envía el mensaje y, a continuación, prosigue con su trabajo, asumiendo
que el servicio recibirá y procesará el mensaje en algún momento.

Flujo del
programa

Figura 17.2. La comunicación asincrona es una forma de comunicación sin esperas.

La comunicación asincrona ofrece varias ventajas sobre la comunicación síncrona. Vamos


a analizarlas en detalle, aunque antes veremos cómo se envían mensajes de forma asincrona.

Enviar mensajes
La mayoría asumimos que el servicio postal es algo que existe y funciona sin problemas.
A diario, millones de personas de todo el mundo ponen en manos de los trabajadores del
sector cartas, tarjetas y paquetes, confiando en que lleguen a su destino. El mundo es un
Mensajería en Spring 491

lugar demasiado grande para que podamos entregar estos elementos por nosotros mismos
y, por ese motivo, confiamos en el sistema postal. Para ello, indicamos la dirección en
nuestros envíos, pegamos los sellos correspondientes y, por último, los echamos al buzón,
sin pararnos a pensar en cómo llegan a su destino.
La clave del servicio postal es el direccionamiento. Cuando se acerca el cumpleaños de
la abuela, sería algo incómodo tener que entregarle una felicitación en mano. Dependiendo
de donde viva, tendríamos que conducir durante horas para efectuar el envío. Por fortuna,
el servicio postal realiza este trabajo por nosotros mientras continuamos con nuestra vida
diaria. De forma similar, el direccionamiento es la clave de la mensajería asincrona. Cuando
una aplicación envía información a otra, no existe un enlace directo entre ambas. En su
lugar, la aplicación remitente coloca el mensaje en manos de un servicio que va a asegurarse
de su entrega a la aplicación receptora.
En la mensajería asincrona hay dos actores principales: agentes de mensajes y destinos.
Cuando una aplicación envía un mensaje, lo pone en manos de un agente de mensajes,
similar a una oficina de correos. El agente de mensajes se asegura de que el mensaje se
entregue en el destino especificado, permitiendo que el remitente pueda dedicarse a otros
asuntos.
Al enviar una carta por correo, es importante incluir la dirección para que el servicio
postal sepa dónde debe entregarse. Del mismo modo, en mensajería asincrona los mensajes
se envían con un destino, que son como los buzones en los que se entregan los mensajes
hasta que alguien venga a recogerlos.
Sin embargo, a diferencia de las direcciones de correo, que pueden hacer referencia a
una persona específica o a una calle, los destinos son menos específicos. Solo se preocupan
de la ubicación en la que el mensaje se va a recoger y no de quién va a hacerlo. En este
sentido, los destinatarios son como esas cartas de publicidad con la leyenda: "Residente
en este domicilio".
Aunque cada opción de mensajería ofrece un sistema de direccionamiento de mensajes
distinto, hay dos tipos de destinos comunes: colas y temas. Cada uno se asocia a un modelo
de mensajería específico, que puede ser de punto a punto (para colas) o de publicación-
suscripción (para temas).

Mensajería punto a punto


En el modelo punto a punto, cada mensaje tiene un solo remitente y un solo destinatario
(véase la figura 17.3). Cuando el agente de mensajes recibe uno, lo añade a una cola. Cuando
el receptor solicita recibir el siguiente mensaje de la cola, éste se entrega al receptor. Como
el mensaje se elimina de la cola cuando se recoge, nos aseguremos de que solo se entrega
a un único receptor.

Figura 17.3. Una cola de mensajes separa al remitente del destinatario del mensaje. Aunque una
cola puede contar con varios destinatarios, cada mensaje va a ser recogido solo por un receptor.
492 Capítulo 17

Aunque cada mensaje de la cola se entrega a un solo receptor, no quiere decir que solo
uno pueda recoger mensajes de la cola. De hecho, lo más probable es que varios receptores
estén procesando mensajes de la cola, aunque cada uno de ellos solo recibe sus propios
mensajes.
Es algo parecido a esperar en la cola del banco. Mientras espera, puede que se dé cuenta
de que hay varios cajeros disponibles para ayudarle con su transacción financiera. Cuando
un cliente termina, el cajero llama al siguiente de la cola. Al llegar su turno, será atendido,
mientras que el resto de los cajeros asisten a otros clientes.
Otra observación sobre el ejemplo del banco es que, mientras hace cola, probablemente
no sepa qué cajero va a atenderle. Podría contar el número de personas que hay en la cola,
compararlo con el número de cajeros disponibles, fijarse en qué cajero es más rápido e
intentar imaginar qué persona va a atenderle. Sin embargo, lo más seguro es que se equi­
voque y termine atendiéndole una persona diferente a la que espera.
De forma similar, en la mensajería punto a punto, si tenemos varios receptores a la
escucha en una cola, no hay forma de saber cuál va a procesar un mensaje específico. Esta
incertidumbre es algo positivo, ya que permite a la aplicación ampliar la capacidad de
procesamiento de mensajes añadiendo otro escuchador a la cola.

Mensajería de publicación-suscripción
En el modelo de mensajería de publicación-suscripción, los mensajes se envían a un tema.
Al igual que con las colas, podemos tener varios receptores a la escucha de un tema. Sin
embargo, a diferencia de éstas, donde un mensaje se entrega a un solo receptor, todos los
receptores de un tema van a recibir una copia del mensaje, como se muestra en la figura 17.4.

Figura 17.4. Al igual que las colas, los temas separan a los remitentes de los destinatarios de
los mensajes. A diferencia de éstas, un mensaje de tema puede entregarse a varios suscriptores.

Como probablemente haya adivinado, el modelo de publicación-suscripción se parece


mucho al de una revista y sus suscriptores. La revista (el mensaje) se publica, se envía por
correo y, por último, todos los suscriptores reciben su copia. La comparación con la revista
deja de ser válida cuando nos damos cuenta de que el editor de la revista no tiene ni idea
Mensajería en Sprmg 493

de quiénes son los suscriptores. Solo sabe que se va a publicar un mensaje en un tema
concreto y no quién está a la escucha. Esto también implica que el editor desconoce cómo
se va a procesar el mensaje. Ahora que ya hemos visto los aspectos básicos de la mensajería
asincrona, vamos a compararla con la comunicación RPC síncrona.

Evaluar las ventajas de Sa mensajería asincrona


A pesar de su carácter intuitivo y de ser fácil de configurar, la comunicación síncrona
impone varias limitaciones sobre el cliente de un servicio remoto. Las más importantes son:

• La comunicación síncrona implica tener que esperar: Cuando un cliente invoca un


método sobre un servicio remoto, debe esperar a que el método remoto se complete
antes de poder continuar. Si el cliente se comunica con frecuencia con el servicio
remoto, o si el servicio remoto es lento en responder, esto podría afectar de forma
negativa al rendimiento de la aplicación cliente.
• El cliente está vinculado al servicio a través de la interfaz del servicio: Si ésta cambia,
todos los clientes del servicio también tendrán que modificarse, en consecuencia.
• El cliente está vinculado a la ubicación del servicio: El cliente debe configurarse
con la ubicación de red del servicio para que así sepa cómo ponerse en contacto con
éste. Si se modifica la topología de la red, el cliente tendrá que configurarse con la
nueva ubicación.
• El cliente está vinculado a la disponibilidad del servicio: Si el servicio deja de estar
disponible, dejará de funcionar.

Aunque la comunicación síncrona juega su papel, debe tener en cuenta estas desventajas
al decidir qué mecanismo de comunicación es el más adecuado para las necesidades de su
aplicación. Si estas limitaciones suponen un problema, quizás la comunicación asincrona
sea una mejor opción.

Sin esperas
Cuando se envía un mensaje de forma asincrona, el cliente no tiene que esperar a que
se procese o se entregue. El cliente entrega el mensaje al agente de mensajes y pasa a otra
tarea, esperando que el mensaje llegue al destino adecuado.
Al no tener que esperar, el cliente está disponible para llevar a cabo otras actividades. Con
todo este tiempo libre disponible, el rendimiento del cliente mejora de manera considerable.

Orientación del mensaje y desacoplamiento


A diferencia de la comunicación RPC, que suele estar orientada en torno a la invocación
de un método, los mensajes enviados de forma asincrona se centran en los datos. Esto
quiere decir que el cliente no está unido de forma fija a una firma de método específica.
494 Capítulo 17

Cualquier cola o suscriptor de tema que pueda procesar los datos enviados por el cliente
puede procesar el mensaje. El cliente no tiene que estar informado de ninguno de los
aspectos específicos del servicio.

Independencia de la ubicación
Los servicios RPC síncronos suelen localizarse por su dirección de red. Por ello, los
clientes no son inmunes a los cambios en la topología de red. Si se modifica la dirección IP
de un servicio o si se configura para que escuche en un puerto diferente, el cliente también
debe modificarse o no podrá acceder al servicio. En comparación, los clientes de mensajería
no tienen ni idea de quién va a procesar sus mensajes o de dónde se encuentra el servicio.
El cliente solo conoce la cola o el tema a través de los que se envían los mensajes. El resul­
tado es que no importa la ubicación en la que se encuentre el servicio, siempre y cuando
pueda recuperar los mensajes desde la cola o desde el tema. En el modelo punto a punto, es
posible sacar partido a la independencia de la ubicación para crear un clúster de servicios.
Si el cliente no conoce la ubicación del servicio y su único requisito es poder acceder al
agente de mensajes, no hay motivo por el que varios servicios no puedan configurarse para
obtener mensajes desde la misma cola. Si el servicio se sobrecarga y su capacidad queda
por debajo de lo deseado, todo lo que tendríamos que hacer es añadir más instancias del
servicio para estar a la escucha en la misma cola. La independencia de la ubicación tiene otro
efecto secundario interesante en el modelo de publicación y suscripción. Varios servicios
pueden suscribirse a un único tema, recibiendo copias duplicadas del mismo mensaje. Sin
embargo, cada servicio podría recibir ese mensaje de forma diferente. Por ejemplo, digamos
que cuenta con un conjunto de servicios que, de forma conjunta, procesan un mensaje que
detalla la contratación de un nuevo empleado. Un servicio podría añadir al empleado al
sistema de pagos, otro al portal de RR.EIH, y otro se aseguraría de que el empleado cuenta
con acceso a los sistemas que necesita para llevar a cabo su trabajo. Cada servicio trabaja
de forma independiente sobre los mismos datos que ha recibido desde un tema.

Recepción garantizada
Para que el cliente pueda comunicarse con un servicio síncrono, el servicio debe escuchar
en la dirección IP y el puerto especificados. Si el servicio está desactivado o deja de estar
disponible, el cliente no podrá continuar. Sin embargo, cuando enviamos mensajes de forma
asincrona, el cliente puede estar seguro de que se van a entregar. Incluso aunque el servicio
no esté disponible cuando se envíe el mensaje, se va a almacenar hasta que vuelva a estarlo.
Ahora que ya conoce los aspectos básicos de la mensajería asincrona, los veremos en
acción. Primero usaremos JMS para enviar y recibir mensajes.

Enviar mensajes con jM S ______________________


JMS (Java M essage Service, Servicio de mensajes de Java) es un estándar de Java que
define un API común para trabajar con agentes de mensajes. Antes de que apareciera JMS,
cada agente de mensajes tenía un API propietario, de modo que el código de mensajería
Mensajería en Spring 495

de las aplicaciones era menos portable entre agentes, pero con la llegada de JMS se puede
trabajar con todas las implementaciones a través de una interfaz común, de forma similar a
cómo JDBC proporciona una para operaciones de base de datos. Spring admite JMS a través
de una abstracción basada en plantillas denominada Jm s T e m p la te . Con Jm s T e m p la te
resulta muy sencillo enviar mensajes entre colas y temas desde el lado del productor y
recibirlos en el del consumidor. Spring también admite el concepto de POJO basados en
mensajes: sencillos objetos de Java que reaccionan a la llegada de mensajes a una cola o
tema de forma asincrona.
Analizaremos la compatibilidad de Spring con JMS, incluyendo Jm s T e m p la te y los
POJO basados en mensajes, pero antes de poder enviar y recibir mensajes necesita un agente
de mensajes que los pueda transmitir entre productores y consumidores. Comenzaremos
el análisis de Spring JMS con la configuración de un agente de mensajes en Spring.

Configurar un agente de mensajes en Spring


ActiveMQ es un magnífico agente de mensajes de código abierto y una gran opción
para la mensajería asincrona con JMS. En el momento de publicarse este libro, la versión
disponible de ActiveMQ es la 5.9.1. Para comenzar a trabajar con ActiveMQ tendrá que
descargarla distribución binaria desde h t t p : / / a c t i v e m q . a p a c h e . org. A continuación,
descomprima el archivo en su disco duro local. En la carpeta l i b encontrará a c tiv e m q -
c o r e . 5 . 9 . 1 . j a r , el archivo JAR que tendrá que añadir a la ruta de clases de la aplicación
para poder utilizar el API de ActiveMQ.
Dentro del directorio b in encontrará varias carpetas para los diferentes sistemas opera­
tivos. Incluyen secuencias de comandos para iniciar ActiveMQ. Por ejemplo, para iniciar
el agente en Mac OS X, ejecute a c tiv e m q s t a r t desde el directorio b in /m a c o sx . En un
momento, ActiveMQ estará activo y listo para procesar sus mensajes.

Crear una fábrica de conexiones


A lo largo de este capítulo vamos a ver diferentes formas de utilizar Spring para enviar
y recibir mensajes mediante JMS. En todos los casos, vamos a necesitar una fábrica de
conexiones para poder enviar los mensajes mediante el agente. Como vamos a utilizar
ActiveMQ, tendremos que configurar la fábrica de conexiones JMS para que sepa cómo
conectarse a ActiveMQ. En concreto, la fábrica de conexiones que vamos a utilizar es
A c tiv e M Q C o n n e c tio n F a c to r y , que puede configurar de esta forma en Spring:

cbean id="connectionFactory"
class="org.apache. activemq.spring.ActiveMQConnectionFactory">

De forma predeterminada, A c t iv e M Q C o n n e c t io n F a c t o r y asume que el agente


ActiveMQ escucha en el puerto 61616 de l o c a l h o s t . Es correcto para tareas de desa­
rrollo pero es muy probable que en producción su agente ActiveMQ escuche en otro host
y /o puerto. En ese caso, puede especificar la URL del agente por medio de la propiedad
b ro k erU R L :
496 Capítulo 17

cbean id="connectionFactory"
class="org.ap ache. activem q.spring.ActiveMQConnectionFactory"
p:brokerURL="tcp: //lo c a lh o s t:61616"/>

De forma opcional y como sabemos que estamos trabajando con ActiveMQ, podemos
utilizar el propio espacio de nombres de configuración de Spring para este agente (dispo­
nible en todas las versiones de ActiveMQ, desde la 4.1) para declarar la fábrica de cone­
xiones. En primer lugar, asegúrese de declarar el espacio de nombres amq en el archivo de
configuración XML de Spring:
<?xml v e rsio n = "l.0" encoding="UTF-8"?>
<beans xmlns="h ttp : //www.springframework.org/schema/beans"
xm lns:xsi="h ttp : //www.w3.org/2001/XMLSchema-instance"
xmlns: jms="h ttp : / /www.springframework.org/schema/jms"
xmlns: amq="h ttp : //activem q.apache. org/schema/core"
x s i : schemaLocation="h ttp : //activem q. apache. org/schema/core
h ttp : / /activemq.apache. org/schema/core/activemq-core.xsd
http://www.springframework.org/schema/jms
h ttp : / /www.springframework.org/schema/jms/spring-jms.xsd
h ttp : / / www. springframework. org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">

</beans>

A continuación, podemos utilizar el elemento <am q: c o n n e c t io n F a c t o r y > para


declarar la fábrica de conexiones:
<amq:connectionFactory id="connectionFactory"
brokerURL="tcp: / /lo c a lh o s t:61616"/>

El elem ento <ajnq: c o n n e c t i o n F a c t o r y > es específico de ActiveMQ. Si va a utilizar


una implementación de agente de mensajes diferente, puede que haya o no un espacio de
nombres de configuración para Spring disponible. De no ser así, tendrá que conectar la
fábrica de conexiones como < b e a n > .
Más adelante, en este capítulo, vamos a utilizar bastante c o n n e c t i o n F a c t o r y . Por
el momento, basta con decir que b ro k erU R L indica a la fábrica de conexiones dónde se
encuentra el agente de mensajes. En este caso, la URL proporcionada a b ro k erU R L indica
a la fábrica de conexiones que se conecte con ActiveMQ en el equipo local a través del
puerto 6 1 616 (el puerto que ActiveMQ utiliza de forma predeterminada para la escucha).

Declarar un destino de mensajes ActíveMQ


Además de una fábrica de conexiones, vamos a necesitar un destino para los mensajes,
que puede ser tanto una cola como un tema, dependiendo de las necesidades de la aplicación.
Con independencia de que utilice uno u otro, debe configurar el bean de destino en
Spring utilizando una clase de implementación específica para el agente. Por ejemplo, la
siguiente declaración <bean > declara una cola ActiveMQ:
cbean id="queue"
class="org.apache. activemq.command.ActiveMQQueue">
c:_="sp itter.q u eu e" />
Mensajería en Spring 497

De forma similar, el siguiente <bean > declara un tema para ActiveMQ:


cbean id="topic"
class="org.apache.activemq.command.ActiveMQTopic">
c:_="spitter.queue" />

En cualquier caso, el constructor recibe el nombre de la cola, conocido por el agente de


mensajes. En este caso, s p i t t e r . t o p ic .
Como sucede con la fábrica de conexiones, el espacio de nombres ActiveMQ ofrece un
método alternativo para declarar colas y temas. Para colas, también podríamos utilizar el
elemento <amq: queue>:
<amq:queue id="spittleQueue" physicalName="spittle.alert.queue" />

Asimismo, si se trata de un tema JMS, utilice <amq: t o p i c >:


<amq:topic id="spittleTopic" physicalName="spittle.alert-topic" />

En cualquier caso, el atributo phys icalName configura el nombre del canal del mensaje.
Llegados a este punto, hemos visto cómo declarar los componentes esenciales para
trabajar con JMS. Ya podemos enviar y recibir mensajes. Para ello, vamos a utilizar el
elemento JmsTemplate de Spring, la pieza central para la compatibilidad de Spring con
JMS, pero antes, veamos qué ofrece JmsTemplate analizando JMS sin este elemento.

Util : ir \c piar ti ia J 3 de Spring


Como hemos visto, JMS proporciona a los desarrolladores de Java un API estándar para
interactuar con agentes de mensajes y para recibir y enviar mensajes. Además, prácticamente
todas las implementaciones de agente de mensajes son compatibles con JMS. Por tanto,
no hay motivos para aprender el funcionamiento de los API de mensajería de tecnología
exclusiva para cada agente de mensajes con el que tratemos.
Aunque JMS ofrece una interfaz universal para todos los agentes de mensajes, esta
comodidad tiene un coste. El envío y recepción de mensajes con JMS no es tan sencillo
como pegar un sello y echar un sobre al buzón. Como vamos a ver, JMS también exige que,
a modo de metáfora, llene el depósito de la camioneta de reparto del cartero.

Gestionar código JMS descontroiado


Con anterioridad le hemos mostrado cómo el código JDBC convencional puede ser un
desastre de cara al control de conexiones, instrucciones, conjuntos de resultados y excep­
ciones. Lamentablemente, JMS sigue un modelo similar, como se ilustra en el listado 17.1.

Listado 17.1. Envío de un mensaje con JMS convencional (sin Spring).


ConnectionFactory cf =
new ActiveMQConnectionFactory("tcp://localhost:61616");
Connection conn = nuil;
Session session = nuil;
498 Capítulo 17

try {
conn = cf.createConnection();
session = conn.createSession(false, Session.AUTO_ACKNOWLEDGE);
Destination destination = new ActiveMQQueue("spitter.queue");
MessageProducer producer = session.createProducer(destination);
TextMessage message = session.createTextMessage();

message.setText("Hello world!");
producer.send(message); // Enviar el mensaje.
} catch (JMSException e) {
// ¿Gestionar la excepción?
} finally {
try {
if (session != null) {
session.close() ;
}
if (conn J= null) {
conn.close() ;
}
} catch (JMSException ex) {
}
}
Aún a riesgo de ser repetitivo, es un claro ejemplo de código descontrolado. En este
ejemplo JDBC tenemos casi 20 líneas de código para enviar un sencillo mensaje de tipo
"Hola mundo". Solo algunas de estas líneas envían el mensaje, mientras que el resto se
utilizan para configurar el contexto para realizar el envío.
Para recibir el mensaje, el panorama no mejora mucho, como se aprecia en el listado 17.2.

Listado 17.2. Recepción de un mensaje con JMS convencional (sin Spring).


ConnectionFactory cf =
new ActiveMQConnectionFactory("tcp://localhost:61616");
Connection conn = null;
Session session = null;
try {
conn = cf.createConnection();
conn.start();
session = conn.createSession(false, Session.AUTO_ACKNOWLEDGE);
Destination destination =
new ActiveMQQueue("spitter.queue");
MessageConsumer consumer = session.createConsumer(destination);
Message message = consumer.receive();
TextMessage textMessage = (TextMessage) message;
System.out.printIn("GOT A MESSAGE: " + textMessage.getText());
conn.start();
} catch (JMSException e) {
//¿Gestionar la excepción?
} finally {
try {
if (session != null) {
session.close() ;
}
if (conn != null) {
Mensajería en Spring 499

conn.cióse ();
}
} catch (JMSException ex) {
}
}
Como sucedía en el listado 17.1, tenemos mucho código para realizar una tarea muy
sencilla. Si realiza una comparación línea por línea, se dará cuenta de que ambos ejemplos
son prácticamente idénticos, y si comparase entre sí otros mil ejemplos JMS, podrá ver que
son terriblemente parecidos. Algunos pueden obtener sus fábricas de conexiones desde
JNDI y otros pueden utilizar un tema en lugar de una cola. Sin embargo, todos van a seguir
más o menos el mismo patrón.
Como consecuencia de tener todo este código reutilizable, acabará por utilizar el mismo
código una y otra vez cuando trabaje con JMS. Peor todavía, se verá repitiendo el código
JMS de otros desarrolladores.
En un capítulo anterior vimos cómo usar JmsTemplate de Spring para controlar código
predefinido de JDBC. Ahora, veamos cómo JmsTemplate puede hacer lo mismo con el
código JMS.

Trabajar con plantillas JMS


JMSTemplate es la respuesta de Spring al código JMS repetitivo. Se encarga de crear
una conexión, obtener una sesión y proceder al envío o a la recepción de mensajes. Esto le
permite centrarse en crear el mensaje para enviarlo o en procesar los mensajes recibidos.
Asimismo, JmsTemplate puede gestionar cualquier excepción JMSException que
pueda generarse a lo largo del proceso. Si se genera alguna, JmsTemplate la captura
y la vuelve a generar como una de las subclases sin comprobar la propia excepción
JmsExcept ion de Spring. En la tabla 17.1 se muestra cómo Spring asigna las excepciones
JmsException a las excepciones JmsException no comprobadas de Spring.

Tabla 17.1. JmsTemplate de Spring captura excepciones JMSException estándar y las genera
de nuevo como subclases no comprobadas de la propia excepción JmsException de Spring.

Spring (org.springframework.jms.*) JMS estándar (javax.jms.’ )

DestinationResolutionException Especifica de Spring. Se genera cuando


Spring no puede resolver un nombre de
destino.
IllegalStateException IllegaiStateException
InvalidClientIDException InvaiideiientIDException
InvalidDestinationException InvalidDestinationException
InvalidSelectorException InvaiidSelectorException

JmsSecurityException JmsSecurityException

ListenerExecutionFailedException Específica de Spring. Se genera cuando falla


la ejecución de un método de escucha.
500 Capítulo 17

Spring (org.springframework.jms.*) JMS estándar (javaxjms.*)

MessageConversionException Específica de Spring. Se genera cuando


falla la conversión de mensajes.
MessageEOFException MessageEOFException

Mess age Format Exc ept ion MessageFormatException


MessageNotReadableException MessageNotReadableException

MessageNotWriteableException MessageNotWriteableException
ResourceAllocationException ResourceAllocationException
SynchedLocalTransactionFailedException Específica de Spring. Se genera cuando una
transacción local sincronizada no puede
completarse.
TransactionlnProgressException TransactionlnProgressException

TransactionRolledBackException TransactionRolledBackException

UncategorizedJmsException Específica de Spring. Se genera cuando


no se aplica ninguna otra excepción.

Para ser justa con el API JMS, JMSException cuenta con un completo y descriptivo
conjunto de subclases que le indican el origen del error. No obstante, todas son excepciones
comprobadas que hay que capturar. JmsTemplate se encarga de ello y captura y vuelve a
generar cada excepción como la correspondiente subclase sin comprobar de JmsException.
Para utilizar JmsTemplate, tenemos que declararlo como bean en el archivo de confi­
guración de Spring. Para ello, vamos a utilizar el siguiente código XML:
<bean id="jmsTemplate"
class="org.springframework.jms.core.JmsTemplate">
c:_-ref="connectionFactory" />

Como JmsTemplate tiene que saber cómo establecer conexiones con el agente de
mensajes, debemos configurar la propiedad connectionFactory con una referencia al
bean que implementa la interfaz ConnectionFactory de JMS. En este caso, la hemos
conectado a una referencia al bean connectionFactory que hemos declarado con
anterioridad. Es todo lo necesario para configurar JmsTemplate. Ya podemos empezar
a enviar mensajes.

Enviar mensajes
Una de las características que nos gustaría incluir en la aplicación Spittr es la opción de
alertar a otros usuarios (quizás mediante correo electrónico) siempre que se cree un spittle.
Podríamos crear esta característica directamente en la aplicación, en el punto en el que se
añade un spittle. Sin embargo, saber a quién se envían estas alertas y proceder a su envío
podría requerir tiempo y afectar al rendimiento aparente de la aplicación. Cuando se añada
Mensajería en Spring

el nuevo spittle, queremos que la aplicación responda con rapidez con una respuesta. En
lugar de emplear tiempo en enviar esos mensajes en el momento en que se añade el spittle,
tiene más sentido incluir ese trabajo en una cola y gestionarlo más tarde, una vez que la
respuesta haya llegado al usuario. El tiempo necesario para enviar un mensaje a la cola
es inapreciable, sobre todo si lo comparamos con el tiempo necesario para enviar alertas
a otros usuarios.
Para permitir el envío de alertas de spittle de forma asincrona cada vez que se cree uno,
vamos a incluir AlertService en la aplicación Spittr:
package com.habuma.spittr.alerts;
import com.habuma.spittr.domain.Spittle;

public interface AlertService {


void sendSpittleAlert(Spittle spittle);
i
Como puede ver, AlertService es una interfaz que define una única operación,
sendSpittleAlert () .AlertServicelmpl es la implementación de la interfaz
AlertService, que utiliza JmsTemplate para enviar objetos Spittle a una cola de
mensajes que se van a procesar en un momento posterior (véase el listado 17.3).

Listado 17.3. Envío de un Spittle con JmsTemplate.


package com.habuma.spittr.alerts;
import javax.jms.JMSException;
import javax.jms.Message;
import javax.jms.Session;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jms.core.JmsOperations;
import org.springframework.jms.core.MessageCreator;
import com.habuma.spittr.domain.Spittle;

public class AlertServicelmpl implements AlertService{

private JmsOperations jmsOperations;

@Autowired
public AlertServicelmpl(JmsOperations jmsOperatons) { // Inyectar plantilla JMS.
this.jmsOperations = jmsOperations;

public void sendSpittleAlert(final Spittle spittle) {


jmsOperations.send( // Enviar mensaje.
"spittle.alert.queue", // Especificar destino.
new MessageCreator() {
public Message createMessage(Session session)
throws JMSException {
return session.createObjectMessage(spittle); // Crear mensaje.

);
}
502 Capítulo 17

El prim er parám etro del m étodo se n d () de Jm s T e m p la te es el nombre del destino


JMS al que se envía el mensaje. Al invocar el método se n d ( ) , Jm s T e m p la te obtiene una
conexión JMS y una sesión, y envía el mensaje en nombre del remitente (véase la figura 17.5).

Figura 17.5. JmsTemplate se encarga de gestionar el envío del mensaje en nombre del remitente.

El mensaje se crea utilizando un M e ss a g e C re a to r y se implementa con una clase


interna anónima. En el método c re a te M e s s a g e () de M e ss a g e C re a to r nos limitamos
a solicitar un mensaje de objeto a la sesión, proporcionándoselo al objeto S p i t t l e desde
el que crear el mensaje de objeto.
Eso sería todo. El método s e n d S p i t t l e A l e r t () se centra completamente en elaborar
y enviar el mensaje. No existe código de conexión ni de gestión de sesiones, ya que
Jm s T e m p la te lo gestiona todo por nosotros. Asimismo, no hay necesidad de capturar
excepciones JM S E x c e p tio n . Jm s T e m p la te captura cualquier excepción generada y la
vuelve a generar como una de las excepciones no comprobadas de Spring de la tabla 17.1.

Configurar un destino predeterminado


En el listado 17.3, hemos especificado de forma explícita un destino al que se va a
enviar el mensaje spittle dentro del método sen d () . La variante de este método es útil
cuando queremos seleccionar un destino mediante programación. Sin embargo, en el caso
de A le r t S e r v i c e lm p l, siempre vamos a enviar el mensaje sp ittle al mismo destino, por
lo que las ventajas de esa forma de sen d () no quedan muy claras.
En lugar de especificar de forma explícita un destino cada vez que enviemos un mensaje,
podríam os conectar un destino predeterm inado a Jm sT e m p la te :
cbean id="jmsTemplate"
class="org.springframework.jms.core.JmsTemplate">
c:_-ref="connectionFactory" />
p:defaultDestinationName="spittle.alert.queue" />

En este caso se establece el nombre del destino en s p i t t l e . a l e r t . queue, pero sola­


mente es un nombre; no indica el tipo del destino. Si ya existe una cola o un tema con ese
nombre, será lo que se utilice. En caso contrario se crea un nuevo destino (generalmente
una cola), pero si quiere un tipo de destino concreto, puede conectar una referencia a una
cola o bean de destino declarado previamente:
<bean id="jmsTemplate"
class=11org. springf ramework. jms.core -JmsTemplate1'
c:_~ref=”connectionFactory"
p :defaultDestination-ref=" spittleTopic11 />

Ahora, la invocación del método s e n d ( ) de J m s O p e r a t io n s se puede simplificar


ligeram ente elim inado el prim er parámetro:
Mensajería en Spring 503

jmsOperations. send(
new MessageCreator(){

i
);
Esta variante del método se n d ( ) solo acepta un M e s s a g e C r e a to r . No hay necesidad
de especificar un destino porque vamos a utilizar el predeterminado.
Al deshacernos del destino explícito en la invocación de se n d ( ) se facilita el proceso,
pero el envío de mensajes puede ser todavía más sencillo si recurrimos a un conversor de
mensajes.

Convertir mensajes al enviarlos


Además del método s e n d ( ) , Jm s T e m p la te cuenta con c o n v e rtA n d S e n d ( ) que, al
contrario de lo que sucede con se n d ( ), no adopta M e s s a g e C r e a t o r como argumento.
En su lugar utiliza un conversor de mensajes integrado para crear el mensaje.
Al usar o n v e rtA n d S e n d ( ) , se puede reducir el m étodo s e n d S p i t t l e A l e r t () a
una sola línea:

public void se n d S p ittleA lert(S p ittle s p ittle ) {


jmsOperations. convertAndSend(spittle);
}
Como por arte de magia, el objeto S p i t t l e se convierte en un objeto M e ssa g e antes
de enviarse, pero como sucede con los trucos de magia, Jm s T e m p la te se guarda un as
en la manga. Utiliza una implementación de M e s s a g e C o n v e r te r para convertir objetos
en mensajes.
M e s s a g e C o n v e r te r es una interfaz definida por Spring que solo tiene dos métodos
para implementar:
public in terfa ce MessageConverter {
Message toMessage(Object o b je ct, Session session)
throws JMSException, MessageConversionException;
Object fromMessage(Message message)
throws JMSException, MessageConversionException;
}
Aunque sea una interfaz sencilla de implementar, no tendrá que crear una implemen­
tación personalizada. Spring le ofrece varias implementaciones, descritas en la tabla 17.2.

Tabla 17.2. Conversores de mensajes de Spring para tareas comunes de conversión (todos
pertenecen al paquete org.springframework.jms.support.converter).
j. , . . . , , „ ------ , ---------------...— — -7 ,

Conversor de mensajes Función


fcá á fe á ü á e ....á É á S S M

MappingJacksonMessageConverter Utiliza la biblioteca Jackson JSON para convertir


mensajes de y a JSON.
MappingJackson2MessageConverter Utiliza la biblioteca Jackson 2 JSON para convertir
mensajes de y a JSON.
504 Capítulo 17

i Converser de mensajes Función


........... .■...........
MarshallingMessageConverter Utiliza JAXB para convertir mensajes de y a XML.
simpleMessageConverter Convierte cadenas a/de TextMessage, matrices de
bytes a/de BytesMessage, mapas a/de MapMessage
y objetos Serializable a/de ObjectMessage.

De form a p red eterm in ad a, J m s T e m p l a t e u sa S i m p l e M e s s a g e C o n v e r t e r


al enviar m ensajes en c o n v e r t A n d S e n d ( ) , pero puede reem plazarlo si declara el
conversor de mensajes como bean y lo inyecta a la propiedad m e s s a g e C o n v e r te r de
Jm s T e m p la te . Por ejemplo, si desea trabajar con mensajes JSON, puede declarar un bean
M a p p in g Ja c k s o n M e s s a g e C o n v e r te r :
<bean id="messageConverter"
class="org.springfram ew ork.jm s. support. converter.
MappingJacksonMessageConverter" />

Tras ello, puede conectarlo a Jm s T e m p la te de esta forma:


ebean id="jmsTemplate"
cla ss= "o rg . springframework. jm s. co re. JmsTemplate"
c:_-ref="connectionFactory"
p:defaultD estinationN am e="spittle. alert.queue"
p :messageConverter-ref="messageConverter" />

Cada conversor de mensajes puede tener una configuración adicional para disponer de
mayor control sobre el proceso de conversión. M a p p in g Ja c k s o n M e s s a g e C o n v e r te r ,
por ejemplo, le perm ite configurar aspectos como la codificación o un O bj e c tM a p p e r de
Jackson personalizado. Consulte la docum entación de Java si necesita más detalles sobre
la configuración de estos conversores de mensajes.

Consumir mensajes
Ya hemos visto cómo enviar un mensaje con Jm s T e m p la te , pero ¿y si lo que queremos
es recibirlo? ¿Podemos utilizar Jm s T e m p la te para esta tarea?
Así es. De hecho, es incluso más sencillo. Todo lo que tenemos que hacer es invocar el
método r e c e i v e () de Jm s T e m p la te , como se muestra en el listado 17.4.

Listado 17.4. Recepción de un mensaje con JmsTemplate.

public S p ittle re c e iv e S p ittle A le rt() {


try {
ObjectMessage receivedMessage =
(ObjectMessage) jm sO perations.receiveO ; / / R ecib ir mensaje,
return (S p ittle ) receivedM essage.getobject( ) ; / / Conseguir o b jeto .
} catch (JMSException jmsException) {
throw Jm sU tils. convertJmsAccessException(jmsException); / / Generar excepción
/ / convertida.
Mensajería en Spring 505

Al invocar el método r e c e i v e () de Jm sT em p late, se intenta recuperar un mensaje


del agente de mensajes. Si no hay ninguno disponible, el método r e c e i v e () aguarda
hasta que lo haya (véase la figura 17.6).

Figura 17.6. La recepción de mensajes desde un tema o una cola con JmsTemplate es tan sencilla
como invocar el método r e c e iv e (). JmsTemplate se encarga del resto.

Como sabemos que el mensaje spittle se ha enviado como mensaje de objeto, puede
convertirse a O b j e c tM e s s a g e tras su llegada. A continuación, podemos invocar g e t
O bj e c t () para extraer el objeto S p i t t l e de O bj e c t M e s s a g e y devolverlo.
El problema aquí es que tenemos que hacer algo con la posible excepción JM S E x ce p tio n
que puede generarse. Como ya he mencionado, Jm s T e m p la te es adecuado para gestionar
las excepciones J m s E x c e p t io n s generadas, ya que las vuelve a generar como una de
las excepciones J m s E x c e p t io n no comprobadas de Spring. Sin embargo, esto solo se
aplica cuando invocamos uno de los métodos de Jm s T e m p la te , que no puede hacer
mucho por las excepciones J M S E x c e p tio n que puedan generarse tras la invocación del
método g e t o b j e c t () de Obj e c tM e s s a g e . Por tanto, tenemos que capturar la excepción
JM S E x c e p tio n , o declarar que el método la genere. Si mantenemos la filosofía de Spring
de evitar las excepciones comprobadas, no vamos a permitir que el método JM S E x ce p t io n
escape de este método por lo que, en su lugar, vamos a capturarlo. En el bloque c a t c h
podemos utilizar el método c o n v e r t J m s A c c e s s E x c e p t io n () de la clase J m s U t i l s
para convertir la excepción comprobada JM S E x ce p t io n en una no comprobada. Es lo
que Jm s T e m p la te hace por nosotros en otros casos. Para aclarar el mensaje de r e c e i -
v e S p i t t l e A l e r t () puede recurrir a un conversor de mensajes. Ya hemos visto cómo
convierten objetos a mensajes en c o n v e rtA n d S e n d ( ) , pero también los puede usar en el
extremo receptor con r e c e iv e A n d C o n v e r t () de Jm sT e m p la te :
public S p ittle r e tr ie v e S p ittle A le r t() {
return (S p ittle ) jmsOperations. receiveAndConvert(};
}
Ahora ya no es necesario convertir M e ssa g e en O bj e c t M e s s a g e , recuperar S p i t t l e
mediante la invocación de g e tO b j e c t O , ni preocuparse de la excepción JM S E x ce p t io n
comprobada. Este nuevo método r e t r i e v e S p i t t l e A l e r t () es mucho más conciso,
pero sigue habiendo un problema nada evidente.
La gran desventaja de consumir mensajes mediante Jm sT em p late es que los métodos
r e c e i v e () y re c e iv e A n d C o n v e rt () son síncronos, de modo que el receptor debe
esperar con paciencia a que el mensaje llegue, ya que ambos métodos bloquean el proceso
hasta que hay un mensaje disponible (o hasta que se cumpla el tiempo de caducidad). ¿No
le parece raro que un mensaje enviado de forma asincrona se consuma de forma síncrona?
Este es el punto en el que los POJO basados en mensajes pasan a ser muy útiles. Vamos
a ver cómo recibir mensajes de forma asincrona utilizando componentes que reaccionan a
los mensajes en lugar de esperarlos.
506 Capítulo 17

Crear POJO basados en mensajes


Cuando estudiaba en la universidad, tuve el privilegio de trabajar en el Parque Nacional
de Yellowstone. El trabajo no era nada parecido a ser guarda del parque; consistía, más
bien, en hacer camas, limpiar cuartos de baño y fregar suelos. Nada del otro mundo pero,
al menos, tuve la suerte de trabajar en uno de los lugares más hermosos del mundo.
Cada día, después del trabajo, acudía a la oficina postal local para saber si había reci­
bido correo. Estuve lejos de casa durante varias semanas, por lo que era agradable recibir
una tarjeta o una carta de mis amigos de la escuela. No tenía un apartado postal, así que
le preguntaba al hombre del mostrador, y ahí comenzaba la espera.
El problema es que ese hombre debía tener como 200 años y, como la mayoría de las
personas de su edad, era algo lento. Se levantaba de la silla, caminaba con lentitud y desa­
parecía detrás de una pared. Más tarde, aparecía de nuevo, volvía con lentitud a su silla y
se sentaba. En ese momento, me miraba y decía: "No tienes correo".
El método r e c e i v e () de Jm s T e m p la te es muy similar a este empleado de correos.
Al invocar r e c e i v e ( ) , busca un mensaje en la cola o en el tema y no vuelve hasta que
lo encuentra o hasta que se supera el tiempo de caducidad. Mientras tanto, su aplicación
está con los brazos cruzados, sin hacer nada y esperando a saber si hay algún mensaje.
¿No sería mejor que su aplicación pudiera dedicarse a su trabajo y recibir una notificación
cuando llegase un mensaje?
Uno de los aspectos destacados de la especificación EJB 2 fue la inclusión de bean
basados en mensajes (MDB). Los MDB son EJB que procesan mensajes de forma asincrona.
Es decir, los MDB reaccionan a los mensajes en un destino JMS como eventos y responden
a estos. Contrasta con los receptores síncronos de mensajes, que se bloquean hasta que
haya un mensaje disponible. Los MDB fueron un elemento destacado en el panorama EJB.
Incluso los detractores más acérrimos de esta tecnología tuvieron que reconocer que los
MDB eran una forma elegante de gestionar los mensajes. El único aspecto negativo que se
pudo detectar en contra de los MDB de EJB 2 fue que tenían que implementar j a v a x . e j b .
M e s s a g e D riv e n B e a n , por lo que también tenían que implementar métodos de retrolla-
mada del ciclo de vida EJB. Es decir, los MDB de EJB 2 eran todo lo contrario a un POJO.
Con la especificación EJB 3, los MDB se mejoraron para parecerse más a los POJO. Ya
no hay que implementar la interfaz M e s s a g e D riv e n B e a n . En su lugar se utiliza j a v a x .
jm s . M e s s a g e L i s t e n e r , más genérica, y se anotan con @ M e ssa g e D riv e n .
Spring 2.0 tuvo en cuenta la necesidad del consumo asincrono de mensajes y, para ello,
incluyó su propia forma de bean basados en mensajes, muy similar a los de EJB 3. En este
apartado, veremos cómo Spring admite el consumo de mensajes asincronos mediante POJO
basados en mensajes (que denominaremos MDP para abreviar).

Crear un escuchador de mensajes


Si creáramos nuestro gestor de alertas de spittle con el modelo basado en mensajes de
EJB, tendría que anotarse con @ M essageD riven y, aunque no es estrictamente obligatorio,
es recomendable que el MDB implemente la interfaz M e s s a g e L is te n e r . El resultado es
parecido al siguiente:
Mensajería en Spring 507

@MessageDriven (mappedName=’1jms/spittle.alert.queue")
public class SpittleAlertHandler implements MessageListener {
@Resource
prívate MessageDrivenContext mdc;
public void onMessage{Message message) {

}
}
Durante un momento, trate de imaginar un mundo más sencillo en el que los compo­
nentes basados en mensajes no tienen que implementar la interfaz M e s s a g e L i s t e n e r .
En un lugar como ese, el cielo tendría un azul increíble, los pájaros siempre cantarían su
canción favorita y no tendría que implementar el método o n M essag e () ni que inyectar
M e s s a g e D r iv e n C o n te x t. De acuerdo. Puede que los requisitos de un MDB fijados por
la especificación EJB 3 no sean tan complejos. Sin embargo, lo cierto es que la implemen-
tación EJB 3 de S p i t t l e A l e r t H a n d l e r está demasiado vinculada a los API basados en
mensajes de EJB y no se parece tanto a un POJO como nos gustaría. Lo ideal es que el gestor
de alertas pudiera gestionar los mensajes, aunque no tendría que estar codificado como si
supiera lo que va a tener que hacer.
Spring permite que el método de un POJO pueda gestionar mensajes desde una cola
o un tema JMS. Por ejemplo, la implementación POJO de S p i t t l e A l e r t H a n d l e r del
listado 17.5 es suficiente.

Listado 17.5. Un MDP de Spring que recibe y procesa mensajes de forma asincrona.

package com.habuma.spittr.alerts;
import com.habuma.spittr.domain.Spittle;
public class SpittleAlertHandler {
public void processSpittle(Spittle spittle) { // Método controlador.
// ... la implementación va aquí...
}
}
Aunque el color del cielo y los pájaros que cantan canciones se encuentran fuera del
ámbito de Spring, lo cierto es que el listado 17.5 muestra que el mundo de fantasía que
hemos descrito se acerca mucho más a la realidad. Hablaremos sobre los detalles del método
p r o c e s s S p i t t l e () más adelante. Por el momento, tenga en cuenta que no hay nada en
S p i t t l e A l e r t H a n d l e r que muestre indicios de JMS. A todos los efectos, se trata de un
POJO. Puede gestionar mensajes como su equivalente EJB. Todo lo que necesita es una
configuración especial en Spring.

Configurar escuchadores de mensajes


El truco para permitir que un POJO pueda recibir mensajes es configurarlo como escu-
chador de mensajes. El espacio de nombres j ms de Spring cuenta con todo lo necesario
para llevar a cabo esa tarea. En primer lugar, debemos declarar el gestor como <bean>:
<bean id="spittleHandler"
class="com.habuma.spittr.alerts.SpittleAlertHandler" />
508 Capítulo 17

A continuación, para convertir S p i t t l e A l e r t H a n d l e r en un POJO basado en


mensajes, podem os declarar el bean para que sea un escuchador de mensajes:

<jm s: lis t e n e r - container connection-factory="connectionFactory">


<jms: lis te n e r d e s tin a tio n = "s p itte r.a l e r t . queue"
re f =,,spittleH andler" method="handlerSpittleAlert" />
< /jm s: listen er-co n tain er>

En este caso, tenemos un escuchador de mensajes dentro de un contenedor de escucha-


dores de mensajes. Se trata de un bean especial que examina un destino JMS a la espera de
que llegue un mensaje. Una vez recibido, lo recupera y lo proporciona a cualquiera de los
escuchadores de mensajes interesados (véase la figura 17.7).

Figura 17.7. Un contenedor de escuchadores de mensajes analiza una cola o un tema.


Cuando se recibe un mensaje, lo redirige a un escuchador de mensajes (como a un POJO
basado en mensajes).

Para configurar el contenedor de escuchadores de mensajes y el escuchador de mensajes


en Spring, hemos utilizado dos elementos del espacio de nombres jm s. < jm s : l i s t e n e r -
c o n t a i n e r > s e utiliza para contener elementos <jm s : l i s t e n e r > . E n este caso, el atributo
c o n n e c t i o n F a c t o r y podría haberse omitido, ya que su valor por defecto es c o n n e c t io n
F a c t o r y . El elemento < jm s : l i s t e n e r > se utiliza para identificar un bean y el método
que debe gestionar los mensajes entrantes. Para la gestión de mensajes de alerta sobre spittle,
el elemento r e f hace referencia a nuestro bean s p i t t l e H a n d l e r . Cuando un mensaje
llega a s p i t t e r . a l e r t . q u e u e (como indica el atributo d e s t i n a t i o n ) , el método
p r o c e s s S p i t t l e () del bean s p i t t l e H a n d l e r recibe la llamada (según lo dispuesto
por el atributo m ethod). Conviene recordar que si el bean identificado por el atributo r e f
implementa M e s s a g e L i s t e n e r , no es necesario especificar el atributo m eth od, ya que
o n M essa g e () se invoca de forma predeterminada.

Utilizar RPC basados en mensajes


Con anterioridad hemos visto diferentes opciones en Spring para exponer métodos
de bean como servicios remotos e invocar dichos servicios desde clientes. En este capí­
tulo, hemos visto cómo enviar mensajes entre aplicaciones a través de colas y temas
de mensajes. Ahora, combinaremos ambos conceptos para ver cómo realizar llamadas
remotas mediante JMS. Para admitir RPC basadas en mensajes, Spring cuenta con
J m s l n v o k e r S e r v i c e E x p o r t e r para exportar bean como servicios basados en mensajes
y con J m s I n v o k e r P r o x y F a c t o y B e a n para que los clientes utilicen esos servicios. Son
dos opciones muy similares entre sí, aunque cada una tiene sus ventajas y desventajas.
Veremos ambos enfoques para que pueda decidir cuál se adapta mejor a sus necesidades.
Comenzaremos con la compatibilidad de Spring con los servicios basados en JMS.
Mensajería en Spring 509

Recordará de un capítulo anterior que Spring cuenta con varias opciones para exportar
beancomo servicios remotos. Hemos utilizado R m i S e r v ic e E x p o r t e r para exportar bean
como servicios RMI, H e s s ia n E x p o r t e r y B u r l a p E x p o r t e r para servicios Hessian y
Burlap a través de HTTP, y H t t p I n v o k e r S e r v i c e E x p o r t e r para crear servicios de invo-
cador HTTP a través de HTTP. Sin embargo, Spring cuenta con u n exportador de servicios
más del que aún no hemos hablado.

Exportar servicios basados eo JMS


J M S I n v o k e r S e r v ic e E x p o te r e s m uy similar al resto de exportadores de servicios. De
hecho, verá que existe cierta sim etría en los nombres de J m s I n v o k e r S e r v i c e E x p o r t e r
y H t t p I n v o k e r S e r v i c e E x p o r t e r . Si este últim o exporta servicios que se comunican a
través de HTTP, J m s I n v o k e r S e r v ic e E x p o r t e r debe exportar servicios que se comunican
a través de JMS. Para m ostrar el funcionamiento de este elemento, fíjese en el siguiente
código de A l e r t S e r v i c e l m p l .

Listado 17.6. AlertServicelmpl: un POJO sin JMS para procesar mensajes JMS.

package com. habuma. s p i t t r . a l e r t s ;


import org. springframework.m ail. SimpleMailMessage;
import org. springframework.m ail. javam ail. JavaMailSender;
import org. springframework. stereotyp e. Component;
import com. habuma. s p i t t r . domain. S p i t t le ;

©Component( "a le rtS e rv ic e ")


public cla ss AlertServicelm pl implements A lertS erv ice{

private JavaMailSender mailSender;


private String alertEmailAddress;
public AlertServicelm pl (JavaMailSender mailSender,
String alertEmailAddress){
this.mailSender=mailSender ;
t h i s . alertEmailAddress=alertEmailAddress;
}
public void send SpittleA lert (fin a l S p ittle s p ittle ) { / / Enviar alerta sobre S p ittle .
SimpleMailMessage message = new SimpleMailMessage();
String spitterName = s p i t t l e . g e tS p itte r ( ) . getFullName();
message . setFrom ("noreply@ spitter. com11) ;
message. setTo(alertEm ailAddress);
message. se tS u b je c t( "Newspittle from " + spitterName);
message. setText(spitterName + " says: " + s p i t t l e . g etT ex t( ) ) ;
mailSender. send(message);
}
}
No se preocupe demasiado por los detalles del funcionamiento de s e n d S p i t t l e
A l e r t () (hablaremos con mayor detalle sobre el envío de correos electrónicos en Spring
más adelante). El aspecto en el que debe fijarse es que A l e r t S e r v i c e l m p l es un POJO
sencillo y no cuenta con nada que indique que va a utilizarse para gestionar mensajes JMS.
Para ello implementa la interfaz A l e r t S e r v i c e :
510 Capítulo 17

package com.habuma. s p i t t r . a le r t s ;
import com.habuma. s p i t t r . domain. S p i t t le ;
public in terfaceA lertServ ice{
void se n d S p ittleA lert(S p ittle s p i t t l e ) ;
}
Como puede ver, A l e r t S e r v i c e l m p l se anota con @Com ponent, por lo que va a
descubrirse y registrarse como bean de forma automática en el contexto de aplicación con
el ID a l e r t S e r v i c e . Vamos a hacer referencia a este bean al configurar un elemento
Jm s In v o k e r S e r v ic e E x p o r te r :
cbean id= " alertServiceE xp o rter11
cla ss= "o rg . springframework. jm s. remoting.JmsInvokerServiceExporter"
p : s e r v ic e -re f="a lertS erv ice "
p : servicelnterface="com .habum a.spittr. a le r ts .A le r tS e r v ic e " />

Las propiedades de este bean describen el aspecto que debe tener el servicio expor­
tado. La propiedad S e r v i c e se conecta para hacer referencia al bean a l e r t S e r v i c e ,
que es la implementación del servicio remoto. Mientras tanto, la propiedad s e r v i c e
I n t e r f a c e se configura con el nombre de clase completamente cualificado de la interfaz
que el servicio proporciona. Las propiedades del exportador no describen los aspectos
específicos de la transmisión del servicio a través de JMS. Sin embargo, la buena noticia
es que J m s I n v o k e r S e r v i c e E x p o r t e r puede actuar como escuchador JMS. Por tanto,
podemos configurarlo como tal en un elemento < jm s : l i s t e n e r - c o n t a i n e r > :
<jm s: liste n e r-co n ta in e r connection-factory="connectionFactory">
<jm s: lis te n e r d e stin a tio n = "sp itte r. a l e r t . queue"
r e f ="alertServ iceE xp o rter"/>
< /jm s: liste n er-co n tain e r>

Al contenedor de escuchadores JMS se le proporciona la fábrica de conexiones para


que pueda saber cómo conectarse al agente de mensajes. Mientras tanto, a la declaración
< jm s : l i s t e n e r > se le asigna el destino al que se dirige el mensaje remoto.

Consumir servicios basados en JMS


Llegados a este punto, el servicio de alertas basado en JMS debería estar listo y a la espera
de que lleguen mensajes RPC a la cola s p i t t e r . a l e r t . q u eu e. En el lado del cliente,
vamos a utilizar J m s I n v o k e r P r o x y F a c t o r y B e a n para acceder al servicio.
J m s I n v o k e r P r o x y F a c t o r y B e a n se parece mucho al resto de bean de fábrica de
proxy remotos que hem os visto. Oculta los detalles del acceso al servicio remoto detrás
de una cóm oda interfaz, a través de la cual el cliente interactúa con el servicio. La gran
diferencia es que, en lugar de derivar m ediante proxy los servicios basados en RMI o en
HTTP, I n v o k e r P r o x y F a c t o r y B e a n deriva un servicio basado en JMS exportado por
Jm s I n v o k e r P r o x y F a c t o r y B e a n . Para utilizar el servicio de alertas, podemos conectar
J m s I n v o k e r P r o x y F a c t o r y B e a n de esta manera:
cbean id ="alertS erv ice"
class="org.springframework.jms.remoting.JmsInvokerProxyFactoryBean"
p : connectionFactory-ref="connectionFactory"
p:queueName="spittle. a l e r t . queue"
propp: servicelnterface="com.habuma. s p ittr .a le r ts .A le r tS e r v ic e " />
Mensajería en Spring 511

Las propiedades c o n n e c t i o n F a c t o r y y queueName especifican la forma de entregar


los mensajes RPC, en este caso en la cola s p i t t e r . a l e r t . q u eu e del agente de mensajes
configurado en la fábrica de conexiones proporcionada, s e r v i c e l n t e r f a c e especifica
que el proxy debe exponerse a través de la interfaz A l e r t S e r v i c e .
Durante mucho tiempo, JMS ha sido la solución de mensajería utilizada para aplica­
ciones de Java, pero no es la única disponible para los programadores de Java y Spring. En
los últimos años el Protocolo avanzado de cola de mensajes (AMQP) ha cobrado especial
relevancia y, como era de esperar, Spring admite el envío de mensajes con AMQP, como
veremos a continuación.

Mensajería con AMQP _______________ ___________


Seguramente se pregunte para qué necesita otra especificación de mensajería, si no basta
con JMS y qué ofrece AMQP que no se incluya en JMS.
AMQP ofrece ciertas ventajas con respecto a JMS. Por un lado, define un protocolo de
nivel de conexión para mensajería, mientras que JMS define una especificación de API que
garantiza que todas las implementaciones JMS se puedan usar a través de un API común
pero que no determina que los mensajes enviados por una implementación de JMS se
pueden consumir c o n o tr a diferente. P o r su p a r t e , e l p r o t o c o l o de nivel de conexión de
AMQP especifica el formato que adoptan los mensajes entre el productor y el consumidor.
Por ello, AMQP es más versátil que JMS, no solo entre diferentes implementaciones de
AMQP, sino también entre distintos lenguajes y plataformas (si ha interpretado que AMQP
va más allá del lenguaje y la plataforma Java, empieza a entenderlo).
Otra significativa ventaja de AMQP con respecto a JMS es que su modelo de mensa­
jería es mucho más flexible y transparente. Con JMS solo puede elegir entre dos modelos
de mensajería: punto a punto y publicación/suscripción. Ambos se admiten también en
AMQP, pero AMQP le permite dirigir mensajes de diversas formas y para ello desvincula
al productor del mensaje de la cola a la que se añaden los mensajes.
Spring AMQP es una extensión del marco de trabajo Spring que permite mensajería de
estilo AMQP en aplicaciones de Spring. Como veremos en breve, Spring AMQP proporciona
un API que permite que el uso de AMQP sea muy similar a la abstracción JMS de Spring,
lo que significa que gran parte de lo visto hasta ahora en el capítulo se puede usar para
entender el envío y recepción de mensajes con Spring AMQP.
Veremos en breve cómo trabajar con Spring AMQP pero antes es recomendable analizar
sus peculiaridades.

Breve introducción a AMQP


Para entender el modelo de mensajería de AMQP, conviene recordar brevemente el de
JMS. En JMS hay tres participantes principales: eí productor deí mensaje, eí consumidor y”
un canal (una cola o un tema) para transmitir el mensaje entre productores y consumidores.
Los fundamentos del modelo de mensajería de JMS se ilustran en las figuras 17.3 y 17.4.
512 Capítulo 17

En JMS, el canal contribuye a desvincular al productor del consumidor pero ambos


siguen vinculados al canal. Un productor publica mensajes en una cola o tema concreto, y
el consumidor los recibe a través de una cola o tema concreto. El canal tiene la doble misión
de transmitir los mensajes y de determinar cómo se dirigen: las colas usan un algoritmo
punto a punto y los temas un sistema de publicación/suscripción.
Por el contrario, los productores AMQP no publican directamente en una cola, sino
que AMQP añade un nuevo nivel de direccionamiento entre el productor y las colas que
transmiten el mensaje: el intercambio. En la figura 17.8 se ilustra esta relación.

Cola

Figura 17.8. En AMQP, los productores de mensajes se desvinculan de las colas de mensajes
mediante un Intercambio que se encarga de dirigir el mensaje.

Como puede apreciar, un productor de mensajes publica un mensaje en un intercambio.


Dicho intercambio, vinculado a una o varias colas, dirige el mensaje a las mismas. Los
consumidores extraen los mensajes de la cola y los procesan.
Lo que no se aprecia en la figura 17.8 es que el intercambio no es un mecanismo de paso
a una cola. AMQP define cuatro tipos de intercambio diferentes, uno para cada algoritmo
de direccionamiento que determina si añadir o no un mensaje a una cola. En función del
algoritmo del intercambio, puede tener en cuenta la clave de direccionamiento del mensaje
y /o argumentos que comparar con la clave y argumentos de la vinculación entre el inter­
cambio y una cola (imagine que una clave de direccionamiento es como el campo Para:
de un correo electrónico en el que se especifica el destinatario). Si el algoritmo admite la
comparación, el mensaje se dirige a la cola. En caso contrario, no.
Los cuatro tipos estándar de intercambios de AMQP son los siguientes:

• Directo: Un mensaje se dirige a una cola si su clave de direccionamiento es una coin­


cidencia directa con la de la v in cu la c ió n .
• Tema: Un mensaje se dirige a una cola si su clave de direccionamiento es una coin­
cidencia de comodín con la de la vinculación.
• Encabezados: Un mensaje se dirige a una cola si los encabezados y valores de su
tabla de argumentos coinciden con los de la tabla de argumentos de la vinculación.
El encabezado x -m a tc h puede especificar si todos los valores deben coincidir o solo
alguno.
• Abanico: Un mensaje se dirige a todas las colas vinculadas al intercambio, indepen­
dientemente de la clave de direccionamiento o los encabezados/valores de la tabla
de argumentos.
Con estos cuatro tipos de intercambios puede definir sistemas de direccionamiento más
allá de las opciones de punto a punto o publicación/suscripción (y ni siquiera he mencionado
que se pueden vincular intercambios para crear una jerarquía anidada de direccionamiento).
Mensajería en Spring 513

Afortunadamente, para enviar y recibir mensajes, el algoritmo empleado apenas afecta a la


forma de desarrollar productores y consumidores de mensajes. Simplemente los produc­
tores publican en un intercambio con una clave de direccionamiento y los consumidores
recuperan mensajes de una cola.
Ha sido una presentación rápida de los fundamentos de la mensajería AMQP, suficiente
para que pueda empezar a enviar y recibir mensajes con Spring AMQP, pero le recomiendo
que profundice en AMQP y consulte la especificación y demás materiales que encontrará
en www. amqp. o rg o lea un manual especializado sobre el tema.
A continuación nos alejaremos del análisis abstracto de AMQP para empezar a crear
código para enviar y recibir mensajes con Spring AMQP. Primero veremos parte de la confi­
guración habitual de Spring AMQP necesaria tanto para productores como consumidores.

Configurar Spring para mensajería AMQP


Al empezar a trabajar con la abstracción JMS de Spring, configuramos una factoría de
conexiones. Del mismo modo, para empezar a usar Spring AMQP hay que configurar una
factoría de conexiones, pero en este caso de AMQP, en concreto una factoría de conexiones
RabbitMQ.

¿Qué es RabbitMQ?
RabbitMQ es un conocido agente de m ensajes de código abierto que implementa
AMQP. Spring AMQP es compatible con RabbitMQ, incluyendo una factoría de
conexiones, una plantilla RabbitMQ y un espacio de nombres de configuración.
Tendrá que in stalar RabbitMQ antes de poder usarlo para enviar y re cib ir
m ensajes. Encontrará las instrucciones de instalación en www.rabbitmq.com/
download.html. Varían en función del SO que ejecute, de modo que tendrá que
seguir las instrucciones correspondientes a cada entorno.

La forma más sencilla de configurar una factoría de conexiones RabbitMQ consiste en


usar el espacio de nombres de configuración r a b b i t proporcionado por Spring AMQP.
Para utilizarlo, declare su esquema en su configuración XML de Spring:
<?xml version="1 . O" encoding=nUTF-8"?>
cbeansrbeans xmlns="http://www.springframework.org/schema/rabbit"
xm lns:beans="http;//www.springframework.org/schema/beans"
xm lns:xsi="h ttp : //www.w3. org/2001/XMLSchema-instance"
x s i : schemaLocation="h ttp : //www. springframework. org /schema/rabbit
h ttp : //www.springframework.org/schema/rabbit/spring-ra b b it- 1 . O.xsd
h ttp : //www. springframework. org/schema/beans
h ttp : //www.springframework.org/schema/beans/spring-beans.xsd">

</beans :beans >


514 Capítulo 17

Aunque sea opcional, en este caso he decidido declarar el espacio de nombres r a b b i t


como el principal de la configuración y convertir al espacio de nombres b e a n s en el secun­
dario. De este modo me anticipo a la declaración de mayor número de elementos r a b b i t
que bean en esta configuración y prefijo los escasos elementos de bean con b e a n s : y
mantengo los elementos r a b b i t sin prefijo.
El espacio de nombres r a b b i t incluye varios elementos para configurar RabbitMQ
en Spring. El que más nos interesa ahora es c c o n n e c t io n f a c to r y > . En su versión más
sencilla podemos configurar una factoría de conexiones RabbitMQ sin atributos:
<connection-factory/>

Funcionará pero dejará al bean de factoría de conexiones resultante sin un ID de bean


útil, lo que dificulta la conexión de la factoría de conexiones a cualquier otro bean que la
necesite. Por ello, seguramente le convenga asignarle un ID de bean con el atributo id :
cconnection-factory id=11connectíonFactory11 />

De forma predeterminada, la factoría de conexiones asume que el servidor RabbitMQ


escucha en l o c a l h o s t en el puerto 5672 y que tanto el nombre de usuario como la contra­
seña son g u e s t. Son valores predeterminados razonablemente correctos para tareas de
desarrollo, pero debería cambiarlos al pasar a producción.
El siguiente elemento < c o n n e c tio n - f a c t o r y > está configurado para reemplazar los
valores predeterminados:
<connection-factory id="connectíonFactory"
host="$ {rabbitmq.h o st}"
p ort="$ {rabbitmq.p o rt}"
username="${rabbitmq.username}"
password="${rabbitmq.password}" />

Se usan marcadores de posición para especificar valores para poder gestionar la confi­
guración de forma externa a la de Spring (en un archivo de propiedades, por ejemplo).
Además de la factoría de conexiones, hay otros elementos de configuración que puede
usar. A continuación veremos cómo configurar Spring AMQP para crear colas, intercambios
y vinculaciones.

Declarar colas, intercambios y vincuIaciop.es


Al contrario de lo que sucede con JMS, donde el comportamiento de direccionamiento
de colas y temas lo establece la especificación, el direccionamiento AMQP es más completo
y flexible, por lo que depende de nosotros definir colas e intercambios, así como su vincu­
lación. Una forma de declarar colas, intercambios y vinculaciones consiste en usar diversos
métodos de la interfaz C hannel de RabbitMQ, pero trabajar directamente con esta interfaz
es algo complejo. ¿Puede ayudarle Spring AMQP a declarar sus componentes de direccio­
namiento de mensajes?
Afortunadamente el espacio de nombres r a b b i t incluye varios elementos para facilitar
la declaración de colas, intercambios y vinculaciones, descritos en la tabla 17.3.
Mensajería en Spring 515

Tabla 17.3. Elementos del espacio de nombres rabbit de Spring AMQP para crear colas,
intercambios y vinculaciones entre ambos.

<queue> Crea una cola.


<fanout-exchange > Crea un intercambio de abanico.

cheader-exchange> Crea un intercambio de encabezados.


<topic-exchange> Crea un intercambio de tema.
direct -exchange > Crea un intercambio directo.
<bindings> <binding/> </bindings> El elemento <bindings> define un conjunto de
uno o varios elementos <binding>. Crea una
vinculación entre un intercambio y una cola.

Estos elementos de configuración se usan junto al elemento <adm in>, que crea un
componente administrativo de RabbitMQ que genera automáticamente (en el agente
RabbitMQ en caso de que no existan) las colas, intercambios y vinculaciones declarados
con los elementos de la tabla 17.3.
Por ejemplo, para declarar la cola s p i t t l e . a l e r t . q u e u e , b a s t a con añadir los dos
siguientes elementos a su configuración de Spring:
<admin connection-factory="connectionFactory"/
> <queue id="spittleA lertQ ueue" nam e="spittle. a le r ts " />

En tareas simples de mensajería es todo lo que necesita, ya que existe un intercambio


directo predeterminado sin nombre y todas las colas se vinculan al mismo con una clave
de direccionamiento que coincide con el nombre de la cola. Con esta sencilla configuración
podría enviar mensajes al intercambio sin nombres y especificar la clave de direccionamiento
s p i t t l e . a l e r t . queue para que los mensajes se dirijan a la cola. Básicamente lo que
hace es recrear un modelo punto a punto de estilo JMS.
En direccionamientos más interesantes, tendrá que declarar uno o varios intercambios
y vincularlos a colas. Por ejemplo, para que un mensaje se dirija a varias colas indepen­
dientemente de la clave de direccionamiento, puede configurar un intercambio de abanico
y varias colas de esta forma:
<admin connection-factory="connectionFactory" /
> <queue nam e="spittle. a l e r t . queue.1" > <queue n am e="spittle.alert.queue
.2" > <queue name="s p i t t l e . a l e r t . queue.3" > <fanoutexchange
nam e="spittle . fanout’■ > <bindings> cbinding q u eu e= "sp ittle. a l
ert.q u eu e.1" /> cbinding q u eu e= "sp ittle .a lert.q u eu e.2" /
> cbinding queue="s p i t t l e . a le rt.q u e u e .3" /> c/bindings> c/fanoutexchange>

Con los elementos de la tabla 17.3 dispone de infinidad de formas de configurar el


direccionamiento en RabbitMQ, pero como no contamos con infinidad de páginas para
describirlas todas, le dejaré que lo practique por su cuenta y seguiremos con la descripción
del envío de mensajes.
516 Capítulo 17

Enviar mensajes con RabbitTempIate


Como su nombre sugiere, la factoría de conexiones RabbitMQ se utiliza para crear cone­
xiones con RabbitMQ. Si desea enviar mensajes a través de RabbitMQ, podría inyectar el
bean c o n n e c t i o n F a c t o r y a su clase A l e r t S e r v i c e l m p l , usarla para crear un objeto
C o n n e c t io n , utilizar esa conexión para crear un elemento C h a n n e l y recurrir a ese canal
para publicar un mensaje en un intercambio.
Claro que podría hacerlo, pero sería demasiado trabajo y tendría que escribir demasiado
código predefinido, y algo que Spring aborrece es el código predefinido. Ya hemos visto
varios casos en los que Spring ofrece plantillas para eliminar el código predefinido, incluida
Jm s T e m p la te , que elimina el de JMS. No debería sorprenderle que Spring AMQP propor­
cione R a b b itT e m p I a te para eliminar código predefinido asociado al envío y recepción
de mensajes con RabbitMQ. La configuración más sencilla de R a b b itT e m p I a te se puede
realizar con el elemento < t e m p la t e > del espacio de nombres de configuración r a b b i t
de esta forma:
ctemplate id="rabbitTemplate"
connect ion-factory=" connectionFactory" / >

Ahora, para enviar un mensaje basta con inyectar el bean de plantilla en A l e r t S e r v i c e


Im p l y utilizarlo para enviar un objeto S p i t t l e . El siguiente código muestra una nueva
versión de A l e r t S e r v i c e l m p l que usa R a b b itT e m p I a te en lugar de Jm s T e m p la te
para enviar rma alerta S p i t t l e .

Listado 17.7. Envío de Spittle con RabbitTempIate.

package com.habuma. s p i t t e r . a l e r t s ;

import org. springframework. amqp. ra b b it. co re. RabbitTempIate;


import org.springframework.beans. fa cto ry . annotation.Autowired;
import com.habuma. s p i t t e r . domain. S p i t t le ;

public cla ss AlertServicelm pl implements A lertService {

private RabbitTempIate ra b b it;

@Autowired
public AlertServicelmpl(RabbitTempIate rabbit) {
th is .r a b b it = ra b b it;
i
public void se n d S p ittleA lert(S p ittle s p ittle ) {
ra b b it. convertAndSend{"s p i t t l e . a l e r t . exchange",
" s p ittle .a le r ts " , s p ittle );
i
i
Como puede apreciar, ahora el método s e n d S p i t t l e A l e r t () invoca el método
c o n v e rtA n d S e n d () en la R a b b itT e m p I a te inyectada. Pasa tres parámetros: el nombre
del intercambio, la clave de direccionamiento y el objeto que enviar. Comprobará que no
Mensajería en Spring 517

se especifica cómo se dirige el mensaje, a qué colas se envía o qué consumidores esperan
recibirlo. R a b b itT e m p la t e dispone de varias versiones sobrecargadas de c o n v e rtA n d
Sen d () para simplificar su uso. Por ejemplo, con uno de los métodos co n v e rtA n d S e n d ()
sobrecargados puede excluir el nombre del intercambio al invocar c o n v e rtA n d S e n d ( ) :

rab b it.co n v ertA n d Sen d ("sp ittle.alerts", s p i t t l e ) ;

Con otra, puede excluir tanto el nombre del intercambio como la clave de direcciona-
miento si lo desea:
r a b b it. convertAndSend(spittle);

Cuando se excluye el nombre del intercambio o este y la clave de direccionamiento de


la lista de parámetros, R a b b itT e m p la te usa su nombre de intercambio y clave de direc­
cionamiento predeterminados.
Tal y como hemos configurado la plantilla, el nombre del intercambio predeterminado
está vacío y también lo está la clave de direccionamiento predeterminada, pero puede
configurar distintos valores predeterminados por medio de los atributos ex ch a n g e y
r o u t i n g - k e y del elemento < te m p la te > :
<template id="rabbitTemplate"
connectio n -facto ry ="connectionFactory"
exchanges»s p i t t l e .a l e r t . exchange"
routing-key="s p i t t l e . a le r t s " />

Independientemente de qué valores predeterminados configure, siempre puede reem­


plazarlos al invocar convertA ndSend () si los especifica explícitamente como parámetros.
Puede que le interese usar algunos de los demás métodos de R a b b itT e m p la te para
enviar mensajes. Por ejemplo, puede usar el método send () de nivel inferior para enviar
un objeto o r g . s p r in g f ram ew ork. amqp. c o r e . M essage de esta forma:
Message helloMessage =
new Message("Helio WorldI" .g etB y tes(), new M essageProperties( ) ) ;
r a b b it. send("h e lio . exchange", "h e lio . rou ting ", helloM essage);

Como sucede con co n v ertA n d S en d ( ) , el método sen d () se sobrecarga para no


necesitar el nombre del intercambio y /o la clave de direccionamiento.
El truco para usar los métodos se n d () consiste en crear un objeto M essage para enviar.
En el ejemplo H e lio W orld se crea una instancia M essage asignándole la matriz de bytes
de la cadena. Es muy sencillo con valores S t r i n g pero se puede complicar cuando el
mensaje es un objeto complejo.
Por ese motivo existe c o n v e rtA n d S e n d ( ) , que convierte automáticamente un objeto
en M e ssa g e , y lo hace con ayuda de un conversor de mensajes. El predeterminado es
S im p le M e s s a g e C o n v e r te r , adecuado para trabajar con objetos S t r i n g , instancias
S e r i a l i z a b l e y matrices de bytes. Spring AMQP le ofrece otros conversores de mensajes
que puede utilizar, como los que permiten trabajar con datos JSON y XML.
Después de enviar un mensaje, nos centraremos en el otro extremo de la conversación
y aprenderemos a recuperarlo.
518 Capítulo 17

Recibir mensajes AMQP


Como recordará, la compatibilidad de Spring con JMS le ofrece dos formas de recuperar
un mensaje de una cola: de forma síncrona a través de Jm s T e m p la te y de forma asincrona
mediante POJO basados en mensajes. Spring AMQP le ofrece opciones similares para recu­
perar mensajes enviados a través de AMQP Como ya contamos con R a b b itT e m p la t e ,
veremos primero cómo usarla para recuperar de forma síncrona un mensaje de una cola.

Recibir mensajes con RabbitTemplate


R a b b itT e m p la t e le ofrece diversos métodos para recibir mensajes. Los más sencillos
son los métodos r e c e i v e ( ) , los homólogos del lado del consumidor de los métodos
s e n d () de R a b b itT e m p la t e . Con los métodos r e c e i v e () puede recuperar un objeto
M e ssa g e de la cola:
Message message = r a b b it. re c e iv e ("s p i t t l e . a l e r t . queue") ;

Si lo prefiere, puede configurar una cola predeterminada para recibir mensajes si esta­
blece el atributo queue al configurar la plantilla:
<témplate id="rabbitTemplate"
connection-factory="connectionFactory"
exch an ge="sp ittle. a l e r t . exchange"
ro u tin g -k ey = "sp ittle. a le r ts "
queue="s p i t t l e . a l e r t . queue" />

De este modo puede invocar el método r e c e i v e () sin parámetros para recibir desde
la cola predeterminada:
Message message = r a b b it. re c e iv e 0 ;

Una vez conseguido el objeto M e ssa g e , probablemente tendrá que convertir la matriz
de bytes de su propiedad b o d y al objeto que desee. Al igual que era complicado convertir
objetos de dominio en mensajes para su envío, es complicado convertir mensajes reci­
bidos en objetos de dominio. Por lo tanto, en su lugar puede usar el método r e c e iv e A n d
C o n v e r t () de R a b b itT e m p la t e :

S p ittle s p it t le =
(S p ittle ) ra b b it. receiveAndConvert( " s p i t t l e . a l e r t . queue") ;

También puede excluir el nombre de la cola de los parámetros de la invocación para


usar el predeterminado:
S p ittle s p i t t le = (S p ittle ) r a b b it. receiveAndConvert();

El m étodo r e c e iv e A n d C o n v e r t () usa los m ismos conversores de m ensajes que


se n d A n d C o n v e rt () para convertir un objeto M e ssa g e en el tipo que lo haya originado.
Las invocaciones de r e c e i v e () y r e c e iv e A n d C o n v e r t () devuelven un resultado
inmediato, que puede ser n u l 1 si no hay mensajes en espera en la cola. En ese caso tendrá
que gestionar las consultas y subprocesamientos necesarios para monitorizarla.
Mensajería en Spring 519

En lugar de realizar consultas síncronas y esperar la llegada de mensajes, Spring AMQP


le ofrece POJO basados en mensajes que recuerdan a los de Spring JMS. Veamos a conti­
nuación cómo consumir mensajes mediante POJO AMQP basados en mensajes.

Definir POJO de AMQP basados en mensajes


Lo primero que necesita para consum ir un objeto S p i t t l e de forma asincrona en un
POJO basado en mensajes es el propio POJO. En este ejemplo es S p i t t l e A l e r t H a n d l e r
el que desarrolla ese rol:
package com. habuma. s p i t t r . a l e r t s ;
import com.habuma. s p i t t r . dom ain.Spittle

public cla ss SpittleA lertH andler {

public void h a n d le S p ittleA lert(S p ittle s p ittle ) {


/ / . . . añadir aquí la implementación . . .
}
}
Es el mismo S p i t t l e A l e r t H a n d l e r que utilizamos para consumir mensajes S p i t t l e
con JMS. Se puede reutilizar el mismo POJO porque no tiene nada que dependa de JMS ni
de AMQP. Simplemente es un POJO, listo para procesar un objeto S p i t t l e independien­
temente del mecanismo de mensajería sobre el que se transmita.
También tendrá que declarar S p i t t l e A l e r t H a n d l e r como bean en el contexto de
aplicación de Spring:
<bean id = "s p ittle L is te n e rM
class="com.habuma. s p i t t r . a l e r t . SpittleA lertH andler" />

Pero como antes, ya lo hicimos al trabajar con MDP basados en JMS. No hay diferencias.
Por último, tendrá que declarar un contenedor de escuchadores y un escuchador que
invoque S p i t t l e A l e r t H a n d l e r al recibir un mensaje. Ya lo hicimos con MDP basados
en JMS, pero en la configuración de AMQP es ligeramente diferente:
d iste n e r-c o n ta in e r connection-factory=,'connectionFactory">
c lis te n e r re f= "s p ittle L is te n e r "
met hod="handleSp i1 1 1eA lert"
queue-nam es="spittle.alert.queue" />
< /listen e r-co n ta in er>

¿Aprecia la diferencia? Asumo que no es evidente. Los elementos < l i s t e n e r -


c o n t a in e r > y < l i s t e n e r > parecen similares a sus homólogos en JMS. Sin embargo,
provienen del espacio de nombres r a b b i t y no del espacio de nombres de JMS. Ya avisé
que no era evidente.
Y hay otra pequeña diferencia. En lugar de especificar una cola o un tema al que escuchar
a través el atributo d e s t i n a t i o n (como hicimos con JMS), en este caso especificamos la
cola en la que escuchar mensajes por medio del atributo queu e-ñam es. El resto funciona
de la misma forma.
520 Capítulo 17

Como habrá supuesto, el atributo queue-ñam es indica un plural. En el ejemplo solo


especificamos una cola a la que escuchar pero puede indicar todos los nombres de cola que
desee, separados mediante comas.
Otra forma de especificar colas para escuchar consiste en hacer referencia a los bean de
cola declarados con el elemento < queue >. Puede hacerlo a través del atributo queue s:
< liste n er-co n ta in e r connection-factory=,,connectionFactoryM>
c lis te n e r r e f = "sp ittle L iste n e r"
m ethod="handleSpittleAlert"
queues="spittleAlertQueue" />
</lis te n e r-c o n ta in e r>

Como antes, este atributo acepta una lista de ID de cola separados mediante comas.
Evidentemente para ello tendrá que declarar colas con ID. En el siguiente ejemplo volvemos
a declarar la cola a l e r t pero con un ID:
<queue id="spittleA lertQ ueue" n am e="spittle.alert.queue" />

Se usa el atributo id para asignar un ID de bean a la cola en el contexto de aplicación


de Spring. El atributo ñame especifica el nombre de la cola en el agente RabbitMQ.

Resumen
La mensajería asincrona ofrece varias ventajas sobre las RPC síncronas. La comunica­
ción indirecta permite que las aplicaciones tengan un acoplamiento débil entre sí, lo que
reduce el impacto en caso de que alguno de los sistemas falle. Además, como los mensajes
se envían a sus destinatarios, no hay necesidad de esperar a recibir una respuesta. En la
mayoría de los casos, esto supone un aumento del rendimiento de la aplicación.
Aunque JMS cuenta con un API estándar para todas las aplicaciones Java que deseen
participar en la comunicación asincrona, su uso puede ser complejo. Spring elimina la nece­
sidad de utilizar código JMS reutilizable, así como del código de control de excepciones,
lo que facilita el uso de la mensajería asincrona.
En este capítulo hemos visto diferentes formas en las que Spring puede ayudarnos a
establecer una comunicación asincrona entre dos aplicaciones por medio de agentes de
mensajes y JMS. La plantilla JMS de Spring elimina el código reutilizable que suele ser
necesario en el modelo de programación JMS tradicional. Asimismo, los bean basados en
mensajes de Spring permiten declarar métodos de bean que reaccionan a los mensajes que
llegan en una cola o e n u n tema. También hemos visto cómo utilizar el invocador JMS de
Spring para proporcionar bean de Spring a RPC basadas en mensajes.
En este capítulo hemos visto cómo usar comunicación asincrona entre aplicaciones. En el
siguiente, continuaremos con el tema y veremos cómo habilitar la comunicación asincrona
entre un cliente basado en el navegador y un servidor por medio de WebSocket.
Capítulo

18 M ensajería con
W e b So ck e t y S T O M P *•

CONCEPTOS FUNDAMENTALES:

• Envío de mensajes entre el navegador y el servidor.


• Procesamiento de mensajes en controladores de Spring
MVC.
• Envío de mensajes dirigidos al usuario.
En el capítulo anterior vimos formas de enviar mensajes entre aplicaciones por medio
de JMS y AMQP. La mensajería asincrona es una forma habitual de comunicación entre
aplicaciones, pero cuando una de las implicadas se ejecuta en un navegador Web, necesi­
tamos algo distinto.
WebSocket es un protocolo que proporciona comunicación de tipo dúplex completo a
través de una única conexión. Permite, entre otras cosas, la mensajería asincrona entre un
navegador Web y un servidor. Al ser de dúplex completo, el servidor puede enviar mensajes
al navegador y viceversa.
Spring 4.0 añade compatibilidad con la comunicación WebSocket e incluye lo siguiente:

• Un API de nivel inferior para enviar y recibir mensajes.


• Un API de nivel superior para procesar mensajes en controladores del MVC de Spring.
• Una plantilla de mensajería para enviar mensajes.
• Compatibilidad con SockJS para superar la falta de compatibilidad con WebSocket
en navegadores, servidores y proxy.

En este capítulo aprenderemos a establecer comunicaciones asincronas entre un


servidor y una aplicación de navegador por medio de las funciones WebSocket de Spring.
Empezaremos con el API WebSocket de nivel inferior de Spring.

Trabajar con el API WebSocket de nivel


inferior de Spring ______ _
En su versión más sencilla, WebSocket es un simple canal de comunicación entre dos
aplicaciones. En uno de los extremos, una aplicación envía un mensaje, que se procesa en el
otro. Al ser de dúplex completo, ambos extremos pueden enviar en mensajes y procesarlos
(véase la figura 18.1).

Figura 18.1. Un WebSocket es un canal de comunicación de dúplex completo


entre dos aplicaciones.

La comunicación WebSocket se puede usar entre todo tipo de aplicaciones pero se suele
usar principalmente para facilitar la comunicación entre un servidor y una aplicación basada
en el navegador. Un cliente JavaScript en el navegador abre una conexión al servidor y el
servidor actualiza el navegador en dicha conexión. Suele ser un proceso más eficaz y natural
que la alternativa tradicional de consultar las actualizaciones al servidor.
524 Capítulo 18

Para ilustrar el API WebSocket de nivel inferior de Spring crearemos un sencillo ejemplo
de WebSocket en el que un cliente basado en JavaScript juega una partida interminable de
Marco Polo con el servidor. La aplicación del lado del servidor procesará un mensaje de
texto ("Marco! " ) y reaccionará enviando otro ("Polo!" ) por la misma conexión. Para
procesar mensajes en Spring con compatibilidad con el API WebSocket de nivel inferior
debe crear una clase que implemente WebSocketHandler:
public in te rfa ce WebSocketHandler {
void afterConnectionEstablished(WebSocketSession session) throws Exception;
void handleMessage(WebSocketSession session,
WebSocketMessage<?> message) throws Exception;
void handleTransportError(WebSocketSession session,
Throwable exception) throws Exception;
void afterConnectionClosed(WebSocketSession session,
CloseStatus closeStatu s) throws Exception;
boolean supportsPartialM essages();

Como puede apreciar, la interfaz W e b S o c k e tH a n d le r requiere la implementación


de cinco métodos. En lugar de implementar directamente W e b S o ck e tH a n d le r, resulta
más sencillo ampliar A b s t r a c t W e b S o c k e t H a n d l e r , una implementación abstracta
de W e b S o c k e tH a n d le r. El siguiente código muestra M a rco H a n d le r, una subclase de
A b s t r a c tW e b S o c k e t H a n d le r que procesa mensajes en el servidor.

Listado 18.1. MarcoHandler procesa mensajes de texto a través de WebSocket.


package marcopolo;

import o r g .s lf 4j.L ogger;


import o r g .s l f 4 j. Logger Factory;
import org. springframework.web. so ck et. TextMessage;
import org.springframework.web.so ck et.WebSocketSession;
import org.springframework.web.socket.hand ler.AbstractWebSocketHandler;

public cla ss MarcoHandler extends AbstractWebSocketHandler {

p riv ate s t a t ic fin a l Logger logger =


LoggerFactory. getLogger(MarcoHandler. c l a s s ) ;

protected void handleTextMessage( / / Procesar mensaje de te x to .


WebSocketSession session, TextMessage message) throws Exception {
logger. info ("Received message: " + message. getPayload( ) ) ;

Thread.sleep(2000); / / Simular retardo,


session.sendMessage(new TextMessage("Polo! " ) ) ; / / Enviar mensaje de tex to .

i
Aunque sea una clase abstracta, A b s t r a c t W e b S o c k e t H a n d l e r no requiere que
reemplacemos ningún método concreto. En su lugar, nos deja decidir los que queramos
reemplazar. Además de los cinco métodos definidos en W e b S o ck e tH a n d le r, también
puede reemplazar otros tres definidos por A b s tr a c tW e b S o c k e tH a n d le r :
Mensajería con WebSocket y STOMP 525

• h a n d le B in a r y M e s s a g e ()
• h a n d le P o n g M e s s a g e ()
• h a n d le T e x tM e s s a g e ()

Son simples especializaciones del método h a n d le M e s s a g e ( ) , cada una para un tipo


de mensaje concreto.
Como M a rc o H a n d le r procesa el mensaje de texto " M a r c o ! " , tiene sentido que reem­
place h a n d l eT extM e s s a g e ( ) . Al recibir un mensaje de texto, se registra y, tras un retraso
simulado de dos segundos, se devuelve otro mensaje de texto sobre la misma conexión.
Los demás m étodos no reem plazados por M a r c o H a n d l e r los im plem enta
A b s t r a c t W e b S o c k e t H a n d l e r con implementaciones sin operaciones, por lo que
M a r c o H a n d le r también procesará mensajes binarios y pong, aunque no hace nada
con ellos. También podría ampliar T e x tW e b S o c k e tH a n d le r en lugar de A b s t r a c t
W e b S o c k e tH a n d le r:

public cla ss MarcoHandler extends TextWebSocketHandler {

T e x tW e b S o c k e tH a n d le r es una subclase de A b s t r a c tW e b S o c k e t H a n d le r que


rechaza el procesamiento de mensajes binarios. Reemplaza h a n d le B in a ry M e ssa g e ( ) para
cerrar la conexión WebSocket si recibe una conexión binaria. Del mismo modo, Spring también
ofrece B in a ry W e b S o c k e tH a n d le r, una subclase de A b s tr a c tW e b S o c k e tH a n d le r que
reemplaza h a n d l eTextM e s s age ( ) para cerrar la conexión si se recibe un mensaje de texto.
Independientemente de que procese mensajes de texto, binarios o ambos, puede que
también le interese procesar la creación y el cierre de conexiones. En ese caso puede reem­
plazar a f t e r C o n n e c t i o n E s t a b l i s h e d () y a f t e r C o n n e c t i o n C l o s e d () :
public void afterConnectionEstablished(WebSocketSession session)
throws Exception {
logger. in f o ("Connection estab lish ed ") ;
}
(©Override
public void afterConnectionClosed(
WebSocketSession session, CloseStatus status) throws Exception {
logger. in fo ("Connection closed. Statu s: " + s ta tu s );
}

Las conexiones se encierran entre a f t e r C o n n e c t i o n E s t a b l i s h e d () y a f t e r


C o n n e c tio n C lo s e d () . Al establecerse una nueva conexión, se invoca el método a f t e r
C o n n e c t i o n E s t a b l i s h e d ( ) y, al cerrarse, el método a f t e r C o n n e c t i o n C l o s e d () . En
este ejemplo, los eventos de conexión solamente se registran pero estos métodos pueden
ser útiles para configurar y eliminar recursos durante la vida de la conexión.
Comprobará que ambos métodos comienzan por la palabra a f t e r (después), lo que
significa que solo reaccionarán a los eventos después de que se produzcan y que no pueden
cambiar el resultado.
526 Capítulo 18

Tras definir la clase de controlador de mensajes tendrá que configurarla para que Spring
le entregue mensajes. En la configuración Java de Spring, debe anotar una clase de confi­
guración con @ E n a b le W e b S o c k e t e implementar la interfaz W e b S o c k e tC o n fig u r e r ,
como ilustra el siguiente código.

Listado 18.2. Habilitación de WebSocket y asignación de un controlador de mensajes


en configuración de Java.

package marcopolo;

import org. springframework. co n tex t. annotation. Bean;


import org. springf ramework.web. so ck et. conf i g . annotat io n . EnableWebSocket;
import org. springframework.web. so ck et. co n fig . annotation.WebSocketConfigurer;
import org. springframework.web. so ck et. co n fig . annotation.WebSocketHandlerRegistry;
@EnableWebSocket
public cla ss WebSocketConfig implements WebSocketConfigurer {

@Override
public void registerWebSocketHandlers(
WebSocketHandlerRegistry reg istry ) {
r e g is tr y . addHandler(marcoHandler( ), "/m arco"); / / Asignar MarcoHandler a "/marco".

}
@Bean
public MarcoHandler marcoHandler{) { / / D eclarar bean MarcoHandler.
return new MarcoHandler();
}
}

El m étodo r e g is t e r W e b S o c k e t H a n d le r s () es la clave para registrar un controlador


de mensajes. Si lo reemplaza, recibirá un W e b S o c k e tH a n d le r R e g is t r y a través del que
puede invocar a d d H a n d le r () para registrar un controlador de mensajes. En este caso, se
registra M a rc o H a n d le r (declarado como bean) y se asocia a la ruta /m a rc o .
Si prefiere configurar Spring en XML, puede recurrir al espacio de nombres w eb so ck et,
como se muestra a continuación.

Listado 18.3. El espacio de nombres websocket habilita la configuración XML para WebSocket.

<?xml version="1.0" encoding="UTF-8"?>


<beans xmlns="h ttp : //www.springframework.org/schema/beans"
xm lns:xsi="http://www.w3. org/2001/XMLSchema-instance"
xmlns:websocket="http://www.springframework.org/schema/websocket"
x s i : schemaLocation="
h ttp : / /www. springframework.org/schema/websocket
h ttp : //www.springframework. org/schema/websocket/spring-websocket.xsd
h ttp : //www. springframework. org/schema/beans
h ttp : / /www. springframework. org/schema/beans/spring-beans.xsd" >

<websocket: handlers >


/ / Asignar MarcoHandler a 11/marco” .
<websocket.-mapping handler="marcoHandler" path="/marco" />
Mensajería con WebSocket y STOMP 527

</websocket:handlers>

<bean id ="marcoHandler" / / D eclarar bean MarcoHandler.


class="marcopolo.MarcoHandler" />
</beans>

Independientemente de la configuración que utilice, será todo lo que necesita.


Ahora podemos centrarnos en el cliente que envía el mensaje de texto "M a rco ! " al
servidor y escucha los que devuelve. El siguiente código JavaScript abre un WebSocket
nativo y lo utiliza para enviar mensajes al servidor.

Listado 18.4. Un cliente JavaScript que se conecta a la conexión Web "marco".

var u rl = 'w s :// ' + window.location.host + ' /websocket/marco' ;


var sock = new WebSocket(url); / / Abrir Websocket.

sock.onopen = fu n ctio n () { / / Procesar evento de apertura,


console. lo g ( ' Opening') ;
sayMarco( ) ;
};
sock.onmessage = fu n ctio n (e) { / / Procesar mensaje,
console. lo g ( 'Received message: e .d a ta );
setTim eout(function( ) { sayMarco( ) } , 2000);
};
sock.onclose = functionO { / / Procesar evento de c ie r re ,
consolé. lo g ( ' C lo sin g ') ;
};
function sayMarco() {
co n so lé.lo g ( 'Sending M arco!');
sock. send("Marco! " ) ; / / Enviar mensaje.
}
Lo primero que hace este código es crear una instancia de W ebsocket, un tipo nativo
de los navegadores que admiten WebSocket. Al crear la instancia se abre el WebSocket a
la URL proporcionada. En este caso, se añade el prefijo ws : / / a la URL para indicar que
se trata de una conexión WebSocket básica. Si fuera una conexión WebSocket segura, el
prefijo del protocolo sería w ss : / / .
Tras crear la instancia W e b s o c k e t, se configura el WebSocket con funciones de proce­
samiento de eventos. Los eventos onopen, o n m e ssa g e y o n c l o s e de WebSocket coin­
ciden con los métodos a f t e r C o n n e c t i o n E s t a b l i s h e d ( ) , h a n d l e T e x t M e s s a g e () y
a f t e r C o n n e c t i o n C l o s e d () de M a rc o H a n d le r. El evento on o p en recibe una función
que invoca sa y M a rc o () para enviar el mensaje " M a r c o ! " en el WebSocket. Al hacerlo,
se inicia la partida interminable de Marco Polo ya que M a rco H a n d le r en el servidor reac­
ciona devolviendo " P o l o ! " . Cuando el cliente recibe el mensaje del servidor, el evento
o n m e ssa g e le devuelve otro mensaje " M a r c o ! ".
Y el intercambio prosigue hasta que la conexión se cierra. No se muestra en el listado
18.4, pero una invocación a s o c k . c i ó s e () acaba con este sinsentido. El servidor también
podría cerrar la conexión o el navegador podría cambiar de página y la conexión se cerraría.
528 Capítulo 18

En cualquier caso, tras cerrarse la conexión, se desencadena el evento o n c lo s e . En este


caso, la acción se marca con un sencillo mensaje en el registro de la consola. Llegados a este
punto hemos creado todo lo necesario para habilitar la compatibilidad con WebSocket de
nivel inferior de Spring, incluida una clase de controlador que recibe y envía mensajes, y
un sencillo cliente JavaScript para hacer lo mismo en el navegador. Si generara el código y
lo implementara en un contenedor de servlet, podría incluso funcionar.
¿Ha notado cierto pesimismo en la elección de "podría"? En realidad no puedo asegurar
que funcione. De hecho, es muy probable que no lo haga. Aunque hayamos realizado los
pasos correctos, la suerte está en nuestra contra. Veamos qué impide el funcionamiento del
código del WebSocket y busquemos una solución.

Afrontar la falta de compatibilidad con WebSocket*•


WebSocket es una especificación relativamente reciente. Aunque se estandarizó a finales
de 2011, no cuenta con una compatibilidad completa en todos los navegadores Web y
servidores de aplicaciones. Firefox y Chrome la tienen desde hace tiempo, pero el resto ha
comenzado a incluirla ahora. La siguiente lista indica las versiones mínimas de los nave­
gadores más utilizados compatibles con WebSocket:
• Internet Explorer: 10.0.
• Firefox: 4.0 (parcial), 6.0 (completa).
• Chrome: 4.0 (parcial), 13.0 (completa).
• Safari: 5.0 (parcial), 6.0 (completa).
• Opera: 11.0 (parcial), 12.10 (completa).
• Safari de iOS: 4.2 (parcial), 6.0 (completa).
• Navegador de Android: 4.4.
Desafortunadamente, muchos usuarios no conocen ni entienden las funciones de los
nuevos navegadores Web y tardan en actualizarlos. Es más, muchas empresas usan una
versión concreta de un navegador de forma estándar, lo que dificulta (o impide) que sus
empleados utilicen otras más nuevas. Con este panorama, es muy probable que el público
de su aplicación no pueda usarla si utiliza WebSocket.
Sucede lo mismo con la compatibilidad con WebSocket en servidores. GlassFish la tiene
desde hace un par de años pero el resto empiezan a añadirla ahora. Por ejemplo, tuve que
probar el ejemplo anterior en una versión de prueba de Tomcat 8.
Aunque las versiones de los navegadores y los servidores de aplicaciones se alineen, y
se admita WebSocket en ambos extremos, el problema puede aparecer en el camino. Los
proxy de cortafuegos suelen bloquear todo el tráfico que no sea HTTP. Son incapaces, o
todavía no se han configurado, para permitir comunicaciones por WebSocket.
Asumo que he pintado un panorama muy negro, pero no debería impedirle intentar
usar WebSocket. Cuando funciona, es una opción magnífica. Cuando no lo hace, solo
necesitará un plan alternativo.
Mensajería con WebSocket y STOMP 529

Afortunadamente, ese plan alternativo es la especialidad de SockJS, un emulador de


WebSocket que imita su API todo lo posible en la superficie, pero que entre bastidores es lo
bastante inteligente como para elegir otra forma de comunicación c u a n d o n o e s tá disponible
WebSocket. SockJS siempre elige WebSocket primero, pero si no puede hacerlo, determina
la mejor opción disponible de entre las siguientes:

• Flujos XHR.
• Flujos XDR.
• Orígenes de eventos iFrame.
• Archivo HTML iFrame.
• Consultas XHR.
• Consultas XDR.
• Consultas XHR de iFrame.
• Consultas JSONP.

La buena noticia es que no tiene que entender todas estas opciones para poder usar
SockJS, ya que le permite desarrollar un modelo de programación coherente, como si la
compatibilidad con WebSocket fuera completa, y se encarga de las opciones alternativas
entre bastidores. Por ejemplo, para habilitar comunicación SockJS en el servidor, solo tiene
que solicitarla en la configuración de Spring. Volviendo al método r e g i s t e r W e b S o c k e t
H a n d le r s () del listado 18.2, puede habilitar WebSocket si añade lo siguiente:

@Override
public void registerWebSocketHandlers(
WebSocketHandlerRegistry registry) {
registry.addHandler(marcoHandler{), "/marco").withSockJS();
}
Con la mera invocación de w it h S o c k J S () en el W e b S o c k e t H a n d le r R e g i s t r a t i o n
devuelto por la invocación de a d d H a n d le r ( ) , decimos que queremos habilitar SockJS y
activar sus alternativas si no se puede usar WebSocket.
Si utiliza XML para configurar Spring, para habilitar SockJS basta con añadir el elemento
< w e b s o c k e t: s o c k j s> a la configuración:
<websocket:handlers>
<websocketrmapping handler="marcoHandler" path="/marco" />
<websocket:sockj s />
</websocket:handlers>

Para usar SockJS en el cliente tendrá que cargar su biblioteca cliente. El proceso exacto
depende de si utiliza un cargador de módulos JavaScript (como r e q u i r e . j s o c u r l . j s)
o si simplemente carga las bibliotecas JavaScript con una etiqueta < s c r i p t >. La forma
más sencilla de cargar la biblioteca cliente SockJS consiste en hacerlo desde el CDN SockJS
con cma etiqueta < s c r i p t > :
«script src="http://cdn.sockj s .org/sockj s-0.3.min.js"></scripts
530 Capítulo 18

Resolver recursos Web con W ebjars


En el código de ejem plo utilizo W ebJars para resolver las bibliotecas JavaScript
como parte de la versión Maven o Gradle del proyecto, como si fuera cualquier
otra dependencia. P ara ello, he definido un controlador de recursos en la
configuración del MCV de Spring para resolver solicitudes de la ruta estándar
de W ebJars en las que la ruta empieza por /w e b ja rs /** :
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("/webjars/**")
.addResourceLocations("classpath:/META-INF/resources/webjars/");
}
Con este controlador de recursos puedo cargar la biblioteca SockJS en una página
Web con la siguiente etiqueta < s c rip t> :
<script th:src="@{/webj ars/sockj s-Client/0.3.4/sockj s.min.js}">
</script>

Esta etiqueta < s c r ip t> concreta proviene de una plantilla Thym eleaf y recurre
a la expresión @{ . . . } para calcular la ruta de URL completa relativa al contexto
del archivo JavaScript.

Aparte de cargar la biblioteca cliente SockJS, solo tiene que cambiar dos líneas del listado
18.4 para usar SockJS:
var url = 'marco';
var sock = new SockJS(url);

El primer cambio se realiza en la URL. SockJS procesa URL con h t t p : / / o h t t p s : / /


en lugar de ws -. / / y w ss : / / . No obstante, puede usar URL relativas para no tener que
derivar la URL completamente cualificada. En este caso, si la página que contiene el código
JavaScript se encuentra en h t t p : / / l o c a l h o s t : 8 08 0 / w e b s o c k e t , la ruta m a rc o
devuelta genera una conexión a h t t p : / / l o c a l h o s t : 8 0 8 0 / w e b s o c k e t / m a r c o .
No obstante, el cambio más importante consiste en crear una instancia de S o c k J S en
lugar de W eb S o ck e t. Como S o c k J S imita a W e b so c k e t en todo lo que puede, se puede
conservar el resto del código del listado 18.4. Las mismas funciones para controlar los
eventos onopen, o n m essa g e y o n c l o s e seguirán respondiendo a sus respectivos eventos,
y la misma función s e n d () enviará " M a r c o ! " al servidor.
No hemos cambiado tanto código y sin embargo se ha producido una gran diferencia en
el funcionamiento de los mensajes entre cliente y servidor. Puede estar seguro de que una
comunicación similar a la de WebSocket funcionará entre el navegador y el servidor, aunque
WebSocket no se admita en el navegador, en el servidor o en cualquier proxy intermedio.
WebSocket permite la comunicación entre navegador y servidor, y SockJS ofrece comu­
nicación alternativa cuando no se admite WebSocket, pero en ambos casos es un tipo de
comunicación de un nivel demasiado inferior para un uso práctico. A continuación veremos
Mensajería con WebSocket y STOMP 531

cómo usar STOMP (Sim ple Text O riented M essag in g P rotocol, Protocolo simple de mensajería
orientada a texto) sobre WebSocket para añadir una verdadera semántica de mensajería a
la comunicación entre navegadores y servidores.

Trabajar con mensajes STOMP


Si le propongo escribir una aplicación Web, seguramente ya sepa las tecnologías y marcos
de trabajo base que podría utilizar incluso antes de discutir los detalles. Incluso para una
sencilla aplicación de tipo Hola Mundo, puede que se le ocurra crear un controlador de
Spring MVC para procesar una solicitud y una plantilla JSP o Thymeleaf para la respuesta.
Como mínimo, podría crear una página HTML estática y dejar que el servidor Web la
entregue al navegador Web que la solicite. Seguramente no le preocupe cómo solicita la
página cada navegador o cómo se entrega.
Imagine ahora que HTTP no existe y que tiene que crear una aplicación Web únicamente
con conexiones TCP. Seguro que piensa que estoy loco. Se podría conseguir, pero tendría
que diseñar su propio protocolo de conexión que tanto cliente como servidor acordaran
usar para facilitar una comunicación eficaz. En resumen, no sería nada sencillo.
Afortunadamente, el protocolo HTTP se encarga de todos los detalles relacionados con
la solicitud del navegador Web y de la respuesta del servidor a la misma. Por ello, muchos
programadores nunca tienen que crear código para la comunicación de conexiones TCP
de nivel inferior.
Trabajar directamente con WebSocket (o SockJS) es muy similar a desarrollar una apli­
cación Web solamente con conexiones TCP. Sin un protocolo de conexión de nivel superior,
tendrá que decidir la semántica del envío de mensajes entre aplicaciones, y tendrá que
asegurarse de que ambos extremos de la conexión están de acuerdo con dicha semántica.
Afortunadamente, no tiene que trabajar con conexiones WebSocket de forma directa.
Al igual que HTTP añade un modelo de respuestas a solicitudes sobre conexiones TCP,
STOMP sobre WebSocket añade un formato de conexión basado en marcos para definir
semántica de mensajería.
Asimple vista, los marcos de mensajes STOMP parecen estructuralmente similares a las
solicitudes HTTP. Como sucede con las solicitudes y respuestas HTTP, los marcos STOMP
están formados por un comando, uno o varios encabezados y una carga de trabajo. Por
ejemplo, el siguiente marco STOMP sirve para enviar datos:
SEND
destination:/app/marco
content-length:20

{\ "message\11:\ "Marco!\ "}

En este sencillo ejemplo, el comando STOMP es SEND, lo que indica que algo se está
enviando. Después aparecen dos encabezados: uno indica el destino del mensaje y otro
el tamaño de la carga útil. Tras una línea en blanco, el marco finaliza con la carga útil, en
este caso un mensaje JSON.
532 Capítulo 18

El encabezado d e s t i n a t i o n es seguramente lo más interesante del marco STOMP.


Revela que STOMP es un protocolo de mensajería, muy similar a JMS o AMQP. Los mensajes
se publican en destinos que de hecho pueden estar respaldados por agentes de mensaje
reales. En el otro extremo, un escuchador de mensajes puede escuchar en esos destinos
para recibir los mensajes enviados.
En el contexto de la comunicación con WebSocket, una aplicación JavaScript basada
en el navegador puede publicar un mensaje para un destino que se procese por un
componente del lado del servidor, y también funciona a la inversa. Un componente del
lado del servidor puede publicar un mensaje en un destino para que lo reciba un cliente
JavaScript.
Spring ofrece mensajería basada en STOMP mediante un modelo de programación
basado en Spring MVC. Como veremos en breve, el procesamiento de mensajes STOMP
en un controlador de Spring MVC es similar al de solicitudes HTTP, pero antes tendrá que
configurar Spring para habilitar la mensajería basada en STOMP.

Habilitar mensajería STOMP


Pronto veremos cómo anotar métodos de controlador con @ M essageM appin g para
procesar mensajes STOMP desde Spring MVC de forma muy similar al procesamiento de
solicitudes HTTP con métodos anotados con @ R eq u estM ap p in g , pero al contrario de lo
que sucede con @ R eq u estM a p p in g , @ M essageM appin g no se habilita con la anotación
@EnableW ebM vc.
La mensajería Web de Spring se basa en un agente de mensajes, por lo que tendrá que
hacer algo más que indicar a Spring que desea procesar mensajes. También tendrá que
configurar un agente de mensajes y detalles básicos sobre el destino.
El siguiente código muestra la configuración Java básica necesaria para habilitar mensa­
jería Web basada en agentes.

Listado 18.5. @EnableWebSocketMessageBroker habilita STOMP sobre WebSocket.

package marcopolo;
import org. springframework. co n tex t. annotation. Configuration;
import org. springframework.web. so ck et. co n fig . annotation.
AbstractWebSocketMessageBrokerConf ig u rer;
import org.springframework.web.socket. config.annotation.EnableWebSocketMessageBroker;
import org. springframework.web. so ck et. co n fig . annotation. StompEndpointRegistry;

©Configuration
©EnableWebSocketMessageBroker / / H ab ilitar mensajería STOMP,
public cla ss WebSocketStompConfig
extends AbstractWebSocketMessageBrokerConfigurer {

©Override
public void registerStompEndpoints(StompEndpointRegistry reg istry ) {
r e g is tr y . addEndpoint(" /marcopolo") .withSockJS( ); / / H a b ilita r SockJS sobre /
marcopolo.
}
Mensajería con WebSocket y STOMP 533

@Override
public void configureMessageBroker(MessageBrokerRegistry reg istry ) {
r e g is tr y . enableSimpleBroker{"/queue", " / t o p ic " ) ;
reg istry .setA p p licatio n D estin atio nP refixes("/ap p ") ;
}
}
Al contrario de lo que sucede con la clase de configuración del listado 18.2, W eb S o ck et
S to m p C o n fig se anota con @ E n a b le W e b S o c k e tM e s s a g e B r o k e r , lo que indica que
esta clase de configuración no solo configura WebSocket, sino también mensajería STOMP
basada en agentes.
Reemplaza el método r e g i s t e r S t o m p E n d p o i n t s () para registrar / m a r c o p o lo
como punto final de STOMP. Es una ruta totalmente diferente a cualquier ruta de destino
que podría usar para enviar o recibir mensajes. Es el punto final al que se conecta el cliente
antes de suscribirse o publicar en una ruta de destino.
W e b S o c k e tS to m p C o n fig también configura un sencillo agente de mensajes reem­
plazando el método opcional c o n f i g u r e M e s s a g e B r o k e r ( ) . Si no lo reemplaza, se
configura un sencillo agente de mensajes en memoria para procesar mensajes con el prefijo
/ t o p ic , pero en este ejemplo lo reemplazamos para que el agente de mensajes se encargue
de mensajes con los prefijos / t o p i c y / q u eu e. Además, todos los mensajes destinados
a la aplicación tendrán el prefijo /ap p . La figura 18.2 ilustra el flujo de mensajes en esta
configuración.

Figura 18.2. El agente STOMP de Spring es un sencillo agente en memoria que imita varias
funciones de agente de STOMP.

Cuando llega un mensaje, el prefijo del destino determina cómo se procesa. En la figura
18.2, los destinos de aplicación tienen el prefijo /a p p y los de agente los prefijos / t o p i c o
/queue. Un mensaje dirigido a un destino de aplicación se envía directamente a un método de
controlador anotado con @ M essageM apping. Los mensajes destinados al agente, incluidos
los resultantes de los valores devueltos por métodos anotados por @ M essageM appin g, se
dirigen al agente y en última instancia se envían a los clientes suscritos a dichos destinos.
534 Capítulo 18

Habilitar un enlace de agente de STOMP


Este sencillo agente es muy útil para dar los primeros pasos, pero resulta algo limitado.
Aunque imite a un agente de mensajes STOMP, solamente admite un subconjunto de sus
comandos, y al estar basado en memoria, no es adecuado para clúster en los que cada nodo
gestiona su propio agente y conjunto de mensajes.
Para una aplicación de producción, seguramente le interese usar un agente STOMP real
con su mensajería WebSocket, como por ejemplo RabbitMQ o ActiveMQ. Estos agentes
ofrecen una mensajería más robusta y escalabable, además del conjunto completo de
comandos STOMP. Tendrá que configurar su agente para STOMP de acuerdo a su docu­
mentación. Tras ello, puede sustituir el agente predeterminado en memoria por un enlace
de agente STOMP si reemplaza el método c o n f ig u r e M e s s a g e B r o k e r () de esta forma:
@Override
public void configureMessageBroker(MessageBrokerRegistry reg istry ) {
r e g is tr y . enableStompBrokerRelay( " / to p ic ", "/queue" ) ;
reg istry .setA p p licatio n D estin atio n P refix es{"/&pp") ;
}

La primera línea de c o n f ig u r e M e s s a g e B r o k e r () habilita el enlace de agente STOMP


y establece sus prefijos de destino en / t o p i c y /q u eu e. De este modo indica a Spring
que todos los mensajes cuyo destino empiece por / t o p i c o /q u eu e deben enviarse al
agente STOMP. En función del que seleccione, las opciones de prefijo de destino pueden
variar. RabbitMQ, por ejemplo, solo permite destinos de tipo / tem p - queue, / e x c h a n g e ,
/ t o p i c , /q u e u e, /am q/qu eu e y /r e p ly q u e u e / . En la documentación de su agente
podrá consultar los tipos de destino admitidos y su función. Además del prefijo de destino,
la segunda línea de c o n f i g u r e M e s s a g e B r o k e r () establece el prefijo de aplicación
en /app. Todos los mensajes cuyo destino empiece por /ap p se dirigirán a un método
@ M essageM appin g y no se publicarán en una cola o tema de agente.
La figura 18.3 ilustra la integración del enlace de agente en el procesamiento de mensajes
STOMP de Spring. Como puede apreciar, la principal diferencia es que en lugar de imitar
la funcionalidad del agente STOMP, el enlace de agente entrega los mensajes a un agente
real para su procesamiento.
Tanto e n a b l e S t o m p B r o k e r R e l a y () com o s e t A p p l i c a t i o n D e s t i n a t i o n
P r e f i x e s () aceptan un argumento S t r i n g de longitud variable, de m odo que puede
configurar varios prefijos de destino y de aplicación. Por ejemplo:
@Override
public void configureMessageBroker(MessageBrokerRegistry reg istry ) {
r e g is tr y . enableStompBrokerRelay("/to p ic ", " /queue" ) ;
r e g is tr y . setApplicationD estinationPref ixes (11/app", " /fo o " ) ;
}
De forma predeterminada, el enlace de agente STOMP asume que el agente escucha en
el puerto 61613 de l o c a l h o s t y que el nombre de usuario y la contraseña del cliente son
g u e s t (invitado). Si su agente STOMP se encuentra en otro servidor o está configurado
con credenciales de cliente distintas, puede configurar esos detalles al habilitar el enlace
de agente STOMP:
Mensajería con WebSocket y STOMP 535

(»Override
public void configureMessageBroker(MessageBrokerRegistry reg istry ) {
r e g is tr y . enableStompBrokerRelay(" /to p ic " , "/queue")
. setRelayHost("r a b b it. someotherserver")
. setR elayP ort(62623)
. setC lientL ogin("marcopolo")
. setC lientPasscode("letm einO l") ;
r e g is tr y . setA pplicationD estinationPrefixes ("/ctpp", " /fo o " ) ;

Este fragmento de configuración determina el servidor, el puerto y las credenciales,


pero no es necesario configurar todos los datos. Si solamente tiene que cambiar el host,
puede invocar s e t R e la y H o s t () y excluir los demás métodos de establecimiento de la
configuración.
Ahora Spring ya está configurado y listo para procesar mensajes STOMP.

Figura 18.3. El enlace de agente STOMP delega el procesamiento de mensajes STOMP


en un agente real.

Procesar mensajes STOMP desde el cliente


Como vimos en un capítulo anterior, el MVC de Spring le ofrece un modelo de programa­
ción orientado a anotaciones para procesar solicitudes Web HTTP. @ R eq u estM ap p in g , su
principal anotación, asigna solicitudes HTTP a métodos para procesarlas. El mismo modelo
de programación se amplía para servir recursos REST, como vimos en un capítulo anterior.
STOMP y WebSocket se orientan más hacia la mensajería síncrona que el enfoque de
solicitudes y respuestas de HTTP. No obstante, Spring ofrece un modelo de programación
muy similar al de Spring MVC para procesar mensajes STOMP. De hecho, es tan pare­
cido que los métodos de controlador para STOMP son miembros de clases anotadas con
© C o n tr o lle r .
Spring 4.0 introdujo @ M essa g eM a p p in g , similar a @ R e q u e stM a p p in g de Spring
MVC. Un método anotado con @ M essageM appin g puede procesar mensajes que llegan a
un destino especificado. Por ejemplo, fíjese en la clase de controlador del siguiente código.
536 Capítulo 18

Listado 18.6. @MessageMapping procesa mensajes STOMP en un controlador.

package marcopolo;
import org. s l f 4j .Logger;
import org. s l f 4j . LoggerFactory;
import org. springframework.messaging. handler. annotation.MessageMapping;
import org. springframework. stereotyp e. C on tro ller;

@Controller
public cla ss MarcoController {

private s t a t ic fin a l Logger logger =


LoggerFactory.getLogger(MarcoController. c l a s s ) ;

@MessageMapping("/marco") / / Procesar mensajes para e l destino /app/marco.

public void handleShout(Shout incoming) {


logger. in f o ("Received message: " + incoming.getMessage( ) ) ;
}
}
A primera vista parece otra clase de controlador de Spring MVC. Está anotada con
@Cont r o l 1 e r , de modo que el análisis de componentes la selecciona y registra como bean,
y tiene un método de controlador, como cualquier otra clase © C o n t r o l l e r .
Pero es ligeram ente diferente a lo que hemos visto hasta ahora. En lugar de @ R e q u e st
M apping, el método h a n d le S h o u t () se anota con @ M essageM appin g, lo que significa
que h a n d le S h o u t () debe procesar todos los mensajes que lleguen el destino especificado,
en este caso a / a p p /m a rc o (el prefijo / app es el que hem os configurado como prefijo de
destino de aplicación).
Como h a n d le S h o u t () acepta un parámetro S h o u t, la carga útil del mensaje STOMP
se convertirá en S h o u t por medio de uno de los conversores de mensajes de Spring. La
clase S h o u t es un sencillo JavaBean con una propiedad que incluye un mensaje:
package marcopolo;

public cla ss Shout {


private Strin g message;

public Strin g getMessage() {


return message;
}
public void setM essage(String message) {
this.message = message;
}
}

Como aquí no trabajamos con HTTP, no será una de las implementaciones H ttp
M e s s a g e C o n v e r te r de Spring la que se encargue de convertir el objeto S h o u t. En su
lugar, Spring 4.0 le ofrece una serie de conversores de mensajes como parte de su API
de mensajería. En la tabla 18.1 se describen los que puede usar para procesar mensajes
STOMP.
Mensajería con WebSocket y STOMP 537

Tabla 18.1. Spring puede convertir cargas útiles de mensajes en tipos de Java por medio
de un conversor de mensajes.

Conversor de mensajes Descripción

ByteArrayMessageConverter Convierte un mensaje con un tipo MIME application/


octet-stream en y desde byte [] .
MappingJackson2MessageConverter Convierte un mensaje con un tipo MIME application/
j son en y desde un objeto de Java.
StringMessageConverter Convierte un mensaje con un tipo MIME text/plain
en y desde s t r in g .

Si el mensaje procesado por h a n d le S h o u t () tiene un tipo de contenido a p p l i c a t i o n /


j so n (que seguramente sea así, ya que S h o u t no es ni b y t e [] ni S t r i n g ) , se encarga
a M a p p in g Ja c k s o n 2 M e s s a g e C o n v e r te r que convierta el mensaje JSON en un objeto
S h o u t. Al igual que su homólogo orientado a HTTP, M a p p in g Ja c k s o n 2 H ttp M e s s a g e
C o n v e r t e r , M a p p in g Ja c k s o n 2 M e s s a g e C o n v e r te r delega gran parte de su trabajo en
el procesador JSON Jackson 2 subyacente. De forma predeterminada Jackson usa reflexión
para asignar propiedades JSON a propiedades de objetos de Java. Aunque en este ejemplo
no sea necesario, puede modificar la conversión si anota el tipo de Java con anotaciones
Jackson.

Procesar suscripciones
Además de la anotación @ M essag in g M ap p in g, Spring también le ofrece @ S u b s c r ib e
M app in g. Como sucede con los métodos anotados con @ M e s sa g in g M a p p in g , cual­
quier método anotado con @ S u b s c r i b e M a p p i n g se invoca al recibir un mensaje
de suscripción de STOMP. Conviene recordar que, como sucede con los métodos
@M essageM app in g , los métodos @ S u b s c rib e M a p p in g reciben sus mensajes a través
de A n n o ta tio n M e th o d M e s s a g e H a n d le r (véanse las figuras 18.2 y 18.3). De acuerdo
a la configuración del listado 18.5, significa que los métodos @ S u b s c rib e M a p p in g solo
pueden procesar mensajes para destinos que tengan el prefijo / app.
Parece extraño, ya que los mensajes salientes suelen dirigirse a destinos de agente con
los prefijos / t o p i c o /q u e u e . Los clientes se suscriben a dichos destinos, no a los prefi­
jados con /a p p . Si los clientes se suscriben a destinos / t o p i c y /q u e u e , un método @
S u b s e r ib eM a p p in g no podrá procesar dichas suscripciones. Si fuera cierto, ¿para qué
querríamos entonces @ S u b s c rib e M a p p in g ?
@ S u b scrib eM ap p in g se usa sobre todo para implementar un patrón de solicitudes
y repuestas, en el que el cliente se suscribe a un destino y espera una respuesta directa en
el mismo.
Fíjese en el siguiente método anotado con @ S u b s c rib e M a p p in g :
@SubscribeMapping({"/marco"})
public Shout handleSubscription() {
Shout outgoing = new Shout{);
538 Capítulo 18

outgoing.setMessage("Polo I " ) ;
return outgoing;

Como puede apreciar, el m étodo h a n d l e S u b s c r i p t i o n () se anota con @ S u b s c r ib e


M appingpara procesar suscripciones a / ap p /m arco (como sucede con @M essageM apping,
el prefijo /a p p se supone). Al procesar la suscripción, h a n d l e S u b s c r i p t i o n () genera un
objeto S h o u t saliente y lo devuelve. Tras ello, el objeto S h o u t se convierte en un mensaje
y se devuelve al cliente, al mismo destino al que se haya suscrito.
Si está pensando que este patrón de solicitud y respuesta es muy parecido al de solici­
tudes y respuestas GET de HTTP, no está desencaminado. La principal diferencia es que
mientras que una solicitud GET de HTTP es síncrona, una solicitud-respuesta de suscripción
es asincrona, lo que permite al cliente usar la respuesta siempre que esté disponible y no
tener que esperar.

Crear el cliente JavaScript


El método h a n d le S h o u t () ya puede procesar mensajes mientras se envían. Ahora
necesitamos un cliente que los envíe. El siguiente código muestra un cliente JavaScript que
puede conectarse al punto final / m a r c o p o lo y enviar un mensaje " M a r c o ! ".

Listado 18.7. Envío de mensajes desde JavaScript con la biblioteca STOMP.


var u rl = ' h t t p :/ / ' + window.location.host + ' /stomp/marcopolo';
var sock = new S o ck JS (u rl); / / Crear conexione SockJS.

var stomp = Stomp. over(sock); / / Crear c lie n te STOMP.

var payload = JSO N .stringify({ 'm essage': 'Marco!' } ) ;

stomp. connect( 'g u e st' , 'g u e s t', function(frame) { / / Conectar a l punto f in a l STOMP.
stomp. send("/marco", { } , payload); / / Enviar mensaj e .
}>;

Como en el ejemplo anterior del cliente JavaScript, el código comienza creando urna
instancia de S o c k J S para una URL concreta, que en este caso hace referencia al punto
final STOMP configurado en el listado 18.5 (sin incluir la ruta de contexto de la aplicación,
/ stom p).
Aquí la diferencia es que nunca se usa SockJS directamente, sino que se construye una
instancia del cliente STOMP mediante la invocación de S to m p . o v e r ( s o c k ) . De esta forma
se envuelve SockJS en la conexión WebSocket para enviar mensajes STOMP.
Tras ello, se usa el cliente STOMP para realizar la conexión y, si es satisfactoria, enviar
un mensaje con urna carga útil JSON al destino / m arco. El segundo parámetro pasado a
send () es un mapa de encabezados que incluir en el marco STOMP, aunque en este caso
no utilizamos ninguno y el marco está vacío.
Ya tenemos un cliente que envía un mensaje al servidor y un método de controlador
para procesarlo. Es un buen comienzo, pero habrá reparado que se centra únicamente en
uno de los lados. Demos al servidor la oportunidad de enviar mensajes al cliente.
Mensajería con WebSocket y STOMP 539

Enviar mensajes a! cliente


Hasta ahora, el cliente es el que se encarga de enviar todos los mensajes al servidor, que
está obligado a escucharlos. Aunque sea un uso válido de WebSocket y STOMP, seguramente
no sea el que tiene en mente al pensar en WebSocket. WebSocket suele considerarse como
una forma para que un servidor envíe datos al navegador sin que sean una respuesta a una
solicitud HTTP. ¿Cómo podemos comunicarnos con un cliente basado en el navegador a
través de Spring y WebSocket/STOMP? Spring le ofrece dos formas de enviar datos a un
cliente:
• Como efecto secundario del procesamiento de un mensaje o de una suscripción.
• Con una plantilla de mensajes.
Ya conoce varios métodos para procesar mensajes y suscripciones, por lo que primero
veremos el envío de mensajes al cliente como efecto secundario de dichos métodos. Tras
ello, describiremos S im p M e s sa g in g T e m p la te de Spring para el envío de mensajes desde
cualquier punto de una aplicación.

Enviar un mensaje tras procesarlo


El método h a n d le S h o u t () del listado 18.6 simplemente devuelve v o id . Su labor es
procesar un mensaje, no contestar al cliente.
Incluso así, si desea enviar un mensaje como respuesta a la recepción de otro, solo tiene
que devolver algo que no sea vo i d. Por ejemplo, si desea enviar un mensaje " P o l o ! " como
reacción a un mensaje " M a r c o ! ", podría cambiar el método h a n d le S h o u t () de esta forma:
@MessageMapping("/marco”)
public Shout handleShout(Shout incoming) {
logger. in f o ("Received message: " + incoming.getMessage 0 ) ;

Shout outgoing = new Shout();


outgoing. setMessage(”Polo 1");
return outgoing;
i
En esta nueva versión de h a n d le S h o u t ( ) , se devuelve un nuevo objeto S h o u t. Con
tan solo devolver un objeto, un método de procesador también puede ser un método
emisor. Cuando un método anotado con @ M essageM appin g devuelve un valor, el objeto
devuelto se convierte (a través de un conversor de mensajes) y se añade a la carga útil de
un marco STOMP, y se publica en el agente. De forma predeterminada, el marco se publica
en el mismo destino que haya desencadenado el método de controlador, pero con el prefijo
/ t o p i c . En el caso de h a n d le S h o u t ( ) , significa que el objeto Shou t devuelto se escribirá
en la carga útil de un marco STOMP y se publicará en el destino / t o p i c / m a r c o , pero
puede reemplazar el destino si anota el método con @SendTo:
@MessageMapping{"/marco")
@SendTo("/to p ic/sh o u t")
public Shout handleShout(Shout incoming) {
540 Capítulo 18

logger. in f o ("Received message: " + incoming.getMessage( ) ) ;


Shout outgoing = new ShoutO;
outgoing. setMessage("Polo! " ) ;
return outgoing;
}
Al añadir esta anotación @SendTo, el m ensaje se publica en / t o p i c / s h o u t . Todas
las aplicaciones suscritas a ese tema (como por ejemplo el cliente), recibirán el mensaje.
Ahora el método h a n d le S h o u t () envía un mensaje como respuesta a haber recibido un
mensaje. De forma similar, un método anotado con @ S u b sc rib e M a p p in g puede enviar un
mensaje como respuesta a una suscripción. Por ejemplo, podría enviar un mensaje S h o u t
cuando el cliente se suscriba si añade el siguiente método al controlador:

@SubscribeMapping( " /marco")


public Shout handleSubscription() {
Shout outgoing = new ShoutO;
outgoing. setMessage("Polo 1");
return outgoing;
}
La anotación @ S u b sc rib e M a p p in g designa el método h a n d l e S u b s c r i p t i o n () para
que se invoque siempre que un cliente se suscriba al destino / a p p /m a rc o (con el prefijo
de destino de aplicación /a p p ). El objeto S h o u t que devuelve se convierte y se vuelve a
enviar al cliente.
La diferencia con @ S u b s c r ib eM a p p in g es que el mensaje S h o u t se envía directamente
al cliente sin pasar por el agente. Si anota el método con @SendTo, el m ensaje se envía al
destino especificado, a través del agente.

Enviar un mensaje desde cualquier punto de una aplicación


@ M essageM appin g y @ S u b s c rib e M a p p in g constituyen una sencilla forma de enviar
mensajes como consecuencia de la recepción de un mensaje o del procesamiento de una
suscripción, pero Sim p M essa g in g T em p la te de Spring permite el envío de mensajes desde
cualquier punto de una aplicación, incluso sin haber recibido antes un mensaje.
La form a más sencilla de usar S im p M e s s a g in g T e m p la te consiste en conectarla auto­
m áticam ente (o su interfaz S im p M e s sa g e S e n d in g O p e ra t io n s ) al objeto que la necesite.
Para ponerlo en práctica, volvamos a la página de inicio de la aplicación Spittr para
ofrecer unfeed en directo. Actualmente, el controlador que procesa la solicitud de la página
principal recupera la lista de objetos S p i t t l e más recientes y los añade al modelo para
representarlos en el navegador del usuario. Aunque sea un funcionamiento válido, no
ofrece un feed en directo de las actualizaciones de S p i t t l e . Si el usuario quiere ver un feed
actualizado, tendrá que actualizar la página en su navegador.
En lugar de obligar al usuario a actualizar la página, puede suscribir la página principal
a un tema STOMP para que reciba una lista de actualizaciones de S p i t t l e en cuanto se
creen. En la página de inicio tendrá que añadir el siguiente fragmento de JavaScript:
<scrip t>
var sock = new SockJS( ' s p i t t r ') ;
var storap = Stomp. over(sock);
Mensajería con WebSocket y STOMP 541

stomp.connect( 'g u e s t', 'g u e s t', function(frame) {


console. lo g ( ' Connected') ;
stom p.subscribe("/to p ic /s p ittle fe e d " , h a n d le S p ittle);
}>»
function handleSpittle(incoming) {
var s p i t t le = JSON.parse(incoming.body);
console. lo g ( 'Received: s p ittle );
var source = $ ( "# sp ittle -te m p la te ") .htm l();
var template = Handlebars. com pile(source);
var spittleH tm l = te m p la te (s p ittle );
$ ( ' . s p i t t le L i s t ' ) .prepend(spittleH tm l);
}
< /scrip t>

Como en ejemplos anteriores, se crea una instancia de S o c k J S y después otra de


Stom p sobre la instancia S o c k J S . Tras conectarse al agente STOMP, se suscribe a / t o p i c /
s p i t t l e f e e d y designa la función h a n d l e S p i t t l e () para procesar actualizaciones de
S p i t t l e en cuanto se reciban. La función h a n d l e S p i t t l e O analiza el cuerpo del mensaje
entrante en el correspondiente objeto de JavaScript y después usa la biblioteca Handlebars
para representar los datos S p i t t l e en código HTML que se añade por delante de la lista.
La plantilla Handlebars se define de esta forma en una etiqueta < s c r i p t > independiente:
<s c rip t id ="sp ittle -tem p late " type=" text/x-hand lebars-tem p late">
< l i id = "p ree x ist">
<div class="spittleM essage">{{m essage}}</div>
<div>
<span class="sp ittleT im e">{{tim e}}</sp an >
<span c la ss= "sp ittle L o c a tio n "> ( { {la titu d e } } , { {lon gitu d e}} ) </span>
</div>
< /li>
< /scrip t>

En el servidor, puede usar S im p M e s sa g in g T e m p la te para publicar los nuevos objetos


S p i t t l e como mensajes en el tema / t o p i c / s p i t t l e f eed . El siguiente código muestra
S p i t t l e F e e d S e r v i c e i m p l , un sencillo servicio que se encarga precisamente de eilo.

Listado 18.8. SimpMessagingTemplate publica mensajes desde cualquier punto de una aplicación.

package s p itt r ;
import o rg .sp rin gframewor k .beans. fa c to ry .annotation.Autowired;
import org. springframework.messaging. simp. SimpMessageSendingOperatio n s;
import org. springframework. stereotyp e. S erv ice;

@Service
public cla ss SpittleFeedServicelm pl implements SpittleFeed Service {

private SimpMessageSendingOperations messaging;

@Autowired
public SpittleFeedServicelm pl(
SimpMessageSendingOperations messaging) { / / Inyectar p la n tilla de
/ / m ensajería.
542 Capítulo 18

this.m essaging = messaging;


}
public void b ro a d ca stS p ittle (S p ittle s p ittle ) {
messaging. convertAndSend("/to p ic /s p ittle fe e d " , s p i t t l e ) ; / / Enviar mensaje.
}
}
Como efecto secundario de configurar la compatibilidad con STOMP en Spring, ya existe
un bean S im p M essa g eT em p la te en el contexto de la aplicación de Spring. Por lo tanto, no
tendrá que crear una nueva instancia. En su lugar, el constructor S p i t t le F e e d S e r v ic e lm p l
se anota con @ A u to w ired para inyectar la S im p M e s s a g in g T e m p la te existente (como
S im p M e s s a g e S e n d in g O p e r a tio n s ) al crear S p i t t l e F e e d S e r v i c e l m p l .
En el m étodo b r o a d c a s t S p i t t l e () se envía el mensaje S p i t t l e . Invoca c o n v e r t
A n d S en d O en S im p M e s s a g e S e n d in g O p e r a t io n s para convertir S p i t t l e en un
mensaje y enviarlo al tema / t o p i c / s p i t t l e f eed . Si el método c o n v e rtA n d S e n d ()
le resulta fam iliar es porque im ita a los métodos del mismo nombre de Jm s T e m p la te y
R a b b itT e m p l a t e .
Al publicar un m ensaje en un tema STOMP con c o n v e rtA n d S e n d () o como resultado
de un método de controlador, cualquier cliente suscrito a dicho tema recibirá el mensaje.
Si desea que todos los clientes estén actualizados con un feed de S p i t t l e en directo,
perfecto, pero en ocasiones le interesará m andar un mensaje a un usuario concreto, no a
todos los clientes.

Trabajar con mensajes para usuarios específicos*•


Hasta ahora, hemos enviado y recibido mensajes entre un cliente (en un navegador
Web) y el servidor. No hemos tenido en cuenta al usuario de ese cliente. Al invocar un
método anotado con @ M essageM appin g, sabemos que el mensaje se ha recibido, pero
no de quién proviene. Del mismo modo, si desconocemos quién es el usuario, todos los
mensajes enviados se enviarán a todos los clientes suscritos al tema en el que se transmite
el mensaje; no hay forma de enviar ese mensaje a un usuario concreto.
No obstante, si sabe quién es el usuario, puede trabajar con mensajes asociados al mismo,
no solo con los asociados a un cliente. La buena noticia es que ya sabe cómo identificar
al usuario. Con ayuda del mismo mecanismo de autenticación empleado en un capítulo
anterior puede usar Spring Security para autenticar al usuario y trabajar con mensajes para
usuarios concretos. Hay tres formas de trabajar con un usuario autenticado en la mensajería
con Spring y STOMP:

• Los métodos @ M e ssa g e M a p p in g y @ S u b s c r ib e M a p p in g pueden recibir un


elemento P r i n c i p a l para el usuario autenticado.
• Los valores devueltos por los métodos @ M essageM apping, @ S u b s c rib e M a p p in g
y @ M e s s a g e E x c e p tio n se pueden enviar como mensajes al usuario autenticado.
• S im p M e s sa g in g T e m p la te puede enviar mensajes a un usuario concreto.
Mensajería con WebSocket y STOMP 543

Veremos primero las dos primeras técnicas, que permiten a los métodos de procesamiento
de mensajes de un controlador trabajar con mensajes de usuario.

Trabajar con mensajes de usuario en un controlador


Como ya hemos mencionado, hay dos formas de que el método @ M essageM appin g o
@ S u b sc rib e M a p p in g de un controlador se centre en un usuario durante el procesamiento
de mensajes. Si solicitamos un elemento P r i n c i p a l como parámetro de un método de
controlador, el método sabe quién es el usuario y utiliza esa información para centrar su
labor en los datos de ese usuario. Además, se puede anotar un método de controlador coir
@ S en d T o U ser para indicar que el valor que devuelve debe enviarse en un mensaje al
cliente y solamente al cliente del usuario autenticado.
Para ilustrarlo, crearemos un método de controlador que crea un nuevo objeto Sp i 1 1 1 e
a partir de un mensaje entrante y devuelve una respuesta que indica que ese objeto S p i t t l e
se ha guardado. Si le resulta familiar, es porque ya lo hemos implementado como punto final
REST en un capítulo anterior. Evidentemente, REST es una de las formas de implementar
esta funcionalidad, pero las solicitudes REST son de naturaleza síncrona y el cliente tiene
que esperar mientras el servidor las procesa. Al publicar S p i t t l e como mensaje STOMP,
aprovechamos todas las ventajas de la naturaleza asincrona de la mensajería STOMP.
Fíjese en el siguiente método h a n d l e S p i t t l e ( ) , que procesa un mensaje entrante y
lo guarda como S p i t t l e :
@MessageMapping( " / s p i t t l e " )
@SendToUser( " / q u eu e/n o tification s")
public N o tificatio n h a n d le S p ittle(
P rincipal p rin cip al, SpittleForm form) {

S p ittle s p it t le = new S p i t t l e (
p rin c ip a l. getName(), form.g etT ex t(), new D ate( ) ) ;

s p ittle R e p o .s a v e (s p ittle );

return new N o tific a tio n ("Saved S p i t t l e " ) ;


}
Como puede apreciar, h a n d l e S p i t t l e () acepta tanto un objeto P r i n c i p a l como un
objeto Sp i 1 1 1 e Form y lo utiliza para crear una instancia de Sp i 1 1 1 e que después guarda
en S p i t t l e R e p o s i t o r y . Por último, devuelve una nueva notificación ( N o t i f i c a t i o n )
para indicar que se ha guardado el objeto S p i t t l e .
Evidentemente, lo que sucede dentro del método no es tan interesante como lo que
pasa fuera. Como este método está anotado con @ M essageM appin g, se invoca siempre
que se recibe un mensaje en el destino / a p p / s p i t t l e . Se crea S p i t t l e F o r m a partir del
mensaje y, si el usuario está autenticado, también se deriva P r i n c i p a l de los encabezados
del marco STOMP.
Sin embargo, debe prestar especial atención al destino de la notificación devuelta.
La anotación @ S e n d T o U s e r especifica que la notificación devuelta se envíe como
mensaje al destino / q u e u e / n o t i f i c a t i o n s . En la superficie, no parece que / q u e u e /
544 Capítulo 18

n o t i f i c a t io n s sea específico de un usuario concreto, pero como se trata de la anotación


@SendToU ser y no de @SendTo, hay mucho más. Para entender cómo publica Spring el
mensaje, retrocedamos ligeramente y veamos cómo se suscribe un cliente al destino en el
que este método de controlador publica una notificación. Fíjese en la siguiente línea de
JavaScript que suscribe a un destino de un usuario concreto:
stomp. su b scrib e("/iiser/q u e u e /n o tifica tio n s", h an d leN o tificatio n s);

El destino tiene el prefijo / u s e r . De forma interna, los destinos con el prefijo / u s e r se


procesan de form a especial. En lugar de fluir a través de A n n o ta tio n M e th o d M e s sa g e
H a n d le r (como un m ensaje de aplicación) o a través de S im p le B ro k e rM e s s a g e H a n d le r
o S to m p B r o k e r R e la y M e s s a g e H a n d le r (como un mensaje de agente), los m ensajes
/ u s e r fluyen a través de U s e r D e s t in a t io n M e s s a g e H a n d le r (véase la figura 18.4).

Figura 18.4. Los mensajes de usuario fluyen a través de UserDestinationMessageHandler,


que los dirige a un destino exclusivo de un usuario.

La principal tarea de U s e r D e s t i n a t io n M e s s a g e H a n d l e r es dirigir mensajes de


usuario a un destino exclusivo de un usuario concreto. En el caso de una suscripción, deriva
el destino final eliminando el prefijo / u s e r y añadiendo un sufijo basado en la sesión del
usuario. Por ejemplo, una suscripción a / u s e r / q u e u e / n o t i f i c a t i o n s puede dirigirse
al destino / q u e u e / n o t i f i c a t i o n s u s e r 6 h r 8 3 v 6 t .
En nuestro ejemplo, se anota h a n d l e S p i t t l e () con @ S e n d T o U se r (" /q u e u e /
n o t i f i c a t i o n s " ). El prefijo de este nuevo destino es /q u eu e, uno de los que puede
procesar S to m p B ro k e rR e la y M e s s a g e H a n d le r (o S im p le B r o k e r M e s s a g e H a n d le r ),
de modo que el mensaje se enviará allí. Como el cliente está suscrito a ese destino, recibirá
el mensaje N o t i f i c a t i o n .
Mensajería con WebSocket y STOMP 545

La anotación @ Sen d T o U ser y un parámetro P r i n c i p a l son muy útiles cuando traba­


jamos desde un método de controlador, pero en el listado 18.8 vimos cómo enviar mensajes
desde cualquier punto de una aplicación por medio de una plantilla de mensajes. Veamos
ahora cómo usar S im p M e s s a g in g T e m p la te para enviar mensajes a un usuario concreto.

Enviar mensajes a un usuario concreto


Además de c o n v e rtA n d S e n d ( ) , S im p M e s s a g in g T e m p la te también cuenta con
c o n v e r tA n d S e n d T o ü s e r ( ) , un método que nos permite enviar mensajes dirigidos a
un usuario concreto.
Para ilustrarlo, añadiremos a la aplicación Spittr una función que notifique al usuario
la publicación por parte de otro de un S p i t t l e que le mencione. Por ejemplo, si el texto
del objeto S p i t t l e incluye @ jb a u e r , se debe enviar un mensaje al cliente en el que se
haya conectado un usuario con el nombre jb a u e r . El método b r o a d c a s t S p i t t l e () del
siguiente código usa c o n v e rtA n d S e n d T o ü s e r () para notificar a un usuario que están
hablando de él.

Listado 18.9. convertAndSendToUser() puede enviar un mensaje a un usuario concreto.

package s p itt r ;
import ja v a .ú t i l . regex.Matcher;
import ja v a .ú til.re g e x .P a tte rn ;
import org.springframework.beans. fa cto ry . annotation.Autowired;
import org.springframework.messaging.simp.SimpMessagingTemplate;
import org. springframework. stereotyp e.S e rv ice;

@Service
public cla ss SpittleFeedServicelm pl implements SpittleFeed Service {

private SimpMessagingTemplate messaging;


private Pattern p attern = Pattern.com pile( ”\\@(\\S+)" ) ; / / Patrón de expresión
/ / regular para la mención de usuarios.

@Autowired
public SpittleFeedServicelmpl{SimpMessagingTemplate messaging) {
this.m essaging = messaging;
i
public void b ro a d ca stS p ittle (S p ittle s p ittle ) {

messaging.convertAndSend("/to p ic /s p ittle fe e d " , s p i t t l e ) ;

Matcher matcher = p a tte rn .matcher( s p it t l e . getMessage( ) ) ;


i f (matcher. fin d ()) {
String username = matcher.group(1);
messaging. convertAndSendToüser( / / Enviar n o tifica c ió n a l usuario.
username, "/q u e u e /n o tifica tio n s",
new N o tific a tio n ("You ju s t got m entioned!"));
}
}
}
546 Capítulo 18

Desde b r o a d c a s t S p i t t l e ( ) , si el mensaje del objeto S p i t t l e proporcionado


contiene lo que parece ser un nombre de usuario (es decir, cualquier texto que empiece
por se envía una nueva notificación ( N o t i f i c a t i o n ) al destino / q u e u e /
n o t i f i c a t i o n s . Por lo tanto, si el objeto S p i t t l e tiene un mensaje que contenga
j b a u e r ", la notificación se publica en el destino / u s e r / j b a u e r / q u e u e / n o t if i c a t i o n s .

Controlar excepciones de mensajes


En ocasiones las cosas no funcionan de la forma esperada. Al procesar un mensaje, puede
producirse un error y que se genere una excepción. Debido a la naturaleza asincrona de
la mensajería STOMP, puede que el remitente nunca sepa que se ha producido un error.
Aparte de registrarse en Spring, la excepción podría perderse y no recuperarse jamás.
En Spring MVC, si se produce una excepción durante el procesamiento de solicitudes,
se le permite a un método @ E x c e p tio n H a n d le r que la controle. Del mismo modo, se
puede anotar un método de controlador con @ M e ssa g e E x ce p t io n H a n d le r para procesar
excepciones generadas en un método @M essageM apping. Por ejemplo, el siguiente método
controla excepciones generadas desde métodos de procesamiento de mensajes:
@MessageExceptionHandler
public void handleExceptions(Throwable t) {
lo g g e r.e rro r( "Error handling message: " + t .getMessage( ))/
}

En su versión más sencilla, @ M e ssa g eE x cep tio n H a n d ler controla cualquier excep­
ción generada desde un método de procesamiento de mensajes, pero puede declarar como
parámetro el tipo concreto de excepción que debe controlarse:
@MessageExceptionHandler(SpittleException.class)
public void handleExceptions(Throwable t) {
lo g g er.erro r("E rro r handling message: " + t.getM essage( ) ) ;
}

También puede especificar varios tipos de excepción en una matriz de parámetros:


@MessageExceptionHandler(
{Sp ittleE xcep tio n . c la s s , DatabaseException. c la s s })
public void handleExceptions(Throwable t) {
lo g g e r.e rro r("Error handling message: " + t.getM essage( ) ) ;
}

Aunque solamente registre la presencia de un error, este método puede hacer mucho
más. Por ejemplo, puede responder con un error:
@Mes s ageExc ept i onHandler(Spi 111eExcept io n . class)
@SendToUser("/queue/errors")
public SpittleE xception handleExceptions(SpittleException e) {
lo g g er.erro r("E rro r handling message: " + e . getMessage( ) ) ;
return e;
}
Mensajería con WebSocket y STOMP 547

En este caso, si se genera una excepción S p i t t l e E x c e p t i o n , se registra y se devuelve.


Como vimos en un apartado anterior, U se rD e s tin a tio n M e s s a g e H a n d le r redirigirá el
mensaje a un destino exclusivo de ese usuario.

Resumen
WebSocket es una atractiva forma de enviar mensajes ente aplicaciones, en especial si
una de ellas se ejecuta en un navegador Web. Resulta imprescindible para crear aplicaciones
Web interactivas que transfieran datos a un servidor.
La compatibilidad de Spring con WebSocket incluye un API de nivel inferior que le
permite trabajar con conexiones WebSocket. Desafortunadamente, la compatibilidad con
WebSocket no está demasiado extendida entre navegadores Web, servidores y proxy. Por
ello, Spring también admite SockJS, un protocolo que recurre a sistemas de comunicación
alternativos en caso de que WebSocket no funcione.
Además, Spring ofrece un modelo de programación de nivel superior para procesar
mensajes WebSocket con el protocolo de nivel de conexión STOMP. En este modelo, los
mensajes STOMP se procesan en controladores del MVC de Spring, de forma similar al
procesamiento de los mensajes HTTP.
En los últimos capítulos hemos visto diversos modos de enviar mensajes entre apli­
caciones de forma asincrona, pero existe otro tipo de mensajería asincrona en Spring: el
correo electrónico.
:

: Capítulo

i
19 Enviar correo
con S p rin g

CONCEPTOS FUNDAMENTALES:

• Configuración de la abstracción de correo electrónico


de Spring.
• Envío de mensajes de correo electrónico avanzados.
• Uso de plantillas para crear mensajes de correo
: electrónico.

i
Es evidente que el correo electrónico se ha convertido en una forma común de comu­
nicación, desplazando a medios más tradicionales como el correo postal, el teléfono y, en
cierta medida, la comunicación personal cara a cara. El correo electrónico ofrece muchas
de las mismas ventajas asincronas que las opciones de mensajería descritas en un capítulo
anterior, pero con usuarios humanos como emisores y receptores. Al pulsar el botón Enviar
de un cliente de correo electrónico, el emisor puede dedicarse a otra tarea sabiendo que el
receptor acabará por recibir el mensaje y, supuestamente, lo leerá.
Pero los emisores de correo electrónico no siempre son humanos. En muchos casos, son
aplicaciones las que envían correo electrónico a los usuarios. Puede ser la confirmación de
un pedido realizado por el usuario en un sitio de comercio electrónico o una notificación
automática de una actividad de nuestra cuenta bancaria. Independientemente del motivo,
es muy probable que tenga que desarrollar aplicaciones en las que se envíe correo electró­
nico. Por suerte cuenta con la ayuda de Spring.
En un capítulo anterior utilizamos la compatibilidad con mensajería de Spring para
incluir trabajos en colas de forma asincrona, con el fin de enviar alertas de spittle a otros
usuarios de la aplicación Spittr, pero dejamos el trabajo a medias ya que no enviamos
ningún correo electrónico. Completaremos el proceso analizando la abstracción de Spring
del envío de correo electrónico y la utilizaremos para enviar alertas sobre spittle.

Configurar Spring para enviar correo electrónico


En el núcleo de la abstracción de correo electrónico de Spring se encuentra la interfaz
Mai lS e n d e r . Como su nombre implica, y como se muestra en la figura 19.1, la implementa-
ción Mai lS e n d e r envía correo electrónico conectándose a un servidor de correo electrónico.

Emisor Servidor
de correo de correo

Figura 19.1. La interfaz MailSender de Spring es el componente principal del API de abstracción
de correo electrónico de Spring. Permite enviar un mensaje a un servidor de correo electrónico
para su entrega.

Spring cuenta con una implementación de la interfaz M ailSen d er: Ja v a M a ilS e n d e rlm p l,
que utiliza el API JavaMail para enviar correo electrónico. Antes de poder enviar mensajes
de correo desde una aplicación de Spring tendrá que conectar J a v a M a ilS e n d e r lm p l
como bean en el contexto de la aplicación de Spring.

Configurar un emisor de correo electrónico


En su versión más sencilla, J a v a M a ilS e n d e r lm p l se puede configurar como bean
con varias líneas en un m étodo @Bean:
550 Capítulo 19

@Bean
public MailSender mailSender(Environment env) {
JavaMailSenderlmpl mailSender = new JavaMailSenderlmplO;
m ailSender.setH ost(env.getProperty("m ailserver.h o s t" )) ;
return mailSender;

La propiedad h o s t es opcional (de forma predeterminada es el anfitrión de la sesión


JavaMail subyacente) pero probablemente querrá configurarla. Especifica el nombre del
host del servidor de correo que vamos a utilizar para enviar los mensajes. En este caso, se
configura tras obtener el valor de entorno (E n v iro n m e n t) inyectado para poder gestionar
la configuración del servidor fuera de Spring (por ejemplo en un archivo de propiedades).
De forma predeterminada, J a v a M a ilS e n d e r lm p l asume que el servidor de correo
escucha en el puerto 25 (el puerto SMTP estándar). Si su servidor utiliza otro puerto, espe­
cifique el número correcto utilizando la propiedad p o r t. Por ejemplo:
@Bean
public MailSender mailSender(Environment env) {
JavaMailSenderlmpl mailSender = new JavaMailSenderlmplO;
mailSender. setH ost(env.getProperty("m ailserv er.h ost" ) ) ;
mailSender. setP ort(env.getP roperty("m ailserv er.p ort" ) ) ;
return mailSender;
}
De forma similar, si el servidor de correo requiere autenticación, querrá configurar
valores para las propiedades u sernam e y passw ord:
@Bean
public MailSender mailSender(Environment env) {
JavaMailSenderlmpl mailSender = new JavaMailSenderlmplO;
mailSender. setH ost(env.getProperty("m ailserver.h o s t") ) ;
m ailSender.se tP o r t(env.getProperty("m ailserv er.p o r t" )) ;
mailSender. setUsername(env.getProperty("m ailserver.username") ) ;
mailSender. setPassword(env. getProperty("m ailserver.password") ) ;
return mailSender;

Hasta el momento, Ja v a M a ilS e n d e r lm p l se ha configurado para crear su propia sesión


de correo electrónico, pero puede que ya haya configurado un elemento j a v a x . m a i l .
M a i l S e s s i o n en JNDI (o que su servidor de aplicaciones haya incluido uno). En ese caso,
no tiene sentido configurar J a v a M a ilS e n d e r lm p l con todos los detalles del servidor. En
su lugar, puede configurarlo pasa usar el elemento M a i l S e s s i o n de JDNI que ya tiene.
Por medio de J n d iO b j e c t F a c t o r y B e a n puede configurar un bean que busque la
M a i l S e s s i o n de JNDI con el siguiente método @Bean:
@Bean
public JndiObjectFactoryBean m ailSession() {
JndiObjectFactoryBean jn d i = new JndiObjectFactoryBean( );
jn d i. setJndiName("m ail/Session") ;
jn d i. setP ro xy ln terface(M ailSessio n .class);
jn d i. setR esourceR ef(true);
return jn d i;
}
Enviar correo con Spring 551

También hemos visto cómo recuperar objetos desde JNDI utilizando el elemento de
Spring < j e e : jn d i- lo o k u p > . Por tanto, vamos a utilizarlo para crear un bean que haga
referencia a una sesión de correo de JNDI:
<j e e : jndi-lookup id="m ailSession"
jndi-name="mail/Session" resou rce-ref="tru e" />

Ahora que contamos con el bean de sesión de correo, podemos conectarlo al bean
m a ilS e n d e r de esta manera:
@Bean
public MailSender mailSender(MailSession mailSession) {
JavaMailSenderlmpl mailSender = new JavaMailSenderlmpl{ );
mailSender. setSessio n (m ailSessio n );
return mailSender;
}
Al conectar la sesión de correo a la propiedad s e s s i o n de Ja v a M a ilS e n d e r lm p l,
sustituimos la configuración explícita anterior del servidor (así como el nombre de correo
y la contraseña). Ahora, la sesión de correo está completamente configurada y gestionada
en JNDI. De esta forma, J a v a M a ilS e n d e r lm p l puede centrarse en el envío de mensajes
y despreocuparse de cómo conectarse al servidor de correo.

Conectar y utilizar el remitente de correo


Con el remitente de correo configurado, ya podemos conectarlo al bean que va a utili­
zarlo. En la aplicación Spittr, la clase S p i t t e r E m a i l S e r v i c e l m p l es la ubicación más
adecuada desde la que enviar correo. Esta clase cuenta con una propiedad m a ilS e n d e r
que se anota con @ A u tow ired :
@Autowired
JavaMailSender mailSender;

Cuando Spring crea S p i t t e r E m a i l S e r v i c e l m p l como bean, intenta localizar un


bean que implemente M a ilS e n d e r que pueda conectar a la propiedad m a ilS e n d e r .
Debería encontrar nuestro bean m a il S e n d e r y utilizarlo. Tras ello, podremos crear y
enviar mensajes.
Como queremos enviar un mensaje de correo a un usuario Spitter para avisarle sobre los
nuevos spittle publicados por sus amigos, necesitamos un método que, con una dirección de
correo y un objeto S p i 1 1 1 e, envíe el mensaje. El método s e n d S i m p l e S p i t t 1 eEm ai 1 ()
se encarga de esta operación.

Listado 19.1. Envío de correo electrónico con Spring utilizando MailSender.

public voidsendSimpleSpittleEmail(String to, Spittle spittle){


SimpleMaílMessage message=new SimpleMaílMessage( ) ; / / Crear e l mensaje.
String spitterN am e=spittle.g e tS p itte r ( ) . getFullName( );
message. setFrom("norep ly@ spitter. com" ) ; / / Dirección de correo electró n ico ,
message. s e tT o (to );
552 Capítulo 19

m essage.setSu bject( "New s p it t le from " + spitterName);


message. setText(spitterName + " says: "+ / / E stablecer texto del mensaje,
s p i t t l e . getText 0 ) ;

mailSender.send(message); / / Enviar correo electró n ico .

Lo primero que hace sendSimpleSpittleEmail () es crear una instancia de Simple


MailMessage. Este objeto de mensaje de correo es ideal para el envío de mensajes sencillos.
A continuación, se establecen los detalles del mensaje. El remitente y el destinatario
se especifican con los métodos setFrom () y setTo () del mensaje de correo. Después
de configurar el asunto con setSubject ( ) , se crea el "sobre" virtual. Solo falta invocar
setText () para configurar el contenido del mensaje. El último paso es transmitir el mensaje
al método send () que va a remitir el mensaje. Tras ello, el mensaje estará en camino.
Hemos configurado un emisor de correo y lo hemos usado para enviar un sencillo
mensaje de correo electrónico. Habrá comprobado lo sencillo que resulta trabajar con la
abstracción de correo electrónico de Spring. Podríamos dejarlo aquí y pasar al siguiente
capítulo, pero entonces se perdería la parte más divertida. A continuación veremos cómo
añadir archivos adjuntos y crear mensajes de correo más completos.

Crear mensajes de correo electrónico avanzados


Los mensajes de correo electrónico en texto sencillo son útiles pero ¿y si queremos añadir
un adjunto? ¿O si queremos que el cuerpo del mensaje tenga un aspecto determinado?
Veamos cómo. Los mensajes de correo en texto sencillo son muy útiles para tareas simples
como pedirle a un amigo que venga a casa a ver el partido, pero no sirven de mucho si
hay que enviar fotos o documentos, y no consiguen captar la atención del receptor, como
sucede con el correo electrónico comercial.
Afortunadamente, las funciones de correo electrónico de Spring no terminan con los
mensajes en texto sencillo. Puede añadir archivos adjuntos e incluso decorar el cuerpo del
mensaje con HTML. Empezaremos por la tarea básica de añadir archivos adjuntos para
después pasar al uso de HTML en mensajes de correo.

Añadir archivos adjuntos


El truco para enviar mensajes de correo con archivos adjuntos es crear mensajes multi-
parte, correos formados por diferentes elementos, uno de los cuales es el cuerpo del mensaje
y el resto contiene los datos adjuntos.
La clase S i m p le M a il M e s s a g e es demasiado sencilla para permitir el envío de archivos
adjuntos. Para poder enviar mensajes multiparte, debe crear un mensaje MIME (M ultipurpose
In tern et M ail E xtensions, Extensiones multipropósito de correo de Internet). Se puede crear
con el método c r e a t e M i m e M e s s a g e () del objeto del remitente:
MimeMessage message = mailSender. createMimeMessage();
Enviar correo con Spring 553

Ya contamos con un mensaje MIME con el que trabajar. Ahora, todo lo que tenemos que
hacer es incluir las direcciones, un asunto, algo de texto y un archivo adjunto, pero no es
tan sencillo como parece. La clase ja v a x . m a i l . i n t e r n e t .MimeMessage cuenta con
un API demasiado complejo. Por fortuna, Spring cuenta con M im eM essageH elper para
ayudarle en la tarea.
Para utilizar M im e M e s s a g e H e lp e r cree una instancia y pase M im eM essage a su
constructor:
MimeMessageHelper helper = new MimeMessageHelper(message, tr u e ) /

El segundo parámetro del constructor, un valor booleano t r u e , indica que este mensaje
es de tipo multiparte.
A partir de la instancia M im eM essageH elper ya podemos crear nuestros mensajes
de correo electrónico. La única gran diferencia, en este caso, es que vamos a proporcionar
los elementos específicos del mensaje mediante métodos en el asistente en lugar de en el
propio mensaje:
String spitterN am e=spittle.g e tS p itte r ( ) . getFullName()/
helper.setFrom (''[email protected]" ) ;
h elp er. setTo(to) ;
h elp er. se tS u b je c t("New s p it t le from " + spitterName);
h elp er. setText(spitterName + " says: " + s p ittle .g e tT e x tO ) ;

Lo único que necesitamos antes de poder enviar el correo electrónico es adjuntar la


imagen del cupón. Para ello, tenemos que cargarla como recurso y, a continuación, pasarlo
al invocar el método asistente ad d A ttach m ent ():
FileSystemResource couponlmage =
new FileSystem Resource("/collateral/coupon.png") ;
help er. addAttachment("Coupon.png", couponlmage) ;

En este caso utilizamos F ile S y s t e m R e s o u r c e de Spring para cargar el archivo


co u p o n .p n g desde la ruta de clases de la aplicación. A partir de ahí, puede invocar
ad d A ttach m ent (). El primer parámetro es el nombre que asignar al archivo adjunto del
mensaje. El segundo parámetro es el recurso de imagen.
Ya hemos creado el mensaje multiparte y estamos listos para enviarlo. A continuación
se muestra el método s e n d S p ittle E m a ilW ith A tta c h m e n t () completo.

Listado 19.2. Envío de mensajes de correo con archivos adjuntos con MimeMessageHelper.

public void sendSpittleEmailWithAttachment(


String to , S p ittle s p ittle ) throws MessagingException {
MimeMessage message = mailSender. createMimeMessage( );
MimeMessageHelper helper =
new MimeMessageHelper(message, tr u e ); / / Crea a sis te n te de mensajes.
String spitterName = s p it t le .g e t S p it t e r ( ) .getFullName( );
h elp er. setFrom("norep ly@ spitter. com" ) ;
h elp er.setT o ( to ) ;
h elp er. se tS u b je c t( "New s p itt le from " + spitterName);
h elp er. setText (spitterName + " says: " + s p i t t l e . g etT ext{ ) ) ;
FileSystemResource couponlmage =
554 Capítulo 19

new FileSystemResource("/co llateral/co u p on .p n g ") ;


h elp er. addAttachment("Coupon.png", couponlmage); / / Añadir archivo adjunto.
mailSender. send(message);
}
La inclusión de archivos adjuntos en un correo electrónico es tan solo una de las opera­
ciones que podemos hacer con los correos electrónicos multiparte. Además, si especificamos
que el cuerpo del correo es HTML, podemos crear mensajes con un aspecto más personali­
zado, con una apariencia mejor que la del texto sin formato. Veamos cómo enviar atractivos
mensajes de correo con M im eM essageH elpers de Spring.

Enviar mensajes de correo con contenido enriquecido


El envío de un mensaje de correo electrónico enriquecido no se diferencia mucho al
de uno con texto sin formato. La diferencia entre ambos radica en configurar el texto del
mensaje como código HTML. Es tan sencillo como proporcionar una cadena HTML al
método asistente s e t T e x t () y establecer en t r u e el segundo parámetro:
h elp er.setT ext ("<html><bodyximg src = ' cid : spitterLogo' >" + "
<h4>" + s p it t le .g e t S p it t e r ( ) .getFullName() + " say s. . . </h4>" +
" <i>" + s p ittle .g e tT e x t() + "< /i> " +
"</bod yx/htm l>", tru e );

El segundo parámetro indica que el texto proporcionado como primer parámetro es


código HTML, de forma que el tipo de contenido de esa parte del mensaje se va a confi­
gurar de manera adecuada.
Fíjese en que el código HTML proporcionado cuenta con una etiqueta <im g> para
mostrar el logotipo de la aplicación Spittr como parte del correo electrónico. El atributo
s r c podría configurarse con una URL estándar h t t p : para obtener el logotipo de la Web.
Sin embargo, en este caso, hemos incrustado la imagen en el propio mensaje. El valor
c i d : s p i t t e r L o g o indica que en una de las partes del mensaje habrá una imagen iden­
tificada como s p i t t e r L o g o .
Añadir la imagen incrustada al mensaje es muy parecido a añadir un archivo adjunto.
En lugar de invocar el método asistente ad d A ttach m ent ( ) , debemos invocar el método
a d d l n l i n e ().
ClassPathResource image =
new ClassPathResource("spitter_logo_50.png") ;
h elp er. ad d lnline(" sp itterL ogo", image);

El primer parámetro de a d d l n l i n e especifica la identidad de la imagen incluida, la


misma que especificamos con el atributo s r c de <im g>. El segundo parámetro es la refe­
rencia de recurso para la imagen, que aquí hemos creado con C l a s s P a t h R e s o u r c e de
Spring para recuperar la imagen de la ruta de clases de la aplicación.
Obviando la invocación ligeramente diferente de s e t T e x t () y el uso del método
a d d l n l i n e ( ) , el envío de un mensaje con contenido enriquecido es muy similar al de
uno con texto sin formato con archivos adjuntos. Para que lo pueda comparar, aquí tiene
el nuevo método s e n d R i c h S p i t t e r E m a i l ( ) .
Enviar correo con Spring 555

public void sendRichSpitterEm ail(String to , S p ittle s p ittle )


throws MessagingException {
MimeMessage message = mailSender. createMimeMessage();
MimeMessageHelper helper = new MimeMessageHelper(message, tru e );
h elp er. setFrom("norep ly@ spitter. com" ) ;
h elp er. setT o ("craig@habuma. com" ) ;
h elp er. se tS u b je c t("New s p it t le from " +
s p i t t l e . g e tS p itte r ( ) . getFullName( ) ) ;
h elp er. se tT e x t("<html><bodyximg s r c = 'c i d :sp itterL o g o '>" + / / E stablecer cuerpo HTML.
"<h4>" + s p it t le .g e t S p it t e r ( ) .getFullName() + " says. . . </h4>" +
"<i>" + s p ittle .g e tT e x t() + "< /i> " +
"</bodyx/htm l>" , tru e );
ClassPathResource image =
new ClassPathResource("sp itter_lo go _50.p n g");
h elp er. ad d inline(" sp itterL ogo", image); / / Añadir imagen.
mailSender. send(message);
}
Y, de esta forma, podemos enviar mensajes de correo con contenido enriquecido e
imágenes adjuntas. Podríamos detenernos aquí. Sin embargo, me molesta que el cuerpo
del mensaje de correo se cree utilizando concatenación de cadenas para crear un mensaje
HTML. Antes de finalizar el tema del correo electrónico, veamos cómo sustituir ese mensaje
por una plantilla.

Generar correo electrónico mediante plantillas


El problema de crear un mensaje de correo electrónico mediante concatenación de
cadenas es que no queda claro cuál va a ser el aspecto del mensaje final. Resulta difícil
analizar mentalmente el marcado HTML e imaginar cuál va a ser su aspecto al repre­
sentarlo. Además, el problema es mezclar el código HTML con el de Java. Estaría bien
extraer el diseño del mensaje a una plantilla con la que un diseñador gráfico pueda
trabajar. Lo que necesitamos es una forma de mostrar el diseño del correo en algo similar al
código HTML resultante y, a continuación, transformar esa plantilla en un elemento S t r i n g
que transmitir al método s e t T e x t () del asistente de mensajes. Para transformar plantillas
en cadenas dispone de varias opciones, como Apache Velocity y Thymeleaf. Veamos cómo
crear mensajes de correo avanzados con todas estas opciones, empezando con Velocity.

Crear mensajes de correo electrónico con Velocity


Apache Velocity es un motor de creación de plantillas de propósito general de Apache.
Velocity se usa desde hace tiempo en todo tipo de operaciones, como la generación de
código, y como alternativa a JSP. También se puede usar para aplicar formato a mensajes de
correo electrónico, como haremos en este apartado. Para usar Velocity para generar nues­
tros mensajes, primero debe conectar V e l o c i t y E n g i n e a S p i t t e r E m a i l S e r v i c e l m p l .
Spring cuenta con un bean de fábrica llamado V e l o c i t y E n g i n e F a c t o r y B e a n , que
genera un elemento V e l o c i t y E n g i n e en el contexto de la aplicación. La declaración de
V e l o c i t y E n g i n e es la siguiente:
556 Capítulo 19

@Bean
public VelocityEngineFactoryBean velocityEngine{) {
VelocityEngineFactoryBean velocityEngine =
new VelocityEngineFactoryBean();

Properties props = new P ro p ertie s();


props. setP rop erty("resource. lo ad er", " c la s s " ) ;
props. setP roperty("c la s s . resou rce. load er. c la s s ",
ClasspathResourceLoader.class.getMame( ) ) ;
velocityEngine. setV elocityP ro p erties(p ro p s);
return velocityEngine;

La única propiedad que tenemos que configurar en V e l o c i t y E n g i n e F a c t o r y B e a n


es v e l o c i t y P r o p e r t i e s . En este caso, la configuramos para cargar plantillas de Velocity
desde la ruta de clases (consulte la documentación de Velocity para obtener más informa­
ción al respecto).
Ya podemos conectar el motor de Velocity a S p i t t e r E m a i l S e r v i c e l m p l . Como la
implementación se registra de forma automática con el analizador de componentes, podemos
utilizar @ A utow ired para conectar de forma automática una propiedad v e l o c i t y E n g i n e :
@Autowired
VelocityEngine velocityEngine;

Tras ello, podemos usar la propiedad v e l o c i t y E n g i n e para transformar una plantilla


de Velocity en un elemento S t r i n g para enviarlo como texto de nuestro mensaje. Spring
cuenta con V e l o c i t y E n g i n e U t i l s para facilitar el trabajo de combinar una plantilla de
Velocity y datos de modelo en un elemento S t r i n g . Veamos cómo utilizarlo:
Map<String,String>model=newHashMap<String, S tr in g > ();
model.put("spitterName", spitterName);
model.put("s p ittle T e x t", s p i t t l e . g etT ext( ) ) ;
String em ailText=VelocityEngineUtils.mergeTemplatelntoString(
velocityEngine, "emailTeraplate.vm", model);

Para preparar el procesamiento de la plantilla, creamos primero un Map que contenga


los datos de modelo utilizados por la plantilla. En nuestro código concatenado con cadenas
anterior, necesitábamos el nombre completo de la persona que publicaba el spittle y su texto.
En este caso, también vamos a necesitarlo. Para generar el texto de correo electrónico combi­
nado, se invoca el método m e r g e T e m p l a t e l n t o S t r i n g () de V e l o c i t y E n g i n e U t i l s
y se le pasa el motor Velocity, la ruta a la plantilla (relativa al nivel raíz de la ruta de clases)
y la asignación del modelo. Todo lo que queda por hacer en el código de Java es entregar
el texto de mensaje de correo combinado al método asistente s e t T e x t ():
h elp er. setText(em ailText, tr u e );

La plantilla se encuentra en el nivel raíz de la ruta de clases, en el archivo em ailT em -


p l a t e . vm:
<html>
<body>
<img s rc = 'c id :s p itte rL o g o '>
Enviar correo con Spring 557

<h4>${spitterName}says. . .</h4>
< i> $ {s p ittle T e x t}< /i>
</body>
</html>

Como puede ver, leer el archivo de plantilla es mucho más fácil que la versión concate­
nada con cadenas. Por tanto, también va a ser más fácil de mantener y editar. En la figura
19.2 puede ver un ejemplo del tipo de correo electrónico que se va a generar.

n o n New spittle from Craig Walls £3

0 E g L ^j
Cet Mail Write Address Book Reply Reply Alt Forward Tag Delete
® Subject: N e w s pftfie from Cra kj Wa Its
From: [email protected] *
Sate: 12:11 AM
To: [email protected] ?

Craig Walk says...


Hey, here's a test spittlel
2 ________________________________________ y
Figura 19.2. Una plantilla de Velocity y varias imágenes incrustadas pueden mejorar de manera
considerable un mensaje de correo.

Velocity se usa desde hace años como motor de creación de plantillas para muchas
tareas, pero como vimos en un capítulo anterior, hay una nueva opción de plantilla cada
vez más popular. Veamos cómo usar Thymeleaf para crear mensajes de correo de spittle.

Utilizar Thymeleaf para crear mensajes de correo


Como vimos en un capítulo anterior, Thymeleaf es un atractivo motor de creación de
plantillas para HTML ya que nos permite crear plantillas WYSIWYG. Al contrario de lo que
sucede con JSP y Velocity, las plantillas Thymeleaf no contienen bibliotecas de etiquetas
especiales ni marcado inusual. De este modo los diseñadores de plantillas pueden usar las
herramientas HTML que deseen sin tener que preocuparse por aceptar un marcado especial.
Al convertir una plantilla de correo electrónico a plantilla Thymeleaf, la naturaleza
WYSIWYG de Thymeleaf es evidente:
<!DOCTYPE htral>
<html xmlns: th="h ttp : //www.thymeleaf. org">
<body>
<img src = ,TspitterLogo.png" th : src=' cid : spitterLogo' >
<h4xspan th: text="${spitterN am e} ">Craig Walls</span> sa y s ...< /h 4 >
c ix s p a n th : tex t= "$ { s p ittle T e x t} " >Hello there ! < /s p a n x /i>
</body>
</html>
558 Capítulo 19

Apreciará que no hay etiquetas personalizadas (como las que vería en JSP), y aunque se
haga referencia a los atributos del modelo con la notación $ { } , se limita a valores de atri­
butos en lugar de mostrar un carácter tan genérico como sucede en Velocity. Esta plantilla
se podría abrir en un navegador Web y verse de forma completa sin necesidad de recurrir
al motor Thymeleaf para procesarla.
El uso de Thymeleaf para generar y enviar mensajes de correo electrónico HTML es
muy similar a lo que hicimos con Velocity:
Context ctx = new ContextO;
c tx .s e tV a ria b le ("spitterName", spitterName);
c tx .s e tV a ria b le ("s p ittle T e x t", s p ittle .g e tT e x t( ) ) ;
Strin g emailText = thym eleaf.process("emailTemplate.html", c t x );

h elp er. setText(em ailText, tr u e );


mailSender.send(message);

Lo primero es crear una instancia C o n t e x t de Thymeleaf y completarla con datos del


modelo. Es similar a completar un elemento Map con datos del modelo, como hicimos con
Velocity. Tras ello, pedimos a Thymeleaf que procese la plantilla, combinando con esta los
datos de modelo del contexto mediante la invocación del método p r o c e s s () en el motor
Thymeleaf. Por último, se establece el texto resultante en el mensaje por medio del método
de ayuda del mensaje y se envía mediante el emisor de correo. Parece sencillo pero ¿de
dónde proviene el motor Thymeleaf (representado por la variable th y m e le a f)?
Este motor Thymeleaf es el mismo bean S p r i n g T e m p l a t e E n g i n e que confi­
guramos para crear vistas Web en un capítulo anterior, pero antes se inyecta en
S p i t t e r E m a i l S e r v i c e l m p l mediante inyección de constructores:
@Autowired
private SpringTemplateEngine thymeleaf;

@Autowired
public SpitterEmailServicelmpl(SpringTemplateEngine thymeleaf) {
t h i s .thymeleaf = thymeleaf;
}

No obstante, tendrá que realizar un pequeño cambio en elbean S p rin g T e m p la te E n g in e .


En un capítulo anterior, lo dejamos configurado para resolver plantillas del contexto de
servlet. Las plantillas de correo electrónico tendrán que resolverse desde la ruta de
clases. Por ello, además de S e r v l e t C o n t e x t T e m p l a t e R e s o l v e r necesitaremos
C ía ss L o a d e rT e m p la te R e s o lv e r:
@Bean
public ClassLoaderTemplateResolver emailTemplateResolver() {
ClassLoaderTemplateResolver resolver =
new ClassLoaderTemplateResolver();
re so lv e r. s e tP re fix ("m a il/") ;
re so lv e r. setTemplateMode("HTML5" ) ;
re so lv e r. setCharacterEncoding("UTF-8 " );
setO rder(1 );
return resolv er;
}
Enviar correo con Spring 559

En su gran mayoría configuraremos el bean C l a s s L o a d e r T e m p l a t e R e s o l v e r tal


y como hicimos con S e r v l e t C o n t e x t T e m p l a t e R e s o l v e r . Sin embargo, la propiedad
p r e f i x se establece en m a i l / para indicar que espera encontrar plantillas Thymeleaf en
el directorio de correo de la raíz de la ruta de clases. Por lo tanto, el nombre de su archivo
de plantilla de correo electrónico debe ser e m a i l T e m p l a t e . h t m l y debe ubicarse en el
directorio de correo relativo a la raíz de la ruta de clases. Y como ahora tenemos dos solu-
cionadores de plantilla, tendrá que indicar cuál tiene preferencia por medio de la propiedad
o r d e r . El bean C l a s s L o a d e r T e m p l a t e R e s o l v e r tiene el orden 1. Cambie la configu­
ración de S e r v l e t C o n t e x t T e m p l a t e R e s o l v e r y establezca o r d e r en 2:
@Bean
public ServletContextTemplateResolver webTemplateResolver() {
ServletContextTemplateResolver resolver =
new ServletContextTemplateResolver();
re so lv e r. setPrefix("/W EB-IN F/tem plates/") ;
re so lv e r. setTemplateMode("HTML5 " ) ;
re so lv e r. setCharacterEncoding("UTF-8") ;
setO rder(2);
return resolv er;
}
Solo falta cambiar la configuración del bean S p r i n g T e m p l a t e E n g i n e para que utilice
los dos solucionadores de plantilla:
@Bean
public SpringTemplateEngine templateEngine(
Set<ITemplateResolver> resolvers) {
SpringTemplateEngine engine = new SpringTemplateEngine0 ;
engine. setTem plateR esolvers(resolvers);
return engine;
}
Antes solo teníamos uno, por lo que lo inyectamos en la propiedad t é m p l a t e
R e s o l v e r de S p r i n g T e m p l a t e E n g i n e . Como ahora tenemos dos, tendrá que inyectarlos
como miembros de un conjunto ( S e t ) en la propiedad t e m p l a t e R e s o l v e r s (en plural).

Resumen
El correo electrónico es una forma importante de comunicación entre humanos y una
forma fundamental de comunicación entre humanos y aplicaciones. Spring se basa en
las prestaciones de correo electrónico proporcionadas por Java y abstrae JavaMail para
conseguir un uso y una configuración más sencilla para aplicaciones de Spring. En este
capítulo hemos visto cómo usar la abstracción de correo electrónico de Spring para enviar
mensajes de correo sencillos, para después abordar el envío de mensajes avanzados con
archivos adjuntos y formato HTML. También hemos visto el uso de motores de creación de
plantillas como Velocity y Thymeleaf para generar texto enriquecido de correo electrónico
sin recurrir a la creación de HTML mediante la concatenación de cadenas. En el siguiente
capítulo aprenderemos a añadir opciones de administración y notificación a bean de Spring
por medio de JMX (Java M an agem en t E xtensions, Extensiones de administración de Java).
Capítulo

20 G e stio n a r bean
de S p rin g con J M X

CONCEPTOS FUNDAMENTALES:

• Exposición de bean de Spring como bean adminis­


trados.
• Administración remota de bean de Spring.
• Control de notificaciones JMX.
La compatibilidad de Spring con la inyección de dependencias (DI) es una forma exce­
lente de configurar propiedades de bean en una aplicación. Sin embargo, una vez que se
encuentra implementada y en funcionamiento, la DI no puede hacer mucho para ayudarle
a realizar cambios en dicha configuración. ¿Y si queremos modificar una aplicación en
funcionamiento y realizar cambios sobre la marcha? Aquí es donde entra en escena JMX
(Java M an agem en t E xtensions, Extensiones de administración de Java).
JMX es una tecnología que le permite instrumentar aplicaciones para llevar a cabo tareas
de gestión, supervisión y configuración. Aunque en principio estaba disponible como exten­
sión independiente de Java, JMX ahora es un elemento estándar de la distribución Java 5.
El elemento esencial de una aplicación que se instrumenta para su gestión con JMX es
el MBean (o bean gestionado). Un MBean es un JavaBean que expone ciertos métodos que
definen la interfaz de gestión. La especificación JMX define cuatro tipos de MBean:

• MBean estándar: Aquellos cuya interfaz de administración viene determinada por


el reflejo de una interfaz Java fija que es implementada por la clase del bean.
• MBean dinámicos: Aquellos cuya interfaz de administración se determina en tiempo
de ejecución invocando métodos de la interfaz DynamicMBean. Como la interfaz
de gestión no se define mediante una interfaz estática, puede variar en tiempo de
ejecución.
• MBean abiertos: Un tipo especial de MBean dinámicos cuyos atributos y operaciones
se limitan a tipos primitivos, contenedores de clases para tipos primitivos, así como
cualquier tipo que pueda dividirse en elementos primitivos o en contenedores
primitivos.
• MBean modelo: Un tipo especial de MBean dinámico que vincula una interfaz de
gestión a un recurso administrado. No suelen escribirse y tienden a ser declarados
en la mayoría de los casos. Suele generarlos una fábrica que utiliza metadatos para
crear la interfaz de administración.

El módulo JMX de Spring le permite exportar bean de Spring como MBean modelo, para
que así pueda examinar el contenido de su aplicación y ajustar la configuración, incluso
cuando se encuentra en funcionamiento. Veamos cómo activar Spring JMX en nuestra
aplicación para así poder gestionar bean en el contexto de aplicación de Spring.

Exportar bean da Spring como MBean_____ _


Hay diferentes formas de utilizar JMX para gestionar bean dentro de la aplicación Spittr.
Con el fin de mantener nuestro ejemplo sencillo, vamos a realizar un modesto cambio en
S p i t t l e C o n t r o l l e r para añadir una nueva propiedad s p i t t l e s P e r P a g e :

public s t a t ic f in a l in t DEFAULT_SPITTLES_PER_PAGE = 25;


prívate in t spittlesPerPage = DEFAULT_SPITTLES_PER_PAGE;

public void setSp ittlesP erP ag e(in t spittlesPerPage) {


t h i s . spittlesPerPage = spittlesPerP age;
562 Capítulo 20

}
public in t g etSp ittlesP erP ag e() {
return sp ittlesPerP age;
}
Con anterioridad, cuando S p i t t l e C o n t r o l l e r invocaba f i n d S p i t t l e s () en
S p i t t e r S e r v i c e , pasaba 2 0 para el segundo argumento, para solicitar solamente los
últimos 20 objetos S p i t t l e . Ahora, en lugar de limitarnos a esa decisión en tiempo de
generación con un valor fijo, vamos a utilizar JMX para dejar la decisión abierta y poder
modificarla en tiempo de ejecución. La nueva propiedad s p i t t l e s P e r P a g e es el primer
paso para lograrlo.
Por sí sola, la propiedad s p i t t l e s P e r P a g e no activa la configuración externa del
número de spittle que se muestran en la página. Se trata solo de la propiedad de un bean y es
igual a cualquier otra. Lo que tenemos que hacer es exponer el bean S p i t t e r C o n t r o l 1 e r
como MBean. De esta forma, la propiedad s p i t t l e s P e r P a g e se expondrá como atributo
gestionado del MBean y podremos modificar su valor en tiempo de ejecución.
M B e a n E x p o r t e r de Spring es la clave para poder utilizar bean con JMX. Se trata de
un bean que exporta uno o más bean gestionados por Spring como MBean modelo en un
servidor de MBean. El servidor, en ocasiones denominado agente MBean, es un contenedor
en el que se encuentran los MBean y a través del cual se accede a ellos.
Como se muestra en la figura 20.1, la exportación de bean de Spring como MBean de
JMX permite que una herramienta de gestión basada en JMX, como JConsole o VisualVM,
pueda acceder a una aplicación en funcionamiento para examinar las propiedades de los
bean e invocar sus métodos.

Figura 20.1. MBeanExporter exporta propiedades y métodos de bean de Spring como atributos
y operaciones de JM X en un servidor MBean. Después, se puede utilizar una herramienta
de gestión JM X como JConsole para examinar los detalles de la aplicación en funcionamiento.

El siguiente método @ Bean declara un M B e a n E x p o r t e r en Spring para exportar el


bean S p i t t l e C o n t r o l l e r como MBean de modelo:
Gestionar bean de Spring con JMX 563

@Bean
public MBeanExporter m beanExporter(SpittleController sp ittle C o n tro lle r) {
MBeanExporter exporter = new MBeanExporter( );
Map<String, Object> beans = new HashMap<String, O b ject> ();
beans.put("sp itter:n am e=Sp ittleC o n tro ller", s p ittle C o n tro lle r);
exporter.setB eans(beans);
return exporter;
}
En su forma más directa, M B e a n E x p o r te r puede configurarse mediante su propiedad
b e a n s inyectando un Map en uno o varios bean que quiera exponer como MBean modelo
en JMX. El elemento k e y de cada < e n t r y > es el nombre que se va a proporcionar al MBean
(formado por un nombre de dominio de administración y un par de clave y valor; en el caso
del MBean S p i t t l e C o n t r o l l e r , s p i t t e r : n a m e = S p i t t l e - C o n t r o l l e r ) . El valor de
e n t r y es una referencia al bean gestionado por Spring que se va a exportar. En este caso,
estamos exportando el bean s p i t t l e C o n t r o l l e r deforma que sus propiedades puedan
gestionarse en tiempo de ejecución a través de JMX.
Tras conseguir M B e a n E x p o r te r , el bean h o m e C o n t r o l l e r se exporta como MBean
modelo al servidor de MBean para su gestión bajo el nombre S p i t t l e C o n t r o l l e r . En
la figura 20.2 podemos ver cómo se muestra al examinarlo en JConsole.

r\ Java Monitoring & Management Console . . ___________


Connection Window Help _ ________ _____ _______________________
Cs _______ ptd: 1032 ofg.codehaus.classworlds.Laurscher "clean" "jatty:run"
| ......... -..... ......---fO v e m e w : Memory - Threads ; Classes' VMSummary ^MBeans-”|----- — — «#> \
. , ...........
► IMlmplementation
► C1 com.sun.manaqement iName Value
► iava.lanq jSpittlesPerPage 25
► Q3 java, util. l o g g i n g ( Refresh )
▼ EM spitter
r m SpittleController MBeaiiAttributelnfo
▼ Attributes Name Value
SES3S1 Attribute:
T Operations Name SpittlesPerPage
setSpfttlesPerPage Description spittiesPerPage
getSpittîesPerPage i Readable true
{Writable true
Notifications
- Is false
Type lot

Descriptor
Name Value
Attribute:
descriptorType attribute
displayName SpittiesPerPage
getMethod getSpittiesPerPage
name SpittiesPerPage
setMethod setSpittlesPerPage

Figura 20.2. SpittleController exportado como M Bean y examinado mediante JConsole.

Como puede comprobar en el lado izquierdo de la figura 20.2, todos los miembros
públicos del bean S p i t t l e C o n t r o l l e r se exportan como operaciones y atributos de
MBean. Probablemente no sea lo que queremos, ya que en realidad queremos configurar la
564 Capítulo 20

propiedad s p i t t l e s P e r P a g e . No tenemos que invocar el método showHomePage () o


tratar con otros componentes de S p i t t l e C o n t r o l l e r . Por tanto, necesitamos una forma
de seleccionar qué atributos y operaciones van a estar disponibles.
Para conseguir un control más preciso sobre los atributos y operaciones de un MBean,
Spring incluye varias opciones, como por ejemplo:

• Declarar métodos de bean expuestos o ignorados por nombre.


• Mostrar el bean con una interfaz para seleccionar los métodos expuestos.
• Anotar el bean para designar los atributos y operaciones gestionados.
Vamos a probar cada una de estas opciones para ver cuál se adapta mejor a nuestro
MBean S p i t t l e C o n t r o l l e r . Comencemos por la primera.

El servidor MBean
Tal y como está configurado, MBeanExporter asume que se ejecu ta en un servidor
de aplicaciones (como Tomcat) o dentro de otro contexto que proporciona un
servidor de MBean. Sin embargo, si su aplicación de Spring se va a ejecutar de
forma individual o en un contenedor que no va a contar con un servidor de MBean,
puede que quiera configurarlo en el contexto de Spring.
En configuración XML, el elemento ccontext :mbean-server> se puede encargar
de ello. En configuración de Java tendrá que adoptar un enfoque más directo
y configurar un bean de tipo MBeanServerFactoryBean () (justo lo que hace
ccontext :mbeanserver> en XML).
MBeanServerFactoryBean crea un servidor de MBean como bean en el contexto de
la aplicación de Spring. De forma predeterminada, el ID del bean es mbeanServer,
que puede conectar a la propiedad server de MBeanExporter para especificar el
servidor de MBean desde el que debe exponerse el MBean.

Exponer métodos por nombre


Un ensamblador de información de MBean es la clave para restringir las opera­
ciones y los atributos que se van a exportar en un MBean. Uno de ellos es M e th o d
NameBasedMBeanlnf o A ssem b ler. Al ensamblador se le proporciona una lista de nombres
de método que exportar como operaciones de MBean. Para el bean S p i 1 1 1 e Cont r o l 1 e r ,
queremos exportar s p i t t l e s P e r P a g e como atributo gestionado. ¿Cómo puede ayudarnos
un ensamblador basado en nombres a realizar esta tarea?
Recuerde que según las normas de JavaBean (que no son necesariamente las normas sobre
bean de Spring), lo que convierte a s p i t t l e s P e r P a g e en una propiedad es que cuenta con
los métodos de acceso s e t S p i t t l e s P e r P a g e () y g e t S p i t t l e s P e r P a g e () .Para limitar
la exposición de MBean, tenemos que indicar a MethodNameBasedMBeanlnf o A s s e m b le r
que solo incluya esos métodos en la interfaz de MBean. En la siguiente declaración de
M eth od N am eB ased M Beanln f o A s s e m b l e r se incluyen estos métodos:
Gestionar bean de Spring con JMX 565

@Bean
public MethodNameBasedMBeanlnfoAssembler assem blerò {
MethodNameBasedMBeanlnfoAssembler assembler =
new MethodNameBasedMBeanlnfoAssembler();
assembler. setManagedMethods(new S trin g [] {
"getSp ittlesP erP ag e", "setSpittlesPerP age"
});
return assembler;
}
La propiedad m anagedM ethods acepta una lista de nombres de método. Son los que se
exponen como operaciones gestionadas de MBean. Como son métodos de acceso a propie­
dades, también van a dar lugar a un atributo gestionado s p i t t l e s P e r P a g e en el MBean.
Para poner en marcha el ensamblador, tenemos que conectarlo a M B e a n E x p o r te r :
@Bean
public MBeanExporter mbeanExporter(
S p ittle C o n tro lle r s p ittle C o n tro lle r,
MBeanlnfoAssembler assembler) {
MBeanExporter exporter = new MBeanExporter( );
Map<String, Object> beans = new HashMap<String, O b je ct> ();
beans.put("sp itter:n am e=Sp ittleC o n tro ller", s p ittle C o n tro lle r);
exp orter. setBeans(beans);
exp orter. setAssembler(assembler);
return exporter;

Ahora, si iniciamos la aplicación, s p i t t l e s P e r P a g e de S p i t t l e C o n t r o l l e r estará


disponible como atributo gestionado. Sin embargo, el método s p i t t l e s () no se va
exponer como operación gestionada. En la figura 20.3 se muestra su aspecto en JConsole.
Otro ensamblador basado en nombres de método que tener en cuenta es M eth od
E x c l u s i o n M B e a n l n f o A s sem b ler. Este ensamblador de información MBean es el opuesto
a M ethodN am eBasedM Beanlnf o A s s e m b le r . En lugar de especificar qué métodos se van
a exponer como operaciones gestionadas, a M e t h o d E x c l u s i o n M B e a n l n f o A s s e m b l e r
se le p r o p o r c io n a u n a lista de métodos que no se van a mostrar como operaciones gestio­
nadas. Así, en este ejemplo podemos ver cómo utilizar M e t h o d E x c l u s i o n M B e a n l n f o
A s s e m b l e r para no considerar a s p i t t l e s () como operación gestionada:
@Bean
public MethodExclusionMBeanlnfoAssembler assem bler() {
MethodExclusionMBeanlnfoAssembler assembler =
new MethodExclusionMBeanlnfoAssembler();
assembler. setlgnoredMethods(new Strin g [] {
"s p ittle s "
}>;
return assembler;
}
Los ensambladores basados en nombres de método son fáciles de utilizar. Sin embargo,
¿qué sucedería si se exportaran varios bean de Spring como MBean? Pasado un rato, la lista
de nombres de método proporcionada al ensamblador sería enorme. Asimismo, existe la
posibilidad de que quiera exportar un método desde un bean mientras otro bean cuenta con
566 Capítulo 20

un método con el mismo nombre que no queremos exportar. Claramente, en términos de


configuración de Spring, el enfoque de nombres de método no puede ampliarse con facilidad
cuando exportamos varios MBean. Para este caso, vamos a ver cómo utilizar interfaces.

ja v a Monitoring & Lonsoíe


C o n n e ctio n W ind o w Help

iít O t o ptd: 6136 of-g.codehaus.classworidsXauncher “ clears“ “je tty iru rf


__
Overview Memory Threads : C lasses VM Summary -fstSeaasi§ .-...-.. ................
■ <%>]
CS ^Implementation [¡■■Attribute value..
1 ► Q com. sun.managernent S \N a m e ; Value
* Cj iava.lang ItspmiesPerPage.. *25.........
j ► S java.util.logging Refresh 1
¡ * rfspmer
■ ▼ (<$
SpittleController MBeanA tí ri btitetefb -
r
Attributes Name
Attribute:
' Operations Name SpHtiesPerPage
spittles Description spittlesPerPap
setSpitfíesPerPage Readable true
Writable true
getSptttlesPerPage
is false
N otifications Type Snt

r Descriptor“ “™"^ --- .— „„ — „ ................ii


Name 1Value ____________. . ||
Attribute:
descriptorType attribute 1
displayName SpittlesPerPage 1
getMethod getSpittlesPerPage 1
name SpittlesPerPage
seiMethod seiSpittlesPerPage

Figura 20.3. Después de especificar qué métodos se exportan en el MBean SpittleController,


el método showHomePageQ deja de ser una operación gestionada.

Usar interfaces cara definir operaciones


y atributos de MBean
I n t e r f a c e B a s e d M B e a n l n f o A s s e m b l e r de Spring es otro ensamblador de informa­
ción de MBean que le permite utilizar interfaces para elegir y seleccionar métodos de un bean
para exportarlos como operaciones gestionadas por MBean. Es similar a los ensambladores
basados en nombres de método, con la diferencia de que en lugar de mostrar los nombres de
los métodos que se van a exportar, se muestran las interfaces que definen dichos métodos.
Por ejemplo, supongamos que quiere definir la interfaz S p i t t l e C o n t r o l l e r M a n a g e d
O p e r a t i o n s de la siguiente manera:

package com.habuma.spitter.jmx;

public in te rfa ce SpittleControllerManagedOperations{


in t g etSp ittlesP erP ag e();
void setS p íttlesP erP ag e(in t sp ittlesP erP a g e);
}
Gestionar bean de Spring con JMX 567

En este caso, hem os seleccionado los métodos s e t S p i t t l e s P e r P a g e O y g e t


S p i t t l e s P e r P a g e como operaciones que exportar. De nuevo, estos métodos de acceso
van a exportar de forma indirecta la propiedad s p i t t l e s P e r P a g e como atributo gestio­
nado. Para utilizar este ensamblador, solo tiene que utilizar el siguiente bean a s s e m b l e r
en lugar de los ensambladores basados en nombres de método anteriores:

@Bean
public InterfaceBasedMBeanlnfoAssembler assembler() {
InterfaceBasedMBeanlnfoAssembler assembler =
new InterfaceBasedMBeanlnfoAssembler();
assembler.setM anagedlnterfaces(
new Class<?>[] { SpittleControllerManagedOperations. cla s s }
);
return assembler;
}
La propiedad m a n a g e d l n t e r f a c e s acepta una lista con una o más interfaces que sirven
como interfaces de operación gestionadas con MBean, en este caso, la interfaz S p i t t l e
C o n tro lle rM a n a g ed O p era tio n s.
Lo que puede no ser aparente, aunque sí interesante, es que S p i t t l e C o n t r o l l e r no
tiene que implementar de forma explícita S p i t t l e C o n t r o l l e r M a n a g e d O p e r a t i o n s . La
interfaz está ahí para el exportador, aunque no tenemos que implementarla directamente
en nuestro código. S p i t t l e C o n t r o l l e r debería implementar la interfaz al menos para
reforzar el contrato entre los MBean y la clase de implementación.
Lo bueno del uso de las interfaces para seleccionar operaciones gestionadas es que
podemos recopilar decenas de métodos en unas pocas interfaces y mantener la configu­
ración de I n t e r f a c e B a s e d M B e a n l n f o A s s e m b l e r limpia. Esto nos permite mantener
ordenada la configuración de Spring, incluso cuando exportamos varios MBean.
Al final, estas operaciones gestionadas tienen que declararse en alguna parte, ya sea
en la configuración de Spring o en otra interfaz. Además, la declaración de operaciones
gestionadas representa una duplicación del código: nombres de método declarados en
una interfaz o en el contexto de Spring y nombres de método en la implementación. Esta
duplicación solo existe para satisfacer el elemento M B e a n E x p o r te r .
Una de las cosas para las que las anotaciones son útiles es para contribuir a eliminar
esta duplicación. Veamos cómo anotar un bean gestionado por Spring para que pueda
exportarse como MBean.

Trabajar con MBean basados en anotaciones


Además de los ensambladores de información MBean que hemos visto hasta el momento,
Spring cuenta con otro ensamblador llamado M e ta d a ta M B e a n ln f o A s s e m b le r que puede
configurarse para utilizar anotaciones y designar métodos de bean como operaciones y
atributos gestionados. Podría mostrarle cómo utilizar este ensamblador pero no lo haré, ya
que su conexión manual resulta complicada y no merece la molestia hacerlo solo para utilizar
anotaciones. En su lugar, voy a mostrarle cómo utilizar el elemento < c o n t e x t -.rabean -
e x p o r t > del espacio de nombres de configuración c o n t e x t de Spring. Este elemento
568 Capítulo 20

permite conectar un exportador de MBean y todos los ensambladores adecuados para


activar MBean basados en anotaciones de Spring. Todo lo que tiene que hacer es utilizarlo
en lugar del bean M B e a n E x p o r t e r actual:
ccontext :mbean-export server="mbeanServer" />

Ahora, para convertir cualquier bean de Spring en un MBean basta con anotarlo con
@ M a n a g e d R e s o u r c e y anotar sus métodos con @ M a n a g e d O p e r a t io n o @Managed
A t t r i b u t e . Por ejemplo, en el listado 20.1, podemos ver cómo modificar S p i t t l e
C o n t r o l l e r para exportarlo como MBean utilizando anotaciones.

Listado 20.1. Anotación de SpittleController para convertirlo en un MBean.

package com.habuma. s p itt e r .mvc;


import ja v a .util.M ap;
import org .sp rin gframework.beans. factory.annotation.Autow ired;
import org. springframework. jmx. export. annotation.Manage(¿Attribute ;
import org. springframework. jmx. export. annotation.ManagedResource ;
import org.springframework. stereotyp e. C ontroller;
import org. springframework.web.bind. annotation. RequestMapping;
import com. habuma. s p i t t r . s e rv ic e . S p ittrS e rv ic e ;
©Controller
@ManagedResource (objectName=" s p it t e r :name=Spit tleCont ro l 1er" ) / / Exportar SpittleController
/ / como MBean.
public cla ss S p ittle C o n tro lle r{

@ManagedAttribute / / Exponer spittlesPerPage como atrib u to gestionado,


public void setS p ittlesP erP ag e(in t sp ittlesP erP a g e){
t h i s . spittlesPerPage = spittlesPerP age;
}
@ManagedAttribute//
public in t g etSp ittlesP erP ag e(){
return spittlesPerP age;
}
i
La anotación @ M a n a g ed R e so u rc e se aplica a nivel de la clase para indicar que este
bean debe exportarse como MBean. El atributo ob j ectN am e indica el dominio ( s p i t t e r )
y el nombre ( S p i t t l e C o n t r o l l e r ) del MBean.
Los métodos de acceso para la propiedad s p i t t l e s P e r P a g e se anotan con @Managed
A t t r i b u t e para indicar que deben exponerse como atributo gestionado. Tenga en cuenta
que no es estrictamente necesario anotar ambos métodos de acceso. Si solo decide anotar
el método s e t S p i t t l e s P e r P a g e ( ) , podrá configurar la propiedad mediante JMX pero
no podrá ver cuál es su valor. Del mismo modo, g e t S p i t t l e s P e r P a g e () va a permitir
que el valor de la propiedad pueda verse como de solo lectura mediante JMX.
Tenga en cuenta también que es posible anotar los métodos de acceso con @Managed
O p e r a t i o n en lugar de @ M a n a g e d A t r ib u te . Por ejemplo:
@ManagedOperation
public void setS p ittlesP erP ag e(in t sp ittlesP erP a g e){
t h i s . spittlesPerPage = sp ittlesPerP age;
Gestionar bean de Spring con JMX 569

}
@ManagedOperation
public in t g etSp ittlesP erP ag e(){
return sp ittlesP erP age;
}
Este código expone estos métodos mediante JMX pero no expone la propiedad
s p i t t l e s P e r P a g e como atributo gestionado. Se debe a que los métodos anotados con
@ M a n a g e d O p e ra tio n se tratan de forma estricta como métodos y no como métodos de
acceso JavaBean en lo relativo a exponer la funcionalidad MBean. Por tanto, @Managed
O p e r a t i o n debe reservarse para exponer métodos como operaciones MBean y
@ M a n a g e d A t t r i b u t e debe utilizarse al exponer atributos gestionados.

Controlar colisiones de MBean*•


Hasta el momento hemos visto cómo publicar un MBean en un servidor MBean utili­
zando diferentes enfoques. En todos los casos, hemos asignado al MBean un nombre de
objeto formado por un nombre de dominio de gestión y un par de clave y valor. Suponiendo
que no existe un MBean con el nombre que hemos asignado al nuestro, no deberíamos
tener problemas para publicarlo. Sin embargo, ¿qué sucede si hay una colisión de nombres?
De forma predeterminada, M B e a n E x p o r t e r genera una excepción I n s t a n c e
A l r e a d y E x i s t s E x c e p t io n en caso de que se intente exportar un MBean con un nombre
igual al de un MBean que ya existe en el servidor de MBean. Sin embargo, puede modi­
ficar este comportamiento y especificar cómo va a gestionarse la colisión a través de la
propiedad r e g i s t r a t i o n B e h a v i o r N a m e de M B e a n E x p o r te r , o a través del atributo
r e g i s t r a t i o n de c c o n t e x t : m b e a n - e x p o r t > .
Hay tres formas de controlar una colisión de nombres de MBean por medio de la
propiedad r e g i s t r a t i o n P o l i c y :

• FAIL_ON_EXISTlNG: Falla si existe un MBean con el mismo nombre (es el compor­


tamiento predeterminado).
• IGNORE_EXl STING: Se ignora la colisión y el nuevo MBean no se registra.
• REPLACING_EXISTING: El MBean existente se sustituye por el nuevo.

Por ejemplo, si utiliza M B ean E xp orter puede configurarlo para que ignore las colisiones,
configurando para ello la propiedad r e g i s t r a t i o n P o l i c y como R e g i s t r a t i o n P o l i c y .
IGNORE_EXISTING de esta manera:

@Bean
public MBeanExporter mbeanExporter(
S p ittle C o n tro lle r S p ittle C o n tro lle r,
MBeanlnfoAssembler assembler) {
MBeanExporter exporter = new MBeanExporter( );
Map<String, Object> beans = new HashMap<String, O b ject> ();
beans.put("sp itter:n am e=Sp ittleC o n tro ller", S p ittle C o n tro lle r);
exp orter. setBeans(beans);
570 Capítulo 20

exp orter. setAssembler(assem bler);


exp orter. setR eg istratio n P o licy (R eg istratio n P o licy . IGNORE_EXISTING);
return exporter;

La p ro p ied ad r e g i s t r a t i o n P o l i c y acepta un v alo r de la en u m e ració n


R e g i s t r a t i o n P o l i c y que representa uno de los tres comportamientos disponibles
para el control de colisiones.
Una vez registrados nuestros MBean con M B e a n E x p o r te r , necesitamos una forma de
acceder a los mismos para gestionarlos. Como ya ha visto, podemos utilizar herramientas
como JConsole para acceder a un servidor local de MBean y manipular los MBean. Sin
embargo, esta herramienta no permite una gestión programática de estos elementos. ¿Cómo
vamos a poder manipular los MBean de una aplicación desde dentro de otra aplicación?
Por fortuna, es posible acceder a MBean como objetos remotos. Veamos cómo.

Acceso remoto a MBean


Aunque la especificación JMX original hacía referencia a la gestión remota de aplica­
ciones mediante MBean, no definía el protocolo o el API que debía utilizarse para ello.
Por tanto, era responsabilidad de los proveedores JMX definir sus propias soluciones de
acceso remoto para JMX.
Para cubrir la necesidad de un método estándar de acceso remoto a JMX, Java Community
Process publicó JSR-160, la especificación sobre el API remoto de extensiones de gestión
de Java. Esta especificación define un estándar para el acceso remoto con JMX, para lo
cual se requiere como mínimo una vinculación RMI y, de forma opcional, el protocolo de
mensajería JMX (JMXMP).
En esta sección veremos cómo Spring permite el acceso remoto mediante MBean. Para
ello, vamos a comenzar configurando Spring para que exporte S p i t t l e C o n t r o l l e r
como MBean remoto. A continuación, utilizaremos Spring para manipular ese MBean de
forma remota.

Exponer MBean remotos


La forma más sencilla de que nuestros MBean estén disponibles como objetos remotos
es configurar C o n n e c t o r S e r v e r F a c t o r y B e a n de Spring:
@Bean
public ConnectorServerFactoryBean ConnectorServerFactoryBean() {
return new ConnectorServerFactoryBean();
}
C o n n e c t o r S e r v e r F a c t o r y B e a n crea e inicia un J M X C o n n e c t o r S e r v e r de JSR-160.
De forma predeterminada, el servidor escucha el protocolo JMXMP en el puerto 9875, por
lo que está vinculado a S e r v i c e : j m x : jmxmp: / / l o c a l h o s t : 9 8 7 5 . Sin embargo, no
estamos limitados a exportar MBean utilizando únicamente JMXMP.
Gestionar bean de Spring con JMX 571

En función de la implementación JMX, podrá elegir entre diferentes protocolos, como


RMI, SOAP, Hessian/Burlap e incluso ITOP. Para especificar una vinculación remota dife­
rente para nuestros MBean, solo tenemos que configurar la propiedad s e r v i c e U r l de
C o n n e cto rS e rv e rF a cto ry B e a n . Por ejemplo, si queremos utilizar RMI, configuraríamos
s e r v i c e U r l de la siguiente manera:
@Bean
public ConnectorServerFactoryBean ConnectorServerFactoryBean() {
ConnectorServerFactoryBean csfb = new ConnectorServerFactoryBean {)
c s fb . setS erv iceU rl(
"Service:jm x:rm i: / /lo c a lh o st/jn d i/rm i: //lo c a lh o s t:1099/s p it t e r " ) ;
return csfb ;
i
En este caso, lo vinculamos a un registro RMI que escucha en el puerto 1099 del host
local. Eso quiere decir que también vamos a necesitar un registro RMI ejecutándose y a la
escucha en ese puerto.
Como ya hemos visto con anterioridad, R m i S e r v i c e E x p o r t e r puede iniciar
un registro RMI de forma automática. Sin embargo, en ese caso, no vamos a utilizar
R m i S e r v i c e E x p o r t e r , por lo que tendremos que iniciar un registro RMI declarando un
R m i R e g i s t r y F a c t o r y B e a n en Spring con el siguiente método @Bean:

@Bean
public RmiRegistryFactoryBean rmiRegistryFB() {
RmiRegistryFactoryBean rmiRegistryFB = new RmiRegistryFactoryBean();
rm iR egistryFB.setPort(1099);
return rmiRegistryFB;
i
Y eso es todo. Ahora, nuestros MBean están disponibles a través de RMI. Sin embargo,
no tiene sentido hacerlo si nadie va a acceder al MBean a través de RMI. Centramos nuestra
atención en el lado del cliente del acceso remoto JMX para ver cómo conectar un MBean
remoto en el contexto Spring de un cliente JMX.

Acceder a MBean remotos


El acceso a un servidor remoto de MBean implica la configuración de M B e a n S e r v e r
C o n n e c t i o n F a c t o r y B e a n en el contexto de Spring. La siguiente declaración de bean
configura un M B e a n S e r v e r C o n n e c t i o n F a c t o r y B e a n que puede utilizarse para acceder
al servidor remoto basado en RMI creado en la sección anterior:
@Bean
public MBeanServerConnectionFactoryBean ConnectionFactoryBean() {
MBeanServerConnectionFactoryBean mbscfb =
new MBeanServerConnectionFactoryBean();
mbscfb. setS erv iceU rl(
"S e rv ice: jmx:rmi: //lo c a lh o s t/jn d i/rm i: //lo c a lh o s t: 1 0 9 9 /s p itte r ") ;
return mbscfb;
572 Capítulo 20

Como su propio nombre indica, M B e a n S e r v e r C o n n e c t i o n F a c t o r y B e a n es un


bean de fábrica que crea un M B e a n S e r v e r C o n n e c t i o n que actúa como proxy local
para el servidor remoto de MBean. Este puede conectarse a una propiedad de bean como
M B ean S erv erC on n ection :
@Bean
public JmxClient jmxClient(MBeanServerConnection connection) {
JraxClient jmxClient = new JmxClient () ;
jmxClient.setMbeanServerConnection(connection);
return jmxClient;
}
M B e a n S e r v e r C o n n e c t i o n proporciona varios métodos que nos permiten ejecutar una
consulta sobre el servidor MBean remoto e invocar métodos de los MBean almacenados.
Por ejemplo, digamos que queremos saber el número de MBean registrados en el servidor
MBean remoto.
El siguiente fragmento de código muestra esta información:
int mbeanCount = mbeanServerConnection.getMBeanCount0 ;
System.out.println("There are " + mbeanCount + " MBeans")?

También podemos ejecutar una consulta sobre el servidor remoto para obtener los
nombres de todos los MBean utilizando el método queryNam es ():
java.útil.Set mbeanNames = mbeanServerConnection.queryNames(nuil, nuil);

Los dos parámetros proporcionados a queryNames () se utilizan para refinar los resul­
tados. Si proporcionamos n u i l a ambos parámetros, estamos solicitando los nombres de
todos los MBean registrados.
Las consultas al servidor remoto de MBean para realizar recuentos y nombres de
MBean son divertidas pero no demasiado útiles. El verdadero valor del acceso remoto a
un servidor de MBean radica en el acceso a los atributos y a las operaciones de invocación
de los MBean registrados en éste.
Para acceder a atributos de MBean, vamos a utilizar los métodos g e t A t t r i b u t e () y
s e t A t t r i b u t e ( ) . Por ejemplo, para recuperar el valor de un atributo MBean, se invoca
el método g e t A t t r i b u t e ():
String cronExpression = mbeanServerConnection.getAttribute(
new ObjectName("spitter:name-SpittleController"), "spittlesPerPage");

De forma similar, para cambiar el valor de un atributo MBean, podemos utilizar el


método s e t A t t r i b u t e ():
mbeanServerConnection.setAttribute
new ObjectName("spitter:name=SpittleController"),
new Attribute("spittlesPerPage", 10) );

Si quiere invocar una operación de MBean, el método in v o k e () es lo que necesita.


El siguiente ejemplo muestra cómo invocar el método s e t S p i t t l e s P e r P a g e () en el
MBean S p i t t l e C o n t r o l l e r :
Gestionar bean de Spring con JMX 573

mbeanServerConnection.invoke(
new ObjectName("sp itter:n am e=Sp ittleC o n tro ller") , "setSp ittlesP erP a g e",
new O bject[] { 100 },
new Strin g [] { " i n t " } ) ;

Asimismo, puede hacer muchas cosas más con los MBean remotos utilizando los métodos
disponibles mediante M B e a n S e r v e r C o n n e c t i o n . No dude en explorar las distintas
opciones. Sin embargo, invocar métodos y configurar atributos en MBean remotos es algo
raro cuando se realiza a través de M B e a n S e r v e r C o n n e c t i o n . Para algo tan sencillo
como la invocación del método s e t S p i t t l e s P e r P a g e () hay que crear una instancia
Obj ectN am e y proporcionar varios parámetros más al método i n v o k e (). Este enfoque
no es tan intuitivo como el de la invocación normal de un método, y para poder contar
con un método más directo, vamos a tener que utilizar un proxy con el MBean remoto.

Derivar MBean mediante proxy


M B e a n P r o x y F a c t o r y B e a n de Spring es unbean de fábrica de proxy similar a los que
hemos visto con anterioridad. Sin embargo, en lugar de proporcionar acceso mediante proxy
a bean remotos gestionados por Spring, le permite acceder directamente a los mismos
(como si fueran bean configurados localmente). La figura 20.4 ilustra su funcionamiento.

Figura 20.4. MBeanProxyFactoryBean genera un proxy a un MBean remoto. El cliente del proxy
puede interactuar con el MBean remoto como si fuera un POJO configurado localmente.

Por ejemplo, fíjese en la siguiente declaración de M B e a n P r o x y F a c t o r y B e a n :


@Bean
public MBeanProxyFactoryBean remoteSpittleControllerMBean(
MBeanServerConnection mbeanServerClient) {
MBeanProxyFactoryBean proxy = new MBeanProxyFactoryBean( );
proxy. setObj ectName( " " ) ;
proxy.. setServer (mbeanServerClient) ;
proxy.setProxylnterface(SpittleControllerM anagedOperations. c l a s s ) ;
return proxy;
}
574 Capítulo 20

La propiedad o b j ectN am e especifica el nombre de objeto del MBean remoto que


se va a derivar localmente mediante proxy. En este caso, hace referencia al MBean
S p i t t l e C o n t r o l l e r que hemos exportado con anterioridad.
La propiedad s e r v e r hace referencia a u n M B e a n S e r v e r C o n n e c t io n a través del cual
se redirige toda la comunicación con el MBean. En este caso, la hemos conectado al elemento
M B e a n S e r v e r C o n n e c t i o n F a c t o r y B e a n configurado con anterioridad. Por último, la
propiedad p r o x y l n t e r f a c e especifica la interfaz que va a implementar el proxy. En este
caso, vamos a utilizar la misma interfaz S p i t t l e C o n t r o l l e r M a n a g e d O p e r a t i o n s defi­
nida en un apartado anterior. Tras declarar el bean r e m o t e S p i t t l e C o n t r o l l e r M B e a n ,
podemos conectarlo a cualquier propiedad de bean cuyo tipo sea S p i t t l e C o n t r o l l e r
M a n a g e d O p e r a t io n s y utilizarlo para acceder al MBean remoto. Tras ello, podremos
invocar los métodos s e t S p i t t l e s P e r P a g e () y g e t S p i t t l e s P e r P a g e ( ) .
Ya hemos visto diferentes formas de comunicarnos con los MBean y ahora podemos
ver y ajustar nuestra configuración de bean de Spring mientras se ejecuta la aplicación. Sin
embargo, hasta el momento ha sido una conversación en un solo sentido. Hemos hablado
con los MBean pero estos no nos han respondido en ningún momento. Es hora de que
oigamos lo que tienen que decirnos a través de sus notificaciones.

Controlar notificaciones
Consultar un MBean para obtener información es tan solo una de las formas de conocer
el estado de una aplicación. Sin embargo, no es el método más eficiente para estar informado
sobre los eventos de importancia dentro de ésta.
Por ejemplo, supongamos que la aplicación Spittr tuviera que contar el número de spittle
que se han publicado y queremos saber cuándo se incrementa esta cantidad en un millón
de spittle (es decir, al millón, a los dos millones, a los tres, etc.). Una forma de obtener esta
información sería crear un código que ejecutara una consulta de forma periódica sobre
la base de datos, realizando un recuento del número de spittle. Sin embargo, este proceso
mantendría a la base de datos ocupada, ya que comprobaría constantemente el número
total de elementos.
En lugar de ejecutar consultas de forma repetida, un mejor enfoque sería que un MBean
nos notificase el momento en que se alcanzase el millón de spittle. Las notificaciones JMX
(véase la figura 20.5), son la forma en la que los MBean pueden comunicarse con el mundo
exterior, en lugar de esperar a que una aplicación externa les solicite información.
La compatibilidad de Spring con el envío de notificaciones se consigue a través de la
interfaz N o t i f i c a t i o n P u b l i s h e r A w a r e . Cualquier bean convertido en MBean que
desee enviar notificaciones tiene que implementarla. Veamos, por ejemplo, el caso de
S p i t t l e N o t i f i e r l m p l en el siguiente código.

Listado 20.2. Uso de NotificationPublisher para enviar notificaciones JMX.


package com.haburaa. s p itt e r . jmx;
import javax.management.Notification;
import org .sp rin gframework.jmx.export.annotation.M anagedNotification;
Gestionar bean de Spring con JMX 575

import org. springframework. jmx. export. annotation. ManagedResource;


import org. springframework. jmx. export. n o tific a tio n .N o tificatio n P u b lish er; ^
import org. springframework. jmx. export. n o tific a tio n .N o tificationPublisherAware^
import org. springframework. stereotyp e. Component ; f /

(»Component
@ManagedResource("sp itter:n am e=Sp itterN o tifier")
@ManagedNotification(
n o tificatio n T y p es="S p ittleN o tifier.O n eM illio n S p ittles",
name="TODO")
public cla ss Sp ittleN o tifierlm p l
implements NotificationPublisherAware, S p ittle N o tifie r {
/ / Implementar NotificationPublisherAware.

private N otificationP ublisher n o tifica tio n P u b lish er;

public void setN o tificatio n P u b lish er( / / inyectar publicador de n o tifie a e io n s s .


N otificationP ublisher n o tifica tio n P u b lish e r){
t h i s .n o tificatio n P u b lish er=n o tificatio n P u b lish er;
}
public void m illio n th Sp ittleP o sted (){
n o tif icatio n P u b lish er. sendNotif ic a tio n ( / / Enviar n o tifica c ió n ,
new N o tific a tio n (
"S p ittleN o tifier.O n eM illio n S p ittles" , t h i s ,0 ) ) ;
}
}

Figura 20.5. Las notificaciones JMX permiten que los MBean se comuniquen con el mundo
exterior.

Como puede ver, S p i t t l e N o t i f i e r l m p l implementa N o t i f i c a t i o n P u b l i s h e r


Aware. No es una interfaz muy exigente y solo requiere que se implemente el método
se tN o tific a tio n P u b lish e r.
S p i t t l e N o t i f i e r l m p l también implementa un único método de la interfaz
S p i t t l e N o t i f i e r , m i l l i o n t h S p i t t l e P o s t e d (). Este método utiliza N o t i f i c a t i o n
P u b l i s h e r , que se inyecta de forma automática a través del método s e t N o t i f i c a t i o n
P u b l i s h e r () para enviar una notificación cada vez que se publique un millón de spittle
más.
576 Capítulo 20

Tras invocar el método sendNot i f i c a t i ó n () , la notificación está camino a... Vaya, aún
no hemos decidido quién la va a recibir. Para ello, necesitamos configurar un escuchador
de notificaciones que la reciba y reaccione a su presencia.

Escuchar notificaciones
La forma estándar de recibir notificaciones MBean es im plem entar la interfaz
j a v a x . m a n a g e m e n t . N o t i f i c a t i o n L i s t e n e r . Por ejemplo, considere P a g i n g
N o tific a tio n L iste n e r:

package com.habuma. s p i t t e r . jmx;


import ja v a x .management.Notification;
import j avax.management.NotificationListener;
public cla ss PagingN otificationListener
implements N o tificatio n L isten er {
public void hand leN otification(
N o tificatio n n o tific a tio n , Object handback) {
// . . .

}
}
P a g i n g N o t i f i c a t i o n L i s t e n e r es un escuchador de notificaciones JMX típico.
Cuando se recibe una notificación, se invoca su método h a n d l e N o t i f i c a t i o n () para
reaccionar a la notificación. Presumiblemente, el método h a n d l e N o t i f i c a t i o n () de
P ag in g N ot i f i c a t i o n L i s t e n e r va a enviar un mensaje a un teléfono móvil para notificar
la publicación de otro millón de spittle (dejo la implementación real a imaginación del lector).
Lo irnico que queda es registrar P a g i n g N o t i f i c a t i o n L i s t e n e r con M b ean E xporter:
@Bean
public MBeanExporter mbeanExporter() {
MBeanExporter exporter = new MBeanExporter( );
Map<?, N o tificatio n L isten er> mappings =
new HashMap<?, N o tificatio n L isten e r>0 ;
mappings.put( "Spitter:nam e=PagingN otificationListener",
new PagingN otificationL istener( ) ) ;
exporter.setNotificationListenerM appings(m appings);
return exporter;

La propiedad n o t i f i c a t i o n L i s t e n e r M a p p i n g s de M B e a n E x p o r t e r se utiliza
para asignar escuchadores de notificaciones a los MBean pendientes. En este caso, hemos
configurado P a g i n g N o t i f i c a t i o n L i s t e n e r para que detecte cualquier notificación
publicada por el MBean S p i t t l e N o t i f i e r .

Resumen
Con JMX puede abrir una ventana al funcionamiento interno de su aplicación. En este
capítulo hemos visto cómo configurar Spring para que exporte de forma automática bean
de Spring como MBean de JMX, y así sus elementos puedan examinarse y editarse mediante
Gestionar bean de Spring con JMX 577

herramientas de gestión JMX. También hemos visto cómo crear y utilizar MBean remotos en
aquellas ocasiones en las que los MBean y las herramientas están lejos los unos de las otras.
Por último, hemos aprendido a utilizar Spring para publicar y recibir notificaciones JMX.
Llegados a este punto, se habrá dado cuenta de que quedan pocas páginas para terminar
este libro. Nuestro viaje a través de Spring casi ha terminado pero, antes de finalizar, quedan
un par de paradas rápidas. En el siguiente capítulo presentaremos Spring Boot, una atractiva
forma de crear aplicaciones de Spring sin apenas configuración explícita.
Capítulo

21 desarrollo de S p rin g
con S p rin g B o o t

CONCEPTOS FUNDAMENTALES:

• Inclusión de dependencias de proyectos con iniciadores


Spring Boot.
• Configuración automática de bean.
• Groovy e ILC de Spring Boot.
• Spring Boot Actuator.
Recuerdo la primera vez que en clase de Matemáticas aprendimos a calcular derivadas.
Teníamos que realizar complicados cálculos con límites para llegar a las derivadas de una
función. Aunque las funciones fueran sencillas, el trabajo necesario para calcular las deri­
vadas era una auténtica pesadilla.
Tras un montón de deberes, trabajos en grupo y un examen, toda la clase consiguió sacarlo
adelante, pero el aburrimiento era insoportable. Si era lo primero que habíamos aprendido
en esa clase de matemáticas, ¿qué monstruosidades nos esperaban en el curso siguiente?
Entonces el profesor nos dio una pista. Si aplicábamos una sencilla fórmula, las deri­
vadas se calculaban más rápidamente (si tiene conocimientos de matemáticas sabrá a lo
que me refiero). Gracias a este truco pudimos calcular derivadas de decenas de funciones
y tardábamos lo mismo que antes para calcular una sola.
Uno de mis compañeros dijo lo que todos estábamos pensado: "¿Por qué no nos lo
enseñó el primer día?".
El profesor contestó que al aprender de la forma complicada seríamos capaces de apre­
ciar el verdadero sentido de las derivadas y que nos ayudaría a forjar nuestra personalidad
(o algo parecido).
Después de ver todo un libro completo sobre Spring, me encuentro en la misma posición
que mi profesor de matemáticas. Aunque la principal ventaja de Spring es que facilita el
desarrollo con Java, en este capítulo le enseñaré cómo Spring Boot lo facilita todavía más.
Spring Boot es sin duda lo más apasionante que ha pasado en Spring desde su creación.
Añade un nuevo modelo de desarrollo a Spring y elimina el tedio asociado al desarrollo
de aplicaciones con este marco de trabajo.
Comenzaremos con un repaso a los trucos empleados por Spring Boot para simplificar
Spring. Antes de finalizar el capítulo habremos desarrollado una aplicación completa (y
muy sencilla) con Spring Boot.

introducción a Spring Boot_____________________


Spring Boot es un apasionante proyecto nuevo de la familia Spring. Ofrece cuatro carac­
terísticas principales que cambiarán la forma de desarrollar sus aplicaciones de Spring:•

• Iniciadores de Spring Boot: Añaden agrupaciones de dependencias comunes a


una única dependencia que se puede añadir a la generación Maven o Gradle de un
proyecto.
• Configuración automática: La función de configuración de Spring Boot recurre a
la compatibilidad de Spring 4 con la configuración condicional para determinar los
bean que su aplicación necesita y configurarlos de forma automática.
• CLI (C o m m a n d -lin e in t e r fa c e , Interfaz de línea de comandos o ILC): La ILC de
Spring Boot recurre al lenguaje de programación Groovy y, junto a la configuración
automática, simplifica todavía más el desarrollo de aplicaciones Spring.
• Agente: Spring Boot Actuator añade determinadas funciones de administración a
una aplicación de Spring Boot.
580 Capítulo 21

A lo largo del capítulo crearemos una sencilla aplicación con todas estas características,
pero antes las describiremos brevemente para que se haga una idea de su contribución al
modelo de programación de Spring.

Añadir dependencias de inicio ___________ _______


Hay dos formas de hacer una tarta. El repostero ambicioso mezclará harina, huevos,
azúcar, levadura, sal, mantequilla, vainilla y leche. O puede comprar un paquete de mezcla
para tartas que incluye la mayoría de los ingredientes y después añadir los que falten, como
agua, huevos o aceite.
Estas mezclas añaden muchos de los ingredientes de una receta de tarta a un único
ingrediente, lo mismo que hace Spring Boot: añadir las distintas dependencias de una
aplicación a una única dependencia.
Fara ú’ustrarío, imagine que frene que e m p e z a r d e c e r o u n n u e v e p r o y e c t e d e S p rin g .
Será un proyecto Web, por lo que necesitará Spring MVC. También habrá un API REST que
expone recursos como JSON, por lo que necesitará la biblioteca JSON Jackson en su proyecto.
Como la aplicación va a utilizar JDBC para almacenar y recuperar datos de una base de
datos relacional, tendrá que incluir el módulo JDBC de Spring (para Jd b c T e m p la te ) y el
de transacciones (para admitir transacciones declarativas). En cuanto a la base de datos,
nos servirá la de H2. Y también necesitará Thymeleaf para las vistas. Si genera su proyecto
con Gradle, necesitará como mínimo las siguientes dependencias en b u i l d . g r a d le :
dependencies {
compile("org.springframework:spring-web:4. 0 .6 .RELEASE")
compile{ "org.springframework:spring-webmvc: 4 .0 . 6 .RELEASE")
compile{ "com .fasterxm l. jack so n .co re: jackson-databind:2 .2 .2 " )
compile ( "o rg . springf ramework: spring-jdbc -.4.0.6. RELEASE" )
compile( "org.springfram ew ork:spring-tx:4 . 0 .6 .RELEASE")
compile{ "com.h2database:h2:1 .3 .1 7 4 ")
compile("org.thym eleaf: thym eleaf-spring4: 2 .1 . 2 .RELEASE")
}
Afortunadamente, Gradle permite expresar dependencias de forma m u y sucinta (no le
mostraré el aspecto de esta lista de dependencias en un archivo pom. xml de Maven), pero
aun así, resulta muy costoso crear esta lista y todavía más mantenerla. ¿Cómo sabemos si
estas dependencias funcionan correctamente? Cuando la aplicación evolucione, la admi­
nistración de dependencias será mucho más complicada.
Pero si utiliza las dependencias predefinidas de los iniciadores de Spring Boot, la lista
de dependencias de Gradle se puede reducir:
dependencies {
compile("org.springframework.boot: sp ring-boot-starter-w eb:
1 .1 .4 . RELEASE")
compile("org.springframework.boot: sp rin g -b o o t-sta rte r-jd b c:
1 .1 .4 . RELEASE")
compile( "com.h2database:h2:1 .3 .1 7 4 ")
compile("o rg . thymeleaf: thym eleaf-spring4: 2 .1 . 2 . RELEASE")
Simplificar el desarrollo de Spring con Spring Boot 581

Como puede apreciar, los iniciadores Web y JDBC de Spring Boot sustituyen varias
dependencias. Tendrá que seguir incluyendo las de H2 y Thymeleaf, pero el resto se incluye
en las dependencias de inicio. Una vez reducida la lista de dependencias, tendrá la seguridad
de que las versiones de las proporcionadas por los iniciadores son compatibles entre sí.
Los iniciadores Web y JDBC son solo algunos de los que ofrece Spring Boot. En la tabla
21.1 se enumeran todos los disponibles al cierre de esta edición.

Tabla 21.1. Las dependencias de inicio de Spring Boot agrupan dependencias habitualmente
necesarias para un proyecto.

Iniciador Proporciona
........................................ .................................... .................. ................. ..... ..J
spring-boot-starter-actuator spring-boot-starter, spring-boot -
actuator, spring-core.
spring-boot-starter-amqp spring-boot-starter, spring-boot -
rabbit, spring-core, spring-tx.
spring-boot-starter-aop spring-boot-starter, spring-aop, tiempo
de ejecución AspectJ, AspectJ Weaver, spring-
core.
spring-boot-starter-batch spring-boot-starter, HSQLDB, spring-
jdbc,spring-batch-core, spring-core.
spring-boot-starter-elasticsearch spring-boot -starter, spring-data-
elasticsearch, spring-core, spring-tx.
spring-boot-starter-gemfire spring-boot-starter, Gemfire, spring-
core, spring-tx, spring-context, spring
context-support, spring-datagemf ire.
spring-boot-starter-data-jpa spring-boot -starter, spring-boot
starter-jdbc, spring-boot-starter
aop, spring-core, EntityManager de
Hibernate, spring-orm, spring-data-jpa,
spring-aspects.
spring-boot-starter-data-mongodb spring-boot -starter, controlador Java
MongoDB, spring-core, spring-tx, spring-
datamongodb.
spring-boot-starter-data-rest sp rin g -b o o t -starter , spring-
bootstarter-web, anotaciones Jackson,
vinculación de datos Jackson, spring-core,
spring-tx, spring-data-rest-webmvc.
spring-boot-starter-data-solr spring-boot-starter, Solrj, spring-core,
spring-tx, spring-data-solr, Mime HTTP
Apache.
spring-boot-starter-freemarker sp rin g -b oo t -start er , spring-
bootstarter-web, Freemarker, spring-core,
spring-context-support.
582 Capítulo 21

...... -....
r Iniciador Proporciona i

spring-boot-starter-groovy-templates spring-boot -starter, spring-boot


starter-web, Groovy, Plantillas Groovy
spring-core.
spring-boot-starter-hornetq spring-boot-starter, spring-core,
spring-jms, cliente JMS Hornet.
spring-boot-starter-integration spring-boot - starter, spring-aop,
spring-tx, spring-web, spring-webmvc,
spring-integration-core, spring-
integration-file, spring-integra
tion-http, spring-integration-ip,
spring-integration-stream.
spring-boot-starter-j dbc spring-boot-starter, spring-j dbc,
tomcat-jdbc, spring-tx.
spring-boot-starter-jetty jetty-webapp, jetty-jsp.
spring-boot-starter-log4j jcl-over-slf4j, jul-to-slf4j, slf4j-
log4j12,log4j.
spring-boot-starter-logging jcl-over-slf4j,jul-to-slf4j, log4j-
over-slf4j, logback-classic.
spring-boot-starter-mobile spring-boot-starter, spring-boot-
starter-web, spring-mobile-device.
spring-boot-starter-redis spring-boot-starter, spring-data-
redis, lettuce.
spring-boot-starter-remote-shell spring-boot-starter-actuator,
spring-context, or g .crashub.**.
spring-boot-starter-security s p r i n g - b o o t - s tarter, s p r i n g -
security-config, spring-security-
web, s p r ing-aop, s p r i n g - b e a n s ,
spring-context,spring-core, spring-
expression, spring-web.
spring-boot-starter-social-facebook spring-boot-starter, spring-boot -
starter-web, spring-core, spring-
social -config, spring-social-core,
spring-social-web, spring-social-
facebook.
spring-boot-starter-social-twitter spring-boot-starter, spring-boot-
starter-web, spring-core, spring-
social -config, spring-social-core,
spring-social-web, spring-social-
twitter.
spring-boot-starter-social-linkedin spring-boot-starter, spring-boot-
starter-web, spring-core, spring-
social -config, spring-social-core,
Simplificar el desarrollo de Spring con Spring Boot 583

spring-social-web, spring-social-
linkedin.
spring-boot-starter spring-boot, spring-boot-autoconfi
gure, spring-boot-starter-logging.
spring-boot-starter-test spring-boot-starter-logging, spring-
boot, junit, mockito-core, hamcrest-
library, spring-test.
spring-boot-starter-thymeleaf spring-boot-starter, spring-boot-
starter-web,spring-core,thymeleaf-
spring4, thymeleaf-layout-dialect.
spring-boot-starter-tomcat tomcat-embed-core, tomcat-embed-
logging-juli.
spring-boot-starter-web spring-boot-starter, spring-boot-
star ter- tomcat, jackson-databind,
spring-web, spring-webmvc.
spring-boot-starter-websocket spring-boot-starter-web, spring-
websocket, tomcat-embed-core,
tomcat-embed-logging-juli.
spring-boot-starter-ws spring-boot -starter, spring-boot -
starter-web,spring-core, spring-jms,
spring-oxm, spring-ws-core, spring-
ws-support.

Si analiza estas dependencias de inicio entre bastidores, comprobará que no hay dema­
siado misterio en su funcionamiento. Gracias a la resolución de dependencias transitivas
de M aveny Gradle, los iniciadores declaran diversas dependencias en sus propios archivos
pom. xml. Al añadir una de estas dependencias de inicio a su proyecto Maven o Gradle, se
resuelve de forma transitiva, y estas dependencias pueden tener sus propias dependencias.
Un mismo iniciador puede absorber transitivamente decenas de otras dependencias.
Comprobará que muchos de estos iniciadores hacen referencia a otros. Por ejemplo,
el iniciador m o b ile hace referencia al iniciador web que, por su parte, hace referencia al
iniciador Tom cat, y muchos de ellos hacen referencia a s p r in g - b o o t s t a r t e r que, en
esencia, es un iniciador base (aunque haga referencia al iniciador lo g g in g ). Las depen­
dencias se aplican de forma transitiva; al añadir el iniciador m o b ile como dependencia
se añaden todas las dependencias de todos los iniciadores dependientes.

Configuración automática
Mientras que los iniciadores de Spring Boot reducen el tamaño de la lista de depen­
dencias de su proyecto, la configuración automática de Spring Boot reduce la cantidad de
configuración de Spring. Para ello, tiene en cuenta otros factores de su aplicación y deduce
584 Capítulo 21

qué configuración de Spring necesita. Como ejemplo, recordará que en el listado 6.4 nece­
sitábamos al menos tres bean para habilitar plantillas de Thymeleaf como vistas en Spring
MVC: T h y m e le a f V i ewRe s o l v e r , S p r in g T e m p la te E n g in e y T e m p la te R e s o lv e r . Sin
embargo, con la configuración automática de Spring Boot solo tiene que añadir Thymeleaf
a la ruta de clases del proyecto. Cuando Spring Boot detecta la presencia de Thymeleaf
en la ruta de clases, asume que quiere usar Thymeleaf para las vistas de Spring MVC y
configura automáticamente esos tres bean.
Los iniciadores de Spring Boot pueden activar la configuración automática. Por ejemplo,
para usar Spring MVC en una aplicación de Spring Boot, basta con añadir el iniciador
web como dependencia al proyecto. Al hacerlo, aplica transitivamente las dependencias
de Spring MVC. Cuando la configuración Web automática de Spring Boot detecta Spring
MVC en la ruta de clases, configura automáticamente varios bean para admitir Spring MVC,
incluyendo solucionadores de vistas, controladores de recursos y conversores de mensajes.
Solo tendrá que crear las clases de controlador necesarias para procesar las solicitudes.

La ILC de Spring Boot:


La ILC de Spring Boot recurre a la magia proporcionada por los iniciadores y la configu­
ración automática de Spring Boot y la adereza con Groovy. Reduce el proceso de desarrollo
de Spring hasta un punto en el que puede ejecutar una o varias secuencias de comandos
de Groovy a través de la ILC y ver cómo se ejecutan. Durante la ejecución de la aplicación,
la ILC también importa de forma automática tipos de Spring y resuelve dependencias.
Uno de los ejemplos más interesantes para ilustrar la ILC de Spring Boot se incluye en
la siguiente secuencia de comandos de Groovy:
@RestController
cla ss Hi {
@RequestMapping("/")
Strin g h i () {
"Hi! "
}
}
Aunque no se lo crea, es una aplicación de Spring completa (pero muy sencilla) que se
puede ejecutar a través de la ILC de Spring Boot. Incluyendo espacios en blanco, son un total
de 82 caracteres. Puede pegar el código en su cliente de Twitter y enviárselo a sus amigos.
Si eliminamos el espacio en blanco innecesario, conseguimos una línea de 64 caracteres:
@RestController cla ss Hi{@RequestMapping( " / " ) Strin g h i( ) { " H i!" } }

Es una versión tan corta que la puede pegar dos veces en un tuit, pero sigue siendo una
aplicación de Spring completa y operativa (aunque sin apenas funciones). Si ha instalado
la ILC de Spring Boot, la puede ejecutar con la siguiente línea de comandos:
$ spring run Hi. groovy

Aunque sea divertido presumir con este ejemplo, las prestaciones de la ILC de Spring
Boot son mucho más completas. En un apartado posterior aprenderemos a crear una apli­
cación más avanzada con Groovy y la ILC.
Simplificar el desarrollo de Spring con Spring Boot 585

El Agente
Spring Boot Actuator, el Agente, dota de interesantes características a un proyecto de
Spring Boot, como las enumeradas a continuación.

• Puntos finales de administración.


• Procesamiento sensible de errores y una asignación predeterminada para un punto
final / e r r o r .
• Un punto final / i n f o que puede comunicar información sobre una aplicación.
• Una estructura de eventos de auditoría para Spring Security.
Todas estas características son muy útiles, pero los puntos finales de administración son
una de las más interesantes de Actuator. En un apartado posterior veremos varios ejemplos
de cómo Actuator de Spring Boot nos permite adentramos en el funcionamiento interno
de nuestra aplicación.
Después de presentar las cuatro funciones principales de Spring Boot, las pondremos
en práctica en una sencilla pero completa aplicación.

Crear una aplicación con Spring Boot


En lo que resta del capítulo mi objetivo es mostrarle cómo crear completas aplicaciones
reales con Spring Boot. Evidentemente, las cualidades que definen rma aplicación "real" son
subjetivas y seguramente superen el espacio y los objetivos propuestos en este capítulo.
Por lo tanto, en lugar de crear una aplicación del mundo real, desarrollaremos otra "menos
real" pero que represente el tipo de aplicaciones que puede llegar a crear con Spring Boot.
En nuestro caso será una sencilla aplicación de lista de contactos. Permitirá al usuario
introducir información de contacto (nombre, teléfono, dirección de correo electrónico) y
mostrará una lista de todos los contactos que el usuario haya introducido previamente.
Puede crear su aplicación con Maven o Gradle. En mi caso prefiero Gradle, pero también
le mostraré lo que necesita si opta por Maven. El siguiente código reproduce el archivo
de inicio b u i l d . g r a d le . En un principio el bloque de dependencias está vacío, pero lo
completaremos con dependencias a lo largo del proceso.

Listado 21.1. Archivo Gradle para la aplicación Contacts.


b u ild scrip t {
re p o sito ries {
mavenLocal()
}
dependencies {
cla ssp a th ("org.springframework.boot: spring-boot-grad le-plu gin:1 . 1 .4 .RELEASE")
}
}
apply plugin: 'java' // Usar el complemento Spring Boot.
586 Capítulo 21

apply plugin: 'sp ring-boot'

ja r { / / Generar un archivo JAR.


baseName = 'co n ta cts'
version = '0 .1 .0 '
}
rep o sito ries {
mavenCentral()
}
dependencies { / / Añadir aquí la s dependencias.
}
task wrapper(type: Wrapper) {
gradleVersion = '1 .8 '
}

Comprobará que se incluye una dependencia b u i l d s c r i p t en el complemento Spring


Boot Gradle. Como veremos después, le permite generar un archivo JAR ejecutable que
contiene todas las dependencias de la aplicación.
Si prefiere usar Maven, el siguiente código reproduce el archivo pom. xml completo.

Listado 21.2. Archivo Maven para la aplicación Contacts.


<?xml v e rsio n = "l.0" encoding="UTF-8"?>
<project xmlns="h ttp : //maven. apache. org/POM/4 .0 .0 "
xm lns:xsi="http://www.w3.org/2001/XMLSchema-instance"
x s i : schemaLocation="h ttp : //maven. apache. org/POM/4 .0 .0
http://maven.apache.org/xsd/maven-4. 0 .0 .xsd">

<modelVersion>4. 0 . 0</modelVersion>
<groupId>com.habuma</groupId>
< a rtifa c tId > c o n ta c ts< /a rtifa c tId >
<version>0. 1 . 0</version>
<packaging>jar</packaging> / / Generar un archivo JAR.

<parent>
<groupId>org. springframework.boot</groupId> / / Heredar del in iciad o r prin cip al
/ / de Spring Boot.
< a rtifa ctId > sp rin g -b o o t-sta rte r-p a re n t< /a rtifa ctId >
<v ersio n >l. 1 .4 . RELEASE</version>
</parent>

<dependencies> / / Añadir aquí la s dependencias.


</dependencies>

<build>
<plugins>
<plugin> / / Usar e l complemento Spring Boot.
<groupId>org.springframework.boot</groupId>
<artifactId >spring-boot-m aven-p lu gin</artifactId >
</plugin>
</plugins>
</build>

< /p ro ject>
Simplificar el desarrollo de Spring con Spring Boot 587

Al igual que sucede con la versión de Gradle, este archivo pom. xml de Maven usa el
complemento Spring Boot Maven, similar al de Gradle y que también genera un archivo
JAR ejecutable.
Pero al contrario de lo que sucede con la versión de Gradle, la de Maven tiene un proyecto
principal. Al basar la versión Maven de su proyecto en el proyecto principal de Spring
Boot, disfruta de la administración de dependencias de Maven y no tiene que declarar
explícitamente números de versión para muchas de las dependencias de su proyecto, ya
que se heredan del principal.
De acuerdo a la estructura estándar de proyectos para proyectos basados en Maven y
Gradle, cuando termine su proyecto tendrá la siguiente estructura:
$ tree

bu ild.gradle
pom.xml
src
1— main
|— java
1--- contacts
|— A pplication.java
|— Contact. j ava
| ContactController. j ava
|— ContactRepository. java
- resources
|--- schema. sql
|— s t a t ic
| 1— s ty le .c s s
1--- templates
1— home.html

No se preocupe por los archivos de Java y de otros recursos que faltan; los crearemos
en los siguientes apartados. De hecho, empezaremos directamente a desarrollar el nivel
Web de la aplicación.

Procesar solicitudes
Como vamos a desarrollar el nivel Web de la aplicación con Spring MVC, tendrá que
añadir Spring MYC como dependencia a su proyecto. Como ya hemos mencionado, el
iniciador web de Spring Boot es la principal forma de añadir a un proyecto todo lo que
necesita para Spring MVC.
La dependencia de Gradle que necesita es la siguiente:
compile("org. springframework.boot: spring-boot-starter-w eb")

Si utiliza Maven, necesitará las siguientes dependencias:


<dependency>
<groupId>org. springframework.boot</groupId>
<artifactId >sp rin g -b o o t-starte r-w e b </artifa ctId >
< /dependency>
588 Capítulo 21

Como el proyecto principal de Spring Boot especifica la versión de la dependencia del


iniciador web, no es necesario especificarla explícitamente en los archivos b u i l d . g r a d le
o pom. xml del proyecto.
Tras añadir la dependencia del iniciador web, estarán disponibles todas las demás que
necesita para trabajar con Spring MVC. Ya puede crear una clase de controlador para la
aplicación.
El controlador será m uy sencillo: presentará un formulario de contacto para una solicitud
GET de HTTP y procesará el envío del formulario para una solicitud POST. No se encargará
de realizar personalm ente el trabajo, sino que delegará en C o n t a c t R e p o s i t o r y (que
crearemos en breve) para la persistencia de contactos. La clase C o n t a c t C o n t r o l l e r del
siguiente listado captura estos requisitos.

Listado 21.3. ContactController procesa solicitudes Web básicas para la aplicación Contacts.
package co n tacts;
iraport ja v a .ú t i l .L i s t ;
iraport j ava.útil.M ap;
import org.springframework.beans. fa c to ry .annotation.Autowired;
iraport org. springframework. stereotyp e. C on tro ller;
import org.springframework.web.bind.annotation.RequestMapping;
import org. springframework. web.bind.annotation. RequestMethod;

©Controller
@RequestMapping("/")
public cla ss ContactController {
private ContactRepository contactRepo;

@Autowired
public ContactController(ContactRepository contactRepo) { / / Inyectar ContactRepository.
t h i s . contactRepo = contactRepo;
}
@RequestMapping(method=RequestMethod.GET) / / Procesar GET/,
public String home(Map<String,Object> model) {
List<Contact> contacts = contactRepo.findA ll();
model.put("c o n ta cts", co n ta c ts);
return "home";
}
@RequestMapping(method=RequestMethod.POST) / / Procesar POST/,
public Strin g submit(Contact contact) {
contactR epo.save(contact);
3C& 1 U 2TZ2 " 2 T & & J. 3C & C f t .- / "

}
}

Lo primero que debe saber sobre C o n t a c t C o n t r o l l e r es que es un controlador típico


de Spring MVC. Aunque Spring Boot participe en la administración de las dependencias
y minimice la configuración de Spring, en lo que respecta a crear la lógica de la aplicación,
el modelo de programación es el mismo.
Simplificar el desarrollo de Spring con Spring Boot 589

En este caso, C o n t a c t C o n t r o l l e r se ajusta al patrón típico de un controlador de


Spring MVC que muestra y procesa el envío de formularios. El método home () usa el
repositorio C o n t a c t R e p o s i t o r y inyectado para recuperar una lista de todos los objetos
C o n t a c t y los añade al modelo antes de transferir la solicitud de la vista home. Esta vista
representará la lista de contactos junto con un formulario para añadir otros nuevos. El
método s u b m it () procesa la solicitud POST resultante del envío del formulario, guarda
el objeto C o n t a c t y redirige a la página de inicio.
Y como C o n t a c t C o n t r o ll e r está anotado con @ C o n tr o lle r , está sujeto al análisis
de componentes. Por ello, no tendrá que configurarlo explícitamente como bean en el
contexto de la aplicación de Spring.
En cuanto al tipo de modelo de C o n ta c t, es un sencillo POJO con varias propiedades
y métodos de acceso, como se reproduce en el siguiente código.

Listado 21.4. Contact es un sencillo tipo de dominio.

package co n tacts;

public cla ss Contact {


private Long id ; / / Propiedades.
private String firstName;
private String lastName;
private String phoneNumber;
private String emailAddress;

public void setId(Long id) { / / Métodos de acceso.


th i s .i d = id ;
}
public Long g etld () {
return id ;
}
public void setFirstNam e(String firstName) {
t h i s . firstName = firstName;
}
public String getFirstName() {
return firstName;
}
public void setLastName(String lastName) {
t h i s . lastName = lastName;
}
public String getLastName() {
return lastName;
}
public void setPhoneNumber(String phoneNumber) {
.this.phoneNumber = phoneNumber;
}
public String getPhoneNuiriber () {
590 Capítulo 21

return phoneNumber;
}
public void setEmailAddress{String emailAddress) {
this.emailAddress = emailAddress;
}
public String getEmailAddress() {
return emailAddress;
}
}
El nivel Web de la aplicación está prácticamente terminado. Solo falta crear una plantilla
Thymeleaf que defina la vista home.

Crear la vista
Tradicionalmente las aplicaciones Web de Java usan JSP como tecnología para vistas,
pero como vimos en un capítulo anterior, hay un nuevo forastero en la ciudad. Resulta
mucho más agradable trabajar con las plantillas naturales de Thymeleaf que con JSP, y le
permiten crear las plantillas en HTML. Por ello, usaremos Thymeleaf para definir la vista
home de la aplicación Contacts. En primer lugar debe añadir Thymeleaf a su proyecto. En
este ejemplo trabajamos con Spring 4, por lo que se añade el módulo Thymeleaf de Spring
4 al proyecto. En Gradle, la dependencia tendría este aspecto:
compile("org.thymeleaf:thymeleaf-spring4")

Si usa Maven, necesita esta otra dependencia:


<dependency>
<groupId>org.thymeleaf</groupId>
<artifactld>thymeleaf~spring4</artifactld>
</dependency>

Recuerde que al añadir Thymeleaf a la ruta de clases del proyecto se activa la confi­
guración automática de Spring Boot. Al ejecutar la aplicación, Spring Boot detectará que
Thymeleaf se encuentra en la ruta de clases y configurará automáticamente los bean de
solucionador de vistas, de plantillas y de motor de plantillas necesarios para usar Thymeleaf
con Spring MVC. Por lo tanto, no necesita configuración explícita de Spring para utilizar
Thymeleaf en su aplicación. Además de añadir la dependencia Thymeleaf al proyecto,
tendrá que definir la plantilla de vista. En el siguiente código se muestra home. h tm l, una
plantilla Thymeleaf que define la vista home.

Listado 21.5. La vista home representa un formulario para crear nuevos contactos
y para enumerarlos en una lista.

<!DOCTYPE html>
chtml xmlns:th="http://www.thymeleaf.org">
<head>
<title>Spring Boot Contacts</title>
Simplificar el desarrollo de Spring con Spring Boot 591

<link re l= "sty le sh e e t" th :h r e f= "@ {/s ty le . c s s } " /> / / Cargar hoja de e s t ilo .
</head>
<body>
<h2>Spring Boot Contacts</h2>
<form method="POST"> / / Nuevo formulario de contacto,
clab el for="firstN am e">First Ñame: </lab el>
cinput type="text" name="firstName"></inputxbr/>
clab el for="lastName">Last Ñame: </la b el>
cinput type="text" name=,,lastName,,>c/input>cbr/>
clab el for="phoneNumber">Phone # :c /la b e l>
cinput type="text" name=,'phoneNumber">c/input>cbr/>
clab el for="emailAddress">Email: c/la b e l>
cinput type="text" name="emailAddress">c/input>cbr/>
cinput type="submit">c/input>
c/form>
cul th:each="contact : $ {c o n ta c ts}"> / / Representar l i s t a de contactos.
< li>
cspan th :te x t= " $ {co n tact. firstName}" >Firstc/span>
cspan th : text="$ {co n ta c t. lastName}11>Lastc/span> .*
cspan th :te x t= " $(contact.phoneNumber} " >phoneNumberc/span>,
cspan th :te x t= "$ {c o n ta c t. emailAddress}" >emailAddressc/span>
c /li>
c/u l>
c/body>
c/html>

Es una plantilla Thymeleaf muy básica, que se divide en dos partes: un formulario y
una lista de contactos. El formulario publica datos (POST) en el método s u b m it () de
C o n t a c t C o n t r o l l e r para crear un nuevo objeto C o n t a c t . La lista itera por la lista de
objetos C o n t a c t del modelo. Para poder usar este ejemplo, debe prestar especial atención
al nombre y la ubicación correcta dentro del proyecto. Como el nombre lógico de vista que
devuelve el método home () de C o n t a c t C o n t r o l l e r es home, el nombre del archivo de
plantilla debe ser home . h tm l, y como el solucionador de plantillas configurado automá­
ticamente buscará plantillas Thymeleaf bajo el directorio t e m p l a t e s relativo a la raíz de
la ruta de clases, tendrá que incluir h om e. h tm l en su proyecto Maven o Gradle en s r c /
m a i n / r e s o u r c e s / t e m p l a t e s . Solamente queda un cabo suelto relacionado con esta
plantilla Thymeleaf. El código HTML que genera hace referencia a la hoja de estilo s t y l e .
c s s , que tendrá que añadir al proyecto.

Añadir elementos estáticos


Por lo general, suele evitar analizar hojas de estilo e imágenes en el contexto de la crea­
ción de aplicaciones Spring. Sin duda son elementos que aumentan el atractivo estético de
cualquier aplicación (incluidas las de Spring) para el usuario, pero los elementos estáticos
no son esenciales para el análisis de la creación de código de Spring del lado del servidor.
Sin embargo, en el caso de Spring Boot conviene mencionar cómo procesa el contenido está­
tico. Cuando la configuración Web automática de Spring Boot se aplica a bean para Spring
MVC, estos incluyen un controlador de recursos que asigna / * * a varias ubicaciones de
recursos, entre las que destacan las siguientes (son relativas a la raíz de la ruta de clases):
592 Capítulo 21

• /M E T A -IN F /re so u rc e s/
• /re so u rc e s /
• /s ta tic /
• /p u b lic /

En una aplicación convencional creada con M aven/Gradle, el contenido estático se


incluiría en src/m a in /w e b a p p para añadirse a la raíz del archivo WAR generado por
el proyecto. Al crear un archivo WAR con Spring Boot también dispone de esta opción,
pero además cuenta con la posibilidad de incluir contenido estático en una de las cuatro
ubicaciones asignadas al controlador de recursos.
Así pues, para satisfacer la referencia de la plantilla Thymeleaf a / s t y l e . c s s , tendrá
que crear el archivo s t y l e . c s s en una de las siguientes ubicaciones:

• / M E T A - I N F / r e s o u r c e s / s t y le .c s s
• /r e s o u r c e s /s ty le . css
• / s t a t i c / s t y le . css
• /p u b lic /s ty le . css

Sera decisión suya. En mi caso suelo incluir el contenido estático en / p u b l i c , pero las
demás opciones son igualmente válidas.
Aunque el contenido de s t y l e . c s s no sea relevante para nuestro análisis, la siguiente
hoja de estilo imprime a la aplicación un aspecto más limpio:
body {
background-color: #eeeeee;
font-fam ily: s a n s -s e rif;
}
la b el {
display: in lin e -b lo ck ;
width: 120px;
te x t-a lig n : rig h t;
}

Aunque no lo crea, hemos desarrollado la mitad de la aplicación Contacts. El nivel


Web ya está terminado y ahora crearemos el repositorio C o n t a c t R e p o s i t o r y para la
persistencia de los objetos C o n ta c t .

Persistencia de d atos
Para trabajar con bases de datos en Spring dispone de multitud de opciones. Puede usar
JPA o Hibernate para asignar objetos a tablas y columnas de una base de datos relacional, o
recurrir a otro tipo de base de datos diferente como Mongo o Neo4j. Para nuestra aplicación
Contacts, una base de datos relacional es una opción adecuada. Usaremos la base de datos
H2 y JDBC (con J d b c T e m p la t e de Spring) para facilitar el proceso.
Simplificar el desarrollo de Spring con Spring Boot 593

Estas opciones nos llevan a la necesidad de añadir varias dependencias al proyecto.


La dependencia del iniciador se encarga de obtener todo lo necesario para trabajar con
Jd b c T e m p la t e de Spring, pero también tendrá que añadir la dependencia H2 para usar
la base de datos H2. En Gradle, las dos siguientes líneas del bloque de dependencias se
encargan de ello:
compile("org.springframework.boot: sp rin g -b o o t-sta rte r-jd b c")
compile(ncom.h2database:h2")

Para proyectos Maven, necesitará estos dos bloques <dependency>:


<dependency>
<groupId>org. springframework.boot</groupId>
< a rtifa ctId > sp rin g -b o o t-sta rte r-jd b c< / a r tifa c tld >
</dependency>
<dependency>
<groupId>com.h2database</groupId>
< a rtifa ctId > h 2 < /a rtifa ctld >
</dependency>

Con estas dos dependencias ya puede crear su clase de repositorio: C o n ta ctR ep o s i t o r y .


En el siguiente código, recurre a una J d b c T e m p la t e inyectada para leer y escribir objetos
C o n t a c t de la base de datos.

Listado 21.6. ContactRepository guarda y recupera objetos Contact de la base de datos.

package contacts;

import ja v a .ú t i l.L is t ;
import ja v a .s q l.R e s u ltS e t;
import j ava. s q l. SQLExcept io n ;
import org.springframework.beans. fa cto ry . annotation.Autowired;
import org. springframework. jd b c. co re.JdbcTemplate;
import org. springf ramework. j dbc. co re. RowMapper;
import org. springframework. stereotype. Repository?

@Repository
public cla ss ContactRepository {
private JdbcTemplate jdbc;

@Autowired
public ContactRepository(JdbcTemplate jdbc) { / / Inyectar JdbcTemplate.
th is .jd b c = jd bc;
}
public List<Contact> findAllO { / / Consulta para contactos,
return jdbc.query(
"s e le c t id, firstName, lastName, phoneNumber, emailAddress " +
"from contacts order by lastName",
new RowMapper<Contact>() {
public Contact mapRow(ResultSet r s , in t rowNum)
throws SQLException {
Contact contact = new Contact( );
co n ta ct. setId (rs.g etL o n g (1 ));
594 Capítulo 21

co n ta ct. setFirstN am e(rs.getStrin g (2 ));


co n ta ct. setLastN am e(rs.getString(3 ));
co n ta ct. setPhoneNumber(rs.getString(4 ));
co n ta ct. setEm ailA ddress(rs.getString( 5 ));
return contact;
}
}>;
}
public void save(Contact contact) {
jdbc.update( / / Añadir un contacto.
"in se rt in to contacts " +
" (firstName, lastName, phoneNumber, emailAddress) " +
"values (?, ?, ?, ?) ",
co n ta ct. getFirstName(), co n ta ct. getLastName(),
contact.getPhoneNumber(), contact.getEmailAddress( ) ) ;
}
}

Como sucede con C o n t a c t C o n t r o l l e r , esta clase de repositorio es m uy sencilla. No


es diferente a la de una aplicación tradicional de Spring y en su im plem entación no hay
nada que sugiera que se trata de una aplicación de Spring Boot. El método f i n d A ll ()
usa la plantilla J d b c T e m p la t e inyectada para recuperar objetos C o n t a c t de la base de
datos. El m étodo s a v e () usa Jd b c T e m p la te para guardar los nuevos objetos C o n ta c t , y
como C o n t a c t R e p o s i t o r y se anota c o n @ R e p o s ito r y , se seleccionará automáticamente
durante el análisis de com ponentes y se creará como bean en el contexto de la aplicación
de Spring.
¿Y qué sucede con Jd b c T e m p la te ? ¿No tenemos que declarar un bean Jd b c T e m p la te
en el contexto de la aplicación de Spring? Es más, ¿no tenemos que declarar un bean
D a t a S o u r c e H2?
En ambos casos, no. Cuando Spring Boot detecta que el módulo JDBC y H2 de Spring
se encuentran en la ruta de clases, se activa la configuración automática y se configura un
bean J d b c T e m p la t e y otro D a t a S o u r c e para H2. Como antes, Spring Boot se encarga
de la configuración de Spring.
¿Y qué sucede con el esquema de base de datos? Evidentemente tenemos que definirlo
para crear la tabla de contactos, ¿no? Así es; Spring Boot no puede adivinar cuáles son los
contactos, por lo que tendrá que definir un esquema como el siguiente:
create ta b le contacts (
id id e n tity ,
firstName varchar(30) not n u il,
lastName varchar(50) not n u il,
phoneNumber varchar(13),
emailAddress varchar(30)
);

Ahora necesita una forma de cargar este código SQL c r e a t e t a b l e y ejecutarlo sobre
la base de datos H2. Afortunadamente Spring Boot también se encarga de ello. Si asigna el
nombre sch em a. s q l a este archivo y lo ubica en la raíz de la ruta de clases (es decir, en
s r c / m a in / r e s o u r c e s ) , se detecta y se carga al iniciar la aplicación.
Simplificar el desarrollo de Spring con Spring Boot 595

Probar la aplicación
La aplicación Contacts es muy sencilla pero se puede considerar una aplicación de
Spring bastante realista. Cuenta con un nivel Web definido por un controlador Spring MVC
y una plantilla Thymeleaf, y tiene un nivel de persistencia definido por un repositorio y
Jd b c T e m p la t e de Spring.
Llegados a este punto ya hemos creado todo el código necesario para la aplicación
Contacts. Sin embargo, no hemos creado ningún tipo de configuración de Spring, ni hemos
configurado D i s p a t c h e r S e r v l e t en un archivo w eb . xm l o una clase de inicialización
de servlet. ¿Me creería si le dijera que no tiene que escribir configuración alguna?
No puede ser cierto. Después de todo, como dicen los defensores de Spring, Spring se
centra en la configuración. Seguramente haya un archivo XML o una clase de configuración
de Java que hemos pasado por alto. No se puede crear una aplicación de Spring sin confi­
guración... ¿o sí? En la mayoría de los casos, la configuración automática de Spring Boot
elimina gran parte de la configuración necesaria. Por lo tanto, se puede crear una aplicación
completa de Spring sin escribir una sola línea de código de configuración. Evidentemente,
la configuración automática no abarca todos los casos, por lo que una aplicación típica de
Spring Boot seguirá incluyendo cierta cantidad de configuración.
En la aplicación Contacts en concreto, no necesitamos configuración alguna. La confi­
guración automática de Spring se encarga de todo.
No obstante, necesitamos una clase especial que inicialice la aplicación. Por si solo,
Spring no tiene ni idea de la configuración automática,. La clase A p p l i c a t i o n del siguiente
código es un ejemplo típico de clase de inicialización de Spring Boot.

Listado 21.7. Una sencilla clase de inicialización para iniciar la configuración automática
de Spring Boot.

package contacts;
import org. springframework.boot. autoconfigure. EnableAutoConfiguration;
import org. springframework.boot. SpringApplication;
import org. springframework. co n tex t. annotation. ComponentSean;

@ComponentScan
@EnableAutoConfiguration / / H ab ilitar configuración automática,
public cla ss Application {
public s t a t ic void m ain(String[] args) {
SpringA pplication.run(A pplication.class, a r g s ) ;/ / E jecu tar la ap licación .
}
i
De acuerdo, tengo que admitir que A p p l i c a t i o n tiene algo de configuración. Se
anota con @ C om p on en tScan para habilitar el análisis de componentes, y se anota con
@ E n a b le A u to C o n f i g u r a t i o n , que activa la función de configuración automática de
Spring Boot, pero eso es todo.
Aparte de estas dos líneas, no hay más configuración en la aplicación Contacts. Lo
especialmente interesante de A p p l i c a t i o n es su método m ain () . Como veremos en
breve, las aplicaciones Spring Boot se pueden ejecutar de una forma especial, debido al
596 Capítulo 21

método main (). En este método hay una línea que indica a Spring Boot (a través de la clase
SpringApplication) que se ejecute con la configuración de la propia clase Application
y con los argumentos proporcionados desde la línea de comandos.
Prácticamente ya podemos ejecutar la aplicación, solo falta generarla. Si utiliza Gradle, la
siguiente línea de comandos genera el proyecto en bu i l d / 1 ib s / c o n t a c t s - 0 . 1 . 0 . j a r :

$ gradle build

Si usa Maven, genere el proyecto de esta forma:


$ mvn package

Tras ejecutar el proyecto Maven, encontrará la aplicación en la carpeta t a r g e t . Ya


puede ejecutarla. Para ello, tradicionalmente había que implementar el archivo WAR de la
aplicación en un contenedor de servlet como Tomcat o WebSphere, pero ni siquiera tenemos
un archivo WAR, sino uno de tipo JAR.
Sin problema. Puede ejecutarlo desde la línea de comandos de esta forma (hace referencia
al archivo JAR generado por Gradle):
$ java -jar build/libs/contacts-0.1.0.jar

Tras unos segundos, se abrirá la aplicación lista para utilizarse. Dirija su navegador a
h t t p : / / l o c a l h o s t : 8080 y empiece a añadir contactos (véase la figura 21.1).

........................................ ............... —---------------------


____ ¡______ I__________________ És___ i___ S__i_________________
locai¡iost:S 080 _____________________________________ P - -»
- jp - j- ^ ' ___ _ _............................. ...................

Spring Boot Contacts


First Nampr
Last Ñame:
Phone #:
Email:
[~~Enviar consulta j

• Jack Diamond: 312-123-4984, [email protected]


• Shelby Mayer: 310-873-4394, [email protected]
| _ • Percivel Peabody: 415-555-1200, [email protected]

Figura 21.1. La aplicación Contacts de Spring Boot.

Seguramente esté pensando que no es la forma correcta de ejecutar una aplicación Web.
Resulta muy cómodo poder ejecutarla desde la línea de comandos, pero en la vida real no
se hace así. En su trabajo, las aplicaciones Web se implementan como archivos WAR en un
contenedor Web. La política de desarrollo de su empresa no permitirá que no se entregue
un archivo WAR. De acuerdo, está bien.
Simplificar el desarrollo de Spring con Spring Boot 597

Aunque la ejecución de la aplicación desde la línea de comandos sea una opción válida,
incluso para aplicaciones de producción, entiendo que tenga que seguir las normas de su
empresa y que seguramente tenga que generar e implementar archivos WAR.
Por suerte, no tendrá que descartar la simplicidad de Spring Boot si le obligan a usar
un archivo WAR. Lo único que necesita es un pequeño cambio. En el proyecto de Gradle,
añada la siguiente línea para aplicar el complemento war:
apply plugin: 'war'

También tendrá que cambiar la configuración j a r por war, que básicamente consiste
en reemplazar una j por una w:
war {
baseName = 'co n ta cts'
versión = ' 0. 1 . 0'
}
En el proyecto Maven es todavía más fácil. Basta con cambiar el paquete j a r por war:
<packaging>war< /packaging>

Si vuelve a generar el proyecto, encontrará el archivo c o n t a c t s - 0 . l . 0 . war en el direc­


torio, un archivo WAR que puede implementar en cualquier contenedor Web compatible
con Servlet 3.0. Es más, puede seguir ejecutando la aplicación desde la línea de comandos
con lo siguiente:
$ java - ja r b u ild /lib s /c o n ta c ts -0 . 1 .0 . war

Así es: ¡un archivo WAR ejecutable! ¡Lo mejor de ambos mundos!
Como puede apreciar, Spring Boot facilita considerablemente el desarrollo de aplica­
ciones Spring. Los iniciadores de Spring Boot simplifican las dependencias de un proyecto y
la configuración automática elimina la necesidad de una configuración de Spring más explí­
cita, pero como veremos a continuación, si además añade Groovy, es todavía más sencillo.

Groovy y ia ILC de Spring Boot____________ _____


Groovy es un lenguaje de programación mucho más sencillo que Java. Su sintaxis permite
el uso de abreviaturas, como por ejemplo la exclusión de los puntos y coma, y la palabra
clave p u b lic . Además, las propiedades de una clase de Groovy no requieren métodos de
establecimiento ni recuperación como en Java, sin mencionar las demás características de
Groovy que eliminan gran parte de la dificultad del código de Java. Si está dispuesto a crear
el código de su aplicación en Groovy y a ejecutarlo a través de la ILC de Spring Boot, Spring
Boot se aprovechará de la simplicidad de Groovy para simplificar todavía más el desarrollo
con Spring. Para demostrarlo, volveremos a crear la aplicación Contacts en Groovy.
¿Y por qué no? La versión original de la aplicación apenas tiene clases de Java, por lo que
no habría mucho que cambiar en Groovy. Podemos reutilizar la misma plantilla Thymeleaf
y el mismo archivo schem a. s q l, y si mis afirmaciones sobre Groovy son ciertas, el proceso
no será tan complicado.
598 Capítulo 21

Además, nos desharemos de varios archivos. La ILC de Spring Boot es su propio inicia-
lizador, por lo que no necesitaremos la clase A p p l i c a t i o n que creamos antes. También
podemos desprendernos de los archivos generados por Maven y Gradle, ya que ejecuta­
remos archivos de Groovy sin compilar desde la ILC, y sin Maven ni Gradle, se compactará
la estructura del proyecto. La nueva estructura tendrá este aspecto:
$ tree

1 Contact. groovy
| ContactController.groovy
| ContactRepository.groovy
| schema. sql
|--- s t a t ic
| 1---- s ty le .c s s
1--- templates
1--- home.html

Aunque los archivos s c h e m a .s q l, s t y l e . c s s y h om e.htm l no cambian, tendrá


que convertir las tres clases de Java a Groovy. Comenzaremos por el nivel Web en Groovy.

Crear un controlador de Groovy


Como hemos mencionado antes, Groovy es un lenguaje con menos parafernalia que
Java, lo que significa que puede crear código de Groovy sin elementos como los siguientes:

• Puntos y coma.
• Modificadores como p u b lic y p r i v a t e .
• Métodos de establecimiento y recuperación de propiedades.
• La palabra clave r e t u r n para devolver valores de métodos.

Con ayuda de la sintaxis flexible de Groovy (y de la magia de Spring Boot), podemos


reescribir la clase C o n t a c t C o n t r o l l e r en Groovy, como se muestra a continuación.

Listado 21.8. ContactController es más sencilla en Groovy que en Java.

@Grab("thym eleaf-spring4") / / Obtener dependencia Thymeleaf.


(©Controller
@RequestMapping(" / " )
cla ss ContactController {

@Autowired
ContactRepository contactRepo / / Inyectar ContactRepository.

@RequestMapping(method=RequestMethod.GET) / / Procesar GET/.


Strin g home(Map<String,Object> model) {
List<Contact> contacts = contactRepo. fin d A ll()
model.putAll( [co n tacts: co n ta cts])
"home"
Simplificar el desarrollo de Spring con Spring Boot 599

}
@RequestMapping(method=RequestMethod.POST) / / Procesar POST/.
String submit{Contact contact) {
contactRepo. save(co n tact)
" r e d ir e c t:/ "
}
}
Como puede apreciar, esta versión de C o n t a c t C o n t r o l l e r es mucho más sencilla
que su homologa de Java. Al descartar todos los elementos que Groovy no necesita,
C o n t a c t C o n t r o l l e r es más breve y legible.
En el listado anterior falta algo más. Habrá comprobado que no incluye las líneas im port,
típicas de cualquier clase de Java. Groovy importa una serie de paquetes y clases de forma
predeterminada, incluidas las siguientes:

• j a v a . i o .*
• ja v a .la n g .*
• ja v a .m a t h .B i g D e c im a l
• j a v a . m a th . B i g l n t e g e r

• j a v a . n e t .*
• j a v a .ú t il.*
• g ro o v y . la n g .*
• g r o o v y .ú t i l .*

Gracias a estas importaciones predeterminadas, C o n t a c t C o n t r o l l e r no tiene que


importar la clase L i s t . Se encuentra en el paquete j a v a . ú t i l p a c k a g e , uno de los
importados de forma predeterminada.
¿Y qué sucede con los tipos de Spring como ( S C o n t r o l l e r , @ R e q u e stM a p p in g ,
@ A u to w ired y @ R eq u estM eth o d ? Al no estar entre las importaciones predeterminadas,
¿cómo podemos excluir su línea im p o rt?
Cuando después ejecutemos la aplicación, la ILC de Spring Boot intentará compilar
estas clases de Groovy con el compilador de Groovy, y como estos tipos no se han impor­
tado, se producirá un fallo. Pero la ILC no se rinde tan fácilmente y aquí es donde lleva la
configuración automática a un nuevo nivel. La ILC reconocerá que los fallos se deben a
la ausencia de tipos de Spring y realizará dos pasos para corregir el problema. En primer
lugar, obtendrá el iniciador de dependencias web de Spring Boot y todas sus dependencias
transitivamente y las añadirá a la ruta de clases (así es, descargará y añadirá los archivos
JAR a la ruta de clases). Tras ello, añadirá los paquetes necesarios a la lista de importaciones
predeterminadas del compilador de Groovy e intentará volver a compilar el código.
Como consecuencia de esta operación de dependencias/importaciones automáticas de
la ILC, la clase de controlador no necesita importaciones, y tampoco tendrá que resolver
las bibliotecas de Spring ni manualmente ni con Maven o Gradle. La ILC de Spring Boot
se encarga de todo.
600 Capítulo 21

Retrocedamos un paso para ver qué sucede. Al usar un tipo de Spring MVC como
© C o n t r o l l e r o @ R eq u estM a p p in g en el código, la ILC resuelve automáticamente el
iniciador web de Spring Boot. Como las dependencias de este iniciador también se añaden
transitivamente a la ruta de clases, se activa la configuración automática de Spring Boot y
se configuran los bean necesarios para admitir Spring MVC. Como antes, solo ha tenido
que usar los tipos. Spring Boot se ha encargado de todo lo demás.
Evidentemente, las prestaciones de la ILC son limitadas. Aunque sepa cómo resolver
muchas dependencias de Spring y añada automáticamente importaciones de muchos de
sus tipos (además de otras bibliotecas), no lo resuelve ni lo importa todo. La decisión de
usar plantillas Thymeleaf, por ejemplo, es opcional. Por ello, tendremos que solicitarla de
forma explícita en el código por medio de una anotación @Grab.
En muchas de las dependencias no es necesario especificar el ID de grupo ni el número
de versión. Spring Boot se conecta a la resolución de dependencias que realiza @Grab y se
encarga de completar el ID de grupo y la versión.
Además, al añadir la anotación @Grab y solicitar Thymeleaf, activamos la configuración
automática de los bean necesarios para admitir plantillas Thymeleaf en Spring MVC.
Aunque no tenga mucho que ver con Spring Boot, conviene mostrar la clase C o n t a c t
en Groovy para que el ejemplo resulte más completo:
cla ss Contact {
long id
String firstName
String lastName
String phoneNumber
String emailAddress
}
C o m o p u e d e apreciar, C o n ta c t es mucho más sencilla sin puntos y coma, métodos
de acceso y modificadores p u b l i c y p r í v a t e , y todo ello gracias a la sencilla sintaxis
de Groovy. Spring Boot no hace absolutamente nada para simplificar la clase C o n ta c t .
Veamos ahora cómo simplificar la clase de repositorio con la ILC de Spring Boot CLI
y Groovy.

Persistencia con un repositorio de Groovy


Todos los trucos de Groovy y la ILC de Spring Boot aplicados a C o n t a c t C o n t r o l l e r
también se pueden aplicar a C o n t a c t R e p o s i t o r y . El siguiente código muestra la nueva
versión C o n t a c t R e p o s i t o r y en Groovy.

Listado 21.9. En Groovy, ContactRepository es mucho más sucinto.

@Grab("h2") / / Obtener dependencia a la base de datos H2.


import ja v a .sq l.R e su ltS e t

cla ss ContactRepository {

@Autowired
JdbcTemplate jdbc / / Inyectar JdbcTemplate.
Simplificar el desarrollo de Spring con Spring Boot 601

List<Contact> findAHO { / / Consulta para contactos.


jd bc.query(
"s e le c t id, firstName, lastName, phoneNumber, emailAddress " +
"from contacts order by lastName",
new RowMapper<Contact>() {
Contact mapRow(ResultSet r s , in t rowNum) {
new C on tacted : rs .getLong (1) , firstName: rs .g etStrin g (2) ,
lastName: r s .g e tS trin g (3), phoneNumber: r s .g e tS tr in g (4),
emailAddress: r s .g e tS tr in g (5))
}
})

void save(Contact contact) { / / Guardar un contacto,


j dbc. update(
"in se rt in to contacts " +
" (firstName, lastName, phoneNumber, emailAddress) " +
"valúes (?, ?, ?, ?) ",
co n ta ct. firstName, co n ta ct. lastName,
contact.phoneNumber, co n ta ct. emailAddress)
}
}

Además de las evidentes m ejoras de la sintaxis de Groovy, esta nueva clase


C o n t a c t R e p o s i t o r y recurre a la función de importación automática de la ILC de
Spring Boot para importar J d b c T e m p la t e y RowMapper de forma automática. Además,
se resuelve automáticamente la dependencia de inicio JDBC cuando la ILC detecta que
usamos esos tipos. Solo hay dos aspectos con los que las tareas automáticas de importa­
ción y resolución de la ILC no nos pueden ayudar. Como comprobará, hemos tenido que
importar R e s u l t S e t , y como Spring Boot desconoce qué bases de datos queremos usar,
es necesario recurrir a @ G rab para solicitar la base de datos H2.
Hemos convertido todas las clases de Java a Groovy y hemos recurrido a la magia de
Spring Boot en el proceso. Ya podemos ejecutar la aplicación.

Ejecutar la ILC de Spring Boot


Tras compilar la aplicación de Java temamos dos opciones para ejecutarla. Podíamos
hacerlo como archivo JAR o WAR ejecutable desde la línea de comandos, o implementar
un archivo WAR en un contenedor de servlet. La ILC de Spring Boot nos ofrece una tercera
opción.
Como habrá supuesto, la ejecución de aplicaciones a través de la ILC es una forma de
ejecutar la aplicación desde la línea de comandos, pero con la ILC no es necesario generarlas
primero en un archivo JAR o WAR. Se puede ejecutar directamente la aplicación si se pasa
el código fuente de Groovy a través de la ILC.

in sta la r la IL C
Para poder usar la ILC de Spring Boot primero tendrá que instalarla. Para ello dispone
de varias opciones:
602 Capítulo 21

• GVM (G roovy E nvironm ent M anager, Administrador de entornos Groovy).


• Homebrew.
• Instalación manual.
Para instalar la ILC de Spring Boot con GVM, introduzca este comando:
$ gvm in s t a ll springboot

Si trabaja en OS X puede usar Homebrew:


$ brew tap p iv o ta l/tap
$ brew in s t a ll springboot

Si prefiere instalar Spring Boot manualmente, puede descargar las instrucciones


desde h t t p : / / d o c s . s p r i n g . i o / s p r i n g - b o o t / d o c s / c u r r e n t / r e f e r e n c e /
h tm ls in g le /.
Tras instalar la ILC, puede comprobar la instalación y la versión utilizada con la siguiente
línea de comandos:
$ spring --versió n

Si todo se instala correctamente, ya está listo para ejecutar la aplicación Contacts.

Ejecutar la aplicación Contacts con la ILC


Para ejecutar una aplicación con la ILC de Spring Boot, introduzca s p r in g ru n en la
línea de comandos, seguido de uno o varios archivos de Groovy para ejecutar a través de
la ILC. Por ejemplo, si su aplicación solo tiene una clase de Groovy, podría ejecutarla de
esta forma:
$ spring run Helio.groovy

Se ejecuta la clase de Groovy H e l i o . g ro o v y a través de la ILC. Si su aplicación tiene


varios archivos de clase de Groovy, puede ejecutarlos con comodines:
$ spring run * . groovy

Si los archivos de clase de Groovy se encuentran en uno o varios subdirectorios, puede


usar comodines de estilo Ant para buscar clases de Groovy de forma recursiva:
$ spring run * * / * . groovy

Como la aplicación Contacts tiene tres clases de Groovy que leer y como todas se
encuentran en la raíz del proyecto, le servirán las dos últimas opciones. Tras ejecutar la
aplicación, apunte su navegador a h t t p : / / l o c a l h o s t : 8 080 para ver básicamente la
misma aplicación Contacts que creamos antes. Llegados a este punto, hemos creado dos
veces una aplicación de Spring Boot: una en Java y otra en Groovy. En ambos casos, la magia
de Spring Boot nos ha permitido minimizar gran parte de la configuración predefinida y de
las dependencias necesarias, pero Spring Boot tiene un as en la manga. Veamos cómo usar
Spring Boot Actuator para añadir puntos finales de administración a una aplicación Web.
Simplificar el desarrollo de Spring con Spring Boot 603

Adentrarse en una aplicación con Actuator


La principal función de Spring Boot Actuator es añadir puntos finales de administración
a una aplicación basada en Spring Boot. Entre dichos puntos finales destacan los siguientes:

• GET / a u t o c o n f ig : Explica las decisiones adoptadas por Spring Boot al aplicar la


configuración automática.
• GET /b e a n s : Cataloga los bean configurados para la aplicación en ejecución.
• GET / c o n f ig p ro p s : Enumera todas las propiedades disponibles para configurar
las propiedades de los bean de la aplicación con sus valores actuales.
• GET /dump: Enumera subprocesos de aplicación, incluida una huella de pila para
cada uno.
• GET /en v : Enumera todas las variables de entorno y de propiedades del sistema
disponibles en el contexto de la aplicación.
• GET /e n v / { nom bre}: Muestra el valor de una variable de entorno o de propiedad
concreta.
• GET / h e a l t h : Muestra la salud actual de la aplicación.
• GET / i n f o : Muestra información específica de la aplicación.
• GET / m e t r i c s : Enumera datos relativos a la aplicación, como la cantidad de solici­
tudes en determinados puntos finales.
• GET / m e t r i c s / { nom bre}: Muestra datos sobre una clave de datos específica de la
aplicación.
• POST / shutdown: Fuerza el cierre de la aplicación.
• GET / t r a c e : Enumera m etadatos relacionados con solicitudes recientes entregadas
a través de la aplicación, con encabezados de solicitud y de respuesta.

Para habilitar el agente (Actuator), basta con añadir la dependencia de inicio a c t u a t o r


al proyecto. Si está creando su aplicación en Groovy y la ejecuta a través de la ILC de Spring,
puede añadir el iniciador a c t u a t o r con @Grab de esta forma:
@Grab("sp rin g -b o o t-sta rter-a ctu a to r")

Si está creando una aplicación de Java con Gradle, puede añadir la siguiente dependencia
al bloque de dependencias de b u i l d . g r a d le :
compile("org.springframework.boot: sp rin g -b o o t-sta rter-a ctu a to r")

O el siguiente elemento <dependency> al archivo pom. xml de M aven:


<dependency>
<groupId> org. springframework.boot</groupId>
<artifactId >sp ring-boot-actu ator</carlsbad >
</dependency>
604 Capítulo 21

Seguidamente, puede volver a generar e iniciar su aplicación, y apuntar el navegador a


cualquiera de los puntos finales de administración para obtener información adicional. Por
ejemplo, si quiere ver todos los bean del contexto de la aplicación Spring, puede solicitar
h t t p : / / l o c a l h o s t : 8 0 8 0 / b e a n s . Con la herramienta c u r l de línea de comandos, el
resultado será similar al siguiente (se ha cambiado el formato y se ha abreviado para que
resulte más legible):

$ curl h ttp ://lo c a lh o s t: 8080/beans


[
{
"beans": [
{
"bean": "co n tactC o n troller",
"dependencies": [
"contactRepository"
3,
"resource": "n u ll",
"scope": "sin gleto n ",
"type": "ContactController"

{
"bean": "contactRepository",
"dependencies": [
" j dbcTemplate"
3,
"resource": "n u ll",
"scope": "sin gleto n ",
"type": "ContactRepository"
b

{
"bean": "jdbcTemplate",
"dependenc i e s ": [],
"resource": "c la ss path resource
"scope": "sin g leto n ",
"ty p e": "org.springframework.jdbc.core.JdbcTemplate"
}.

Comprobará que hay un bean con el ID c o n t a c t C o n t r o l l e r que depende de otro


bean, c o n t a c t R e p o s i t o r y . Este, a su vez, depende del bean j d b c T e m p la te .
Como he abreviado el resultado, hay otros muchos bean que no se ven y que se verían
en el código JSON generado desde el punto final /b e a n s . De este modo puede hacerse
una idea del misterioso resultado generado por la conexión y la configuración automáticas.
Otro de los puntos finales que revela el funcionamiento de la configuración automática de
Spring Boot es / a u to c o n f ig . El código JSON que genera muestra las decisiones adoptadas
Simplificar el desarrollo de Spring con Spring Boot 605

por Spring Boot para la configuración automática de bean. Por ejemplo, el siguiente código
de JSON (abreviado y con cambios de formato) se obtiene del punto final / a u t o c o n f ig
de la aplicación Contacts:
$ cu rl h ttp : //lo c a lh o s t: 8080/autoconfig
{
"negativeMatches": {
"AopAutoConfiguration": [
{
"con d ition ": "OnClassCondition",
"message": "required @ConditionalOnClass cla sses not found:
o rg .a s p e c tj. lang.annotation.A spect,
o rg .a s p e c tj. lang. reflect.A d v ice"
}
i,
"BatchAutoConfiguration": [
{
"con d ition ": "OnClassCondition",
"message": "required @ConditionalOnClass cla sses not found:
org. springframework.b atch . co re. launch. JobLauncher"
}
],

},
"positiveM atches": {
"ThymeleafAutoConfiguration": [
{
"con d ition ": "OnClassCondition",
"message": "@ConditionalOnClass cla sses found:
org. thymeleaf. spring4. SpringTemplateEngine"
}
],
"ThymeleafAutoConfiguration.DefaultTemplateResolverConfiguration":[
{
"con d ition ": "OnBeanCondition",
"message": "@ConditionalOnMissingBean
(names: defaultTemplateResolver; SearchStrategy: a ll)
found no beans"
}
],
"ThymeleafAutoConfiguration.ThymeleafDefaultConfiguration": [
{
"con d ition ": "OnBeanCondition",
"message": "@ConditionalOnMissingBean (types:
org.thym eleaf. spring4. SpringTemplateEngine;
SearchStrategy: a ll) found no beans"
}
],
"ThymeleafAutoConfiguration.ThymeleafViewResolverConfiguration": [
{
"con d ition ": "OnClassCondition",
"message": "@ConditionalOnClass cla sses found:
j avax. s e r v le t. S e rv le t"
}
606 Capítulo 21

],
"ThymeleafAutoConfiguration.ThymeleafViewResolverConfiguration
#thymeleafViewResolver": [
{
"con d ition ": "OnBeanCondition",
"message": "@ConditionalOniyiissingBean (names:
thymeleafViewResolver; SearchStrategy: a ll)
found no beans"
}
],

}
}
Como puede apreciar, el informe tiene dos secciones: una para coincidencias negativas
y otra para las positivas. Las negativas indican que no se aplicó configuración automática
de AOP y Spring Batch porque no se encontraron las clases necesarias en la ruta de clases.
En la sección de coincidencias positivas, comprobará que, como resultado de detectar
S p rin g T e m p la te E n g in e en la ruta de clases, se aplica la configuración automática de
Thymeleaf. También puede que ver que los bean predeterminados de solucionador de
plantillas, de vistas y de motor de plantillas se configuran automáticamente a menos que
los haya configurado de forma explícita por su cuenta. Es más, el bean de solucionador
de vistas predeterminado solo se configura de forma automática si se encuentra la clase
S e r v l e t en la ruta de clases.
Los puntos finales /b e a n s y / a u t o c o n f i g son dos ejemplos del tipo de información
que nos ofrece Actuator de Spring Boot. En este capítulo no tenemos espacio suficiente
para detallarlos todos pero le recomiendo que los investigue personalmente para ver qué
pueden aportar a sus aplicaciones.

Resumen
Spring Boot es una apasionante novedad de la familia de proyectos Spring. Mientras
que el objetivo de Spring es simplificar el desarrollo con Java, el de Spring Boot es simpli­
ficar Spring.
Spring Boot utiliza dos trucos para eliminar la configuración predefinida de un proyecto
de Spring: iniciadores y configuración automática.
Un único iniciador de dependencias de Spring Boot puede sustituir varias dependencias
comunes de un proyecto de Maven o Gradle. Por ejemplo, con tan solo añadir el iniciador
web de Spring Boot como dependencia a un proyecto, se añaden los módulos Web y Spring
MVC de Spring, además del módulo de vinculación de datos Jackson 2.
La configuración automática recurre a la configuración condicional de Spring 4.0 para
configurar de forma automática determinados bean de Spring para habilitar características
concretas. Por ejemplo, Spring Boot puede detectar que Thymeleaf se encuentra en la ruta
de clases de la aplicación y configurar automáticamente los bean necesarios para habilitar
plantillas Thymeleaf como vistas de Spring MVC.
Simplificar el desarrollo de Spring con Spring Boot 607

La interfaz de línea de comandos (ILC) de Spring Boot simplifica todavía más los
proyectos de Spring con Groovy. Con una simple referencia a un componente de Spring
en el código de Groovy, puede hacer que la ILC añada automáticamente las dependencias
de inicio necesarias (que, a su vez, pueden desencadenar la configuración automática). Es
más, muchos tipos de Spring no requieren instrucciones im p o rt explícitas en código de
Groovy ejecutado a través de la ILC de Spring Boot.
Por último, Spring Boot Actuator añade funciones comunes de administración a una
aplicación Web desarrollada con Spring Boot y permite acceder a los volcados de procesa­
miento, al historial de solicitudes Web o a los bean del contexto de la aplicación.
Tras leer este capítulo, seguramente se haya preguntado por qué he dejado algo tan útil
como Spring Boot para el final. Incluso puede que piense que si lo hubiera descrito antes,
mucho de lo que ha aprendido hubiera sido más fácil. Es cierto que Spring Boot aporta un
modelo de programación muy atractivo a Spring y que una vez lo utilizas, es complicado
imaginarse la creación de aplicaciones de Spring sin Spring Boot.
Podría decirle que al guardarme Spring Boot para el final intentaba que apreciara Spring
en toda su grandeza (y que forjara su personalidad o algo así). Aunque pueda ser cierto, la
realidad es que ya había escrito la mayor parte del libro cuando apareció Spring Boot, por
lo que para no modificar nada tuve que meterlo con calzador: al final del libro.
Quién sabe, puede que la próxima edición empiece con la descripción de Spring Boot.
A Activar
la característica para recordar información, 300
Abanico, 512 perfiles, 96
Acceder a activemq start, 495
la información de autenticación, 302 ActiveMQCorLnectionFactory, 495
MBean remotos, 571 Actualizar, 182,242,454
MongoDB con MongoTemplate, 369 actuator, 603
Servicios Adentrarse en una aplicación con Actuator, 603
Hessian/Burlap, 441 ADMIN, 282
mediante HTTP, 444 admin, 282
Acceso Advanced Message Queuing Protocol, 24,489
a datos mediante plantillas, 315 Aerosmith, 121
de datos e integración, 49 Afrontar la falta de compatibilidad con WebSocket, 528
remoto After, 353
a MBean, 570 ConnectionClosed, 525
en Spring, 429 ConnectionEstablished, 525
access, 304-305 afterPropertiesSet, 46
AccessDeniedException, 417,420 afterReturning, 154
Aceptar Agente, 579
Entradas alert, 520
a través de parámetros de ruta, 178 AlertService, 509, 511
de solicitud, 175 AlertServicelmpl, 501-502,509-510,516
parámetros de consulta, 176 all, 273
Acerca de REST, 453 all-entries, 413
Aconsejar controladores, 241 AlllgnoresCase, 354
action, 182, 296 AllIgnoringCase, 354
Indice alfabético 609

Almacenamiento Auditable, 128


condicional en caché, 406 Ausencia de bloques catch, 315
en caché con Ehcache, 399 Autenticar
Almacenar los datos del cliente, 267 sobre tablas de base de datos, 283
Alrededor, 127 usuarios, 297
Ámbitos de Authentication Exception, 417
bean, 107 Autorización
datos de flujo, 258 posterior de acceso a métodos, 420
amq, 496 previa de acceso a métodos, 419
Análisis de componentes, 60
and, 134,152
annotatedClasses, 340
B
Anotaciones alrededor del consejo, 139 base, 211-213
Anotar base-package, 377
bean para su conexión automática, 66 base-path, 250
entidades gráficas, 378 basename, 205
introducciones, 143 Beans, 514,563
métodos para almacenamiento en caché, 403 alertService, 510
tipos de modelo para persistencia en MongoDB, 366 assembler, 567
any, 273 Audience, 148
Añadir BlankDisc, 83-88,111,117,151
archivos adjuntos, 552 BraveKnight, 34
datos con JdbcTemplate, 329 CacheManager, 400
dependencias de inicio, 580 CDConfig, 89
elementos estáticos, 591 CDPlayer, 66-68, 70-72, 74-76
métodos circle, 119
de consulta, 384 ClassLoaderTemplateResolver, 559
de consulta personalizados, 372 CompactDisc, 61, 63, 70-72, 74
recursos con PUT, 481 CompositeCacheManager, 402
servlet y filtros adicionales, 224 Concert, 138
una página de inicio de sesión personalizada, 297 connectionFactory, 500, 516
valores a la caché, 405 ContentNegotiationManager, 460
aop, 40,110-111, 409 criticismEngine, 155
AOP en funcionamiento, 38 DataSource, 92, 94, 594
API Java Persistence, 337,342 Dessert, 103,105
Aplicar Discography, 80
aspectos, 36 DriverManagerDataSource, 321
autenticación basada en LDAP, 285 EntityManagerFactory, 347
app.properties, 112 GraphDatabaseService, 375-376
appServlet, 227 hessianSpitterService, 440
artist, 84,114,118,121 Hiberna teSpitterRepository, 341
Asignar homeController, 563
excepciones a códigos de estado HTTP, 237 HttpInvokerServiceExporter, 443
nombres a bean de análisis de componentes, 64 IceCream, 103-107
Aspectos básicos de REST, 453 JdbcTemplate, 594
assembler, 567 JedisConnectionFactory, 388
Atrás, 242 mailSender, 551
attributes, 273 MappingJacksonMessageConverter, 504
Audience, 135-137,149-150
610 índice alfabético

MBeanExporter, 568 Buscar el cliente, 265


Minstrel, 40 By, 352, 372
MongoClient, 362 byte, 234
MongoTemplate, 362-364,369
Neo4jTemplate, 381
Notepad, 108 c
Performance, 145
cache, 398,409,412
PersistenceAnnotationBeanPostProcessor, 349
Cake, 102-103
PersistenceExceptionTranslationPostProcessor,342
Calificar bean conectados automáticamente, 103
pizzaFlow Actions, 265-267
Cancel, 257,264,267-268,270
Property SourcesPlaceholderConfigurer, 115
end-state, 272
PropertyPlaceholderConfigurer, 115
Carga
PropertySourcesPlaceholder Configurer, 115
activa, 337
RedisTemplate, 390,401
diferida, 337
remoteSpittleControllerMBean, 574
cart, 391-392
RmiServiceExporter, 438
catch, 313, 315, 433,435, 505
sgt Peppers, 118
catering, 59
SgtPeppers, 62,64-65,74-76,78
CDConfig, 86, 89
ShoppingCart, 109
CDPlayer, 66-67, 71-72, 88
SimpMessageTemplate, 542
CDPlayerConfig, 62-63, 69-70,86-87
SlayDragonQuest, 34
CDPlayerTest, 63, 67, 70, 82
SpitterController, 562
Cerrar sesión, 301
spitterService, 434,439
Channel, 514,516
SpittleController, 572,574
Checkout, 270
spittleHandler, 508
Chrome, 528
SpittleNotifier, 576
circle, 119
SpittleRepository, 410
Clases, 337
spring SecurityFilterChain, 278
@Configuration, 163
SpringRestGraphDatabase, 377
©Controller, 536
springSecurityFilterChain, 278
AlertServicelmpl, 516
SpringTemplateEngine, 216, 306, 558-559
annotatedClasses, 340
Standard ServletMultipartResolver, 237
Application, 595-596,598
StringRedisTemplate, 390
Audience, 134-137,146-147,150
TemplateResolver, 216
BlankDisc, 114,140
ThymeleafViewResolver, 216
BraveKnight, 33, 39
TilesConfigurer, 209
Cake, 103
TilesViewResolver, 209
CDConfig, 86
TrackCounter, 142,151
CDPlayer, 61, 66, 81
ViewResolver, 165
CDPlayerConfig, 62-63, 70
Before, 353
CompactDisc, 61-63
before-invocation, 413
ConfigurableBeanFactory, 108
Between, 353
Contact, 600
bin, 495
ContactController, 588, 598
bin/m acosx, 495
ContactRepository, 601
BinaryWebSocketHandler, 525
cssErrorClass, 218
BlankDisc, 78-79,83-88,151
Helio WorldBean, 30
body, 212,518
IceCream, 104-105
BraveKnight, 32-36, 38-39,41
Item, 367,379-380
brokerURL, 495-496
índice alfabético 611

javax.maiLinternet.MimeMessage, 553 Conectar


JmsUtils, 505 bean con
Knight, 31 Java, 69
List, 599 XML, 72
MagicBean, 98 colecciones, 79
Math, 118 ejecutor de flujo, 250
Minstrel, 38 un servicio RMI, 435
Naming, 435 y utilizar el remitente de correo, 551
Order, 260, 366, 379 Conectarse a Redis, 387
OrderBy, 355 Conexión, 52
OrderRepositorylmpl, 386 automática
Payment, 272 de bean, 60
Popsicle, 105-106 de puntos finales JAX-WS en Spring, 446
ResponseEntityExceptionHandler, 55 Conexiones con SpEL, 115
Servlet, 606 Configuración
SgtPeppers, 61, 64, 76 alternativa de Spring MVC, 223
Shout, 536 automática, 579,583
SimpleMailMessage, 552 Configurar
Spitter, 201 bean de perfiles, 92
SpitterEmailServicelmpl, 551 DispatcherServlet, 161
SpitterRepositorylmpl, 356 el controlador Hessian, 439
SpitterServicelmpl, 438 escuchadores de mensajes, 507
Spittle, 171 JPA gestionado por
SpringApplication, 596 un contenedor, 345
WebConfig, 165 una aplicación, 343
WebSecurityConfigurerAdapter, 416 la comparación de contraseñas, 286
clásico, 130 perfiles en XML, 94
Class, 74-75,199,201,218 propiedades, 81
ClassLoader, 100,129 Spring
classpath, 205 Data Neo4j, 375
Resource, 400 MVC, 161
CLI, 579 para enviar correo e le c tr ó n ic o , 549
client, 367, 369 para mensajería AMQP, 513
Código fuente, 25 un administrador de caché, 399
cold, 104-105 un agente de mensajes en Spring, 495
Combinar un destino predeterminado, 502
comportamientos, 373,385 un emisor de correo electrónico, 549
funcionalidades personalizadas, 356 un origen de datos, 318
todas las piezas: el flujo pizza, 258 un registro de flujo, 250
Compatibilidad de Spring con un servicio
AOP, 129 de usuario personalizado, 289
REST, 454 RMI en Spring, 433
Completar la caché, 403 un solucionador
Componentes de de vistas compatible con JSP, 194
Spring, 47 de vistas Thymeleaf, 214
un flujo, 252 de vistas Tiles, 209
Comprobar el área de reparto, 266 multiparte, 230
Comunicar errores al cliente, 469 una fábrica de administradores de entidades, 342
Condition, 99,101,122 Web Flow en Spring, 249
612 índice alfabético

Connection, 516 una especificación de configuración XML, 73


consumes, 54,467 una fábrica de conexiones, 495
Consumir una sencilla configuración de seguridad, 278
mensajes, 504 URL, 206
recursos REST, 475 vistas JSP, 193
servicios basados en JMS, 510 Criteria, 370
Contad, 589,591-594, 600 Cross-site request forgery, 279
Contains, 354 curl, 604
Contenedor del núcleo de Spring, 49 Customer, 257,267,372,379
Contenedores para bean, 43 Ready, 265
Content
NegotiatingViewResolver, 458
NegotiationConfigurer, 460
D
NegotiationManager, 460 data, 198
ContentNegotiationManagerFactoryBean, 460 DataAccessException, 315
ContentType, 54, 229,467 database, 346
Context, 62, 567 DataSource, 91-92,319,594
Continué, 270 Datos del flujo, 257
Controladores predeterminados, 467 de facto, 21
Controlar Declaración de variables, 257
colisiones de MBean, 569 Declarar
errores, 471 almacenamiento en caché en XML, 409
excepciones, 237 alrededor del consejo, 149
de mensajes, 546 antes y después de un consejo, 147
notificaciones, 574 aspectos en XML, 145
Controller, 440 colas, intercambios y vinculaciones, 514
Conversión de mensajes, 457 consultas personalizadas, 355
Convertir mensajes al enviarlos, 503 DispatcherServlet en web.xml, 226
Cookies, 102 proxy con ámbito en XML, 110
Crear un bean sencillo, 70
aspectos anotados, 134 un destino de mensajes ActiveMQ, 496
bean detectables, 61 un sencillo elemento bean, 74
calificadores personalizados, 104 una sesión de fábrica de Hibemate, 338
el cliente JavaScript, 538 Definición del flujo base, 259
el primer punto final REST, 455 Definir
la vista, 590 anotaciones de calificador personalizadas, 105
mensajes de correo electrónico métodos de consulta, 352
avanzados, 552 mosaicos, 210
con Velocity, 555 plantillas Thymeleaf, 216
métodos de control de excepciones, 239 POJO de AMQP basados en mensajes, 519
POJO basados en mensajes, 506 procesamiento de solicitudes en el nivel de la
repositorios Neo4j automáticos, 382 clase, 169
un controlador de Groovy, 598 un aspecto, 134
un escuchador de mensajes, 506 un diseño con vistas Apache Tiles, 208
un pedido, 268 un evaluador de permisos, 423
un repositorio MongoDB, 370 définitions, 209
un sencillo controlador, 166 déjà vu, 41
una aplicación con Spring Boot, 585 delegate-ref, 153
una clase de configuración, 69 DelegatingFilterProxy, 277
Índice alfabético 613

DELETE, 454-455,478,487 cache:advice, 411-412


Dentro del entorno de Spring, 112 cache:annotation-driven, 398, 410
DERBY, 322 cachexache-evict, 412
Derivar MBean mediante proxy, 573 cache:cache-put, 411
Designar un bean principal, 102 cache:cacheable, 411-413
Después cachexaching, 411
de la devolución, 127 connection-factory, 514
de la generación, 127 constructor-arg, 75-78,82
Dessert, 102-103,105,107 contextxomponentscan, 63
destination, 508, 519, 532 context:mbeanexport, 567
destroy, 47 context:property-place holder, 115
Determinar el tipo de contenido solicitado, 458 decision-date, 254
dev, 93,98 decision-state, 254, 264
development, 324 definition, 210-211
Devolver el estado de un recurso en el cuerpo de la dependency, 603
respuesta, 465 div, 220
Directo, 512 end-state, 255,272
Discography, 80 evaluate, 253,257,265,267,270
Distinct, 353 filter, 277
div, 200 flow, 261
Dynamic, 231 flow:flow-executor, 250
DynamicMBean, 561 flow:flow-location, 250-251
flow:flow-location-pattem, 250
E flow:flow-registry, 250
form, 182,198
Ejecutar la if, 254,266
aplicación Contacts con la ILC, 602 import, 88
ILC de Spring Boot, 601 input, 255, 269, 271
Ejemplos de SpEL, 116 jdbc:embeddeddatabase, 323
element, 200 jdbcscript, 322-323
Elementos jee:jndi-lookup, 318, 347
action-elements, 253 jmsdistener, 508
action-state, 253 jms-.listener-container, 510
admin, 515 jpa:repositories, 351
amqxormectionFactory, 496 label, 201
amq:queue, 497 li, 220
aop: aspectj-autoproxy, 138 list, 79-80, 84
aop:after-returning, 148 listenercontainer, 519
aop:afterthrowing, 148 multipart-config, 232
aop-.aspect, 40,148-149 mvc:annotation-driven, 163
aop:aspectj-autoproxy, 146 neo4jxonfig, 377
aopibefore, 148 null, 79
aopxonfig, 147,149 on-entry, 269,272
aop:declare-parents, 152 output, 261,268
aop:pointcut, 149 pointcut, 40
aop:scoped-proxy, 110 property, 82-84
aop_around, 150 put-attribute, 210
bean, 74-76,103,108,110,156 queue, 520
beans, 73-74, 94-95 ref, 80
614 índice alfabético

secured, 272-273 Establecer


servlet, 232 encabezados en la respuesta, 473
set, 80,257 serializadores de clave y valor, 393
sf:errors, 199 Estados, 454
subflow-state, 254, 261 de acción, 253
témplate, 516-517 de decisión, 254
transition, 256 de subflujo, 254
util:list, 85 de vista, 253
valué, 80 finales, 255
var, 257-258 Evaluar
view-state, 253 colecciones, 121
websockefcsockjs, 529 expresiones regulares, 120
wsdkdefinitions, 450 las ventajas de la mensajería asincrona, 493
wsdkport, 450 Evitar falsificaciones de petición, 295
Eliminar Excepciones de persistencia, 314
código reutilizable con plantillas, 41 Exception, 364
entradas de la caché, 408 exchange, 517
recursos con DELETE, 482 execution, 132
Elite, 356 Exponer
else, 254 bean como servicios HTTP, 443
email, 198 funcionalidad de bean con Hessian o Burlap, 438
embark, 40 MBean remotos, 570
Encabezados, 512 métodos por nombre, 564
Encoreable, 144-145,153 servicios remotos con Hessian y Burlap, 437
enctype, 234 Exportar
EndingWith, 354 bean de Spring como MBean, 561
EndResult, 382 puntos finales JAX-WS independientes, 447
Order, 387 servicios basados en JMS, 509
EndsWith, 354 un servicio
Enterprise JavaBeans, 21 de Burlap, 440
Entornos y perfiles, 91 Hessian, 438
Entregar más que recursos, 469 RMI, 432
Entrelazado, 128 Expresar
Enviar mensajes, 490,500 reglas de acceso a métodos, 419
a un usuario concreto, 545 valores literales, 117
al cliente, 539 expression, 40,253
con JMS, 494 Extraer metadatos de la respuesta, 479
con RabbitTempIate, 516
de correo con contenido enriquecido, 554
desde cualquier punto de una aplicación, 540
F
tras procesarlo, 539 factory-method, 156
Environment, 112-115,365, 550 false, 67,99,412-413,421
Error, 201,218 feed, 540
Escapar contenido, 207 file, 205,234
Escribir Filosofía de acceso a datos de Spring, 311
puntos de corte, 132 Filtrado previo de parámetros de método, 422
un repositorio basado en JPA, 347 Filtrar
Escuchar notificaciones, 576 entradas y salidas de métodos, 421
Especificar consultas personalizadas, 385 solicitudes Web, 277
índice alfabético 615

Finalizar el flujo, 267 header, 212


finished, 262 Helper, 358
Firefox, 528 Hibernate sin código de Spring, 340
First Name, 199-200,218-219 Historia de dos contextos de aplicación, 163
footer, 212 Hoja de ruta, 22
for-each, 382 home, 165,169,589-591
form, 196 host, 550
forward, 185 href, 207, 216, 218
FreeMarkerViewResolver, 193 http, 295,530,554
Friends, 275 Http MessageConverter, 536
Funcionamiento de la DI, 31
I
G if, 471
IGNOREJEXISTING, 569
Generar correo electrónico mediante plantillas, 555
IgnoringCase, 354
Gestión de los pagos, 270
Impl, 356-357,374,386
Gestionados por
import, 599, 607
el contenedor, 343
Importar y combinar configuraciones, 86
la aplicación, 342
In, 354
Gestionar código descontrolado
Independencia de la ubicación, 494
JDBC, 326
Iniciadores de Spring Boot, 579
JMS, 497
Inicializar un bean con inyecciones de constructor, 75
GET, 175, 296,478,538
init, 46
Object, 505
insertDisc, 66
SpittlesPerPage, 567
Instalar la ILC, 601
Groovy
Instance AlreadyExistsException, 569
Environment Manager, 602
Instancias
y la ILC de Spring Boot, 597
BraveKnight, 33
groups, 286
CDPlayer, 76
Guardar archivos en Amazon S3,235
CriticAspect, 156
guest, 514,534
DenyAllPermissionEvaluator, 424
Dispatcher Servlet, 231
H EhCacheCacheManager, 400
EhCacheManagerFactoryBean, 400
H2Dialect, 339 HomeController, 169
Habilitar HttpHeaders, 474
autenticación HTTP básica, 299 MockMvc, 169
la compatibilidad con caché, 397 Mongo, 363
mensajería STOMP, 532 MongoClient, 364-365
MongoDB, 362 MultipartConfigElement, 231
Spring MVC, 163 Neo4jTemplate, 386
un enlace de agente de STOMP, 534 Order, 260
habuma, 244,304-305 RedisTemplate, 402
Hacer referencia a RestS3Service, 236
configuración XML en JavaConfig, 86 RestTemplate, 479
JavaConfig en configuración XML, 88 SgtPeppers, 70-72, 74
un servidor LDAP remoto, 287 ShoppingCart, 109
HEAD, 296 Simple MailMessage, 552
616 índice alfabético

SockJS, 530, 538, 541 SpitterRepository, 350-352, 356-358, 370


Spittle, 543 SpitterService, 4 29,433,441,450
Spittle Controller, 173 Spittle ControllerManagedOperations, 567
Única, 107 Spittle Repository, 173
UriComponents, 474 SpittleControllerManagedOperations, 566,574
WebSocket, 527 SpittleNotifier, 575
Instrumentación, 50 SpittleRepository, 411
Integer, 113 UserDetailsService, 289
Integración de Hibernate con Spring, 338 View, 164,191,194
Intercambiar recursos, 485 ViewResolver, 191
Interceptar solicitudes, 290 WebApplicationlnitializer, 224
Interfaz WebSocketConfigurer, 526
AlertService, 501,509,511 WebSocketHandler, 524
BeanPostProcessor, 46 IntemalResourceView, 173,195
Channel, 514 InternalResourceViewResolver, 165,193-195,216,461
CompactDisc, 61-62, 76 Internet Explorer, 528
Condition, 99-100,122 Introducciones, 128
ConnectionFactory, 500 a Spring Boot, 579
CriticismEngine, 155 a Spring MVC, 159
DisposableBean, 47 a Spring Security, 275
DynamicMBean, 561 Inyecciones con JavaConfig, 71
Encoreable, 144-145 Inyectar
EntityManager, 343 aspectos de AspectJ, 153
FactoryBean, 339,347,400 constructores con referencias de bean, 75
InitializingBean, 46 dependencias, 31
javax.management.NotificationListener, 576 propiedades con valores literales, 83
JdbcOperations, 330 una hazaña en un caballero, 33
JpaRepository, 353 valores
MailSender, 549 en tiempo de ejecución, 111
MessageDrivenBean, 506 externos, 112
MessageListener, 506-507 literales a constructores, 77
MessageSource, 205 IOException, 476
MongoRepository, 371 Is, 354
MultipartResolver, 232 IsAfter, 353
NotificationPublisherAware, 574 IsBefore, 353
OrderOperations, 374 IsBetween, 353
OrderRepository, 370, 374,383 IsContaining, 354
Part, 236 IsEndingWith, 354
PasswordEncoder, 285 IsFalse, 354
Performance, 132,134,145,153 IsGreaterThan, 353
PermissionEvaluator, 424 IsGreaterThanEqual, 353
Quest, 33-34 Isln, 354
RedisSerializer, 393 IsLessThan, 353
Repository, 351,376-377, 383 IsLessThanEqual, 353
RowMapper, 332-333 IsLike, 354
Session, 338 IsNot, 354
SessionFactory, 338 IsNotln, 354
ShoppingCart, 109 IsNotLike, 354
SimpMessageSendingOperations, 540 IsNotNull, 354
Ìndice alfabético 617

IsNull, 354 localhost, 287, 474,495, 514,534


IsStartingWith, 354 Location, 474,484-485
IsTrue, 354 Egging, 583
Item, 367, 369, 379-380, 385 loginld, 303
items, 367,379 Long, 178,351,379-381,383
Iterable, 382
M
J mail, 559
Jack, 198 MailSender, 549, 551
jar, 597 MailSession, 550
Java managedlnterfaces, 567
Activation Framework, 459 managedMethods, 565
Data Objects, 29,337 Map, 477,479,481-483
Dependency Injection, 67 mapping Resources, 339
Management Extensions, 24,559, 561 marco, 530
Message Service, 24,50,489,494 MarcoHandler, 524-527
Persistence API, 23 matches, 120
JavaMailSenderlmpl, 549-551 Math, 118
JavaScript Object Notation, 454 max, 171,177-178,202,206
javaScriptEscape, 207-208 MAX_LONG_AS_STRING, 178
JavaServer Pages Standard Tag Library, 175,193 MBean
jbauer, 185,545 abiertos, 561
JCacheCacheManager, 402-403 dinámicos, 561
Jerarquía de excepciones de Spring, 312 estándar, 561
jms, 507-508 modelo, 561
jukebox, 121 MediaPlayer, 68
Mensajería

K con AMQP, 511


de publicación-suscripción, 492
key, 405-406,412-413,563 punto a punto, 491
Knight, 31,36 mergeTemplatelntoString, 556
Message, 503,505,517-518
META-INF, 343
L method, 148,151,412,508
Métodos, 469
LastModified, 480
@ SubscribeMapping, 537
lastname, 354
@Bean, 72, 88, 91, 562,571
LDAP
©Conditional, 99
Authentication, 290
©Exception Handler, 55
Data Interchange Format, 288
@ExceptionHandler, 240-241,546
Leer datos con JdbcTemplate, 331
@MessageMapping, 534,537,542-543,546
LessThan, 353
@Query, 385
LessThanEqual, 353
@RequestMapping, 246
lib, 495
@SubscribeMapping, 537
Liberar el potencial de los POJO, 30
acceptsProfiles, 101,114
Like, 354
access, 293-294
List, 81,372,599
addCustomer, 267
Order, 372, 386-387
addFlashAttribute, 244
618 índice alfabético

addinline, 554 embark, 33


addPizza, 270 embarkOnQuest, 3 1,33,36, 40
addRow, 332 equals, 172
addSpitter, 330-331,333,350 exchange, 485-486
addSpittle, 416-417 fetchFacebookProfile, 476
afterConnectionCIosed, 525 find, 369
afterConnectionEstablished, 527 findAll, 382,594
afterPropertiesSet, 46 findAUGmailSpitters, 355-356
and, 282,300 findByEmailLike, 355
antMatchers, 291,305 findByUsemame, 352
append Filters, 277 findOne, 180,331-332,381,404
applause, 136,148 findOrdersByType, 374
aspectOf, 156 findRecent, 411
authenticated, 292 findSiAOrders, 385
authorities, 282 findSpittles, 171
authorizeRequests, 295 GET, 478
broadcastSpittle, 542,545 get, 480
build, 475 getAttribute, 572
cacheManager, 400 getBean, 45
cdPlayer, 71-72 getCriticism, 154
check Delivery Area, 266 getDatabaseName, 363
compactDisc, 86 getEmployeeByld, 42-43
configure, 279,281,292,416 getFirst, 480
configure(Authentication ManagerBuilder), 280 getForEntity, 478,484,486
configure(HttpSecurity), 291, 297 getForObject, 478-479,483-484
configureContentNegotiation, 459 getHeaders, 480,484
configureDefaultServletHandling, 165 getObject, 505
configureMessageBroker, 534 getOffensiveSpittles, 421
contextSource, 287 getProperty, 112-113
convertAndSend, 516-517,542 getPropertyAsClass, 113
convertJmsAccessException, 505 getServletConfigClasses, 163
count Track, 151 getServletFilters, 225
count, 382 getSpittleByld, 420
createExpressionHandler, 416,424 getStatusCode, 480
createMessage, 502 graphDatabaseService, 376
createMimeMessage, 552 handleDuplicateSpittle, 240
currentSession, 341 handleMessage, 525
customizeRegistration, 224, 231 handleNotification, 576
dataSource, 92 handleShout, 536,538-540
defaultContentType, 460 handleSpittle, 543
delete, 382,482 handleSubscription, 538, 540
deleteSpittles, 422 hasPermission, 424
demandRefund, 148 hasRole, 293
destroy, 47 home, 167-168,170,589, 591
disc, 112 init, 46
duplicate SpittleHandler, 242 insertDisc, 66
ehcache, 400 invoke, 572-573
eliteStatus, 357 isAnnotated, 100
eliteSweep, 356-357 jdbc Authentication, 283
índice alfabético 619

JMSException, 505 remove, 370,408


IdapAuthentication, 285 requiresChannel, 295
ldif, 288 resolveViewName, 191
loadUserByUsemame, 289-290 ResponseEntityExceptionHandler, 55
lookupCustomer, 265 RestTemplate, 55
main, 36,595-596 retrieveSpittleAlert, 505
mapRow, 332 rightPush, 391
matches, 99-100,285 roles, 282
mergeTemplatelntoString, 556 root, 287
Minstrel, 39 save, 185,239,369,411,594
mongo, 364 saveFile, 235
onMessage, 507 saveOrder, 253, 261
opsForSet, 392 saveSpittle, 239,467, 473-474
Part, 237 selectArtist, 118
passwordCompare, 286 send, 502-503,517-518,552
passwordEncoder, 284, 287 sendNotification, 576
PATCH, 55,455 sendRichSpitterEmail, 554
perform, 133-134,136,140,154 sendSimpleSpittleEmail, 551
performance, 137 sendSpittleAlert, 502-503,516
Permission, 424 sendSpittleEmailWithAttachment, 553
permitAll, 292 setApplicationContext, 46
play, 68, 79, 83,140 setAttribute, 572
playTrack, 140 setBean Factory, 46
POST, 483,485 setBeanName, 46
postFor Object, 483 setCompactDisc, 66,82
postForEntity, 484 setConfigLocation, 400
postForLocation, 485 setDessert, 102,104,106-107
postProcessBeorelnitialization, 46 setFrom, 552
postSpitterForObject, 484 setHostName, 388
proceed, 140 setKeySerializer, 393
process Registration, 188 setLastModified(Date), 128
process, 558 setNotificationPublisher, 575
processRegistration, 184,186,188,242-244 setShoppingCart, 109
processSpittle, 507-508 setSpittlesPerPage, 567-568,572-574
put, 481-482 setText, 555
putObject, 236 setType, 323
query, 384,386 sgtPeppers, 71
queryFor Object, 43,331 SgtPeppers.play, 68
queryForObject, 331 showHomePage, 564
queryNames, 572 showRegistrationForm, 181,184
range, 392 showSpitterProfile, 185
readSpitterByFirstnameOrLastname, 353 singAfterQuest, 39-40
receive, 504-506,518 singBeforeQuest, 39
receiveAnd Convert, 518 spittle, 180,239
receiveAndConvert, 518 spittleByld, 472
regexMatchers, 291 spittleNotFound, 471
registerStompEndpoints, 533 spittles, 174-175,177,, 565
registerWebSocket Handlers, 529 staticcurrentTimeMillis, 116
registerWebSocketHandlers, 526 submit, 589,591
620 Indice alfabético

take Seats, 136 NET, 373


to, 386 new, 43,45
toList, 272 NoCache, 407
toUpperCase, 118 Not, 354
transferTo, 235 Notepad, 108
update, 331,333 Notification Publisher, 575
updateSpittle, 481 notifications, 544
url, 287 null, 39, 63, 67,518,572
userDetailsService, 290 NullPointerException, 79, 82-83
userSearchBase, 286
userSearchFilter, 285
watchPerformance, 139-140,150-151
o
withUser, 282
Object, 424,486
millionthSpittlePosted, 575 Object-Graph Navigation Language, 220
MimeMessage, 553
Obtener
MimeMessageHelpers, 554 EntityManagerFactory desde JNDI, 347
min, 202
información del cliente, 262
mobile, 583
recursos con GET, 478
mock, 173
on, 256
Mockito, 33
on-exception, 256
MockMvc, 169,172-173,183
onclose, 527-528,530
Model, 243-244
onmessage, 527, 530
Modulos
onopen, 527,530
AOP de Spring, 49
Opciones de configuración de Spring, 60
de Sprin Security, 276
OpenJpaVendorAdapter, 345
Mongo, 363
Opera, 528
MongoClient, 362-365
Operaciones de RestTemplate, 476
MongoCredential, 365
Operadores de SpEL, 119
MongoFactoryBean, 363-364
OPTIONS, 296
MongoOperations, 369-370, 374
Or, 355
MongoRepository, 370-371, 383
Order, 369,372,379,384
Order, String, 371
order/item , 378
MongoTemplate, 362-364,374,381
OrderBy, 354-355
Mostrar
orderCreated, 255,270
errores, 198
OrderOperations, 374, 386
mensajes internacionalizados, 204
OrderRepositorylmpl, 374,386
Multipurpose Internet Mail Extensions, 552
OrderRepositoryStuff, 374
orders, 376
N Orders, 372

name, 70,219,234,520 Orientación del mensaje y desacoplamiento, 493


Naming, 435 Orígenes de datos basados en controladores JDBC, 321
OxmSerializer, 393
Navegador de Android, 528
Negociación de contenido, 457
Negociar la representación de recursos, 457 P
Neo4jConfig, 376
Neo4jConfiguration, 376 packagesToScan, 340,346
Neo4jOperations, 386 PagingAndSortingRepository, 352
Neo4jTemplate, 381-382,384,386 PagingNotificationListener, 576
índice alfabético 621

Part, 236-237 Profile, 94-95


Pasar property, 303
datos del modelo a la vista, 170 Proteger
parámetros a consejos, 151 flujos Web, 272
password, 184,186, 320,550 la vista, 302
PATCH, 55,454-455 métodos con anotaciones, 415
path, 198-199,201, 207,250 Prototipo, 107
Payaient, 272 Proxy de servicios JAX-WS en el lado cliente, 449
people, 286 Pruebas, 50
Performance, 132-134,136,153 public, 438,597-598, 600
Persistencia Publicar
con un repositorio de Groovy, 600 datos de recursos con POST, 483
de datos, 592 y consumir servicios Web, 445
de documentos con MongoDB, 361 Puntos de
Personalizar corte, 128
la clave de caché, 405 cruce, 127
la configuración de DispatcherServlet, 223 PurchaseOrder, 337
Phoenicians, 5 PUT, 454-455,481-482, 487
pi, 119
Pizza, 269-270
player, 68 Q
pointait, 148-149 qa, 95, 324
Popsicle, 105-106
quantity, 380-382
port, 550
Qué es la programación orientada a aspectos, 126
portlets, 50
Query, 369
portName, 450 Quest, 33-36
Postfiltrado de valores devueltos por métodos, 421
queue, 518
prefix, 195,216,559
price, 380
primary, 103 R
Principal, 542-543
radius, 119
PrintStream, 34-35, 39
range, 198
prívate, 598, 600
read, 353, 372
Probar
Realizar
el controlador, 168
operaciones en conjuntos, 392
la aplicación, 595
pruebas con perfiles, 98
Procesar
datos de formularios multiparte, 228 Recepción garantizada, 494
Recibir
formularios, 181
mensajes STOMP desde el cliente, 535 el archivo transferido como parte, 236
parámetros en consejos, 140 el estado de un recurso, 466
la ubicación de un recurso, 484
solicitudes, 587
mensajes
de flujo, 251
AMQP, 518
multiparte, 233
con RabbitTemplate, 518
suscripciones, 537
respuestas de objeto desde solicitudes POST, 483
prod, 93, 95, 98
un archivo multiparte, 234
produces, 466-467
Recuperar recursos, 478
Product, 382,391-392
production, 324 Redirecciones con plantillas de URL, 243
622 Indice alfabético

redirect, 185, 242 Shout, 536-540


ref, 78,82,148, 508 Sin esperas, 493
Referencias a bean, propiedades y métodos, 118 SingleConnectionDataSource, 321-322
Reforzar la seguridad del canal, 294 SlayDragonQuest, 33-35
Registrar un nuevo cliente, 265 social, 52
Remote Method Invocation, 50,429 sock.close, 527
Repositorios JPA automáticos con Spring Data, 350 SockJS, 530,538,541
Repository, 351, 370, 376-377, 383 Solicitar un numero de telefono, 264
Representación condicional, 304 Solicitud, 107
Representacional, 454 songs, 121
request, 180 soundsystem, 62,64
required, 67 SoundSystemConfig, 87
Resolución de vistas, 191 Spitter, 182,185,187,484-487
Resolver spittr, 162, 206
marcadores de posición de propiedades, 114 Spring, 174
solicitudes multiparte con Servlet 3.0,230 Batch, 52
vistas JSTL, 195 Boot, 53
resource-ref, 318 Data, 52
Response EntityExceptionHandler, 55 Data Neo4j, 376
Restringir el acceso a métodos con @Secured, 416 Integration, 51
Result, 258, 265 Mobile, 53
rmic, 432 para Android, 53
RootConfig, 163,165-166 run, 602
routing-key, 517 Security, 51
SecurityFilterChain, 278
s Social, 52
solo admite puntos de cruce de metodo, 131
Safari de iOS, 528 Web Flow, 51
saveOrder, 261 Web Services, 51
scope, 108,110,206,303 y el API Java Persistence, 342
Secuenciación, 337 spring-beans, 74
Seguir el ciclo de vida de una solicitud, 159 spring-boot starter, 583
Seguridad con expresiones de Spring, 293 spring-data-neo4j-rest, 376
Seleccionar SpringJUnit4ClassRunner, 63
bean en puntos de corte, 134 Standard ServletMultipartResolver, 237
puntos de cruce con puntos de corte, 131 start-state, 261
servicios de detalles de usuario, 280 static, 145
SEND, 531 Stomp, 541
Serializable, 424,517 suffix, 216
server, 574
service
Interface, 510
T
Name, 439 tablets, 53
Oriented Architecture, 445 target, 596
Url, 445 Tema, 512
SessionFactory, 338-341 templates, 591
Set, 81,559 Terminologia de AOP, 126
Dessert, 102 text, 197
MultipartConfig, 224 then, 254
índice alfabético 623

Tiempo de Usar
carga de clases, 129 expresiones lambda, 332
compilación, 129 interfaces, 566
ejecución, 129 parámetros con nombre, 333
title, 84,111,114,121 User, 290
Tomcat, 583 Username, 184,186,243-244,550
Trabajar con Utilizar
atributos flash, 244 expresiones para proteger métodos, 418
contraseñas codificadas, 284 JDBC con Spring, 325
conversores de mensajes HTTP, 463 la plantilla JMS de Spring, 497
datos las bibliotecas JSP de Spring, 195
de clave-valor en Redis, 387 orígenes de datos JNDI, 318
gráficos en Neo4j, 375 perfiles para seleccionar un origen de datos, 323
el ámbito de sesión y de solicitud, 108 Redis para almacenamiento en caché, 401
el API WebSocket de nivel inferior de Spring, 523 RFC basados en mensajes, 508
el dialecto Spring Security de Thymeleaf, 305 Spring Httplnvoker de, 442
listas, 391 Thymeleaf para crear mensajes de correo, 557
MBean basados en anotaciones, 567 un origen de datos agrupado, 319
Mensajes un origen de datos incrustado, 322
de usuario en un controlador, 543
para usuarios específicos, 542
STOMP, 531
V
Neo4jTemplate, 381 Validar formularios, 186
Plantillas valué, 65, 78,404,418
JDBC, 329 var, 303
JMS, 499 Verificar la configuración automática, 67
RedisTemplate, 389 View, 192,194,459
ResponseEntity, 470 viewClass, 195
RMI, 432 Vincular
Thymeleaf, 214 a una clave, 392
tipos en expresiones, 118 formularios
un contexto de aplicación, 44 al modelo, 196
un repositorio de usuarios en memoria, 281 con Thymeleaf, 218
valores sencillos, 391 void, 457,539
varios administradores de caché, 402
TRACE, 296,477
Transferencia, 454 w
Transferir datos entre solicitudes de redirección, 242
war, 597
Transiciones globales, 256
WEB, 583-584,587-588,606
trae, 99, 207, 412-413
y acceso remoto, 50
try, 435
WEB-INF, 194
type, 197-198, 322-323, 385
welcome, 253, 264,266-267
typesmatching, 153
woodstock, 134

u X
unless, 406-408,412
URI, 481,483 x-match, 512
url, 305,307,320 XmlWebApplicationContext, 45
Spring CUARTA EDIC
S p rin g es u n framework d e c ó d ig o a b ie r to im p r e s c in d ib le p a ra fa c ilita r el d e s a rro llo
d e a p lic a c io n e s e n Java. S p rin g 4, la ú ltim a v e rs ió n , se in te g ra d e fo rm a to ta l c o n Java 8
e in c o rp o r a im p o r ta n te s m e jo ra s c o m o n u e v a s a n o ta c io n e s p a ra el c o n te n e d o r loC ,
el le n g u a je d e e x p re s io n e s y la n e c e s a ria c o m p a tib ilid a d c o n REST. C o n in d e p e n d e n c ia
d e q u e sea u n u s u a rio n o v a to o d e q u e q u ie ra a p re n d e r las n u e v a s c a ra c te rís tic a s ,
n o h a y m e jo r fo rm a d e d o m in a r S p rin g q u e a tra v é s d e e s te lib ro .

C o n u n e s tilo p rá c tic o , C ra ig W a lls p re s e n ta S p rin g d e la fo rm a m ás in te re s a n te p a ra u n


d e s a rro lla d o r Java, in c lu y e n d o e je m p lo s p ro c e d e n te s d e casos rea le s q u e se c e n tra n e n
las c a ra c te rís tic a s y té c n ic a s q u e r e a lm e n te n e c e s ita a p re n d e r. A n a liz a el n ú c le o d e S p rin g
ju n t o a las n o v e d a d e s d e s e g u rid a d , M V C , W e b F lo w , e n tre o tra s . In c lu y e fr a g m e n to s d e
c ó d ig o y e je m p lo s p rá c tic o s q u e le p e r m itir á n c re a r a p lic a c io n e s JEE s e n c illa s y e fica ce s.

Principales novedades de esta edición:

• E sp e cia l re le v a n c ia a la c o n fig u r a c ió n d e S p rin g b a sa d a e n Java.

• C o n fig u r a c ió n y p e rfile s c o n d ic io n a le s p a ra d e c id ir e n t ie m p o d e e je c u c ió n
q u é c o n fig u r a c ió n d e S p rin g u s a r o d e s c a rta r.

• M e jo ra s e n S p rin g M VC , e n e s p e c ia l p a ra la c re a c ió n d e s e rv ic io s REST.

• U tiliz a c ió n d e T h y m e le a f c o n a p lic a c io n e s W e b S p rin g c o m o a lte rn a tiv a a JSP.

• H a b ilita c ió n d e S p rin g S e c u rity m e d ia n te c o n fig u r a c ió n b a sa d a e n Java.

• U so d e S p rin g D a ta p a ra g e n e ra r a u to m á tic a m e n te ¡m p le m e n ta c lo n e s
d e r e p o s ito r io s e n tie m p o d e e je c u c ió n p a ra JPA, M o n g o D B y N e o 4 j.

• N u e v a c o m p a tib ilid a d d e a lm a c e n a m ie n to e n c a c h é d e c la ra tiv o .

• M e n s a je ría W e b a s in c ro n a c o n W e b S o c k e t y STOMP.

• S p rin g B o o t, u n n u e v o e n fo q u e re v o lu c io n a rio .

Craig Walls es d e s a rr o lla d o r d e s o ftw a re e n P iv o ta l. A u to r r e c o n o c id o , c u e n ta e n tre


XDodetin Action y las tre s e d ic io n e s
sus o b ra s ; a n te rio re s d e S p rin g . D e fe n s o r d e l
framework S p rin g , p a rtic ip a c o m o p o n e n te e n c h a rla s y c o n fe re n c ia s . En la a c tu a lid a d ,
v iv e e n C ross R oads, Texas.

2327711
7 8 8 4 4 1 115 3 6 8 2 1

ANAYA
« M U L T IM E D IA * www.anayamultimedia.es

También podría gustarte