Cuarta Edición: Craig Walls
Cuarta Edición: Craig Walls
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 :
Tr a d u c t o r a :
Laura Ayuso Castrillo
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
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
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
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
í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:
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 la parte III nos adentramos en la interfaz de una aplicación para ver cómo se procesan
y conservan los datos:
La cuarta y última parte del libro describe cómo integrar Spring con otras aplicaciones
o servicios:
24 Introducción
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:
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.
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:
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:
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.
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.
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).
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.
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.
@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.
import ja v a .i o .PrintStream;
</beans>
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;
@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
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.
• 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.
import ja v a . i o . PrintStream;
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.
<aop: config>
<aop:aspect r e f = "m instrel">
<aop:pointcut id ="embark"
expression="execution(* * . embarkOnQuest( . . ) ) " / > / / D efin ir punto de co rte.
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.
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
} 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.
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.
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
Ei bean está
listo para su uso
El contenedor
está cerrado
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:
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
20 elementos
b n I
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
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.
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
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 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
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
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
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.
• 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
©Component
public cla ss SgtPeppers implements CompactDisc {
@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.
</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.
import s t a t ic o r g .ju n i t . A s s e r t ;
@RunWith(SpringJUnit4ClassRunner. class)
@CcntextConfiguration(classes=CDPlayerConfig. class)
public cla ss CDPlayerTest {
@Autowired
private CompactDisc cd;
@Test
public void cdShouldNotBeNull() {
assertN otN ull(cd);
}
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.
}
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.
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 {}
@Configuration
@ComponentScan(basePackageClasses={CDPlayer. c la s s , DVDPlayer. c la s s })
public cla ss CDPlayerConfig {}
@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
@ 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.
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
@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
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.
</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.
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
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.
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>
^ r---------^
c : c d - r e f = "compactDisc"
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.
prívate String t i t l e ;
prívate String a r t i s t ;
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" />
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,-
private String t i t l e ;
p rivate String a r t i s t ;
p rivate L ist<Strin g > track s;
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
< / l is t >
< /constructor-arg>
</bean>
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;
@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
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>
</bean>
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"
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.
private String t i t l e ;
private String a r t i s t ;
private L ist<String > track s;
<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
</beans>
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
@Configuration
public cla ss CDConfig {
@Bean
public CompactDisc compactDisc() {
return new SgtPeppers();
©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>
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
<bean id="cdPlayer"
class="soundsystem.CDPlayer"
c : cd -re f="compactDisc" />
</beans>
<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">
</beans>
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();
}
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.
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 ();
}
}
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 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.
(»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.
<jdbc:embedded-database id="dataSource">
<jdbc:script location="classpath:schema.sql" />
<jdbc:script location="classpath:test-data.sql" />
</jdbc:embedded-database>
</beans>
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">
<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>
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
<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
}
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
@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.
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
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 { . . . }
©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.
¡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 { . . . }
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 " ):
©Autowired
@Cold
©Creamy-
public void setDessert(Dessert dessert) {
th is .d e s s e rt = dessert;
}
Á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
@Autowired
public void setShoppingCart(ShoppingCart ShoppingCart) {
t h i s . ShoppingCart = ShoppingCart;
}
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.
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
</beans>
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:
@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
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);
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
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.
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;
©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>
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()}
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.
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'}
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.
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()}
# { 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()}
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.
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}
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 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 }
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']}
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:
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
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
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.
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.
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.
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
package concert;
^------------ i r ^ — i rA
e x e c u t io n (* c o n c e r t . Performance.p erfo rm ( . . ) )
Figura 4.4. Selección del método perform de Performance con una expresión de punto
de corte de AspectJ.
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 . * ) )
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.
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.
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
@Aspect
public cla ss Audience {
}
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.
Anotación Consejo
@Aspect
public d a s s Audience {
}
@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");
}
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">
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.
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 {
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.
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 {
@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.
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 .
@RunWith(SpringJUnit4ClassRunner. class)
©ContextConfiguration(classes=TrackCounterConfig. c la s s )
Spring orientado a aspectos 143
@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);
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.
@Aspect
public cla ss Encoreablelntroducer {
@D eclareParents(value="concert. Performance+",
defaultImpl=DefaultEncoreable. class)
public s t a t ic Encoreable encoreable;
• 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.
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.
Listado 4.9. Clase Audience sin anotaciones, declarada como aspecto en XML.
<aop: config>
<aop:aspect ref="audience">
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.
<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"/>
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.
<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 > .
package concert;
import o rg . aspectj . lang. ProceedingJoinPoint;
public c la ss Audience {
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 ().
package soundsystem;
import java.útil.HashMap;
import java.útil.Map;
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.
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>
package concert;
public aspect C riticA spect {
public C riticA sp ect() {}
pointcut performance() : execu tion{* perform( . . ) ) ;
/ / 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.
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:
Figura 5.1. Una solicitud transporta información durante varias etapas hasta llegar
a los resultados deseados.
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.
package s p i t t r . conf ig (-
@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.
@Configuration
©EnableWebMvc
public cla ss WebConfig {
}
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
©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 ;
@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.
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;
<%@ 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>
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.
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 .
©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 {
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 ;
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,-
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.
©Controller
174 Capítulo 5
@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 .
}
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
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.
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();
@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);
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);
Esta vista no tiene nada especial, como puede apreciar en la figura 5.4.
¡1 ® * » x| I
Helio World! The first ever spittle!
ft 2013-0^-02
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.
©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.
@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.
<%@ 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.
©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);
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;
©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
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
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.
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.
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;
@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
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 ()
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:
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;
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.
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:
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
Prefijo Sufijo
/WEB-INF/views/home. j sp
• 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
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.
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.
< 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
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 / >
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
< / s f : form>
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 :
< / s f : form>
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>
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>
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
_______________ ____
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
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
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.
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;
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!
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>
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>
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 < ;
y > o el navegador interpretará ese código HTML como cualquier otro de la página.
Nada le impide añadir < y > 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>
<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.
@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 ;
}
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.
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.
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.
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
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>
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.
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>.
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
Listado 6.6. home.html: plantilla de página de inicio que usa el espacio de nombres Thymeleaf.
<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>
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
Listado 6.7. Página de registro, que usa Thymeleaf para vincular un formulario a un objeto de
comando.
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 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/>
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:
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 .
©Override
public void onStartup(ServletContext servletContext)
throws ServletException {
@Override
public void onStartup(ServletContext servletContext)
throws ServletException {
<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>
<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
<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.
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
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 > .
@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;
</form>
234 Capítulo 7
}
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.
Listado 7.5. La interfaz MultipartFile de Spring para trabajar con archivos transferidos.
long g e t s i z e ();
byte[] getBytesO throws IOException;
InputStream getlnputStream() throws IOException;
void transferTo(File dest) throws IOException;
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( ) ) ) ;
}
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 .
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:
Tabla 7.1. Excepciones de Spring asignadas de forma predeterminada a códigos de estado HTTP.
'
Código de estado HTTP
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 " ;
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.
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";
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:
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;
}
242 Capítulo 7
R e d ire c c ió n
re a liz a d a
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
• 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.
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
Redirección
realizada
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.
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:
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.
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>
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.
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" />
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 >
■
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>
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
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" />
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.
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, 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
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.
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.
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.
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.
Inicio
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.
<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>
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 .
<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.
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
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
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>
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>
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.
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.
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:
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.
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
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 >
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
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.
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:
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.
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;
©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.
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 .
Descripción
@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.
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.
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.
(©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") ) ;
@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
@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
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):
Tabla 9.4. Métodos de configuración para definir cómo proteger una ruta.
Metodo Funcionamiento
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();
}
Tabla 9.5. Spring Security amplía el lenguaje de expresiones de Spring con una serie de
expresiones específicas para seguridad.
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.
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.
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}" />
.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.
<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 j
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="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>
}
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.
}
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>
}
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.
Tabla 9.6. Spring Security admite seguridad en la capa de la vista con una biblioteca
de etiquetas JSP.
........... .................. ............................ í - T p i .............. ■ ............... ... ' -------......................... ..................- : ................................................- ...... — ....................................................
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.
Tabla 9.7. Puede acceder a varios de los detalles de autenticación del usuario por medio de la
etiqueta de JSP <security:authentication>.
í.......... .. ............... ......... ......... .... ■.................. .......~................. .... ...... ' .......... .............. ..
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.
<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>
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'" ) ;
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.
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
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.
@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
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:
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.
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.
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.
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
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.
@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;
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.
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.
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" >
</beans>
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
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.
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.
Listado 10.4. Uso de JDBC para insertar una fila en una base de datos.
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.
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.
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.
@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.
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
• 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
r s . g e tS trin g ("fullName"),
r s .g e tS tr in g ("em ail"),
r s . getBoolean("updateByEmail" ) ) ;
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.
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.
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.
• 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
@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 ;
}
@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 ;
}
@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.
Listado 11.1. Repositorios de Hibernate sin Spring, habilitados mediante sesiones de Hibernate.
}
Persistencia de datos con asignación relacional de objetos 341
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.
• 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
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
@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
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.
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 .
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
©Repository
©Transact ional
public cla ss JpaSpitterR epository implements SpitterR epository {
@PersistenceContext
private EntityManager em; / / Inyectar EntityManager.
}
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
Listado 11.4. Creación de un repositorio a partir de una definición de interfaz con Spring Data.
</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
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.
• 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 ) ;
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 ) ;
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.
¡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?
Listado 11.6. Repositorio que asciende a los usuarios Spitter más activos al estado Elite.
@PersistenceContext
Persistencia de datos con asignación relational de objetos 357
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();
}
}
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 :
@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:
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
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:
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
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
}
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.
©Override
Trabajar con bases de datos NoSQL 365
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">
</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
Tabla 12.1. Anotaciones de Spring Data MongoDB para asignar objetos a documentos.
Anotación Descripción
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:
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;
private Long id ;
private Order order;
private Strin g product;
private double p rice ;
p riv ate in t quantity;
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.
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" ) ;
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
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.
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.
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
O también rea d :
List<Order> readByCustomer(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);
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.
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;
©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
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
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;
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
}
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.
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;
@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 .
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
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
' ‘ '
' ■
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)
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;
}
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 ( ) .
@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",
• 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();
}
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;
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
RedisConnectionFactory c f = . . . ;
RedisTemplate<String, Product> redis =
new RedisTemplate<String/ Product>();
r e d is . setConnectionFactory(cf) ;
RedisConnectionFactory c f = . . . ;
StringRedisTemplate redis = new StringRedisTem plate(cf);
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.
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.
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.
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.
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 " );
Como los conjuntos carecen de índices o d e u n orden implícito, no puede elegir y recu
perar un elemento concreto.
@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;
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:
@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();
}
}
</beans>
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.
• 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:
@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>
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;
©Configuration
@EnableCaching
public cla ss CachingConfig {
@Bean
public CacheManager CacheManager(RedisTemplate redisTemplate) {
402 Capítulo 13
/ / 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
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
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
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).
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.
Tabla 13.4. Atributos de la anotación @CacheEvict para especificar entradas de caché que eliminar.
</beans>
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
</aop: config>
</cach e: caching>
</cache: advice>
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
ccache: cache-put
method="save"
k ey ="#resu lt. id" />
c/cach e: caching>
c/cach e: advice>
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:
• @ 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 .
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
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.
Tabla 14.1. Spring Security 3.0 incluye cuatro nuevas anotaciones que pueden utilizarse
para proteger métodos utilizando expresiones SpEL
@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 .
}
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.
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
}
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.
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 ) { . . . }
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.
}
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
15 Trabajar con
se rv ic io s rem otos
CONCEPTOS FUNDAMENTALES:
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.
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.
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.
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
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;
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
}
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
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
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.
@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;
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).
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).
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.
¿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.
@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.
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.
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.
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
</wsdl:port>
</wsdl: service>
</wsdl: d efin itio 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:
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.
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".
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.
• 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.
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 {
@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) {
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:
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:
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.
@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.
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.
{
" s p i t t le L i s t ": [
{
"id": 42,
Crear API REST con Spring MVC 463
}
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.
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
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.
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.
[
{
"id": 42,
466 Capítulo 16
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.
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.
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 ;
@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) {
}
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.
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 .
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
@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.
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
@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.. .
ResponseEntity<Spittle> responseEntity =
new R esponseEntity<Spittle>(
s p i t t le , headers, H ttpStatus. CREATED)
return responseEntity;
}
}
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.
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
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.
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
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 ;
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;
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
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
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:
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
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).
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.
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.
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.
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.
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.
cbean id="connectionFactory"
class="org.apache. activemq.spring.ActiveMQConnectionFactory">
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>
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.
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.
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.
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.
JmsSecurityException JmsSecurityException
MessageNotWriteableException MessageNotWriteableException
ResourceAllocationException ResourceAllocationException
SynchedLocalTransactionFailedException Específica de Spring. Se genera cuando una
transacción local sincronizada no puede
completarse.
TransactionlnProgressException TransactionlnProgressException
TransactionRolledBackException TransactionRolledBackException
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;
@Autowired
public AlertServicelmpl(JmsOperations jmsOperatons) { // Inyectar plantilla JMS.
this.jmsOperations = jmsOperations;
);
}
502 Capítulo 17
Figura 17.5. JmsTemplate se encarga de gestionar el envío del mensaje en nombre del remitente.
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.
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 ,
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.
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
@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.
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.
Listado 17.6. AlertServicelmpl: un POJO sin JMS para procesar mensajes JMS.
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>
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.
¿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.
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.
Tabla 17.3. Elementos del espacio de nombres rabbit de Spring AMQP para crear colas,
intercambios y vinculaciones entre ambos.
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 " />
package com.habuma. s p i t t e r . a l e r t s ;
@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 ( ) :
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);
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") ;
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>
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" />
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:
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();
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 ()
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.
package marcopolo;
@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();
}
}
Listado 18.3. El espacio de nombres websocket habilita la configuración XML para WebSocket.
</websocket:handlers>
• 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
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);
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.
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
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
(»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 " ) ;
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 {
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.
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;
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
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 {
@Autowired
public SpittleFeedServicelm pl(
SimpMessageSendingOperations messaging) { / / Inyectar p la n tilla de
/ / m ensajería.
542 Capítulo 18
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.
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 );
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 {
@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 ) {
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( ) ) ;
}
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
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:
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.
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.
@Bean
public MailSender mailSender(Environment env) {
JavaMailSenderlmpl mailSender = new JavaMailSenderlmplO;
m ailSender.setH ost(env.getProperty("m ailserver.h o s t" )) ;
return mailSender;
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.
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 ) ;
Listado 19.2. Envío de mensajes de correo con archivos adjuntos con MimeMessageHelper.
@Bean
public VelocityEngineFactoryBean velocityEngine{) {
VelocityEngineFactoryBean velocityEngine =
new VelocityEngineFactoryBean();
<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.
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] ?
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.
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 );
@Autowired
public SpitterEmailServicelmpl(SpringTemplateEngine thymeleaf) {
t h i s .thymeleaf = thymeleaf;
}
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:
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.
}
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.
@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.
Descriptor
Name Value
Attribute:
descriptorType attribute
displayName SpittiesPerPage
getMethod getSpittiesPerPage
name SpittiesPerPage
setMethod setSpittlesPerPage
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
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.
@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;
package com.habuma.spitter.jmx;
@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.
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.
}
@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.
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
@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.
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");
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.
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.
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.
(»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.
Figura 20.5. Las notificaciones JMX permiten que los MBean se comuniquen con el mundo
exterior.
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:
}
}
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:
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.
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-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.
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.
<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>
<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")
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 .- / "
}
}
package co n tacts;
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")
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.
• /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 /
• / 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;
}
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
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
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
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).
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>
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.
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
• 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.
@Autowired
ContactRepository contactRepo / / Inyectar ContactRepository.
}
@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 .*
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.
cla ss ContactRepository {
@Autowired
JdbcTemplate jdbc / / Inyectar JdbcTemplate.
Simplificar el desarrollo de Spring con Spring Boot 601
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
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
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")
{
"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"
}.
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
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 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 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.
• 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 .
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