Spring Framework
Javier Sevilla Sánchez
Quiero dar las gracias a Sergio, Julián, Elena, Javier y especialmente a Nieves, por todo
su apoyo. A mis amigos, Fernando, Juan Carlos, Manuel, Miguel y a todo aquel que ya
sea empujándome o tendiéndome la mano para alzarme me hace aprender.
Capítulo 1:
Primeros pasos con Spring
[Escribir el subtítulo del documento] [Escriba aquí una descripción breve del documento. Una descripción breve es un resumen corto del contenido del documento. Escriba aquí una descripción breve del documento. Una descripción breve es un resumen corto del contenido del documento.]
Javier Sevilla Sánchez
7
Contenido Introducción al framework de Spring ............................................................................................ 8
Inyección de dependencia e inversión de control ........................................................................ 8
Módulos .................................................................................................................................... 8
El contenedor ........................................................................................................................ 9
Integración y acceso de Datos ............................................................................................. 10
Web ..................................................................................................................................... 10
AOP ...................................................................................................................................... 10
Test ...................................................................................................................................... 10
Primeros pasos con Spring .......................................................................................................... 11
Mi primer Hola mundo con Spring .......................................................................................... 11
Inyección de Dependencia ID (Dependency injection) ............................................................... 13
Inyección de dependencia en la práctica ................................................................................ 13
Programación orientada a aspectos............................................................................................ 16
¿Qué ventajas tenemos con AOP? .......................................................................................... 17
Resumen ...................................................................................................................................... 17
Introducción al framework de Spring
Spring es un framework de código abierto de desarrollo de aplicaciones para la plataforma
Java. La primera versión fue escrita por Rod Jonhson. Es una plataforma Java que otorga una
infraestructura de apoyo global al desarrollo de aplicaciones Java. De este modo, Spring se
encarga de la infraestructura para que nosotros nos centremos en la aplicación.
Unos ejemplos pueden ser:
• Inyección de dependencia e inversión de control.
• Integración del acceso a datos.
• Facilitar el desarrollo de aplicaciones web separando claramente las partes del
modelo, vista y controlador.
• Poder ejecutar métodos transaccionales en una base de datos sin necesidad de lidiar
con API de transacción, métodos remotos sin tener que lidiar con API de
procedimientos remotos, métodos de gestión sin JMX, control de mensajes sin JMS...
Inyección de dependencia e inversión de control
El término “Aplicación Java” es un término tan amplio que podría ir desde un applet hasta
aplicaciones empresariales en servidores de nivel n.
Java proporciona una gran cantidad de herramientas para desarrollar aplicaciones, pero carece
de medios para organizar los elementos. Normalmente es el arquitecto Java el que se encarga
de esta tarea pudiendo utilizar patrones.
La inversión de control de Spring lo que hace es preocuparse de proporcionar una manera
formal de creación de componentes dispares de una manera homogénea y de una manera
funcional. Codifica componentes que se integran en las aplicaciones.
Diversas instituciones y empresas eligen Spring como una manera de diseñar aplicaciones
robustas fáciles de mantener.
Módulos
El framework de Spring consiste en elementos organizados en veinte módulos. Estos módulos
se agrupan en el Contenedor (core container), Acceso a datos e integración, modelo vista
controlador (módulo web MVC), aspectos (AOP), instrumentación y test.
El siguiente diagrama muestra cómo se divide:
9
El contenedor
El contenedor consiste en un núcleo, objetos bean, un contexto y un lenguaje de expresiones.
El núcleo y los beans son la parte fundamental de Spring, incluyendo la inversión de control y
la inyección de dependencia. Este contenedor es una versión más compleja del patrón Factory.
Elimina la necesidad de programar singletons y permite desacoplar la configuración y
especificación de dependencias de la lógica de programación.
El contexto se construye sobre la sólida base del núcleo. Así permite determinadas
configuraciones. Así la internacionalización, propagación de eventos, lectura de recursos o la
creación de contextos (como el web) formarán parte de este módulo.
Los Lenguajes de expresión permiten una potente herramienta de consulta y manipulación de
un objeto en tiempo de ejecución. Es una extensión del “Unified EL”, especificado en la
especificación JSP 2.1. El lenguaje permite asignar y obtener valores de las propiedades,
asignar propiedades, invocar métodos, acceder al contexto de matrices, listas, índices,
operadores aritméticos y lógicos, variables, y obtención de objetos por nombre del contendor
de Spring.
Integración y acceso de Datos
La capa de Integración y acceso a datos consiste en la integración de los módulos JDBC, ORM,
OXM, JMS y de transacción.
El módulo JDBC otorga una capa de abstracción que elimina la necesidad de crear código
tedioso y trasforma las excepciones generadas por el proveedor de base de datos.
El módulo ORM otorga una integración con los APIS más populares de ORM como puedan ser
JPA, JDO, Hibernate o iBatis.
El módulo OXM otorga una capa de abstracción para el mapeo Objeto/XML en distintas
implementaciones como JAXB, Castor, XMLBeans, JiBX o XStream.
El módulo JMS contiene características para la producción y consumo de mensajes.
El módulo de Transacción permite transacciones programáticas y declarativas para las clases
que implementan interfaces especiales y para todos los POJO.
Web
La capa web consiste en los módulos Web, Web-Servlet, Web-Struts y Web-Portlet.
El módulo Web permite integración básica de características como la subida multiparte de un
fichero, la inicialización de la inversión de control del contenedor usando listeners Servlet y un
contexto de lógica web.
El módulo Servlet contiene la implementación modelo vista controlador. El framework Spring
MVC permite una separación entre el modelo, el código y los formularios web y se integra con
todas las otras características de Spring.
El módulo Web-Struts permite la integración de clases integrando Struts, actualmente este
soporte esta obsoleto en Spring 3.0.
El módulo Web-Portlet permite las características web en sistemas empotrados.
AOP
El módulo AOP de Spring permite una implementación de programación orientada a aspectos
permitiendo definir métodos e interceptores, puntos de corte, etc. Para desacoplar el código.
Permite la integración con AspectJ
Él módulo de instrumentación otorga instrumentación de clases así como un cargador de claes
a ser usadas en determinadas aplicaciones de servidor.
Test
11
El módulo de test permite probar las aplicaciones de Spring y los componentes con JUnit o
TestNG. Permite la carga consistente de contextos de Spring. Así se permiten objetos mock
que prueban tu código de manera aislada.
Primeros pasos con Spring
La Inyección de Dependencia es un patrón de diseño orientado a objetos, en el que se inyectan
objetos a una clase en lugar de ser la propia clase quien cree objetos mediante constructores.
Como para muestra un botón, haremos un ejemplo “Hola Mundo” en el que se plasmarán los
conceptos básicos.
Empezaremos creando una clase interfaz “Saludo”, a pesar de que no es necesario para el
ejemplo ni para el uso de Spring, es una buena práctica ya que potencia el uso de la inyección
de dependencia, hace más fácil las pruebas y aumenta la abstracción entre otras ventajas.
Mi primer Hola mundo con Spring
Interfaz Saludo
package es.uah.tfc.javiersevilla.holamundo; public interface Saludo { String getSaludo(); void setSaludo(String saludo); void saluda(); }
Vemos cómo esta interfaz hace que cualquier clase que la implemente cumpla con la
característica de los beans de tener métodos getters y setters. El siguiente paso es crear una
clase que implemente esa interfaz, la llamaremos SaludoImp. Crearemos un constructor vacío
al igual que un constructor con argumento para poder ilustrar las dos maneras que tiene
Spring de inyectar dependencias a un Bean.
Implementación SaludoImpl
package es.uah.tfc.javiersevilla.holamundo; public class SaludoImpl implements Saludo { private String saludo; public SaludoImpl(String saludo){ this.saludo = saludo; } public SaludoImpl(){ } public String getSaludo() {
return this.saludo; } public void setSaludo(String saludo) { this.saludo = saludo; } public void saluda() { System.out.println(saludo); } }
Hay que hacer hincapié en que en ningún momento se instancia un objeto para el atributo
saludo de la clase String sino que éste será inyectado.
Para comprender esto veamos el siguiente fichero de configuración xml.
Fichero de configuración XML
<?xml version ="1.0" encoding ="UTF-8" ?> <beans xmlns ="http://www.springframework.org/schema/beans" xmlns:xsi ="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation ="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/ spring-beans-2.0.xsd" > < bean id ="saludoMetodo" class ="es.uah.tfc.javiersevilla.holamundo.SaludoImpl" > < property name="saludo" value ="¡¡Hola mundo!! (método)" /> </ bean > < bean id ="saludoConstructor" class ="es.uah.tfc.javiersevilla.holamundo.SaludoImpl" > < constructor-arg value ="¡¡Hola mundo!! (constructor)" /> </ bean > </ beans >
Este fichero será el que Spring utilice para la creación de Beans, vemos como se crean dos uno
haciendo uso de la inyección por parámetro y otro haciendo uso de la inyección por
constructor.
Ya están listos todos los componentes, sólo falta ver la puesta en marcha. Para ello crearemos
una clase con un método main.
package es.uah.tfc.javiersevilla.holamundo; import org.springframework.context.ApplicationConte xt; import org.springframework.context.support.ClassPat hXmlApplicationContext; public class HolaMundo { public static void main(String[] args) { ApplicationContext ctx = new ClassPathXmlApplicationContext("holamundo.xml"); Saludo saludoConstructor = (Saludo) ctx.getBean("saludoConstructor");
13
saludoConstructor.saluda(); Saludo saludoMetodo = (Saludo) ctx.getBean("salud oMetodo"); saludoMetodo.saluda(); } }
Fijémonos en el código, vemos como ApplicationContext es el contenedor el cual hemos
cargado con el fichero XML. Este contenedor tiene los beans definidos en el fichero y podemos
acceder a ellos mediante su identificación.
Con este ejemplo queda esbozado el concepto de inyección de dependencia, pasemos a
explicar este término de un modo más amplio.
Inyección de Dependencia ID (Dependency injection)
El anterior ejemplo es algo muy sencillo, las aplicaciones no son como el HolaMundo sino que
suelen estar compuestas por diversas clases que operan de forma conjunta para un fin.
Tradicionalmente eran los propios objetos los responsables de obtener las referencias a otros
objetos con los que colaboraba haciendo un código muy acoplado y muy poco “testeable”. La
inyección de dependencia consiste en que estas dependencias las otorgue una entidad externa
inyectándolas en los objetos. Así la responsabilidad se delega en la entidad externa, haciendo
clases más simples.
Si un objeto sólo conoce sus dependencias mediante interfaces bien definidas, podremos
cambiar la implementación del objeto del que depende sin que se sepa la diferencia es decir
tendremos acoplamiento débil.
Inyección de dependencia en la práctica
Imaginemos que nos piden una aplicación de gestión para la universidad, en ella habría
alumnos, profesores, titulaciones, cursos, asignaturas, temarios, apuntes… etc. Pero de forma
sencilla y muy resumida diremos que un estudiante estudia una carrera y al finalizar obtiene
un título. Bajo esta premisa diríamos que el estudiante es el responsable de matricularse en la
carrera, estudiar y así obtener el título.
Para recrear este escenario usando la inyección de dependencia empezaríamos definiendo
claramente las interfaces que entren en juego como puedan ser Alumno, Carrera y Diploma.
Habrá relación entre ellas, pero debemos de crear un código en el cual las clases que
implementen estos interfaces conozcan lo mínimo posible de las clases de las cuales
dependen, es decir, los objetos sólo han de conocer el interfaz y su implementación ha de ser
inyectada en las clases dependientes.
Código del Interface Carrera:
package es.uah.uahdi.model;
public interface Carrer { public void setName(String carrerName); public void setUniversityName(String universityName ); public Certificate graduate();
}
Código del Interface Alumno:
package es.uah.uahdi.model; public interface Student { public void setCarrer(Carrer carrer); public String getName(); public void setName(String name); public Certificate study(); }
Código del Interface Diploma:
package es.uah.uahdi.model;
public interface Certificate { public String getCarrerName(); public void setCarrerName(String carrerName); public String getUniversityName(); public void setUniversityName(String university Name); }
Código de la implementación de Carrera:
package es.uah.uahdi.model; public class CarrerImp implements Carrer { private String carrerName; private String universityName; public void setName(String carrerName) { this.carrerName = carrerName; } public void setUniversityName(String universityNam e) { this.universityName = universityName; } public Certificate graduate() { return new UniversityCertificate(carrerName, univ ersityName); } }
15
Código de la implementación de Alumno:
package es.uah.uahdi.model; public class StudentImp implements Student { private String name; private Carrer carrer; public StudentImp() { } public Carrer getCarrer() { return carrer; } public void setCarrer(Carrer carrer) { this.carrer = carrer; } public String getName() { return name; } public void setName(String name) { this.name = name; } public Certificate study() { return this.carrer.graduate(); } }
Código de la implementación de Diploma:
package es.uah.uahdi.model; public class UniversityCertificate implements Certi ficate{ private String carrerName; private String universityName; public UniversityCertificate(String carrerName, Str ing universityName) { this.carrerName = carrerName; this.universityName = universityName; } public String getCarrerName() { return this.carrerName; } public void setCarrerName(String carrerName) { this.carrerName = carrerName; } public String getUniversityName() { return universityName; } public void setUniversityName(String universityName ) { this.universityName = universityName; } }
Fichero de configuración de Spring:
<?xml version ="1.0" encoding ="UTF-8" ?> <beans xmlns ="http://www.springframework.org/schema/beans" xmlns:xsi ="http://www.w3.org/2001/XMLSchema-instance" xmlns:aop ="http://www.springframework.org/schema/aop" xmlns:jee ="http://www.springframework.org/schema/jee" xsi:schemaLocation ="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/s pring-beans-2.0.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/sp ring-aop-2.0.xsd" > < bean id ="itig" class ="es.uah.uahdi.model.CarrerImp" > < property name="universityName" value ="Universidad de Alcalá" /> < property name="name" value ="Ingeniería Técnica en Informática de Gestión" /> </ bean > < bean id ="juanito" class ="es.uah.uahdi.model.StudentImp" > < property name="name" value ="Juan Aurelio Gonzalez" /> < property name="carrer" ref ="itig" /> </ bean > </ beans >
En el fichero de configuración vemos como hemos declarado dos beans el primero con el
nombre “itig” el cual tiene dos propiedades y el otro “juanito” que tiene inyectado el bean
“itig”.
La clase principal cargará el contexto, obtendrá el bean juanito y obtendrá un diploma al
ejecutar el método de estudiar de juanito. Es importante destacar que gracias a que hemos
creado interfaces y a su vez implementaciones de estas hemos podido crear un código más
mantenible y reutilizable ya que cada implementación no conoce las implementaciones de las
demás, tan sólo conoce su interfaz. Esto hace un código menos acoplado y probable de
manera unitaria.
Programación orientada a aspectos
La programación orientada a aspectos hace que las funcionalidades de los componentes de las
aplicaciones sean más reutilizables. La programación orientada a aspectos (AOP) es una
técnica de programación que promueve que los sistemas estén bien divididos por
incumbencias. Así cada componente es responsable de una parte (gestión de transacciones,
gestión de registros, gestión de seguridad, gestión de de trazas… etc.) . Sin embargo,
típicamente los componentes acaban llevando funcionalidades que están fuera de su función y
que hacen que aumente la complejidad del código. Los principales problemas son que el
código que implementa las incumbencias del sistema se repite por cada componente que lo
necesite haciendo que si hay un cambio se tendrá que realizar en cada módulo que lo
17
implemente, en vez de en un sitio sólo y que los componentes tienen código que no es de su
funcionalidad principal.
¿Qué ventajas tenemos con AOP?
Como hemos explicado, sin AOP cada componente conoce las funcionalidades de los
demás, introduciendo complejidad adicional a su funcionalidad principal, como resultado, los
objetos empresariales están más implicados con los sistemas de servicios.
AOP hace posible poner en módulos estos servicios y después aplicarlos de manera declarativa
a los componentes que deberían afectar aumentando así la cohesión y haciendo que los POJO
se mantengan simples. Así los aspectos “envuelven” a los demás componentes haciéndolos
más sencillos evitando ensuciarles con lógica de transacciones, seguridad o trazas.
Siguiendo con la línea de la aplicación de la universidad, hagamos un ejemplo que clarifique
este concepto.
Resumen
En este capítulo hemos presentado Spring, viendo que es un framework que facilita la creación
de aplicaciones haciéndolas más comprensibles, desacopladas y fáciles de mantener. Hemos
presentado sus principales componentes haciendo una breve visión de lo que Spring puede
hacer por nosotros.
Hemos hecho una primera visión de los módulos en los que se divide Spring, Estos módulos se
agrupan en el Contenedor (core container), Acceso a datos e integración, modelo vista
controlador (módulo web MVC), aspectos (AOP), instrumentación y test.
Más tarde, a modo de prueba, hemos creado nuestra primera aplicación con Spring, viendo de
una manera más práctica su funcionamiento. Hemos hecho una breve introducción a la
inyección de dependencia así como a la programación orientada a aspectos.
21
[Escriba el título del documento] [Escriba el subtítulo del documento] [Escriba aquí una descripción breve del documento. Normalmente, una descripción breve es un resumen corto del contenido del documento. Escriba aquí una descripción breve del documento. Normalmente, una descripción breve es un resumen corto del contenido del documento.] Javier Sevilla Sánchez
[Seleccione la fecha]
23
Contenido
Conexión de Beans ...................................................................................................................... 24
Introducción ............................................................................................................................ 24
Bean dentro de un contenedor ............................................................................................... 24
Contenedor de beans .............................................................................................................. 24
BeanFactory ............................................................................................................................ 25
ApplicationContext .................................................................................................................. 25
Pasos de la vida de un Bean ................................................................................................... 26
Ejemplo de creación y conexión de beans .............................................................................. 27
Conectar tipos múltiples ......................................................................................................... 31
Tipos de conexión de beans ........................................................................................................ 34
Auto-conexión byName .......................................................................................................... 34
Auto-conexión byType ............................................................................................................ 35
Auto-conexión constructor ..................................................................................................... 35
Auto-conexión autodetect ...................................................................................................... 35
La auto-conexión default ........................................................................................................ 36
La Auto-conexión no ............................................................................................................... 36
Configuración de la creación de los beans .................................................................................. 37
Delimitación de un bean ......................................................................................................... 37
Creación de beans con clases singleton .................................................................................. 38
Uso de init-method y de destroy-method (inicializar y destruir) ............................................ 38
Conexión de Beans
Introducción
En el capítulo anterior hemos dado los primeros pasos con Spring, hemos visto por primera vez
la inyección de dependencia (DI) y la programación orientada a aspectos (AOP) creando unos
sencillos proyectos a modo de ejemplo.
A lo largo de este capítulo veremos más en profundidad el contenedor de Spring y sus distintas
implementaciones. Las principales categorías de las implementaciones son la factoría de beans
y el contexto de aplicación. En este capítulo veremos cómo conectar beans dentro del
contenedor y qué información daremos al fichero XML para crear una aplicación con una DI
potente.
Bean dentro de un contenedor
A diferencia de Java tradicional en el que el ciclo de vida del objeto es desde su instanciación
con new hasta su etiquetado por el recolector, en Spring el ciclo de vida es más complejo.
Aunque dependa de si el bean ha sido creado por una factoría o un contexto, el bean pasa por
una serie de pasos entre los que se encuentran la instanciación, la asignación de propiedades y
nombre, nombre de la fábrica, procesamiento, iniciación…etc. Así hasta que se ejecuta el
método destroy() sobre él.
Contenedor de beans
Como ya dijimos, en el enfoque tradicional las asociaciones llevan a código complejo y no
reutilizable ni “testeable”. En Spring el contenedor es el responsable de la interconexión de
beans.
El contenedor será el responsable de la inyección de dependencia y del ciclo de vida de los
beans, creándolos, interconectándolos y destruyéndolos.
Spring tiene diversos contenedores, pero responden a dos grandes grupos según la
implementación del interfaz BeanFactory o AplicationContext.
25
BeanFactory
La implementación más usual es la XmlBeanFactory al cual se le pasa un objeto que
implemente Resource (hay ocho implementaciones) que proporcionará el XML con las
definiciones de los beans.
Es el contenedor más básico, pero es más que la instanciación y entrega de beans. Cuando una
fábrica entrega un bean lo entrega configurado, consciente de sus dependencias y listo para
usar.
El siguiente ejemplo crea un XmlBeanFactory como implementación de BeanFactory con un
FileSystemResource como implementación de Resource y obtiene el bean llamado
beanEjemplo.
BeanFactory factoria = new XmlBeanFactory(new FileSystemResource(“/home/user/spring/beans.xml”)); BeanExample beanEjemlo = (BeanExample) factory.getB ean(“beanEjemplo”);
ApplicationContext
Las aplicaciones creadas con Spring con normalidad usan implementaciones de
AplicationContext en vez de BeanFactory. Esto es debido a que AplicationContext tiene
funcionalidades adicionales dejando la BeanFactory para aplicaciones con recursos más
limitados como pueda ser un móvil, o sistemas empotrados.
Otra diferencia es cómo abren los beans. Mientras la factoría crea el bean cuando se llama a
getBean() el contexto abre previamente todos los beans.
ApplicationContext tiene la posibilidad de internacionalizar aplicaciones (I18N), generalizar la
apertura de recursos de archivo así como otorgar la posibilidad de publicar eventos para beans
receptores.
De las diversas implementaciones las más comunes son ClassPathXmlApplicationContext,
FileSystemXmlApplicationContext y XmlWebApplicationContext. Ésta última la trataremos más
adelante en el capítulo destinado a Spring MVC.
La diferencia entre FileSystemXmlApplicationContext y ClassPathXmlApplicationContext es que
el primero buscará en el sistema de archivos y el segundo según el path de la clase incluyendo
archivos jar.
Ejemplo de ello sería:
ApplicationContext ctx = new ClassPathXmlApplicatio nContext(“miFichero.xml”);
Pasos de la vida de un Bean
Como ya hemos visto, un bean en Spring tiene una vida más compleja que en Java puro. Sus
pasos son:
1. Instanciar: Spring instancia el bean.
2. Informar de las propiedades: Spring hace la inyección a las propiedades.
3. Asignación del nombre: Spring pasa el nombre del bean si este implementa
BeanNameAware.
4. Establecer nombre de la fábrica y el contexto: Si el bean implementa
BeanNameAware le pasará el nombre de la fábrica y en el caso de implementar
ApplicationContextAware y de estar contenido en un contexto se le pasaría el
nombre del contexto.
5. Iniciación del Bean: Si el bean implementa InitializingBean, se llamaría a su
método afterPropertiesSet(). Existen dos métodos que se llamarían si hubiese
algún BeanPostProccessor, uno antes de la inicialización y otro después.
6. Estado de listo para su uso: Tras esto, el bean ya estaría listo para usarse.
7. Destrucción del bean: Si el bean implementa DisposableBean se llamará al método
destroy().
27
Ejemplo de creación y conexión de beans
Hasta ahora hemos visto distintos proyectos con Spring que creaban y usaban beans, pero no
habíamos entrado en detalle en la explicación de estas conexiones.
Existe un eterno debate de cuál es la mejor opción para la inyección de dependencias si por
constructor o por el contrario por setter. Como para casi todos los debates la respuesta es
“depende”.
Inyección por constructor
Por un lado la inyección por constructor crea una fuerte dependencia con el instanciador ya
que este ha de tener todas las dependencias antes, así garantiza que un bean está
completamente configurado antes de usarse. Si se inyecta por constructor es posible que no
sean necesarios métodos setter, con lo que la clase será más sencilla e inalterables sus
dependencias.
Inyección por setter
Sin embargo, la inyección por constructor tiene también sus inconvenientes. Si el bean tiene
muchas dependencias el constructor será complejo, si existen varios parámetros del mismo
tipo pues puede confundir su instanciación y acaba siendo más complejo cuando la clase
hereda. La configuración por setter puede realizar construcciones más complejas.
Spring permite ambas configuraciones, con lo que no restringe a una forma la inyección.
Ejemplo de Conexión
Continuando con la filosofía de anteriores ejemplos, veamos cómo se conectan beans
tomando como ejemplo de nuevo un escenario universitario. Al inicio del curso, los
responsables de hostelería han visto que este año habrá más alumnos, con lo que han decidido
contratar a más gente. Para el puesto en cuestión se valoraran distintas cualidades como saber
cocinar, limpiar, el trato con la gente… etc.
El código del interfaz trabajador sería el siguiente:
public interface Worker { void work() throws WorkingException; }
La siguiente clase define los candidatos camareros:
package es.uah.uahconnection.cafe.model; public class Waiter implements Worker { private int coffeesServedDaily = 1; public Waiter() { } public Waiter(int coffeesServedDaily) {
this.coffeesServedDaily = coffeesServedDail y; } public void work() throws WorkingException { System.out.println("el camarero está sirvie ndo " + coffeesServedDaily + " cafés"); } }
Veamos cuál sería la implementación más básica de camarero:
<bean id="joselito" class="es.uah.uahconnection.caf e.model.Waiter"/>
Esta es la definición más básica de un bean, tan sólo se le da un identificador y la clase a la que
pertenece el objeto. De los dos constructores que tienen los camareros, Joselito será creado
con el constructor sin parámetros. Tal y como vemos no es muy complicado que se te
considere como camarero, con servir un café al día vale. Pero no creo que Joselito consiga el
trabajo con tan poco esfuerzo.
Pepe viene con más entusiasmo, él asegura que es capaz de servir al menos 30 cafés al día.
Para ello hacemos uso del constructor con parámetro, en Spring el modo de definirlo es así:
<bean id="pepe" class="es.uah.uahconnection.cafe.model.Waiter "> <constructor-arg value="30"/> </bean>
Con la etiqueta <constructor-arg> se pasan los parámetros que el constructor precise siempre
que coincidan en número, tipo y posición así obtenemos la inyección mediante constructor.
Pepe es capaz de servir 30 cafés, pero hay competidores más preparados. Alberto es capaz de
poner el mismo número de cafés mientras limpia el local. Veamos como es el código del
camarero-limpiador.
public class WaiterDustman extends Waiter { private Local local; public WaiterDustman(int coffeesServedDaily, Re staurant restaurant) { super(coffeesServedDaily); this.local = restaurant; } @Override public void work() throws WorkingException{ super.work(); local.clean(); } }
Siendo el interfaz local y su implementación restaurante la siguiente:
interface Local { void clean(); }
29
public class Restaurant implements Local { public void clean() { System.out.println("El ha sido limpiado"); } }
La definición del bean Alberto que haremos en Spring le inyectaremos otro bean
“cafeteriaPolitecnica” el cual es de la clase restaurante.
<bean id="alberto" class="es.uah.uahconnection. cafe.model.WaiterDustman"> <constructor-arg value="25"/> <constructor-arg ref="cafeteriaPolitecnica" /> </bean>
Lo que Spring internamente ejecutaría sería algo similar a esto:
Local restaurant = new Restaurant(); Worker alberto = new WaiterDustman(coffeesS ervedDaily, restaurant);
Como hemos visto en anteriores ejemplos y hemos comentado al principio de este punto,
Spring permite configurar dependencias con los métodos setter como es el caso del cocinero y
su receta.
public class Kitchener implements Worker { private Recipe recipe;//receta private int diners;//comensales public Kitchener() { } public void setRecipe(Recipe recipe) { } public void setDiners(int diners) { this.diners = diners; } public void work() throws WorkingException { System.out.println("Se dispone a cocinar " + recipe.getName()); recipe.develop(); } }
La interfaz receta sería:
public interface Recipe { void develop(); public String getName(); }
Y la clase paella valenciana sería la siguiente:
public class PaellaValenciana implements Recipe {
private static final String NAME = "Paella vale nciana"; public void develop() { fryTheMeat(); sauteVegetables(); pourWater(); addRice(); } public String getName() { return NAME; } public void fryTheMeat(){ System.out.println("La carne se está friend o"); } public void sauteVegetables(){ System.out.println("las verduras se están r ehogando"); } public void pourWater(){ System.out.println("se vierte agua"); } public void addRice(){ System.out.println("se añade el arroz"); } }
Ernesto es un buen cocinero cuya especialidad es la paella valenciana receta de Arguiñano. La
definición de Ernesto así como de su paella valenciana sería la siguiente.
<bean id="paellaValencianaArguiñano" class="es.uah.uahconnection.cafe.model.PaellaVa lenciana"/> <bean id="ernesto" class="es.uah.uahconnection. cafe.model.Kitchener"> <property name="recipe" ref="paellaValencia naArguiñano"/> <property name="diners" value="6"/> </bean>
Como vemos en el código podemos asignar valores mediante el tag <property>. Esto sólo será
así si en la clase tenemos un método setter para tal propiedad. Para inyectar valores simples se
hará con el campo value mientras que para inyectar un bean definido en Spring se hará
mediante el campo ref. Spring sabrá si los valores simples son de tipo numérico, cadena o
booleano. Para definir a Ernesto se ha usado una inyección de bean y otra de valor simple, se
ha pasado la receta de paella que aprendió de Arguiñano y el número de comensales para el
que es capaz de cocinar.
Inyectar beans internos
Los beans internos, al igual que pasaría con las clases declaradas de manera interna dentro de
otra clase, son Beans definidos dentro del rango de bean. El siguiente aspirante al puesto de
trabajo es también cocinero, pero su receta de la paella valencia la guarda celosamente, no
quiere compartirla con el resto.
<bean id="alfredo" class="es.uah.uahconnection. cafe.model.Kitchener"> <property name="diners" value="5"/> <property name="recipe">
31
<bean class="es.uah.uahconnection.cafe. model.PaellaValenciana"/> </property> </bean>
Si la clase cocinero pudiera configurarse por constructor también se podría crear un bean
interno de la siguiente manera:
<bean id="alfredo" class="es.uah.uahconnection. cafe.model.Kitchener"> <constructor-arg value="5"/> <constructor-arg> <bean class="es.uah.uahconnection.cafe. model.PaellaValenciana"/> </constructor-arg> </bean>
Conectar tipos múltiples
Hemos visto como en Spring se inyectan dependencias mediante los operadores value y ref.
Sin embargo se pueden inyectar tipos múltiples como List (cuando sea una lista), Set (una lista
sin duplicados), Map (clave y valor de cualquier tipo) o Properties (clave y valor de tipo
cadena).
Saber cocinar un plato está bien, pero lo útil sería poder acumular un gran número de recetas
para poder ser un buen Chef. La clase Chef refleja esto.
public class Chef implements Worker { private Collection<Recipe> recipes;//también un Array o una lista private int diners;//comensales public Chef() { } public void setRecipe(Recipe recipe) { } public void setDiners(int diners) { this.diners = diners; } public void work() throws WorkingException { for (Recipe recipe : recipes) { System.out.println("Se dispone a cocina r " + recipe.getName()); recipe.develop(); } System.out.println("Todo ello para " + dine rs + " comensales"); } }
Raimundo es un buen Chef, ha estudiado varias recetas. Veamos su definición.
<bean id="raimundo" class="es.uah.uahconnection .cafe.model.Chef"> <property name="recipes"> <list> <ref bean="paellaValencianaArguiñan o"/> <ref bean="tortillaDePatatas"/> <ref bean="pizzaItaliana"/> </list>
</property> <property name="diners" value="50"/> </bean>
La clase de la propiedad recipes podría ser también una lista o incluso un array al igual que a la
hora de definirlo podríamos haberlo hecho con la etiqueta <set> en vez de <list>, esto
aseguraría que no hubiese repetidos.
El trato con la gente es algo muy valorado, ya que todo cliente estará más contento si alguien
le atiende de una manera amable. Borja Mari es un poco pedante, pero cae bien. Mientras
sirve cafés suele amenizar con frases amables. Borja Mari pertenece a la clase camarero
amable cuyo código es el siguiente:
public class KindWaiter extends Waiter { private Properties kindSpeach; public void setKindSpeach(Properties kindSpeach ) { this.kindSpeach = kindSpeach; } @Override public void work() throws WorkingException { super.work(); System.out.println("Mientras da una buena c onversación a sus clientes: "); for (Iterator it = kindSpeach.keySet().iter ator(); it.hasNext();) { String speachType = (String) it.next(); System.out.println(" - para " + speac hType + " dice \"" + kindSpeach.getProperty(speachTy pe) + "\""); } } }
Para poder definirlo en el contenedor de Spring incluiremos el siguiente código en el fichero
xml.
<bean id="borjaMari" class="es.uah.uahconnectio n.cafe.model.KindWaiter"> <property name="kindSpeach"> <props> <prop key="buenosDias">Buenos días, que bonita mañana hace hoy</prop> <prop key="buenasTardes">Buenas y m aravillosas tardes</prop> <prop key="servir">Aquí está su caf é</prop> <prop key="cuenta">Le traigo su cue nta enseguida</prop> <prop key="despedirse">Muchas graci as y vuelva otra vez</prop> </props> </property> </bean>
Hasta ahora todos nuestros aspirantes al puesto vacante para la cafetería de la universidad
han sido muy buenos, pero también se valoran dotes de liderazgo. El señor Antunez es un
buen coordinador y no sólo trabaja sino que sabe organizar a los demás. Para la definición de
la clase coordinador utilizaremos un mapa en el cual queden reflejados los nombres de los
trabajadores para que el señor Antunez pueda referirse por su nombre (la clave de la clase
Map) y el propio trabajador.
33
La clase Jefe refleja lo comentado:
public class Boos implements Worker { private Map<String, Worker> employees; public void work() throws WorkingException { for (String name : employees.keySet()) { System.out.println("Organiza el trabajo para " + name); Worker worker = employees.get(name); worker.work(); } } public void setEmployees(Map<String, Worker> em ployees) { this.employees = employees; } }
Su definición en el fichero xml sería la siguiente
<bean id="antunez" class="es.uah.uahconnection. cafe.model.Boos"> <property name="employees"> <map> <entry key="Alberto" value-ref="alb erto"/> <entry key="Alfredo" value-ref="alf redo"/> <entry key="Ernesto" value-ref="ern esto"/> <entry key="Joselito" value-ref="jo selito"/> <entry key="Pepe" value-ref="pepe"/ > <entry key="Raimundo" value-ref="ra imundo"/> <entry key="BorjaMari" value-ref="b orjaMari"/> </map> </property> </bean>
Tipos de conexión de beans
Existen dos vías para conectar dependencias de beans, la manual y la automática. La manual es
la que hemos ido viendo en los ejemplos que hemos hecho hasta ahora ya sea vía constructor
o vía setter.
La principal razón por la que existe la auto conexión es la reducción de código xml haciéndolo
más simple y más vistoso. Para ello tenemos cuatro tipos distintos de auto conexión, por
nombre, por tipo, por constructor y otra última que combina el constructor y el tipo.
Si tenemos definido un bean cuyo id en el contenedor tenga el mismo nombre que una
propiedad del bean que estamos definiendo podemos usar la auto conexión por nombre
(byName). La auto conexión por tipo nos será útil cuando haya un único bean del mismo tipo
que la propiedad a conectar, si hubiese más se lanzaría una excepción y si no hubiese la
propiedad tendría null.
También se le puede indicar al bean que intente auto conectarse por constructor intentando
que Spring haga corresponder los beans del contenedor con los constructores. Si hubiese
ambigüedades se lanzaría una excepción.
Por último existe la auto conexión con auto detección, esto sería una mezcla de la auto
conexión por tipo y por constructor.
Hay que comentar que también se puede conectar tipos null, gracias a la etiqueta <null/>, esto
es útil aquí en la auto conexión, ya que es posible que no queramos que determinadas
propiedades se conecten.
La autoconexión y la conexión especificada pueden perfectamente convivir. También hay que
decir que hay otro debate si es útil la auto-conexión o no.
Auto-conexión byName
Si el nombre que le damos a un bean coincide con el nombre de la propiedad, éste se auto-
conectará con la propiedad si lleva el campo autowire=”byName”. Como ejemplo podríamos
cambiar la definición de estudiante y carrera del ejemplo de la Inyección de dependencia:
<bean id="itig" class="es.uah.uahdi.model.CarrerImp"> <property name="universityName" value="Univ ersidad de Alcalá de Henares"/> <property name="name" value="Ingeniería Téc nica en Informática de Gestión"/> </bean> <bean id="juanito" class="es.uah.uahdi.model.StudentImp"> <property name="name" value="Juan Aurelio G onzalez"/>
35
<property name="carrer" ref="itig"/> </bean>
Por el siguiente código:
<bean id="carrer" class="es.uah.uahdi.model.CarrerImp"> <property name="universityName" value="Univ ersidad de Alcalá de Henares"/> <property name="name" value="Ingeniería Téc nica en Informática de Gestión"/> </bean> <bean id="juanito" class="es.uah.uahdi.model.StudentImp" autowire="byName"> <property name="name" value="Juan Aurelio G onzalez"/> </bean>
Cuando asignamos la auto-conexión por nombre estamos diciéndole a Spring que busque en el
contenedor beans no los mismo nombres que las propiedades. Las propiedades que definamos
de la manera convencional no se auto-conectarán. El ejemplo sólo podríamos auto-conectar la
misma carrera, si quisiésemos inyectar más beans de distinto tipo en otros lo tendríamos que
hacer sin auto-conexión.
Auto-conexión byType
La auto-conexión por tipo es similar a por nombre. Su funcionamiento es que Spring busca
beans del mismo tipo de la propiedad y las inyecta. En el caso de que Spring encuentre varias
se lanzará una excepción de tipo UnsatisfiedDepedencyException. Si en el ejemplo anterior
sólo podíamos auto-conectar aquellas propiedades cuyo nombre fuese el mismo que el del
bean y si queríamos conectar otras de distinto nombre tenía que ser con el método normal,
con la auto-conexión por tipo no podremos conectar ningún otro bean del mismo tipo. Es
decir, la propiedad que tenga autowire=”byType” obliga a que no haya más beans de la misma
clase en el contenedor.
<bean id=”exampleBean” class=”example.ExampleClass” autowire=”byType”/>
Auto-conexión constructor
La auto-conexión por constructor tiene las mismas limitaciones que la auto-conexión por tipo
pero sólo para las propiedades del constructor, Spring no intentará adivinar que bean auto-
conectar si hay más de uno del mismo tipo.
<bean id=”exampleBean” class=”example.ExampleClass” autowire=”constructor”/>
Auto-conexión autodetect
La conexión por auto-detección hace que Spring primero intente conectar mediante
constructor y luego mediante tipo.
<bean id=”exampleBean” class=”example.ExampleClass” autowire=”autodetect”/>
La auto-conexión default
Si en el tag de beans configuramos el campo default-autowire los beans que definamos en su
cuerpo se autoconectarán de esa forma. También se puede incluir explícitamente en el tag del
bean el valor default.
<beans default-autowire=”byType”> … </beans>
Si se especifica en el propio bean algo distinto se hará de esa manera.
La Auto-conexión no
Si se especifica en la propiedad del bean autowire=”no” no se hará autoconexión, incluso si en
el tag beans indiquemos lo contrario en el campo default-autowire, no se hará.
37
Configuración de la creación de los beans
A Spring se le pueden dar distintas directrices para alterar la simple creación del bean como
hasta ahora hemos hecho en los ejemplos. Las opciones serían las siguientes:
• Inicializar un bean una vez ya creado y ejecutar código antes de su destrucción.
• Crear beans desde métodos estáticos de fábrica en vez de constructores públicos.
• Controlar el número de instancias de un bean. Una instancia por sesión, por petición,
por cada uso o por aplicación.
Delimitación de un bean
Si no se especifica nada, Spring instancia cada bean de forma única, por cada petición siempre
se entrega el mismo bean. Con el campo prototype del tag bean podemos alterar este
comportamiento.
Las delimitaciones de bean de Spring permiten declarar el límite bajo en el que se crean los
beans sin tener que codificar las reglas de limitación en la clase del bean.
Al igual que pasaba con bean, el término Singleton en Spring no es sinónimo del aplicado
tradicionalmente en java. Como veremos, a diferencia de éste, en Spring no son otorgados por
la obligatoriedad de la clase en sí, sino que Spring otorga este diseño de manera ajena a la
propia clase.
Típo de límite
Singleton Limita a una única instancia por contenedor.
Es la opción predeterminada.
Prototype Permite que un bean sea instanciado
cualquier número de veces (se instanciará
cada vez que se use).
Request En Spring MVC es usado para que el bean se
instanciado una vez por petición HTTP
(request).
Session Al igual que request es usado en entorno web
y la instancia del bean durará lo mismo que
dure la sesión en el servidor.
Global-session Limita el bean a una sesión global en el
servidor en entornos web.
Dependiendo de las necesidades del sistema de información, unas veces será útil dejar la
configuración por defecto, es decir, la instanciación singleton y otras veces será útil tener
varias instancias con prototype. Con respecto al servidor web, más tarde veremos todas las
utilidades que podemos encontrar para la construcción de distintos beans.
Creación de beans con clases singleton
Esto es útil si se están usando clases de terceras partes cuyos métodos públicos sean estáticos.
Es decir, para la inclusión de clases tipo singleton dentro del contenedor de Spring. En Spring,
como ya hemos dicho, por defecto se crea una instancia de cada bean, pero no tiene por qué
ser de cada clase. Continuando con el anterior ejemplo, cambiemos la definición de la cafetería
de la universidad, ya que siempre va a ser la misma.
package es.uah.uahconnection.cafe.model; public class Restaurant implements Local { public void clean() { System.out.println("El restaurante ha sido limpiado"); } private static class RestaurantSingletonHolder { static Restaurant instance = new Restaurant (); } public static Restaurant getInstance() { return RestaurantSingletonHolder.instance; } }
En el anterior ejemplo hemos definido una clase estática interna cuya función es devolver una
instancia de la clase restaurante, cumpliendo el patrón de diseño tradicional de singleton. Para
instanciar correctamente este bean en Spring le deberemos especificar en su definición el
campo factory-method el método de instanciación. El código xml sería el siguiente.
<bean id="cafeteriaPolitecnica" class="es.uah.uahconnection.cafe.model.Restaurant" factory-method="getInstance"/>
Uso de init-method y de destroy-method (inicializar y destruir)
Es posible que queramos ejecutar cierto código de inicialización o de destrucción al instanciar
un bean, ya sea para ponerlo en un estado en particular o para eliminar o limpiar ciertas
vicisitudes. Para ello (y para todo aquello que se nos pueda ocurrir), tenemos la posibilidad de
configurar dos campos en la declaración. Éstos son init-method y destroy-method. El primero
ejecutará el código del método al inicio y el segundo al final. Un ejemplo del código xml sería el
siguiente:
39
<bean id="exampleBean" class="exampleClass" init-method="initExample" destroy-method="cleanUpExample"/>
También si tenemos varios beans a los cuales tenemos que inicializar y todos ellos tienen el
mismo nombre de método (usualmente init y clean) podríamos definirlo por defecto en el tag
de beans como muestra el código de ejemplo.
<beans xmlns="http://www.springframework.org/schema /beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-inst ance" xsi:schemaLocation="http://www.springframework.o rg/schema/beans http://www.springframework.org/schema/beans/ spring-beans-2.0.xsd" default-init-method="init" default-destroy-method="clean">
Continuando con el ejemplo de la cafetería, algo muy importante para todo cocinero es
limpiarse las manos antes de cocinar y vestirse adecuadamente con el delantal. Cuando éste
acabe con su jornada laboral deberá quitarse el delantal. Así que, como queremos cocineros
limpios en la universidad modifiquemos el código del cocinero.
public class Kitchener implements Worker { private Recipe recipe;//receta private int diners;//comensales public Kitchener() { } public void setRecipe(Recipe recipe) { this.recipe = recipe; } public void setDiners(int diners) { this.diners = diners; } public void work() throws WorkingException { System.out.println("Se dispone a cocinar " + recipe.getName()); recipe.develop(); System.out.println("Para " + diners + " com ensales"); } public void init(){ System.out.println("El cocinero se pone el delantal"); System.out.println("El cocinero se lava las manos"); } public void clean(){ System.out.println("El cocinero se quita el delantal"); } }
Ahora la definición de cocinero sería tan simple como esta:
<bean id="alfredo" class="es.uah.uahconnection. cafe.model.Kitchener"
init-method="init" destroy-method="clean"> <property name="diners" value="5"/> <property name="recipe"> <bean class="es.uah.uahconnection.cafe. model.PaellaValenciana"/> </property> </bean>
Como no sólo el cocinero manipula alimentos creemos que todos los chef deberían antes de
comenzar su jornada laboral limpiarse y ponerse vestimenta adecuada así como de la misma
forma, cuando terminen su jornada laboral cambiarse de ropa, con lo que todos los chef
implementarán el método init y también el método destroy.
Spring tiene una forma más útil de poder definir un método por defecto de inicio así como otro
de destrucción como ya hemos visto. El código perteneciente a esto sería:
<beans xmlns="http://www.springframework.org/schema /beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-inst ance" xsi:schemaLocation="http://www.springframework.o rg/schema/beans http://www.springframework.org/schema/beans/ spring-beans-2.0.xsd" default-init-method="init" default-destroy-method="clean">
Aún sigue teniendo Spring una forma más de hacer todo esto. Si la clase que definimos
implementa el interfaz InitializingBean y el DisposableBean. Utilizará los métodos
afterPropertiesSet y destroy, como ya comentamos al inicio de éste capítulo.
Vemos como sería el código del cocinero si implementase esto:
public class Kitchener implements Worker, Initializ ingBean, DisposableBean { private Recipe recipe;//receta private int diners;//comensales public Kitchener() { } public void setRecipe(Recipe recipe) { this.recipe = recipe; } public void setDiners(int diners) { this.diners = diners; } public void work() throws WorkingException { System.out.println("Se dispone a cocinar " + recipe.getName()); recipe.develop(); System.out.println("Para " + diners + " com ensales"); } public void init(){ System.out.println("El cocinero se pone el delantal"); System.out.println("El cocinero se lava las manos"); } public void clean(){ System.out.println("El cocinero se quita el delantal"); } public void afterPropertiesSet() throws Excepti on { init(); }
41
public void destroy() throws Exception { clean(); } }
Como seguramente se haya percatado, esta opción liga el desarrollo de las clases de nuestro
sistema de información al framework de Spring, haciéndolas menos reutilizables. Es por ello
que aún que la opción esté y haga que nuestro xml de configuración sea más limpio esta
opción se descarte en muchas ocasiones.
43
Capítulo 3:
Programación
orientada a aspectos
(AOP) Estudio teórico y práctico de AOP
En el primer capítulo hicimos una breve introducción a la programación orientada a aspectos. En éste entraremos en detalle en cómo Spring gestiona este paradigma y daremos ejemplos prácticos.
Javier Sevilla Sánchez
45
Contenido Introducción ................................................................................................................................ 46
Conceptos básicos ................................................................................................................... 46
Advice .................................................................................................................................. 46
Join point ............................................................................................................................. 46
Pointcut ............................................................................................................................... 46
Aspect .................................................................................................................................. 47
Introduction ........................................................................................................................ 47
Target .................................................................................................................................. 47
Proxy .................................................................................................................................... 47
Weaving ............................................................................................................................... 47
AOP en Spring .............................................................................................................................. 47
Ejemplo de aplicación en Spring ................................................................................................. 48
Creación de una clase Advice (Notificación) ........................................................................... 49
MethodInterceptor (Around Advice o notificación alrededor) ............................................... 50
Definir Pointcuts y Advice ....................................................................................................... 50
Declaraciones con expresiones regulares (Regular Expressions Pointcuts) ........................ 51
Definición de puntos de corte con AspectJ ......................................................................... 51
Configuración con ProxyFactoryBean ..................................................................................... 52
Abstracción de ProxyFactoryBean .......................................................................................... 53
Autoproxying ............................................................................................................................... 54
Autoproxy básico de beans. .................................................................................................... 54
@AspectJ ................................................................................................................................. 55
Creación de aspectos POJO ..................................................................................................... 56
Introducción
La Programación Orientada a Aspectos (POA) es un paradigma de programación relativamente
reciente cuya intención es permitir una adecuada modularización de las aplicaciones y
posibilitar una mejor separación de conceptos. Spring puede encapsular los diferentes
conceptos que componen una aplicación en entidades bien definidas, eliminando las
dependencias entre cada uno de los módulos.
Así tenemos un código menos acoplado, sin código dependiente y disperso y con POJOS
sencillos, adaptables y reusables. Spring se ha erigido como un líder dentro de la programación
orientada a aspectos junto a otros frameworks como pueda ser JBoss o AspectJ.
En el desarrollo de software, las funciones que abarcan varios puntos de una aplicación se
suelen llamar aspectos transversales (cross-cutting concerns) y suelen estar separados de la
lógica empresarial. La AOP intenta separar estos aspectos de la lógica de la aplicación.
En el capítulo segundo vimos cómo utilizar la DI para desacoplar los objetos dependientes.
AOP desacopla los objetos transversales.
Veremos a lo largo del capítulo funcionalidades AOP de Spring, así como cómo AspectJ se
acopla perfectamente.
Conceptos básicos
Antes de meternos en harina, es recomendable entender cada uno de los términos que se
manejaran más adelante. La programación Orientada a Aspectos ha creado su propia jerga y
las traducciones al castellano comúnmente no convergen.
Advice
Notificación o consejo es la implementación del aspecto, es decir, contiene el código que
implementa la nueva funcionalidad. Se insertan en la aplicación en los Puntos de Cruce.
Join point
Un punto de unión es un punto en la ejecución de la aplicación en el que se puede insertar un
aspecto, puntos de ejecución dentro del sistema donde un aspecto puede ser tejido, como una
llamada a un método, el lanzamiento de una excepción o la modificación de un campo (Éste
último no contemplado por Spring). El código del aspecto será insertado en el flujo de
ejecución de la aplicación para añadir su funcionalidad.
Pointcut
Los Puntos de Corte definen los Consejos que se aplicarán a cada Punto de Cruce. Se especifica
mediante Expresiones Regulares o mediante patrones de nombres (de clases, métodos o
campos), e incluso dinámicamente en tiempo de ejecución según el valor de ciertos
parámetros.
47
Aspect
Es la fusión de notificación y de punto de corte. El Aspecto es una funcionalidad transversal
(cross-cutting) que se va a implementar de forma modular y separada del resto del sistema. El
ejemplo más común y simple de un aspecto es el logging (registro de sucesos) dentro del
sistema, ya que necesariamente afecta a todas las partes del sistema que generan un suceso.
Otros ejemplos podrían ser transacciones, seguridad o verificación de credenciales.
Introduction
La Introducción permite añadir métodos o atributos a clases ya existentes. Un ejemplo en el
que resultaría útil es la creación de un Consejo de Auditoría que mantenga la fecha de la
última modificación de un objeto, mediante una variable y un método
setUltimaModificacion(fecha), que podrían ser introducidos en todas las clases (o sólo en
algunas) para proporcionarles esta nueva funcionalidad.
Target
El destinatario (Target) es el objeto que está siendo notificado. Puede ser un objeto que usted
escriba o un objeto de terceros al que quiere añadir un comportamiento personalizado.
Proxy
El proxy o resultante, es un objeto creado después de aplicar una notificación al objeto
destinatario. En tanto concierne a los objetos clientes, el objeto destinatario (Pre-AOP) y el
objeto proxy (Post-AOP) son el mismo, el resto de la aplicación cree que el proxy es el
destinatario y no tendrá que cambiar nada para darle soporte.
Weaving
El Tejido es el proceso de aplicar Aspectos a los Objetos Destinatarios para crear los nuevos
Objetos Resultantes en los especificados Puntos de Cruce. Este proceso puede ocurrir a lo
largo del ciclo de vida del Objeto Destinatario:
• Momento de compilación: Los aspectos se tejen cuando se compila la clase
destinataria. Esto requiere un compilador especial. El compilador de AspectJ teje
aspectos de esta forma.
• Momento de apertura de la clase: Los aspectos se entretejen cuando se abre la clase
destinataria en el JMV. Esto requiere un ClassLoader especial que resalta el código de
esa clase destinataria antes de que la clase se introduzca en la aplicación.
• Tiempo de ejecución: Es la manera que tiene Spring de hacerlo. Los aspectos se
entretejen en algún momento durante la ejecución del programa. Normalmente, un
con tenedor AOP generará dinámicamente un objeto resultante que delegará al objeto
destinatario mientras teje los aspectos.
AOP en Spring No todos los marcos de trabajo AOP son iguales. Algunos permiten notificaciones a nivel de
modificación de campo, mientras sólo a invocación de métodos. Ha habido cambios en el
enfoque AOP y han convergido diversos frameworks y otros han desaparecido. Los tres marcos
de trabajo principales son AspectJ, JBoss AOP y Spring AOP.
En Spring se da soporte de cuatro maneras algunas de ellas no incluidas en Spring 1:
1. AOP clásico, basado en proxy.
2. Notación AspectJ (sólo en Spring 2.x)
3. Aspectos POJO puros (sólo en Spring 2.x)
4. Aspectos AspectJ inyectados.
Como hemos comentado, Spring sólo da soporte a la interceptación de métodos, pero
teniendo en cuenta la filosofía de los bean de Java Enterprise Edition, esto lo convertiría en
una buena práctica.
El código del aspecto así como la configuración serán muy familiares a los desarrolladores, ya
que todo se escribe en Java. Sin embargo, AspectJ tiene su propia sintaxis y es una extensión.
En Spring hay cinco tipos de notificación. Cada una de ellas definida por un interfaz.
Tipos Interfaz
Para antes del método org.springframework.aop.MethodBeforeAdvice
Después del método org.springframework.aop.AfterReturningAdvice
Alrededor del método org.aopalliance.intercept.MethodInterceptor
Introducción org.springframework.aop.IntroductionInterceptor
Después del lanzamiento de
una introducción
org.springframework.aop.ThrowsAdvice
Todas las notificaciones a excepción de alrededor pertenecen al framework de Spring.
Ejemplo de aplicación en Spring En el primer capítulo dimos una ligera introducción a cómo Spring gestionaba aspectos con el
ejemplo del conserje que abre la puerta cuando el profesor va a dar clase. Ahora, con los
conceptos más aclarados es momento de volver al ejemplo, pero antes enseñaremos cuál sería
la implementación de la clase profesor si no lo hubiésemos programado con AOP.
public class TeacherImp implements Teacher { private String name; private Subject subject; private Conserje conserje; public TeacherImp(String name) { this.name = name; } public String getName() { return name; }
49
public void setName(String name) { this.name = name; } public Subject getSubject() { return subject; } public void setSubject(Subject subject) { this.subject = subject; } public void setConserje(Conserje conserje) { this.conserje = conserje; } public void teach() { conserje.openClassRoom(this); System.out.println("El profesor " + name + " está dando clase de " + subject.getName()); conserje.closeClassRoom(this); } }
Con esta implementación el profesor debería estar detrás del conserje para que le abriese la
puerta, perdiendo así su tiempo. Al igual que en ejemplo del primer capítulo, no queremos que
eso ocurra.
Creación de una clase Advice (Notificación)
Como hemos dicho, una notificación define lo que hace un aspecto y cuándo lo hace. Para la
notificación que hemos realizado haremos que implemente MethodBeforeAdvice,
AfterReturningAdvice y ThrowsAdvice, es decir, las interfaces para la ejecución de antes del
método, después del método y al producirse una excepción.
package es.uah.uahaop.model; import java.lang.reflect.Method; import org.springframework.aop.AfterReturningAdvice ; import org.springframework.aop.MethodBeforeAdvice; import org.springframework.aop.ThrowsAdvice; public class WatchmanAdvice implements MethodBefore Advice, AfterReturningAdvice, ThrowsAdvice { private Watchman watchman; public WatchmanAdvice(Watchman watchman) { this.watchman = watchman; } public void before(Method arg0, Object[] arg1, Object arg2) throws Throwable { watchman.openClassRoom((Teacher)arg2);//al empezar la clase } public void afterReturning(Object arg0, Method arg1, Object[] arg2, Object arg3) throws Throwable { watchman.closeClassRoom((Teacher)arg3);//al terminar la clase } public void afterThrowing (Object target, Throw able throwable){ watchman.teacherDidNotCome((Teacher target) ;//si el profesor no está }
}
Como vemos en el código, la notificación tiene un conserje como dependencia así que en la
definición le inyectaremos una implementación de conserje.
<bean id="watchmanAdvice" class="es.uah.uahaop.aop.WatchmanAdvice"> <property name="watchman" ref="julian"/> </bean>
Echando un vistazo al código vemos cómo son los métodos que se tienen que implementar. Al
implementar MethodBeforeAdvice se requiere el método before(). Los tres parámetros que
tiene corresponden a java.lang.reflect.Method que es el método ejecutado para aplicar la
notificación. El segundo, la matriz, corresponde a los parámetros del método que se han
pasado. El último es el objeto del cual se ha ejecutado el método.
El método afterReturning es requisito de la interfaz ReturningAdvice y se ejecuta después de la
ejecución del método. Su primer parámetro es el objeto devuelto tras la ejecución del método
siendo el resto igual que los de before().
Por último hemos implementado la interfaz ThrowsAdvice la cual no es requisito implementar
ningún método, se toma como un mero marcador para Spring, diciéndole que esta notificación
es capaz de interceptar una excepción. Este método puede implementar distintos métodos
teniendo parámetros opcionales. Comparte cada uno de los parámetros de before() e incluye
otro más de tipo Throwable todos ellos excepto éste son opcionales.
En nuestro caso, si algo ocurre en el trascurso de la clase que impida que se imparta saltará
una excepción y será entonces cuando nuestra notificación (consejo o Advice) entrará en
escena haciendo que nuestro querido conserje cierre el aula.
MethodInterceptor (Around Advice o notificación alrededor)
La notificación alrededor es una notificación antes, después y si ocurre un lanzamiento. Su
funcionamiento es sencillo, en vez de ejecutar cada método, uno antes, otro después y otro si
hay una excepción, pues se ejecuta un método que se le pasará el método a invocar como
parámetro. La interfaz es MethodInterceptor y requiere que se implemente el método
invoke(). En esta clase podemos hacer un post-procesamiento del retorno del método, así
podemos verificar el objeto, modificarlo… etc. cosa que no podemos hacer con
AfterReturningAdvice.
Definir Pointcuts y Advice
51
Ya que tenemos los Advice lo ideal sería poder darles uso, para ello crearemos puntos de
corte. Los puntos de corte seleccionan uno o más puntos de unión donde debe aplicarse una
notificación mediante un aspecto. Para ello hay distintas maneras. Los puntos de corte
seleccionan el qué se va a notificar, en el caso de Spring a qué método se va a notificar.
Declaraciones con expresiones regulares (Regular Expressions Pointcuts)
En Spring se disponen de dos clases que soportan expresiones regulares para la definición de
puntos de corte ellas son Per15RegexpMethodPointcut y JdkRegexMethodPointcut. La primera
requiere Jakarta ORO y ejecutarse en un jre anterior a 1.4 de Java.
La definición sería así:
<bean id="watchmanPointcut" class="org.springframework.aop.support.JdkR egexpMethodPointcut"> <property name="pattern" value=".*teach"/> </bean>
La propiedad pattern es la expresión regular con la que diremos qué métodos queremos que
sean un punto de corte. En nuestro caso serán todos aquellos que se llamen “teach” ya sean
de cualquier clase. En el bean watchmanAdvisor estamos definiendo que punto de corte
tendrá la notificación.
<bean id="watchmanAdvisor" class="org.springframework.aop.support.Defa ultPointcutAdvisor"> <property name="advice" ref="watchmanAdvice "/> <property name="pointcut" ref="watchmanPoin tcut"/> </bean>
Pero esto seguramente acabe siendo muy repetitivo y acabaremos teniendo un bean patrón
para cada notificación. No hay que preocuparse, porque Spring tiene una clase que soluciona
tanto código xml. La clase org.springframework.aop.support.RegexpMethodPointcutAdvisor
nos lo soluciona.
<bean id="watchmanAdvisor2" class="org.springframework.aop.support.Rege xpMethodPointcutAdvisor"> <property name="advice" ref="watchmanAdvice "/> <property name="pattern" value=".*teach"/> </bean>
Definición de puntos de corte con AspectJ
A diferencia de las expresiones regulares, el lenguaje de puntos de corte AspectJ se definió
especialmente para los puntos de corte. Así tiene una sintaxis especial. Si se prefiere usar
expresiones de tipo AspectJ se usará la clase AspectJExpressionPointcut para definir los puntos
de corte.
Si se quiere reducir el código xml, al igual que pasaba con RegexpMethodPointcutAdvisor
existe una clase en la que podremos definir notificación y punto de corte, ésta es
AspectJExpressionPointcutAdvisor.
Configuración con ProxyFactoryBean
El ejemplo lo haremos con un nuevo profesor, el profesor Aurelio Martínez. La definición de
Aurelio será igual que la que hicimos en el capítulo primero de José Sarmiento.
<bean id="aurelioMartinezTarget" class="es.uah.uahaop.model.TeacherImp"> <constructor-arg value="Aurelio Martínez"/> </bean>
Como hemos dicho la definición es igual a la que hicimos salvo que hemos modificado el
nombre del bean. En vez de llamarlo aurelioMartinez, lo hemos llamado
aurelioMartinezTarget. Esto es debido a que el bean que se le pedirá al contenedor no será la
implementación de profesor que tenemos, sino el proxy que hemos creado para la ocasión. El
siguiente código ilustra lo comentado.
<bean id="aurelioMartinez" class="org.springframework.aop.framework.ProxyF actoryBean"> <property name="target" ref="aurelioMartine zTarget"/> <property name="interceptorNames" value="wa tchmanAdvisor"/> <property name="proxyInterfaces" value="es. uah.uahaop.model.Model" /> </bean>
En esta definición se utiliza la clase ProxyFactoryBean a la cual se le ha de pasar el destinatario
(target), las posibles notificaciones (interceptorNames) y los interfaces que el destinatario
implementa.
53
Figura 1: Lo que realmente está pasando es que la clase que instancie el bean aurelioMartinez cuando ejecute su método teach() Spring interceptará esa llamada gracias al proxy y se le entregará a la notificación antes de la
ejecución del método.
Como vemos en el código quién es aurelioMartinez es el proxy, no la clase de tipo profesor. Lo
que ocurrirá es que cuando una clase pida al contenedor de Spring el bean aurelioMartinez
éste le dará el proxy y no el destinatario, target u objetivo.
Como seguramente haya pensado, habrá bastantes profesores y cada uno de ellos tendrá la
misma definición en el xml, con lo que ¿hay algo que nos permita definir todos aquellos beans
de tipo target que respondan al mismo patrón? La respuesta es sí, utilizando beans abstractos.
Abstracción de ProxyFactoryBean
Como hemos dicho, cuando en una aplicación se tienen varios destinatarios de la misma clase,
éstos tienen una configuración similar en el xml. Para eliminar código xml existe la posibilidad
de hacerlo con ProxyFactoryBean como un bean abstracto. La definición de éste será la
siguiente:
<bean id="watcherProxyBase" abstract="true" class="org.springframework.aop.framework.ProxyF actoryBean"> <property name="interceptorNames" value="wa tchmanAdvisor"/> <property name="proxyInterfaces" value="es. uah.uahaop.model.Model" /> </bean>
Así crearemos la base del bean abstracto con el que luego instanciaremos los proxy de una
manera más sencilla.
<bean id="julianRebolloTarget" class="es.uah.uahaop.model.TeacherImp"> <constructor-arg value="Julian Rebollo"/> </bean> <bean id="adolfoRenatoTarget" class="es.uah.uahaop.model.TeacherImp"> <constructor-arg value="Adolfo Renato"/>
</bean> <bean id="julianRebollo" parent="watcherProxyBa se"> <property name="target" ref="julianRebolloT arget"/> </bean> <bean id="adolfoRenato" parent="watcherProxyBas e"> <property name="target" ref="adolfoRenatoTa rget"/> </bean>
Renombrar a los beans que en principio serían los que la aplicación instanciará añadiéndoles la
palabra target (es decir julianRebollo por julianRebolloTarget) y luego utilizar la clase proxy en
su lugar es un concepto confuso y que cuesta acostumbrarse. Con el autoproxying Spring crea
las conexiones necesarias sin tener que confundirnos con los nombres (como ya vimos en el
capítulo primero).
Autoproxying
Spring da soporte a la conversión en proxy de bean. El Autoproxying nos ofrece una
implementación AOP más completa dejando que la definición de punto de corte de un aspecto
decida qué beans serán proxy.
Se pueden hacer de dos maneras. La primera con el autoproxying básico de beans basado en
beans notificadores declarados en el contexto Spring y la otra con anotación AspectJ.
Autoproxy básico de beans.
El siguiente código ilustra cómo hacerlo:
<bean id="exampleAdvisor" class="org.springframework.aop.aspectj.AspectJE xpressionPointcutAdvisor"> <property name="advice" ref="exampleAdvice" /> <property name="expression" value="execution(* *.methodExampleInvocatio n(..))"/> </bean>
La propiedad advice dice que notificación aplicar y la propiedad expression dice dónde
aplicarla. Spring tiene una implementación de BeanPostProccessor que crea automáticamente
beans proxy con notificadores coincidentes llamada DefaultAdvisorProxyCreator, cuya
declaración es la siguiente.
<bean class="org.springframework.aop.framework.auto proxy. DefaultAdvisorAutoProxyCreator"/>
Este bean no tiene id, eso es debido a que nunca nos referiremos a él. Después de esto ya no
necesitamos declarar ProxyFactoryBean ni poner otro nombre al target.
Esto funcionará en entornos java inferiores a 5. Pero si nuestra aplicación está en Java 5 puede
utilizar anotación de AspectJ.
55
@AspectJ
Con las anotaciones de @AspectJ se pueden trasformar POJOs normales en aspectos.
Volvamos a la clase conserje (watchman), en ella podemos hacer las anotaciones oportunas.
package es.uah.uahaop.model; import org.aspectj.lang.annotation.AfterReturning; import org.aspectj.lang.annotation.AfterThrowing; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Before; import org.aspectj.lang.annotation.Pointcut; @Aspect public class WatchmanImp implements Watchman { private String name; public WatchmanImp(String name) { this.name = name; } public String getName() { return name; } public void setName(String name) { this.name = name; } @Pointcut("execution (* *.teach(..))") public void teaching() { } @Before("teaching()") public void openClassRoom(Teacher teacher) { Subject subject = teacher.getSubject(); ClassRoom classRoom = subject.getClassRoom( ); System.out.println("El conserje " + getName () + " abre la puerta del aula " + classRoom.getName() + " para que pueda dar clase el profesor " + teache r.getName() + " de " + subject.getName()); classRoom.setOpen(true); } @AfterReturning("teaching()") public void closeClassRoom(Teacher teacher) { Subject subject = teacher.getSubject(); ClassRoom classRoom = subject.getClassRoom( ); System.out.println("El conserje " + getName () + " cierra la puerta del aula " + classRoom.getName () + " al haber acabado la clase el profesor " + teach er.getName() + " de " + subject.getName()); classRoom.setOpen(false); } @AfterThrowing("teaching()") public void teacherDidNotCome(Teacher teacher) { Subject subject = teacher.getSubject(); ClassRoom classRoom = subject.getClassRoom( ); System.out.println("El conserje " + getName () + " cierra la puerta del aula " + classRoom.getName () + " al no haber venido el profesor " + teacher.getN ame() + " de " +
subject.getName()); classRoom.setOpen(false); } }
La anotación @Aspect indica que es un aspecto, no un simple POJO. @Pointcut y el método
teaching añadido marcan el punto de corte. Los métodos openClassRoom y closeClassRoom
han sido anotados para que se ejecuten antes y después del método teach y el método
teacherDidNotCome se le ha hecho una anotación para que se ejecute si hay una excepción.
Para que esto funcione se ha de crear una clase creadora de AutoProxy en el contendor de
Spring. Esta clase se llama AnnotationAwareAspectJAutoProxyCreator y es la encargada de
convertir clases con anotaciones @AspectJ en notificaciones proxy. En vez de registrarlo como
un bean, Spring da la oportunidad de hacerlo de una manera sencilla:
<aop:aspectj-autoproxy/>
Con esta etiqueta crearemos la clase AnnotationAwareAspectJAutoProxyCreator en el
contexto y automáticamente creará proxy cuyos métodos correspondan a los puntos de corte
definidos por las anotaciones @Pointcut.
Si no incluimos el namespace aop nada de esto funcionará, así que, como ya hicimos en el
primer capítulo incluiremos la siguiente definición de la etiqueta beans:
<beans xmlns="http://www.springframework.org/schema /beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema- instance" xmlns:aop="http://www.springframework.org/schema/aop" xsi:schemaLocation="http://www.springframewo rk.org/schema/beans http://www.springframework.org/schema/b eans/spring-beans-2.5.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.5.xsd">
También AnnotationAwareAspectJAutoProxyCreator hace lo mismo que DefaultAdvisor-
AutoProxyCreator.
Existe también la posibilidad de crear una notificación alrededor, ésta sería con la anotación
@Around(“metodoAEjecutar()”) y definiríamos todo de la misma manera que con la anterior
anotación.
Creación de aspectos POJO
A pesar de todas las formas que tiene Spring para declarar aspectos no es del todo intuitivo ni
rápido. A partir de la segunda versión de Spring se ofrecen nuevos elementos en el namespace
aop que simplifican la manera de declarar aspectos. Con los elementos de configuración aop
57
podemos convertir cualquier clase en un aspecto. Por fin hemos llegado al punto en el que
dejamos el primer capítulo, así que escribiremos de nuevo la definición que hicimos en el xml.
<aop:config> <aop:aspect ref="julian"> <aop:pointcut id="classRoomDoorToTeach" expression="execution(* *.teach(..)) and target(bean)" /> <aop:before method="openClassRoom" pointcut-ref="classRoomDoorToTeach" arg-names="bean" /> <aop:after-returning method="closeClass Room" pointcut-ref="classRoomDoorToTeach" arg-names="bean" /> </aop:aspect> </aop:config>
Elemento de configuración AOP Utilidad
<aop:advisor> Configura un notificador
<aop:before> Configura una notificación antes de la ejecución.
<aop:after> Configura una notificación posterior (haya habido éxito
en la ejecución o no)
<aop:around> Configura una notificación alrededor
<aop:after-throwing> Configura una notificación de excepción
<aop:after-returning> Configura una notificación después del retorno
<aop:pointcut> Punto de corte definido
<aop:aspect> Definición de un aspecto, etiqueta contenida en
<aop:config>
<aop:config> Elemento padre de las etiquetas aop
Cabe destacar que a pesar de tener un gran motor AOP, como ya hemos dicho, Spring está
basado en proxy y las notificaciones han de ser de método a pesar de que comparta la
anotación @Aspect. Si necesitamos algo más potente deberemos crear aspectos con AspectJ.
Spring permite inyectar beans creados con AspectJ y hacer uso de ellos.
59
Capítulo 4:
Persistencia Peticiones a bases de datos, persistencia y
patrones DAO Hoy en día, la construcción de cualquier aplicación empresarial es inviable sin una base de datos. La información con la que el sistema informático trabaja es de vital importancia y de uso continuado. Es por ello que debemos definir mecanismos que simplifiquen y aseguren la consistencia de los datos.
Javier Sevilla Sánchez
61
Contenido Spring y el acceso a datos ........................................................................................................... 62
Patrón DAO.............................................................................................................................. 62
Excepciones en Spring ............................................................................................................. 63
Plantillas ...................................................................................................................................... 64
Uso de clases de soporte DAO ................................................................................................ 65
Configuración de una fuente de datos ........................................................................................ 66
Configuración JDBC ................................................................................................................. 66
Uso de plantillas JDBC ................................................................................................................. 67
JdbcTemplate .......................................................................................................................... 67
Utilizar parámetros nombrados .............................................................................................. 70
Simplificación de JDBC ............................................................................................................ 70
Clases de Spring para trabajar con JDBC ..................................................................................... 72
Plataformas de persistencia en Spring ........................................................................................ 73
Integración de Hibernate en Spring ............................................................................................ 73
Instanciar SessionFactory de Hibernate .................................................................................. 74
LocalSessionFactoryBean ........................................................................................................ 74
AnnotationSessionFactoryBean .............................................................................................. 74
HibernateTemplate ................................................................................................................. 75
HibernateDaoSupport ......................................................................................................... 76
JPA (Java Persistence API) ........................................................................................................... 78
Configurar EntityManagerFactory ........................................................................................... 78
Configurar LocalEntityManagerFactoryBean ...................................................................... 78
Configurar LocalContainerEntityManagerFactoryBean .......................................................... 79
Plantillas JPA (JpaTemplate) .................................................................................................... 80
Extender de JpaDaoSupport para construir un DAO............................................................... 81
Spring y el acceso a datos
Gracias a los anteriores capítulos tenemos una idea clara de qué es lo que el contenedor de
Spring es capaz de hacer y de cuál es la filosofía de Spring con respecto a los beans, la DI y la
AOP. En este capítulo construiremos una capa de persistencia y explicaremos las múltiples
opciones que Spring nos otorga para ello.
Spring es compatible todos los marcos de persistencia que existen para Java como pueda ser
Hibernate 2, Hibernate 3, Java Persistence API (JPA), iBATIS o cualquier otro. La función
principal será facilitar aún más el trabajo. Spring hace muy buenas migas con JDBC facilitando
la configuración y capturando y dando forma a las excepciones que se produzcan.
Como hemos dicho, en anteriores capítulos hemos visto que Spring tiene como objetivo, entre
otros, inculcar las buenas artes que dentro de la programación orientada a objetos supone la
codificación en interfaces. Como podrá adivinar el modo que tiene Spring para el acceso a
datos cumple también con ello gracias al patrón DAO.
Patrón DAO
El problema que viene a resolver este patrón es el de contar con diversas fuentes de datos
(base de datos, archivos, servicios externos, etc.). De tal forma que se encapsula la forma de
acceder a la fuente de datos. Este patrón surgió de la necesidad de gestionar una diversidad de
fuentes de datos, aunque su uso se extiende al problema de encapsular no sólo la fuente de
datos, sino además ocultar la forma de acceder a los datos. Se trata de que el software cliente
se centre en los datos que necesita y se olvide de cómo se realiza el acceso a los datos o de
cuál es la fuente de almacenamiento.
Las aplicaciones pueden utilizar el API JDBC para acceder a los datos de una base de datos
relacional. Este API permite una forma estándar de acceder y manipular datos en una base de
datos relacional. El API JDBC permite a las aplicaciones JEE utilizar sentencias SQL, que son el
método estándar para acceder a tablas y vistas. La idea de este patrón es ocultar la fuente de
datos y la complejidad del uso de JDBC a la capa de presentación o de negocio.
Un DAO define la relación entre la lógica de presentación y empresa por una parte y por otra
los datos. El DAO tiene un interfaz común, sea cual sea el modo y fuente de acceso a datos.
Los DAO existen para proporcionar un medio para leer y escribir datos en una base de datos.
Deberían exponer esta funcionalidad mediante una interfaz por la que el resto de la aplicación
accediera a ellos.
63
Figura 2 Los objetos de servicio delegan el acceso a datos a los DAO. La interfaz DAO mantiene el acoplamiento débil.
Los objetos de servicio acceden mediante interfaces DAO. Así los objetos de servicio son
comprobables y no están acoplados a una única implementación.
Excepciones en Spring
Al escribir código JDBC estamos obligados a capturar excepciones SQLException. Ésta ha
podido producirse por varias razones, que deberíamos estar averiguando. Otro de los
problemas es que la mayoría de las excepciones responden a una situación fatal que no se
puede tratar en el bloque catch, ya que no poco se puede hacer.
Spring es consciente de que poco se puede hacer, así que hace que no tengamos que atrapar
la excepción.
JDBC de Spring proporciona una jerarquía más amplia de excepciones con las que podemos
resolver más problemas ya que son más descriptivas.
Excepciones de JDBC Excepciones de DB Spring
BatchUpdateException CannotAcquierLockException DataTruncation CannotSerializeTtransactionException SQLException CleanupFailureDataAccessException SQLWarning ConcurrencyFailureDataAccessException DataAccessException DataAccesResourceFailureException DataIntegrityViolationException DataRetrievalFailureException DeadlockLoserDataAccessException
EmpyResultDataAccessException EmptyResultDataAccessException IncorrectUpdateSemantisDataException InvalidDataAccessResourceUsageException OptimisticLockingFailureException PermissionDeniedDataAccessException PessunustucLockingFailureException TypeMismatchDataAcessException UncategorizedDataAccessException
Todas las excepciones tienen como padre a DataAccessException la cual es una excepción sin
comprobación, con lo que no se tiene por qué atrapar nada si no se desea. Sí, Spring prefiere
dejar libre al programador la elección de si quiere capturarla o no, ya que en otras ocasiones
éste se ve obligado a capturar un sinfín de bloques try-catch que a menudo se dejan vacíos.
Plantillas
Un método de plantilla define el esqueleto de un proceso que es fijo en una operación. Así las
plantillas se responsabilizan de una serie de acciones comunes y devuelve el control a la
retrollamada. Spring así separa las partes fijas y variables del proceso de acceso a datos en dos
clases distintas: las plantillas y las retrollamadas.
Figura 3 Las plantillas se responsabilizan de las tareas comunes de acceso a datos. Para las específicas se usa el objeto retrollamada.
Las plantillas gestionan las partes fijas del acceso a datos, controlan las excepciones, asignan
los recursos y manejan las transacciones. Spring tiene varias plantillas dependiendo de la
65
persistencia que vayamos a usar. Usar una plantilla simplifica mucho el código de acceso a
datos y se configura como un bean del contexto de Spring.
Plantilla
CciTemplate Conexiones JCA CCI
JdbcTemplate Conexiones JDBC
NamedParameterJdbcTemplate JDBC con soporte para parámetros nombrados
SimpleJdbcTemplate JDBC simplificadas
HibernateTemplate Hibernate 2
HibernateTemplate (…orm.hibernate3.*) Hibernate 3
SqlMapClientTemplate iBATIS SqlMap
JdoTemplate JDO
JpaTemplate JPA Java Persistence Api
TopLinkTemplate TopLink de Oracle
También se pueden usar las clases DAO de Spring para simplificar más la aplicación. Hay clases
básicas DAO de Spring que pueden gestionar la plantilla por nosotros.
Uso de clases de soporte DAO
Cada plantilla también ofrece métodos prácticos que simplifican el acceso a datos sin
necesidad de crear una implementación de retrollamada explícita. Spring proporciona clases
de soporte DAO destinadas a ser subclases de las clases DAO que hagamos en nuestro
proyecto.
En la siguiente figura se muestra la relación.
Figura 4 La relación entre un DAO de aplicación y el soporte DAO de Spring y las clases plantilla
Spring ofrece varias clases de soporte DAO. Cada plantilla tendrá su soporte DAO de Spring. En
la siguiente tabla se enumeran.
SOPORTE DAO TIPO
CciDaoSupport JCA CCI
JdbcDaoSupport JDBC
NamedParameterJdbcDaoSupport JDBC con soporte para parámetros nombrados
HibernateDaoSupport Hibernate 2
HibernateDaoSupport (orm.hibernate3.support)
Hibernate 3
SqllMapClientDaoSupport iBatis
JdoDaoSupport JDO
JpaDaoSupport JPA
TopLinkDaoSupport TopLink
A pesar de la cantidad de frameworks de persistencia con los que Spring puede trabajar
empezaremos con lo más sencillo, una conexión JDBC. Antes veamos cómo se configura una
fuente de datos.
Configuración de una fuente de datos
Configuración JDBC
Es la fuente de datos más simple. Spring tiene dos clases de fuentes de datos de este tipo.
DriverManagerDataSource que devuelve una nueva conexión cada vez y
SingleConnectionDataSource que devuelve la misma conexión.
Veamos cómo se configura un DriverManagerDataSource en el fichero xml:
<bean id="dataSourceJDBC" class="org.springframework.jdbc.datasource.Driv erManagerDataSource"> <property name="driverClassName" value="com .mysql.jdbc.Driver"/> <property name="url" value="jdbc:mysql://localhost/jdbcspringexample"/> <property name="username" value="ADMIN"/> <property name="password" value="ADMIN"/> </bean>
Como vemos hemos de configurar las propiedades url que será la url de acceso a la base de
datos, el usuario y su contraseña así como qué driver usará, en nuestro caso el driver de
MySql.
67
Uso de plantillas JDBC
Todo aquel que haya trabajado con JDBC sabrá que, a pesar del potencial que tiene este, para
hacer cualquier operación sencilla se tendrá que escribir una innumerable clase con bloques
try-catch obligatorios en los que poco se puede hacer.
Es por ello que Spring trata todo este tipo de código estándar repetitivo. Spring limpia el
código JDBC asumiendo la gestión de recursos y excepciones. Como ya dijimos, Spring abstrae
el código estándar a través de clases plantilla. Hay tres JdbcTemplate,
NamedParameterJdbcTemplate y SimpleJdbcTemplate.
JdbcTemplate
Es la implementación más sencilla, permite el acceso a datos mediante JDBC y consultas
sencillas de parámetros indexados. Tan sólo necesita un DataSource para funcionar así que el
código xml será muy sencillo. Utilizaremos el DataSource definido anteriormente:
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate"> <property name="dataSource" ref="dataSource JDBC"/> </bean>
La propiedad dataSource puede ser cualquier implementación de javax.sql.DataSource.
Interfaz persona y su implementación
Para poder llevar a cabo el ejemplo crearemos una interfaz sencilla de una persona que
tenga métodos de obtener y asignar tanto un id como un nombre.
public interface Person { String getName(); void setName(String name); int getId(); void setId(int id); }
Una implementación de la clase persona podría ser la siguiente:
package es.uah.jdbcspringexample.model; public class PersonImp implements Person {
private int id; private String name; public int getId() { return id; } public void setId(int id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } }
Ésta clase tan sencilla nos servirá para poder ilustrar la importante labor de los DAO. Primero
definamos un interfaz DAO:
public interface PersonDao { Person getPerson(int personID); List<Person> getAllPerson(); void savePerson (Person person); }
Empezaremos con la implementación JDBC para nuestro DAO, el código es el siguiente:
public class JdbcPersonDao implements PersonDao { private JdbcTemplate jdbcTemplate; private static final String PERSON_INSERT = "insert into person (id, name) " + "val ues (?,?)"; private static final String PERSON_SELECT = "select id, name from person "; private static final String PERSON_SELECT_ID = PERSON_SELECT + " where id=?"; public JdbcPersonDao(JdbcTemplate jdbcTemplate) { this.jdbcTemplate = jdbcTemplate; } public void setJdbcTemplate(JdbcTemplate jdbcTe mplate) { this.jdbcTemplate = jdbcTemplate; } public JdbcTemplate getJdbcTemplate() { return jdbcTemplate; } public Person getPerson(int personID) { List matches = jdbcTemplate.query(PERSON_SE LECT_ID, new Object[]{Long.valueOf(personID) }, new RowMapper() {
69
public Object mapRow(ResultSet rs, int rowNum) throws SQLException, Da taAccessException { Person person = new PersonI mp(); person.setId(rs.getInt(1)); person.setName(rs.getString (2)); return person; } }); return matches.size() > 0 ? (Person) matc hes.get(0) : null; } public void savePerson(Person person) { jdbcTemplate.update(PERSON_INSERT, new Obje ct[]{ new Integer(person.getId()), person.get Name()}); } public List<Person> getAllPerson() { List matches = jdbcTemplate.query(PERSON_SE LECT, new RowMapper() { public Object mapRow(ResultSet rs, int rowNum) throws SQLException, Da taAccessException { Person person = new PersonI mp(); person.setId(rs.getInt(1)); person.setName(rs.getString (2)); return person; } }); return matches; } }
Como vemos esta clase se sirve de una plantilla JDBC, la cual inyectaremos desde el fichero de
definición de Spring con el bean que hemos definido anteriormente. La definición sería así de
sencilla:
<bean id="jdbcPersonDao" class="es.uah.jdbcspringexample.dao.JdbcPersonDao" > <constructor-arg ref="jdbcTemplate"/> </bean>
Analizando el método savePerson vemos que es muy intuitivo y sencillo, gracias a la plantilla se
inserta una cadena que contiene una sentencia SQL y un Array de objetos que responden a
cada una de las interrogaciones de la sentencia.
La obtención de datos, en los métodos getPerson y getAllPerson son también sencillas, es clave
el uso del objeto RowMapper y el uso de la plantilla del método query. Vemos que el método
query toma por parámetro la propia query en cadena, una matriz con los objetos que insertara
en cada ‘?’ que se encontrará en la cadena y un objeto RowMapper.
El objeto RowMapper es abstracto y así implementamos el método abstracto rowMap. Éste
método se ejecutará una vez por fila devuelta en la base de datos.
Ésta implementación está muy bien, pero aún hay pegas. Para el método savePerson se
utilizan parámetros que han de llevar un orden con lo que si algún día hay algún cambio
tendremos que tener en cuenta este orden.
Utilizar parámetros nombrados
Podemos usar parámetros nombrados, así podremos dar a cada parámetro en SQL un nombre
explícito. De la siguiente forma:
private static final String PERSON_INSERT = "ins ert into person (id, name)" + "values (:id,:name)";
Es decir, especificaremos cada nombre correspondiente del objeto que vayamos a guardar.
Pero esto no es compatible con JdbcTemplate, así que crearemos una nueva clase que en vez
de usar un JdbcTemplate use un NamedParameterJdbcTemplate. La clase
NamedParameterJdbcPersonDao hace es un ejemplo y su definición en el fichero xml es la
siguiente.
<bean id="namedJdbcTemplate" class="org.springframework.jdbc.core.namedparam.Nam edParameterJdbcTemplate"> <constructor-arg ref="dataSourceJDBC"/> </bean> <bean id="namedParameterJdbcPersonDao" class="es.uah.jdbcspringexample.dao.NamedPa rameterJdbcPersonDao"> <constructor-arg ref="namedJdbcTemplate"/> </bean>
Para ello hemos tenido que definir otra plantilla de la clase NamedParameterJdbcTemplate a
cuyo constructor hemos de pasarle la fuente de datos definida.
Simplificación de JDBC
A partir de la versión 5 de java es posible pasar listas de parámetros de longitud variable a un
método. Así la clase SimpleJdbcPersonDao la codificaremos de la siguiente manera:
public class SimpleJdbcPersonDao implements PersonD ao { private SimpleJdbcTemplate simpleJdbcTemplate; private static final String PERSON_INSERT = "insert into person (id, name) " + "v alues (?,?)"; private static final String PERSON_SELECT = "select id, name from person "; private static final String PERSON_SELECT_ID = PERSON_SELECT + " where id=?"; public SimpleJdbcPersonDao(SimpleJdbcTemplate s impleJdbcTemplate) { this.simpleJdbcTemplate = simpleJdbcTemplat e;
71
} public void setJdbcTemplate(SimpleJdbcTemplate simpleJdbcTemplate) { this.simpleJdbcTemplate = simpleJdbcTemplat e; } public SimpleJdbcTemplate getSimpleJdbcTemplate () { return simpleJdbcTemplate; } public Person getPerson(int personID) { List matches = simpleJdbcTemplate.query(PER SON_SELECT_ID, new ParameterizedRowMapper() { public Person mapRow(ResultSet rs, int rowNum) throws SQLException { Person person = new PersonI mp(); person.setId(rs.getInt(1)); person.setName(rs.getString (2)); return person; } }, personID); return matches.size() > 0 ? (Person) matche s.get(0) : null; } public void savePerson(Person person) { simpleJdbcTemplate.update(PERSON_INSERT, pe rson.getId(), person.getName()); } public List<Person> getAllPerson() { List matches = simpleJdbcTemplate.query(PER SON_SELECT, new ParameterizedRowMapper() { public Object mapRow(ResultSet rs, int rowNum) throws SQLException, Da taAccessException { Person person = new PersonI mp(); person.setId(rs.getInt(1)); person.setName(rs.getString (2)); return person; } }); return matches; } }
Como vemos la manera de utilizar esta plantilla es muy similar a la JdbcTemplate salvo que
ahora nos beneficiamos de las nuevas especificaciones del lenguaje en la versión 5.
En el método getPerson vemos que los parámetros del método query del objeto
SimpleJdbcTemplate relativos a la formulación de la query (es decir qué parámetros se
utilizaran a la hora de ser sustituidos por cada ‘?’) se podrán como los últimos parámetros no
teniendo límite y pudiendo ser primitivos (no como en el ejemplo de JdbcPersonDao en el cual
teníamos que crear objetos Integer), es decir, poder hacer autobox. Como vemos también se
usa un nuevo objeto abstracto, el ParametrizedRowMapper, del cual tendremos que
implementar el método mapRow().
La definición del bean en el fichero xml es la siguiente:
<bean id="simpleJdbcTemplate" class="org.springframework.jdbc.core.simple.Simple JdbcTemplate"> <constructor-arg ref="dataSourceJDBC"/> </bean> <bean id="simpleJdbcPersonDao" class="es.uah.jdbcspringexample.dao.SimpleJdbcPers onDao"> <constructor-arg ref="simpleJdbcTemplate"/> </bean>
Clases de Spring para trabajar con JDBC
Como hemos podido ver, el código que hemos de generar es muy repetitivo. Cada clase DAO
ha de contener una plantilla que a su vez tenga una fuente de datos. Como era de esperar,
Spring proporciona una manera alternativa para poder disponer de todo esto otorgando clases
de las cuales podamos heredar para crear nuestros DAO.
El siguiente código muestra cómo nuestro DAO extiende de la clase JdbcDaoSupport.
public class JdbcPersonDao extends JdbcDaoSupport i mplements PersonDao{ private static final String PERSON_INSERT = "insert into person (id, name) " + "val ues (?,?)"; private static final String PERSON_SELECT = "select id, name from person "; private static final String PERSON_SELECT_ID = PERSON_SELECT + " where id=?"; public Person getPerson(int personID) { List matches = getJdbcTemplate().query(PERS ON_SELECT_ID, new Object[]{Long.valueOf(personID) }, new RowMapper() { public Object mapRow(ResultSet rs, int rowNum) throws SQLException, Da taAccessException { Person person = new PersonI mp(); person.setId(rs.getInt(1)); person.setName(rs.getString (2)); return person; } }); return matches.size() > 0 ? (Person) matc hes.get(0) : null; } public void savePerson(Person person) { getJdbcTemplate().update(PERSON_INSERT, new Object[]{ new Integer(person.getId()), person.get Name()}); } public List<Person> getAllPerson() { List matches = getJdbcTemplate().query(PERS ON_SELECT, new RowMapper() { public Object mapRow(ResultSet rs, int rowNum) throws SQLException, Da taAccessException { Person person = new PersonI mp(); person.setId(rs.getInt(1));
73
person.setName(rs.getString (2)); return person; } }); return matches; } }
Con esto hemos eliminado la tarea de crear métodos getter y setter para la plantilla. De hecho
ni siquiera tendremos que inyectar una plantilla, sino que si inyectamos una fuente de datos el
objeto la creará automáticamente.
Al igual que los anteriores ejemplos (Parámetros nombrados y simplificación de JDBC) también
existen clases abstractas de soporte en Spring con estas plantillas. Estas son
NamedParameterJdbcDaoSupport y SimpleJdbcDaoSupport.
Plataformas de persistencia en Spring
En el mercado existen diversas plataformas para dar solución a un sistema de persistencia
basado en mapeo de modelos de objeto relacionales (ORM). Spring es compatible con varios
marcos de trabajo entre ellos Hibernate, iBatis, Toplink o JPA. Spring aporta compatibilidad
integrada para transacciones declarativas de Spring, manejo de excepciones, plantillas, soporte
DAO y gestión de recursos.
Integración de Hibernate en Spring
Hibernate es el framework que más ha tenido acogida dentro de la comunidad siendo este de
código abierto y desarrollado en un principio por desarrolladores dispersos alrededor del
mundo. No sólo otorga ORM sino que también ofrece solución al duplicado de datos, la carga
perezosa, eager fetching y duplicado de datos.
Un aspecto importante que hemos de tener en cuenta antes de embarcarnos en el desarrollo
es elegir la versión de Hibernate, al margen de las nuevas funcionalidades, ya que el nombre
de los paquetes varia de la versión 2 a la 3 y esto hace que tengamos que diferenciarlo en
Spring. Así para la versión 2 de Hibernate utilizaremos las clases contenidas en
org.springframework.orm.hibernate y para la versión 3 utilizaremos las clases del paquete
org.springframework.orm.hibernate3. Como el lector habrá pensado esta decisión se tomó
para mantener la retrocompatibilidad.
Instanciar SessionFactory de Hibernate
Existen varias opciones para instanciar el objeto SessionFactory de Hibernate en Spring,
utilizando archivos de mapeo clásicos XML o con anotaciones.
LocalSessionFactoryBean
El objeto LocalSessionFactoryBean de Spring es un bean factory de Spring que produce una
instancia local de SessionFactory de Hibernate que toma sus parámetros de mapeo de los
archivos XML.
La definición de este bean sería la siguiente:
<bean id="localSessionFactory" class="org.springframework.orm.hibernate3.L ocalSessionFactoryBean"> <property name="dataSource" ref="dataSource "/> <property name="mappingResources" > <list> <value>es/uah/hibernateaddendum/model/ Exam.hbm.xml</value> <value>es/uah/hibernateaddendum/model/ Student.hbm.xml</value> <value>es/uah/hibernateaddendum/model/ Subject.hbm.xml</value> <value>es/uah/hibernateaddendum/model/SubjectUnit .hbm.xml</value> </list> </property> <property name="hibernateProperties"> <props> <prop key="hibernte.dialect">${hibe rnate.dialect}</prop> </props> </property> </bean>
La propiedad mappingResources define una lista con una entrada por fichero hbm.xml que
definamos para nuestro mapeo objeto relacional. La propiedad hibernateProperties define
propiedades necesarias para la configuración de Hibernate.
AnnotationSessionFactoryBean
Si lo que deseamos es utilizar objetos cuyas clases implementen las anotaciones JPA así como
específicas de Hibernate AnnotationSessionFactoryBean es la clase que deberemos usar. Es
muy similar a LocalSessionFactoryBean salvo que en vez de hacer una lista con los ficheros
hbm.xml la haremos con cada objeto que implemente anotaciones.
<bean id="annotationSessionFactoryBean" class="org.springframework.orm.hibernate3.a nnotation. AnnotationSessionFactoryBean"> <property name="dataSource" ref="dataSource "/> <property name="annotatedClasses" > <list> <value>es.uah.hibernateaddendum.mod el.Exam</value> <value>es.uah.hibernateaddendum.mod el.Student</value> <value>es.uah.hibernateaddendum.mod el.Subject</value> <value>es.uah.hibernateaddendum.mod el.SubjectUnit</value> </list>
75
</property> <property name="hibernateProperties"> <props> <prop key="hibernte.dialect">${hibe rnate.dialect}</prop> </props> </property> </bean>
Como vemos la definición es muy similar a LocalSessionFactoryBean salvo que tenemos la
propiedad annotatedClasses cuya lista se confecciona con las clases con anotaciones
persistentes.
HibernateTemplate
Esta plantilla simplifica trabajar con el objeto Session de Hibernate, siendo responsable de
abrir y cerrar sesiones y gestionar excepciones principalmente. Para crear la plantilla le
tendremos que pasar una instancia de la factoría de sesión de Hibernate de cualquiera de las
maneras vistas.
El siguiente XML muestra cómo se configura HibernateTemplate en Spring:
<bean id="hibernateTemplate" class="org.springframework.orm.hibernate3.Hib ernateTemplate"> <property name="sessionFactory" ref="localS essionFactory" /> </bean>
Y el siguiente sería para utilizar el annotationSessionFactoryBean definido anteriormente.
<bean id="hibernateTemplate" class="org.springframework.orm.hibernate3.Hib ernateTemplate"> <property name="sessionFactory" ref="annota tionSessionFactoryBean" /> </bean>
Ambas definiciones son idénticas, cabe señalar que en atributo sessionFactory podremos
pasarle cualquier clase que implemente FactoryBean.
Una vez hecho todo esto estamos en situación de crear un DAO que haga uso de la plantilla y
así poder persistir y recuperar objetos.
El siguiente código pertenece al DAO HibernateExamsDaoImp y sirve de ejemplo de cómo se
hace uso de la plantilla:
public class HibernateExamsDaoImp { private static final String INSERT = "insert"; private static final String DELETE = "delete"; private static final String GET_EXAM = "getExam "; private static final String EXAM = Exam.class.g etName(); private static final String SELECT_ID = "from " + EXAM + " where id = ?"; private static final String SELECT_ALL = "from " + EXAM; private HibernateTemplate hibernateTemplate; public HibernateTemplate getHibernateTemplate() { return hibernateTemplate; }
public void setHibernateTemplate(HibernateTempl ate hibernateTemplate) { this.hibernateTemplate = hibernateTemplate; } public void update(Exam exam) { getHibernateTemplate().saveOrUpdate(exam); } public void insert(Exam exam) { getHibernateTemplate().saveOrUpdate(exam); } public void insert(Set<Exam> exams) throws Exam sException { for (Iterator<Exam> it = exams.iterator(); it.hasNext();) { this.insert(it.next()); } } public Exam getExam(int id) throws ExamsExcepti on { List results = getHibernateTemplate().find( SELECT_ID, id); if (results.size() == 0) { return (Exam) results.get(0); } throw new ExamNotFoundException(id); } public Collection<Exam> getAllExams() { return getHibernateTemplate().find(SELECT_A LL); } public void delete(int id) throws ExamsExceptio n { getHibernateTemplate().delete(this.getExam( id)); } }
Así las operaciones monótonas necesarias para operar con Hibernate serán controladas por la
plantilla.
La definición dentro del contenedor de Spring es la siguiente:
<bean id="hibernateExamsDaoImp" class="es.uah.hibernatespringintegration.dao. HibernateExamsDaoImp"> <property name="hibernateTemplate" ref="hib ernateTemplate" /> </bean>
HibernateDaoSupport
Al igual que hemos visto con JdbcDaoSupport, Spring también facilita la construcción de DAO
en Hibernate. La clase HibernateDaoSupport otorga una plantilla HibernateTemplate con lo
que sólo le tendremos que inyectar un objeto SessionFactory.
El siguiente código pertenece a la clase HibernateStudentsDaoImp que como vemos hereda de
HibernateDaoSupport:
public class HibernateStudentsDaoImp extends Hibern ateDaoSupport { private static final String STUDENT = Student.c lass.getName();
77
private static final String SELECT_ID = "from " + STUDENT + " where id = ?"; private static final String SELECT_ALL = "from " + STUDENT; public void update(Student student) { getHibernateTemplate().saveOrUpdate(student ); } public void insert(Student student) { getHibernateTemplate().saveOrUpdate(student ); } public void insert(Set<Student> students) throw s StudentsException { for (Iterator<Student> it = students.iterat or(); it.hasNext();) { this.insert(it.next()); } } public Student getStudent(int id) throws Studen tsException { List results = getHibernateTemplate().find( SELECT_ID, id); if (results.size() == 0) { return (Student) results.get(0); } throw new StudentNotFoundException(id); } public Collection<Student> getAllStudents() { return getHibernateTemplate().find(SELECT_A LL); } public void delete(int id) throws StudentsExcep tion { getHibernateTemplate().delete(this.getStude nt(id)); } }
Con las clases de soporte Spring facilita al máximo la codificación de nuevas clases, reduciendo
el número de líneas de código de estas y reduciéndolas a la mínima expresión.
JPA (Java Persistence API)
Java Persistence API, más conocida por sus siglas JPA, es la API de persistencia desarrollada
para la plataforma Java EE o la Java Persistence API, a veces referida como JPA, es un
framework del lenguaje de programación Java que maneja datos relacionales en aplicaciones
usando la Plataforma Java en sus ediciones Standard (Java SE) y Enterprise (Java EE).
El objetivo que persigue el diseño de esta API es no perder las ventajas de la orientación a
objetos al interactuar con una base de datos (siguiendo el patrón de mapeo objeto-relacional),
como sí pasaba con EJB2, y permitir usar objetos regulares (conocidos como POJOs).
La parte de la especificación de EJB 3 que sustituye los beans de entidad se conoce como JPA.
Configurar EntityManagerFactory
Para usar JPA se ha de usar una implementación de EntityManagerFactory para obtener una
instancia de un EntityManager. La especificación JPA define dos tipos de gestores de entidad,
gestionados por la aplicación o por el contenedor. Los gestionados por la aplicación se crean
cuando la aplicación directamente pide un gestor de entidad de una fábrica de gestor de
entidad. Cuando ésta se usa es la aplicación la responsable de abrir y cerrar los gestores de
entidad y de implicar al gestor de entidad en las transacciones. Sin embargo, si utilizamos un
contenedor Java EE la aplicación no interactúa en absoluto con la fábrica de gestor de entidad
y es por ello por lo que debemos usar gestores de entidad gestionados por el contenedor. Así
los obtendremos mediante JNDI o mediante inyección. Ambos gestores implementan
EntityManager.
Para los desarrolladores, al utilizar la interfaz, no tendremos que saber en muchos casos ni
siquiera cuál estamos utilizando. Spring gestionará esto por nosotros.
Para ello Spring tiene dos beans, LocalEntityManagerFactoryBean y
LocalContainerEntityManagerFactoryBean, siendo la primera gestionada por la aplicación y la
segunda por el contenedor.
Ya que como hemos dicho Spring hace trasparente el acceso a ambas, la única diferencia será
la forma en la que las definiremos.
Configurar LocalEntityManagerFactoryBean
En el archivo persistence.xml se hallarán la mayor parte de la configuración. En este fichero
definimos tantas unidades de persistencia como queramos, enumerando las clases
persistentes.
79
<?xml version ="1.0" encoding ="UTF-8" ?> <persistence xmlns ="http://java.sun.com/xml/ns/persistence" xmlns:xsi ="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation ="http://java.sun.com/xml/ns/persistence http://java.sun.com/xml/ns/persistence/persiste nce_1_0.xsd" version ="1.0" > <persistence-unit name="CbosCommonsPU" transaction-type ="RESOURCE_LOCAL"> <provider >org.apache.openjpa.persistence.PersistenceProviderI mpl </ provider >
<properties > <property
name = "openjpa.ConnectionDriverName" value = "org.postgresql.Driver" />
<property name = "openjpa.ConnectionURL" value = "jdbc:postgresql://192.168.1.214:5432/arcadia_cbos" />
<property name = "openjpa.ConnectionUserName" value = "postgres" />
<property name = "openjpa.ConnectionPassword" value = "postavalon" />
</ properties > </ persistence-unit > </ persistence >
Más tarde definiremos el bean en el fichero de Spring y le deberemos pasar la unidad de
persistencia como parámetro.
<bean id ="entityManagerFactory" class ="org.springframework.orm.jpa.LocalEntityManagerFact oryBean" > <property name="persistenceUnitName" value ="youdiversity" />
<property name="jpaVendorAdapter" > <bean
class ="org.springframework.orm.jpa.vendor.TopLinkJpaVendo rAdapter" > <property name="showSql" value ="true" />
<property name="generateDdl" value ="true" /> <property name="database" value ="HSQL" />
</ bean > </ property >
</ bean >
Configurar LocalContainerEntityManagerFactoryBean
Lo que haremos será obtener una EntityManagerFactory utilizando la información
proporcionada por el contendor. Esta es la configuración típica cuando usamos JBoss o
WebLogic, es decir, servidores de aplicación JEE.
El siguiente código muestra cómo configurar un bean
LocalContainerEntityManagerFactoryBean.
<bean id ="entityManagerFactory" class ="org.springframework.orm.jpa.LocalContainerEntityMa nagerFactoryBean" > <property name="dataSource" ref ="youdiversityDataSource" /> <property name="jpaVendorAdapter" > <bean class ="org.springframework.orm.jpa.vendor.TopLinkJpaVendo rAdapter" > <property name="showSql" value ="true" /> <property name="generateDdl" value ="true" /> <property name="database" value ="HSQL" /> </ bean >
</ property > </ bean >
Como vemos hemos tenido que configurar un bean interno de tipo TopLinkJpaVendorAdapter,
eso es porque la propiedad jpaVendorAdapter puede tener varios valores y hace referencia a
la implementación de JPA a utilizar.
Plantillas JPA (JpaTemplate)
Al igual que con otras soluciones de persistencia, Spring otorga una plantilla llamada
JpaTemplate. Esta plantilla contendrá un EntityManagers de JPA. En el siguiente XML vemos
cómo se configura una plantilla.
<bean id ="jpaTemplate" class ="org.springframework.orm.jpa.JpaTemplate" > <property name="entityManagerFactory" ref ="entityManagerFactory" />
</ bean >
La propiedad entityManagerFactory de JpaTemplate debe conectarse con una implementación
de la interfaz javax.persistence.EntityManagerFactory de JPA.
JpaTemplate al igual que otras plantillas tiene muchos de los métodos de acceso a datos
ofrecidos por un EntityManager nativo. Pero implica a los EntityManager en transacciones y
maneja excepciones.
El siguiente DAO es un ejemplo de cómo se utiliza JpaTemplate:
import org.springframework.orm.jpa.JpaTemplate; ... public class UserJpaDao{ private JpaTemplate jpaTemplate ; public JpaTemplate getJpaTemplate() { return jpaTemplate ; } public void setJpaTemplate(JpaTemplate jpaTemplate) { this. jpaTemplate = jpaTemplate; } ... }
Para configurar este DAO simplemente conectamos JpaTemplate a la propiedad jpaTemplate:
<bean id ="userDao" class ="youdiversity.model.dao.jpa.UserJpaDao" > <property name="jpaTemplate" ref ="jpaTemplate" /> </ bean >
Una manera de usar la plantilla sería así.
public void saveUser(User user){ getJpaTemplate().persist(user); } public List<User> getAllUsers(){ return getJpaTemplate().find( "select u from User u;" ); }
81
Extender de JpaDaoSupport para construir un DAO
La clase JpaDaoSupport es una clase abstracta que tiene una plantilla JpaTemplate, así que en
vez de conectar a cada DAO su plantilla, lo que haremos será extender de JpaDaoSupport y
conectar el bean entityManagerFactory.
Así nuestro DAO extenderá de JpaDaoSupport así:
public class BaseDaoJpa<T extends BaseEntity> extends JpaDaoSupport { …
}
La definición será tan sencilla como:
<bean id ="baseDao" class ="youdiversity.model.orm.dao.jpa.BaseDaoJpa" > <property name="entityManagerFactory" ref ="entityManagerFactory" /> </ bean >
83
84
Capítulo 5:
Modelo Vista
Controlador Peticiones web, controladores, formularios
y vistas. Gracias a Spring MVC podremos crear aplicaciones robustas basadas en el patrón Modelo Vista Controlador (MVC) aportando herramientas de fácil comprensión y utilización y encapsulando el manejo y manipulación de los Servlets.
Javier Sevilla Sánchez
85
Contenido ¿Qué es Spring MVC? .................................................................................................................. 87
Características de Spring MVC .................................................................................................... 87
DispatcherServlet ........................................................................................................................ 88
Configuración de DispatcherServlet y ContextLoaderListener ............................................... 89
Usando Spring MVC sin anotaciones........................................................................................... 91
Creando nuestro primer Controlador ..................................................................................... 92
El objeto ModelAndView ........................................................................................................ 93
Configuración del controlador ................................................................................................ 93
View Resolvers ........................................................................................................................ 93
Crear un JSP ............................................................................................................................. 94
Pasos de la petición ..................................................................................................................... 94
Usando Spring MVC con Anotaciones y Con Spring 3 ................................................................. 95
Definir el controlador en el fichero *-servlet.xml. .................................................................. 96
Mapeando peticiones con @RequestMapping ....................................................................... 96
Peticiones URL, usando @RequestMapping ....................................................................... 97
Mapeo avanzado con @RequestMapping .......................................................................... 98
Tipos devueltos soportados .............................................................................................. 100
Enlazando parámetros de la petición a parámetros de métodos con @RequestParam ...... 101
@RequestBody ...................................................................................................................... 102
@ResponseBody ................................................................................................................... 102
HttpEntity .............................................................................................................................. 102
Proporcionando un enlace a los datos del modelo con @ModelAttribute .......................... 103
Especificando atributos para almacenar en la sesión ........................................................... 103
Uso de cookies con @CookieValue ....................................................................................... 104
Mapeando cabeceras de peticiones con @RequestHeader ................................................. 104
Personalizando el enlace a Datos .......................................................................................... 105
Mapeo de controladores ........................................................................................................... 105
Interceptando peticiones con HandlerInterceptor ............................................................... 106
Resolver vistas ........................................................................................................................... 106
ViewResolver ......................................................................................................................... 106
Encadenando ViewResolvers ................................................................................................ 107
Redireccionando a vistas ....................................................................................................... 107
RedirectView ..................................................................................................................... 107
El prefijo “redirect:” .......................................................................................................... 108
El prefijo “forward:” .......................................................................................................... 108
ContentNegotiatingViewResolver ......................................................................................... 108
Configuración regional .............................................................................................................. 110
AcceptHeaderLocaleResolver ................................................................................................ 110
CookieLocaleResolver ........................................................................................................... 110
SessionLocaleResolver .......................................................................................................... 110
LocaleChangeInterceptor ...................................................................................................... 111
Uso de temas ............................................................................................................................. 111
Definiendo los temas ............................................................................................................ 111
Resolutores de tema ............................................................................................................. 112
Subida de ficheros ..................................................................................................................... 112
Subiendo un fichero desde un formulario ............................................................................ 113
Manejo de Excepciones ............................................................................................................. 114
@ExceptionHandler .............................................................................................................. 115
Convenios .................................................................................................................................. 116
87
¿Qué es Spring MVC?
La arquitectura MVC (Modelo vista controlador) se basa en la separación de los datos y
modelo de la aplicación (Modelo), la interfaz de usuario (comúnmente un navegador que
recibe código HTML) y la interacción entre ambos, el controlador.
En una aplicación MVC, la gestión de estado, la validación y el flujo de trabajo son temas
fundamentales y principal foco de atención. Debido a la naturaleza del protocolo HTTP no se
dispone de estado, con lo que se dificulta la tarea.
Spring construye su parte MVC entorno al DispatcherServlet, el cual despacha las peticiones a
los manejadores, con asignaciones de controlador configurables, resolutores de vistas,
resolutor de la configuración local, de temas así como para la subida de ficheros. A lo largo de
Spring ha habido cambios desde la definición inicial de los controladores en el fichero xml (que
será lo primero que explicaremos) hasta las actuales anotaciones @Controller y
@RequestMapping que ofrecen mayor flexibilidad.
En Spring se pueden utilizar también objetos command (anteriores a la versión 3) pero
actualmente se pueden utilizar cualquier objeto como un command. Este objeto será útil para
la extracción de información de formularios. Así con Spring no tendremos que duplicar los
objetos teniendo, por ejemplo, un POJO con cadenas que trasformaremos en el objeto más
complejo.
El ViewResolutor (o resolutor de vista) de Spring es extremadamente flexible, a pesar de que
un controlador puede escribir directamente la respuesta, normalmente devuelve un objeto
ModelAndView que contiene el nombre de la vista y los objetos del modelo. El modelo es
pasado a la vista la cual puede ser JSP o Velocity.
El framework de Spring, específicamente el MVC, está diseñado para facilitar tanto la
construcción de controladores, como las vistas que están asociadas así como la interacción con
los objetos del modelo. Todo esto de la manera más flexible y con la posibilidad de integrar
otros marcos de trabajo conocidos como Struts o JSF.
Características de Spring MVC
• Una clara separación de roles.
• Una potente y sencilla configuración entre el marco de trabajo y las clases de la
aplicación como las JavaBeans.
• Adaptabilidad, no intrusión y flexibilidad.
• Reusabilidad del código empresarial, sin necesidad de duplicado, pudiendo usar código
empresarial existente como comandos o formularios reflejándolos como una clase del
marco de trabajo en particular.
• Validaciones y enlaces personalizados, enlazando valores reales como fechas y
números evitando la conversión de cadenas y duplicado.
• Mapeo y resolutores de vista personalizables, distintas estrategias de mapeo y de
resolutores de vista que van desde la simple URL hasta sofisticadas estrategias.
• Trasferencia del modelo flexible basada en pares nombre/valor, compatible con
cualquier tecnología de vista.
• Se pueden configurar tanto temas como configuración local de diversas maneras
compatibles con JSP o Velocity.
• Spring también tiene una potente librería de tag que ofrece tanto el enlace de datos,
temas o formularios.
• Los beans tienen un ámbito de aplicación de petición o sesión, esto no es específico de
Spring pero Spring lo potencia.
• Spring MVC es compatible con otros marcos de trabajo web como Struts, WebWork
etc. Si no se desea usar Spring MVC se pueden utilizar otras características de Spring e
integrar otro framework que hará uso del contexto de Spring.
DispatcherServlet
Con cada una de las peticiones que llegan desde el navegador del cliente al servidor, este
tendrá que darle sentido, tanto por las distintas URL, métodos, (principalmente POST y GET)
así como los parámetros, objetos JSON etc. El servidor analizará la petición y deberá decidir
qué controlador será quién se haga cargo de ella. En las aplicaciones JEE comunes todo esto se
define en el fichero web.xml en el que asociamos patrones de las peticiones para que se haga
cargo un Servlet.
Spring con DispatcherServlet lo que hace es añadir una capa más, de manera que todas las
peticiones que lleguen (y que queramos que se haga cargo el framework de Spring) llegarán a
el Servlet DispatcherServlet. Éste será el encargado, tras analizar la petición, de pasar la
responsabilidad de “el qué hacer” al controlador definido, es decir, a otra clase. El
HandlerMapping es la clase encargada de decidir qué clase controladora está mapeada para
determinada petición.
Es decir, cuando una petición sale del navegador tiene la URL solicitada y también puede tener
datos adicionales como datos de un formulario, esto llegará al DispatcherServlet (siempre que
cumpla el patrón que para él hayamos configurado). Este Servlet frontal lo que hará será
consultar al HandlerMapping si la petición está contenida y de ser así que Controlador está
asociado. Si existe coincidencia se le devolverá un controlador y un método de este el cuál
ejecutará y tras su ejecución devolverá un objeto ModelAndView el cual encapsula una vista y
los datos generados tras la ejecución del controlador y que deberá interpretar la vista
(normalmente un JSP). Para resolver la vista Spring se valdrá de la clase ViewResolver. Al
cliente le llegará el código HTML tras la ejecución e interpretación del JSP.
89
El framework de Spring MVC, así es como otros marcos de trabajo web en los que hay un
controlador principal que atiende la llamada y la delega en un secundario. Pero es algo más
que eso, ya que está integrado al contenedor de Spring y así puede disfrutar de todas las
ventajas de la inyección de dependencia y de la inversión de control así como cualquier
característica de Spring.
El flujo de trabajo del procesado de una petición está en el siguiente diagrama, la petición llega
ofrecida por el servidor (por ejemplo Tomcat o Glassfish) es delegada al controler frontal (el
DispatcherServlet) este delega en un controlador secundario que devolverá un objeto
ModelAndView (que contendrá el modelo y la vista) tras su ejecución al controlador frontal
que se encargará de buscar una vista, pasarle el modelo para después dar una respuesta al
cliente, el navegador.
Esto ha sido una breve introducción y resumen de lo que Spring MVC hace más comúnmente,
aunque cada una de las piezas comentadas realmente puede tener un comportamiento más
complejo, esta introducción nos da una primera impresión de los fundamentos de éste marco
de trabajo, más tarde daremos una explicación más detallada, pero de momento lo primero
que debemos hacer es configurar nuestro DispatcherServlet en el contenedor de Spring.
Configuración de DispatcherServlet y ContextLoaderListener
Como hemos dicho, DispatcherServlet será el Servlet que funciona como conector principal
con los controladores de Spring. Para que esto ocurra, hemos de configurar la clase como otro
Servlet más en el fichero web.xml y asignarle un patrón de URL. Si la petición coincide con el
patrón definido será redireccionado a este controlador.
<servlet > <servlet-name >helloWorldMvc </ servlet-name >
<servlet-class > org.springframework.web.servlet.DispatcherServlet </ servlet-class >
</ servlet > <servlet-mapping > <servlet-name >dispatcherServlet </ servlet-name > <url-pattern >*.html </ url-pattern > </ servlet-mapping >
El nombre que le demos, en este caso le hemos llamado “helloWorldMvc” es importante si lo
que queremos es configurar varios, Spring te permite tener tantos como queramos. Por otro
lado, por defecto Spring intentará abrir el fichero helloWorldMvc-servlet.xml.
En cuanto al patrón hemos utilizado éste (también el más utilizado en la comunidad Spring
junto a *.htm) como podríamos haber elegido cualquier otro. Bien es cierto que es una buena
práctica darle extensión ya que es muy posible que quisiéramos dejar absolutamente todo en
manos del Servlet de Spring.
DispatcherServlet ahora abrirá el contexto de aplicación web que hayamos definido (o el por
defecto, nombre-servlet.xml). Como ya comentamos en capítulos anteriores y como
seguiremos viendo más adelante, es una práctica muy buena dividir los ficheros de
configuración del contexto de igual modo que las capas lógicas más significativas.
Así tendríamos cuatro ficheros dividiendo las 4 capas más importantes, la capa de seguridad, la
capa de servicio, la capa de persistencia y con esta la capa web. Si dividimos los ficheros de
configuración de contexto de forma coherente con la lógica de la aplicación ganaremos en
reusabilidad, mantenibilidad y facilitaremos la comprensión.
De hecho, cualquiera de estos cuatro iniciales podrían ser reemplazables, es decir, imaginemos
que tenemos un fichero *-data.xml en el que hay una configuración de contexto que hace uso
del motor de persistencia JPA y otro *-data.xml con Hibernate, reemplazar uno por otro sería
facilísimo y no tendría que afectar a las otras capas.
Para asegurarnos que todos los archivos de configuración se abren necesitamos configurar un
cargador de contexto en el archivo web.xml. Éste será ContextLoaderListener cuya
configuración en el web.xml es:
<context-param > <param-name >contextConfigLocation </ param-name > <param-value > /WEB-INF/exampleapp-service.xml /WEB-INF/exampleapp-data.xml /WEB-INF/exampleapp-security.xml </ param-value > </ context-param > <listener >
<listener-class > org.springframework.web.context.ContextLoaderListen er </ listener-class >
</ listener >
91
Dentro de la etiqueta “context-param” definimos tanto el nombre del parámetro (será
contextConfigLocation) y una lista de valores que hacen referencia a cada uno de los archivos
de configuración de contexto de Spring. Aquí se han definido por servicio, datos y seguridad.
Aún no se han comentado la seguridad pero es conveniente que se vaya tratando este tema ya
que una buena modularización es crucial.
El DispatcherServlet es una clase que hereda de HttpServlet y como tal se declara en el
web.xml, por ello se le debe mapear las peticiones que queremos que maneje, este proceso es
un estándar de JEE.
El WebApplicationContext es una extensión del ApplicationContext con algunas características
necesarias para las aplicaciones web se diferencia de un AplicationContext normal en que es
capaz de resolver temas y que sabe en que Servlet está asociado. El WebApplicationContext
está ligado al ServletContext y usando métodos estáticos en la clase RequestContextUtils
podemos acceder al WebApplicationContext si es que necesitamos acceder a él.
El DispatcherServlet de Spring usa beans especiales para procesar la petición y devolver una
vista apropiada. Estos beans son parte del marco de trabajo de Spring y se pueden configurar
en el WebApplicationContext de la misma manera que configuras cualquier otro bean. Sin
embargo para la mayoría de los beans se proporcionan parámetros por defecto, con lo que no
es necesario configurarlo.
Los beans especiales del WebApplicationContext son los siguientes:
• Controladores.
• Manejadores de mapeo o handlerMappings, manejan la ejecución de una lista de pre-
procesos y post-procesos y controlan si son ejecutados si se cumple una determinada
condición.
• Resolutores de vista o ViewResolvers, los cuales resuelven los nombres de las vistas
con las vistas.
• localeResolver o resolutor de configuración local, el cual resuelve la localización del
cliente para darle una vista internacionalizada.
• Resoltores de tema o ThemeResolver, resuelven un tema y apariencia, (por ejemplo
dar un layout personalizado)
• MultipartFileResolver, que ofrece funcionalidades para subir ficheros desde
formularios.
• HandlerExceptionResolvers, contiene funcionalidades para manejar excepciones.
Usando Spring MVC sin anotaciones
En los últimos años el desarrollo del software se ha ido mudando de las clásicas aplicaciones de
escritorio al patrón cliente servidor y de este a la aplicación web gracias al Modelo Vista
Controlador.
• Para desarrollarlas con Spring básicamente necesitaremos hacer lo siguiente:
• Hacer una clase Controladora que interprete la petición del cliente y sus parámetros,
manipule, busque, cree o destruya los objetos del modelo y le devuelva una respuesta.
• Configurar el controlador en el fichero *-servlet.xml (este paso no será necesario con
las anotaciones).
• Crear una página JSP la cual será la parte de la vista, es decir una plantilla para mostrar
los datos y que, como ya sabemos, tras su interpretación se le enviará un .html al
cliente.
• Configurar el archivo que resuelva la página.
Creando nuestro primer Controlador
Como si de un interfaz se tratase, un controlador actúa como pieza que interconecta la
aplicación con el usuario. Si el lector está habituado al uso de otros marcos de trabajo Modelo
Vista Controlador como Struts verá que es bastante similar al concepto Action con la salvedad
de que al ser un bean definido en el contexto Spring podrá beneficiarse de la inyección de
dependencia así como de la Programación Orientada a Aspectos (AOP).
Por lo general un objeto Controlador delegará la responsabilidad de la acción a un objeto de
servicio designado para tal tarea (Darse de alta, crear, borrar, etc).
Hagamos un ejemplo sencillo de lo que podría ser una página de inicio para una aplicación,
Ésta página lo único que mostrará es la fecha actual.
package youdiversity.web; import java.util.Date; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.springframework.web.portlet.ModelAndView; import org.springframework.web.portlet.mvc.AbstractContro ller; /** * * @author javiersevilla */ public class WelcomeController extends AbstractController { public WelcomeController(){ } protected ModelAndView handleRequestInternal(HttpServletRequ est request, HttpServletResponse response) throws Exception { return new ModelAndView( "home" , "dateMessage" , new Date()); } }
Este controlador es de lo más sencillo que podemos hacer con Spring, pero sirve para
ilustrarnos cómo tras ejecutar el método se devuelve un objeto ModelAndView el cual
93
contendrá el nombre de la vista asociada (“home”) y un objeto (u objetos) que representarán
el modelo.
El objeto ModelAndView
Una vez que tengamos los resultados se han de mandar al navegador. En el controlador
anterior tan sólo se devolvía un objeto de nombre dateMessage que era de tipo Date con la
fecha del día de hoy.
Tal y como hemos visto, al crear un objeto ModelAndView hemos pasado en el primer
parámetro el nombre de la vista asociada. El encargado de interpretar este nombre con el
fichero .jsp correspondiente será el resolutor de visualización. Así la definición del controlador
en el fichero *-servlet.xml será de la siguiente manera:
Existen varios constructores del objeto ModelAndView, entre ellos se pueden encontrar el
constructor vacío, con el parámetro de la vista (ya sea el nombre en String o un objeto View) y
también con la vista y los objetos del modelo.
Configuración del controlador
Para dar de alta el controlador que acabamos de escribir iremos al archivo de configuración de
contexto del DispatcherServlet (como ya dijimos *-servlet.xml siendo * el nombre que le
demos al dispatcherServlet en el fichero web.xml).
En este caso no vamos a inyectar ningún otro objeto, en ejemplos posteriores veremos que es
tan sencillo como se hace con el resto de los bean.
El siguiente código muestra cómo hacerlo:
<bean class="org.youdiversity.web.controllers.WelcomeController" name="/welcome.htm"/>
Algo que puede sorprender es que no lleva propiedad “id” mientras que sí que lleva “name” y
éste es un patrón URL. Esto es tan sencillo como que el atributo “id” no puede llevar
caracteres especiales, como la barra inclinada.
Cuando llegue una petición que cumpla dicho patrón le pasará la llamada.
View Resolvers
Las Java Servlet Pages (JSP) se han impuesto como la tecnología java para desarrollo web. La
forma en la que se presentan, como un .xml y con todos los tags de una .html, hacen que sea
una forma muy amigable de presentar contenido web.
Para ayudar a Spring a buscar qué JSP utilizar para cada visualización se ha de incluir un nuevo
bean de tipo ViewResolver. El más sencillo es InternalResourceViewResolver cuya definición es
la siguiente.
<bean class ="org.springframework.web.servlet.view.InternalResou rceViewResolver" p:prefix ="/WEB-INF/jsp/" p:suffix =".jsp" />
Spring tiene varios ViewResolvers pero para las visualizaciones mostradas por JSP
InternalResourceViewResolver es el más sencillo. Su funcionamiento es tan simple como
añadir tanto el prefijo como el sufijo al nombre de la vista devuelta en el objeto
ModelAndView.
Así si nuestro controller devuelve “welcome” como nombre de la vista en el objeto
ModelAndView Spring buscará el archivo /WEB-INF/jsp/welcome.jsp.
Crear un JSP
Como último paso deberíamos crear el fichero “welcome.jsp”, el siguiente código ilustra su
contenido:
<%@ page language ="java" import ="java.util.*" pageEncoding ="ISO-8859-1" %> <! DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"> <html > <head > <title >WELCOME</ title > </ head > <body > ${dateMessage} <br > </ body > </ html >
Pasos de la petición
Ahora lo tenemos todo, el controlador, la página jsp, lo hemos registrado en el contexto de
DispatcherServlet y tenemos un ViewResolver.
Pero, ¿Qué ocurre cuando se recibe una petición?, los acontecimientos son los siguientes:
1. El servidor le pasa al DispatcherServlet la petición (para ello lo configuramos y le dimos
un patrón en el web.xml).
2. DispatcherServlet buscará qué controlador se encarga de la URL.
95
3. Nuestro controlador devuelve un ModelAndView con un nombre lógico de la vista y un
objeto o un mapa de objetos con los objetos del modelo.
4. DispatcherServlet buscará en el ViewResolver la vista con el nombre lógico (en nuestro
caso welcome) y devolverá la ruta completa (WEB-INF/jsp/welcome.jsp).
5. DispatcherServlet reenviará la petición al jsp.
Con esto hemos completado la manera más básica de operar con controladores sin
anotaciones o con marcos de trabajo inferiores a Spring 3. En los siguientes puntos veremos
todas las ventajas que la versión 3 nos aporta.
Usando Spring MVC con Anotaciones y Con Spring 3
Los Controladores facilitan el acceso al comportamiento de la aplicación que suelen definir a
través de un interfaz de servicio. Los controladores interpretan la entrada del usuario y
devuelven un modelo representado en una vista. Spring implementa controladores de una
manera muy abstracta que permite crear una gran variedad de controladores.
A partir de la versión 2.5, Spring introdujo una serie de anotaciones para los controladores
MVC como @Controller, @RequestMapping, @ModelAttribute etc. Estas anotaciones están
disponibles tanto para Servlet MVC o Portlet MVC. Los controladores que son implementados
de este modo no han de extender de clases específicas como AbstractController o
implementar interfaces como Controller. Tampoco tienen dependencias con las API de Servlet
o Portlet aunque se pueden configurar fácilmente el acceso a ellas.
package org.youdiversity.web.controllers; import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMap ping; import org.springframework.web.servlet.ModelAndView; @Controller public class HelloWorldController { @RequestMapping ( "/helloWorld" ) public ModelAndView helloWorld() { ModelAndView mav = new ModelAndView(); mav.setViewName( "helloWorld" ); mav.addObject( "message" , "Hello World!" ); return mav; } }
Como se puede ver, las anotaciones @Controller y @RequestMapping permiten una gran
flexibilidad de métodos y firmas. En este ejemplo no hay parámetros y devuelve un
ModelAndView. Existen múltiples estrategias como se explicarán más adelante en este
capítulo. Con ModelAndView, @Controller y @RequestMapping tenemos los pilares básicos
para construir con Spring MVC.
Definir el controlador en el fichero *-servlet.xml.
La anotación @Controller indica que una clase en particular sirve como un controlador. Spring
como ya hemos dicho, no necesita que extiendas de ninguna implementación de Controller.
El dispatcher escanea en búsqueda de clases que tengan métodos mapeados y detecta
anotaciones @RequestMapping y @Controller.
Se pueden definir estos controladores directamente en el contexto del dispatcher, sin
embargo la anotación @Controller permite que se autodetecte. Para ello tendremos que
incluir el siguiente tag en fichero de configuración de contexto:
<context:component-scan base-package ="org.youdiversity.web.controllers" />
Mapeando peticiones con @RequestMapping
La anotación @RequestMapping se usa para mapear URL ya sea en una clase entera o en un
método en particular. Normalmente si mapeamos a nivel de clase se usarán un método u otro
dependiendo del método de petición (GET o POST) o los parámetros de la petición.
El siguiente código muestra las cabeceras de dos métodos el los cuáles uno será llamado
cuando se hagan peticiones POST y la segunda cuando sean GET:
@RequestMapping (method = RequestMethod. POST) public String processSubmit( @ModelAttribute AdminDataPicker …
…
@RequestMapping (method = RequestMethod. GET)
97
public String setupForm(Model model) {
…
En el ejemplo el @RequestMapping es usado para cada método. Lo que hará que se
diferencien será tanto los parámetros de la petición, si hay sub-URL o si la petición es GET o
POST.
El @RequestMapping a nivel de clase no es necesario, si no lo hacemos cada uno de los
métodos será a nivel absoluto. Como en el siguiente ejemplo:
package org.youdiversity.web.controllers; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMap ping; import org.springframework.web.servlet.ModelAndView; @Controller public class HelloWorldController { @RequestMapping ( "/helloWorld" ) public ModelAndView helloWorld() { ModelAndView mav = new ModelAndView(); mav.setViewName( "helloWorld" ); mav.addObject( "message" , "Hello World!" ); return mav; } }
Peticiones URL, usando @RequestMapping
Para acceder a partes de la petición URL se pueden usar plantillas del modo
“http://www.ejemplo.com/usuarios/546” con @ReuestMapping lo mapearemos del siguiente
modo @RequestMapping(value=“/usuarios/{usuarioId}”). Usaremos @PathVariable para
designar cada uno de las valores contenidos en la URL. El siguiente ejemplo muestra este uso:
@RequestMapping ( "/user/{userId}" ) public ModelAndView userHandler ( @PathVariable ( "userId" ) Long userId) { ModelAndView mav = new ModelAndView( "user/profile" ); mav.addObject( userDao .get(userId)); return mav; }
De este modo si recibiésemos una petición /user/12 el número 12 sería una variable de path
que en este caso nos servirá como en id del usuario.
Sólo si se ha compilado en modo debug no se necesitará la anotación @PathVariable para
enlazar con el valor de la variable.
También se pueden usar varias variables
@RequestMapping ( "/user/{userId}/{subjectId" ) public ModelAndView userHandler ( @PathVariable ( "userId" ) Long userId,
@PathVariable ( "subjectId" ) Long subjectId) {
...
return mav; }
O incluso definir una a nivel global y otras a nivel particular
@Controller @RequestMapping ( "/user/{userId}" ) public class UserProfileController { private UserDao userDao ; @Autowired public UserProfileController(UserDao userDao) { this. userDao = userDao; } @RequestMapping ( "/subject/{subjectId}" ) public ModelAndView userHandler ( @PathVariable ( "userId" ) Long userId,
@PathVariable ( "subjectId" ) Long subjectId) { ...
return mav; } }
Mapeo avanzado con @RequestMapping
Además de las URL la anotación @RequestMapping soporta expresiones de direcciones, como
al estilo de Ant (por ejemplo: /users/*/subject/{subjectId}). Los resolutores de direcciones
MethodNameResolver y PathMethodNameResolver buscaran primero rutas explícitas y tras
descartar cada una de ellas las rutas que tengan una expresión como la citada serán las que
queden por defecto si no ha habido una coincidencia anterior.
Si se tienen un único método por defecto (sin un mapeo específico), entonces todas las
peticiones sin métodos con mapeos más específicos serán servidas a él.
Se pueden especificar que el método está esperando determinados parámetros que han de
estar o no estar, es decir, @RequestMapping (value = "/subject/{subjectd}", params =
"myParam=myValue"). Así este método sólo será mapeado si además de cumplir el
path de la URL también tenga el parámetro “myParam” y con el valor “myValue”. Para
indicar lo contrario sería indicar “!myParam”, así ese método nunca será seleccionado
si encuentra el parámetro “myParam”.
De igual forma, los mapeos pueden ser configurados teniendo en cuenta también el contenido
de la cabecera.
@RequestMapping (value= "/subject/{subjectId}" ,headers = "content-type=text/*" ) public ModelAndView userHandler( @PathVariable ( "userId" ) Long userId, @PathVariable ( "subjectId" ) Long subjectId) { ... return mav; }
99
El método anterior sólo es ejecutado cuando el contenido es text/* como por ejemplo
text/html.
Argumentos de método soportados
Los métodos anotados con @RequestMapping pueden tener una firma muy flexible. Muchos
de ellos pueden ser usados de modo arbitrario.
Se pueden elegir distintas implementaciones del objeto Request o Response del API de Servlet
como ServletRequest o HTTPServletRequest.
Que aparezca un objeto de sesión del API de Servlet fuerza la presencia de una sesión. Con lo
que nunca tendrá un valor nulo.
Los objetos org.springframework.web.context.request.WebRequest o
org.springframework.web.context.request.NativeWebRequest Te permiten el acceso genérico
a un parámetro de la petición así como el acceso a los atributos Request/Session sin que haya
vínculos al API nativo Servlet/Portlet.
java.util.Locale es determinado según el mayor resolutor de locale que esté disponible, de
hecho, el LocaleResolver es un Servlet.
Se puede acceder a la petición con java.io.InputStream o con java.io.Reader de igual manera
que con el API Servlet.
También se puede generar una respuesta con java.io.OutputStream así como con
java.io.Writer como usásemos el API de Servlet.
El objeto java.security.Principal contiene el usuario autenticado.
Los parámetros de acceso de la anotación @PathVariable obtienen el acceso a las variables de
la URI.
Con la anotación @RequestParam se hace referencia a los parámetros de la petición, es decir a
los parámetros específicos de la petición de la Request. Los parámetros son convertidos en el
tipo declarado en el método. Más tarde haremos referencia a como enlazar (o hacer binding)
estos valores.
La anotación @RequestHeader se utiliza para acceder a los parámetros de la cabecera de la
petición. Los valores de los parámetros serán convertidos en los tipos declarados.
La anotación @RequestBody se utiliza para anotar parámetros que se hallen en el cuerpo de la
petición. Los valores serán convertidos en los tipos declarados usando
HttpMessageConverteres. Más tarde veremos cómo hacerlo.
Con el parámetro HttpEntity<?> podremos acceder a las cabeceras HTTP y su contenido de la
petición. El flujo de la petición será convertido en la entidad body usando
HttpMessageConverteres. También lo veremos más tarde.
Podemos usar java.util.Map, org.springframework.ui.Model o
org.springframework.ui.ModelMap para guardar los objetos de modelo.
Los objetos Command o Form los podemos enlazar con parámetros como propiedades de los
beans, campos, conversiones con algún tipo personalizado, dependiendo de los métodos de
@InitBinder y de la configuración del HandlerAdapter.
Con org.springframework.validation.Errors o org.springframework.validation.BindingResult se
valida un comando o formulario.
Con org.springframework.web.bind.support.SessionStatus se maneja el estado para hacer el
proceso de un formulario completo. El cual desencadena la limpieza de los atributos de la
sesión que se han quedado indicados por la anotación @SessionAttributes.
Los parámetros de errores (Errors) o de resultado del enlace (BindingResult) han de seguir el
modelo del objeto que ha sido enlazado inmediatamente. Es decir, si se enlaza un modelo, y
un @ModelAttribute en la firma del método no podremos poner un único BindingResult, sino
que tendremos que poner uno a continuación de cada uno de los objetos enlazados.
Tipos devueltos soportados
El objeto ModelAndView en el que se haya el modelo con los objetos y los resultados de la
referencia a los datos de la anotación @ModelAttribute.
El objeto Model, en este caso el nombre de la vista será determinado implícitamente a través
RequestToViewNameTranslator y el objeto Model, de igual forma a ModelAndView, contendrá
los objetos y resultados.
Un mapa (implementación de Map), que representará los objetos del modelo. De igual forma
cuando se devuelve un Model la vista será determinada por RequestToViewNameTranslator.
Una vista, es decir, un objeto View. El modelo estará implícitamente determinado por los
objetos del comando y los objetos con anotaciones @ModelAttribute. De igual forma, los
objetos de comando estarán implícitos como modelo.
Una cadena que representa la vista lógica. De igual modo, tendrá implícito el modelo con los
objetos de comando y los objetos anotados con @ModelAttribute.
Nada, es decir un método con firma void. En este caso el método manejará la petición por sí
mismo, es decir, escribiendo la respuesta directamente con contenido, declarando un
101
argumento de tipo ServletResponse o HttpResponse o si el nombre de la vista se ha de deducir
a través del objeto RequestToViewNameTranslator.
Si el método está anotado con @ResponseBody, el tipo a devolver tendrá que estar escrito en
el cuerpo de la respuesta HTTP. El valor del tipo devuelto será convertido en el tipo del
argumento del método declarado usando HttpMessageConverters.
Una entidad HttpEntity<?> o ResponseEntity<?>, objeto que proporciona acceso a las
cabeceras de respuesta HTTP y a sus contenidos. El cuerpo de la entidad será convertida en el
flujo de respuesta usando HttpMessageConverters.
Cualquier otro tipo devuelto es considerado un único objeto de modelo. De este modo, como
pasa de otros muchos, se le añadirán los objetos command así como los anotados con
@ModelAttribute y la vista será determinada.
Enlazando parámetros de la petición a parámetros de métodos con
@RequestParam
Se usa @RequestParam para enlazar los parámetros de la petición con los parámetros de los
métodos del controlador.
El siguiente código muestra un ejemplo:
@Controller @SessionAttributes (types = AdminDataPicker. class) @RequestMapping ( "/public/createAdmin" ) public class CreateAdminController { @Autowired private AdminDao adminDao ; @RequestMapping (method = RequestMethod. GET) public String setupForm(Model model) { AdminDataPicker adminDataPicker = new AdminDataPicker(); model.addAttribute( "adminDataPicker" , adminDataPicker); this.refreshData(model); return "public/createAdmin" ; } private void refreshData(Model model) { } @RequestMapping (method = RequestMethod. POST) public String processSubmit(
@ModelAttribute AdminDataPicker adminDataPicker, BindingResult res ult, SessionStatus status, Model model) {
new AdminValidator().validate(adminDataPicker, result, this. adminDao ); if (result.hasErrors()) { this.refreshData(model); return "public/createAdmin" ; } else { Admin user = this. adminDao .saveFromDataPicker(adminDataPicker); status.setComplete(); return "redirect:/userProfile/" + user.getId(); } } }
Los parámetros que usan esta anotación se requieren por defecto, si queremos que sean
opcionales tendremos que añadir el atributo “required” con un valor “false”.
@RequestBody
La anotación @RequestBody cuando se usa sobre un parámetro del método indica a Spring
que ese parámetro del método ha de ser enlazado con el valor de la petición HTTP. Por
ejemplo:
@RequestMapping(value = "/something", method = Requ estMethod.PUT) public void handle(@RequestBody String body, Writer writer) throws IOException { writer.write(body); }
Se puede convertir el cuerpo de la petición usando HttpMessageConverter. Este objeto
también convierte el mensaje de solicitud a un objeto de respuesta. DispatcherServlet soporta
el procesamiento de la anotación.
@ResponseBody
La anotación @ResponseBody es la análoga de la respuesta de @RequestBody. En este caso
puesto sobre un método indica a Spring que el valor de retorno del método será una cadena
con el cuerpo de la respuesta.
@RequestMapping ( "answerSingleQuestion" ) @ResponseBody public String addAnswer( @RequestParam Long questionId, @RequestParam(required = false) List<String> answersId) { List<Long> answersIdLongs = new ArrayList<Long>(); if (answersId != null) { for (String answerId : answersId) { answersIdLongs.add( new Long(answerId)); } } QuestionAnswered questionAnswered = this. questionDao .saveQuestionAnsweredByUser(questionId, answersIdLo ngs); return questionAnswered.getCorrect() ? "Pregunta contestada correctamente" : "Pregunta no contestada correctamente" ; }
Spring convertirá la cadena en una respuesta HTTP usando el conversor HttpMessageConverter.
HttpEntity
103
Similar a @RequestBody o @ResponseBody, el HttpEntity tiene acceso al cuerpo de la petición
o de la respuesta y permite acceder a las cabeceras de las peticiones o de las respuestas.
El siguiente método es un ejemplo:
@RequestMapping("/something") public ResponseEntity<String> handle(HttpEntity<byt e[]> requestEntity) throws UnsupportedEncodingException { String requestHeader = requestEntity.getHeaders().getFirst("MyRequestHeade r")); byte[] requestBody = requestEntity.getBody(); HttpHeaders responseHeaders = new HttpHeaders(); responseHeaders.set("MyResponseHeader", "MyValue" ); return new ResponseEntity<String>("Hello World", responseHeaders, HttpStatus.CREATED);
Proporcionando un enlace a los datos del modelo con @ModelAttribute
Se usa @ModelAttribute cuando lo pones en un parámetro del método indicando que
pertenece a un atributo del modelo o cuando lo pones sobre un método indicando que el valor
devuelto pertenece a un objeto de modelo (comúnmente para iniciarlo).
Para iniciarlo:
@ModelAttribute ( "types" ) public Collection<RoleType> populateRoleTypes() { return RoleType.values(); }
O como hemos dicho, para enlazarlo con un parámetro del método:
@RequestMapping (method = RequestMethod. POST) public String processSubmit( @ModelAttribute AdminDataPicker adminDataPicker, BindingResult result, SessionStatus status, Model model) { new AdminValidator().validate( adminDataPicker, result, this. adminDao ); if (result.hasErrors()) { this.refreshData(model); return "public/createAdmin" ; } else { Admin user = this. adminDao .saveFromDataPicker(adminDataPicker); status.setComplete(); return "redirect:/userProfile/" + user.getId(); }
}
Especificando atributos para almacenar en la sesión
La anotación @SessionAttributes indica que atributos de sesión son usados por un manejador
en particular. Esto lista una serie de nombres de atributos del modelo que se almacenaran
como atributos de sesión.
@Controller @SessionAttributes (types = TeacherDataPicker. class) @RequestMapping ( "/admin/createTeacher" ) public class CreateTeacherController {
Uso de cookies con @CookieValue
La anotación @CookieValue permite enlazar una cookie HTTP con un método. El siguiente
código muestra cómo hacerlo:
@RequestMapping("/displayHeaderInfo.do") public void displayHeaderInfo(@CookieValue("JSESSIO NID") String cookie) { //... }
Mapeando cabeceras de peticiones con @RequestHeader
La anotación @RequestHeader permite enlazar los parámetros de los métodos con la cabecera
de la petición.
Dada la siguiente cabecera:
Host localhost:8080 Accept text/html,application/xhtml+xml,application/xml;q=0 .9 Accept-Language fr,en-gb;q=0.7,en;q=0.3 Accept-Encoding gzip,deflate Accept-Charset ISO-8859-1,utf-8;q=0.7,*;q= 0.7 Keep-Alive 300
Un ejemplo sería el siguiente:
@RequestMapping("/displayHeaderInfo.do") public void displayHeaderInfo(@RequestHeader("Accep t-Encoding") String encoding, @RequestHeader("Keep- Alive") long keepAlive) { //... }
105
Personalizando el enlace a Datos
Se pueden usar personalizaciones de enlace tanto con una clase WebBindingInitializer como
con métodos exclusivos para cada controlador gracias a la anotación @InitBinder.
El siguiente método es un ejemplo
@InitBinder public void initBinder(HttpServletRequest request, ServletRequ estDataBinder binder) throws Exception { binder.registerCustomEditor( Carrer. class, new CarrerCustomEditor( this. carrerDao )); binder.registerCustomEditor( Subject. class, new SubjectCustomEditor( this. subjectDao )); SimpleDateFormat dateFormat = new SimpleDateFormat( "dd/MM/yyyy" ); dateFormat.setLenient( false);
binder.registerCustomEditor( Date. class, new CustomDateEditor(dateFormat, false)); }
Para externalizar y normalizar el enlace de datos a nivel de aplicación implementaremos una
clase de WebBindingInitializer.
<bean class ="org.springframework.web.servlet.mvc.annotation.Ann otationMethodHandlerAdapter" > <property name="webBindingInitializer" > <bean class ="com.campus.web.binding.CampusBindingInitializer" /> </ property > </ bean >
Mapeo de controladores
Como hemos visto, en las versiones anteriores a Spring 2.5, era necesario definir
HandlerMapping en el contexto de la aplicación web. Ahora el DispatcherServlet busca
anotaciones @RequestMapping en los controladores. Hay diversas propiedades que están
inicializadas por defecto y que para modificarlas tendremos que sobrescribir como puedan ser
interceptores, manejadores por defecto, orden de los mapeos, si queremos usar expresiones o
rutas completas … entre otras.
<beans> <bean id="handlerMapping" class="org.springframework.web.servlet.mvc.annotati on.DefaultAnnotationHandlerMapping"> <property name="interceptors"> <bean class="example.MyInterceptor"/> </property> </bean> <beans>
Interceptando peticiones con HandlerInterceptor
El mecanismo de mapeo incluye interceptores que son útiles cuando se desea aplicar una
cierta funcionalidad a determinadas peticiones, por ejemplo, chequeando la principal.
Los interceptores localizados en el manejador del mapeo deben implementar
HandlerInterceptor . Este interfaz define tres métodos, preHandle , postHandle y
afterCompletion. El primero es llamado antes de que se ejecute el manejador actual, el
segundo después y el último después de que la petición se haya terminado. Estos métodos
otorgan completa flexibilidad para procesar cualquier tipo de preproceso y postproceso.
El método preHandle devuelve un valor booleano, dependiendo del valor devuelto se
continuará o cancelará la ejecución del controlador. Si el valor devuelto es falso el
DispatcherServlet asumirá que el propio interceptor ha redireccionado y tomado control de la
petición.
Resolver vistas Todos los marcos de trabajo MVC otorgan maneras de direccionar las vistas. Spring
proporciona resolutores de vistas los cuales permiten enlazar el modelo sin atarse a ninguna
tecnología de vista específica. Spring permite usar vistas JSP, Velocity o XSLT.
ViewResolver y View son los interfaces que Spring utiliza.
ViewResolver
Como hemos comentado, todos los métodos en los controladores de Spring MVC deben
resolver con el nombre de una vista lógica, de manera implícita o explícita. Las vistas de Spring
son mapeadas con una vista lógica resuelta por el resolutor de vista. Spring proporciona los
siguientes resolutores:
• AbstractCachingViewResolver
• XmlViewResolver
• ResourceBundleViewResolver
• UrlBasedViewResolver
• InternalResourceViewResolver
• VelocityViewResolver / FreeMarkerViewResolver
• ContentNegotiatingViewResolver
Con UrlBasedViewResolver las traducciones de resolución de vista se hacen desde una URL
<bean id ="viewResolver" class ="org.springframework.web.servlet.view.UrlBasedViewR esolver" > <property name="viewClass" value ="org.springframework.web.servlet.view.JstlView" /> <property name="prefix" value ="/WEB-INF/jsp/" /> <property name="suffix" value =".jsp" /> </ bean >
107
Cuando se devuelve ejemplo como vista lógica se traducirá el fichero físico /WEB-
INF/jsp/ejemplo.jsp.
Cuando se combinan varias tecnologías de vista se pueden usar
ResourceBundleViewResolver del siguiente modo:
<bean id ="viewResolver" class ="org.springframework.web.servlet.view.ResourceBundl eViewResolver" > <property name="basename" value ="views" /> <property name="defaultParentView" value ="parentView" /> </ bean >
Encadenando ViewResolvers
Spring permite tener múltiples resolutores de vista encadenados. Se puede especificar el
orden con el atributo order, a mayor valor de este más tarde se posicionará.
Redireccionando a vistas
Como hemos dicho un controlador devuelve un nombre de vista lógica. Para tecnologías de
vista como JSP que se procesan a través de motores JSP o Servlet, esta resolución está
típicamente manejada a través de la combinación de InternalResourceViewResolver e
InternalResourceView los cuales redireccionan o incluyen a través del API de Servlet con
los métodos RequestDispatcher.forward() o RequestDispatcher.include().
En ocasiones es deseable una tarea de redirección HTTP de vuelta al cliente antes de devolver
la vista. Esto es deseable cuando un controlador ha sido llamado con datos enviados con POST
y la respuesta es en verdad una delegación de otro controlador que también ve los mismos
datos enviados con POST. Esto es potencialmente problemático si puede confundir con otros
datos esperados también enviados por POST. Otra razón por la que se redirige antes de enviar
a la vista es para eliminar la posibilidad de que el usuario haga un submit múltiple, es decir, un
envío de un formulario varias veces. En este escenario, el navegador enviará un primer POST
con datos entonces el recibirá una respuesta con una redirección a una URL distinta,
finalmente el navegador ejecutará una redirección GET a la URL contenida en la respuesta de
redirección. Así, desde la perspectiva del navegador, la página actual no refleja el resultado de
un POST sino de un GET. El efecto final es que no hay manera que el usuario pueda
accidentalmente mandar los mismos datos en múltiples ocasiones. El refresco fuerza un GET
del resultado de la página, no un re-envío de los datos por POST.
RedirectView
Una manera de forzar una redirección como resultado de la respuesta de un controlador es
crear y devolver una instancia de RedirectView , en este caso, DispatcherServlet no hará
uso del mecanismo normal de resolución de vista. En cambio, como ya se ha dado la
redirección a la vista el DispatcherServlet simplemente deja que la vista haga su trabajo.
La tarea de RedirectView es una llamada al método de redirección
HttpServletResponse.sendRedirect(), el cual devuelve al navegador una redirección HTTP.
Todos los atributos del modelo serán parámetros de consulta HTTP, esto significa que el
modelo debe contener solo objetos (Cadenas o valores convertibles a cadenas), los cuales han
de estar preparados para ser convertidos en parámetros textuales de una consulta HTTP.
El prefijo “redirect:”
Aunque el uso de RedirectView funciona bien, si es controlador se crea en RedirectView , no
hay manera de evitar que el controlador sea consciente de que está pasando una redirección.
Esto no es del todo óptimo, el controlador no debería preocuparse de que la respuesta sea
controlada. En general sólo se debería operar en términos de los nombres de las vistas.
El prefijo especial redirect: permite lograr esto. Si un nombre de vista es devuelto con un
prefijo redirect: el UrlBasedViewResolver reconocerá este aspecto especial como una
redirección. El resto del nombre de la vista será tratado como la redirección URL.
El efecto final es como si el controlador devolviese una redirección RedirectView, pero ahora el
mismo controlador puede operar en términos de nombre de vista lógica. Lo importante de
esto es que el mismo controlador no sabe que la redirección está pasando.
El prefijo “forward:”
También existe la posibilidad de usar un prefijo especial forward: para los nombres de vistas
que serán resueltas por UrlBasedViewResolver . Esto crea un InternalResourceView , el cual
ejecuta un RequestDispatcher.forward() . Este prefijo no es útil con
InternalResourceViewResolver y InternalResourceView pero puede ser útil cuando se
está usando otro tipo de tecnología para la vista pero se quiere forzar un forward a un recurso
controlado por un motor Servlet o JSP.
Como con redirect: el prefijo forward: es inyectado en el controlador, el controlador no
detecta que nada especial esté pasando en términos de manejar la respuesta.
ContentNegotiatingViewResolver
La clase ContentNegotiatingViewResolver no resuelve las vistas ella misma, en su lugar delega
a otros resolutores seleccionando la vista que se asemeja a la representación de la petición del
cliente. Dos estrategias son llevadas a cabo:
Usar una URI distinta para cada recurso, normalmente usando una extensión distinta. Por
ejemplo la URI, http://ejemplo.org/usuarios.pdf y http://ejemplo.org/usuarios.xml, uno
pediría una representación PDF de los usuarios y la otra una representación XML.
Para permitir múltiples representaciones de un recurso Spring dispone del
ContentNegotiantingViewResolver el cual resuelve una vista basada en la extensión del fichero
o de la cabecera de la petición HTTP. ContentNegotiantingViewResolver no interpreta la
109
resolución de la vista sino que delega a una lista de resolutores de vista que especificarán a
través de la propiedad ViewResolver.
La clase ContentNegotiatingViewResolver selecciona un resolutor de vista comparando el tipo
de la petición (content-type) con los distintos ViewResolvers. El primer ViewResolver que sea
compatible con el content-type devolverá la representación al cliente. Si no hubiese una
coincidencia de contenido por los distintos ViewResolvers se consultará la propiedad
DefaultViews. Esta última opción es apropiada para vistas singleton que hará una
representación adecuada de los recursos sin importar el nombre de vista lógico. Se pueden
incluir caracteres comodín, como por ejemplo text/* en el que el content-type podrá ser
text/html, text/xml etc.
Para ayudar a la resolución de la vista basada en extensiones de ficheros se usa la propiedad
mediaTypes en el bean ContentNegotiantingViewResolver para especificar un mapeo de
extensiones de content-type.
<bean class ="org.springframework.web.servlet.view.ContentNegoti atingViewResolver" > <property name="mediaTypes" > <map> <entry key ="atom" value ="application/atom+xml" /> <entry key ="html" value ="text/html" /> <entry key ="json" value ="application/json" /> </ map> </ property > <property name="viewResolvers" > <list > <bean class ="org.springframework.web.servlet.view.BeanNameViewR esolver" /> <bean class ="org.springframework.web.servlet.view.InternalResou rceViewResolver" > <property name="prefix" value ="/WEB-INF/jsp/" /> <property name="suffix" value =".jsp" /> </ bean > </ list > </ property > <property name="defaultViews" > <list > <bean
class ="org.springframework.web.servlet.view.json.MappingJ acksonJsonView"/>
</ list > </ property > </ bean > <bean id ="content"
class ="com.springsource.samples.rest.SampleContentAtomVie w" />
El InternalRessourceViewResolver maneja la traducción de la visa en páginas JSP, mientras que
el BeanNameViewResolver devuelve una vista basada en el nombre del bean. En este ejemplo
el content bean es una clase que hereda de AbstractAtomFeedView la cual devuelve un Atom
feed RSS.
En la siguiente configuración si una configuración es creada con la extensión .html el resolutor
de vista buscará una vista cuyo media type coincida con text/html. El
InternalResourceViewResolver otorga la coincidencia para la vista text/html. Si la petición es
creada con una extensión .atom, el resolutor de vista buscará una vista que coincida con el
media type application/atom+xml. Esta vista será proporcionada por el resolutor
BeanNameViewREsolver que mapea SampleContentAtomView como si el nombre de la vista
devuelto fuera el contenido. Si la petición es creada por una extensión .json la instancia del
bean MappingJacksonJsonViewserá elegido ya que es la opción por defecto. Por otro lado, las
solicitudes se pueden hacer sin extensión del archivo pero con la cabecera Accept definida con
un media-type, en tal caso se hará la misma resolución.
Configuración regional
La mayor parte de la arquitectura de Spring soporta internacionalización, incluida la parte
Spring MVC. DispatcherServlet permite resolver mensajes de manera automática usando el
locale del cliente gracias a los objetos LocaleResolver.
Cuando llega una petición el DispatcherServlet busca un resolutor local, si se encuentra uno se
intentará usar para asignar una configuración regional. Mediante el método
RequestContext.getLocale() se pueden obtener el Locale que fue resuelto por el
LocaleResolver.
A parte de la configuración automática de resolución de Locale, también se puede configurar
un interceptor para que maneje el mapeo para cambiar el Locale en determinadas
circunstancias, como por ejemplo, como parte de la URI, o por parámetros.
Los resolutores de configuración regional y los interceptores están definidos en el paquete
org.springframework.web.wervlet.i18n.
AcceptHeaderLocaleResolver
Este resolutor busca en el parámetro de la cabecera de la petición accept-language que fue
enviada por el cliente. Normalmente este parámetro contiene el Locale del sistema operativo.
CookieLocaleResolver
Este resolutor busca en una cookie que debe existir en el cliente para ver si hay un Locale
específico. Si lo hay, se usará ese locale específico. Se puede definir el nombre de la cookie, su
vida así como aplicarle un path el cual será visible para una parte específica de la aplicación (/
para toda la aplicación).
SessionLocaleResolver
111
El SessionLocaleResolver permite recibir Locales desde la sesión que quizá fue asociada con
una petición del usuario.
LocaleChangeInterceptor
Se puede activar la opción de poder cambiar los Locale añadiendo LocaleChangeInterceptor a
uno de los manejadores de mapeo. Eso hará que detecte un parámetro en la petición y cambie
el Locale. Se llamará a setLocale del objeto LocaleResolver que está en el contexto. El siguiente
ejemplo muestra cómo las llamadas a los recursos *.view contienen un parámetro que se
llama siteLanguage que cambiará el Locale.
<bean id ="localeChangeInterceptor" class ="org.springframework.web.servlet.i18n.LocaleChangeI nterceptor" > <property name="paramName" value ="siteLanguage" /> </ bean > <bean id ="localeResolver" class ="org.springframework.web.servlet.i18n.CookieLocaleR esolver" /> <bean id ="urlMapping" class ="org.springframework.web.servlet.handler.SimpleUrlH andlerMapping" > <property name="interceptors" > <list > <ref bean ="localeChangeInterceptor" /> </ list > </ property > <property name="mappings" > <value >/**/*.view=someController </ value > </ property > </ bean >
Uso de temas Se pueden aplicar temas al marco de trabajo de Spring MVC para el conjunto del look-and-feel
de una aplicación. La resolución del tema es muy similar a la resolución del Locale. Un tema es
una colección de recursos estáticos, hojas de estilo e imágenes que afectan al aspecto visual de
la aplicación.
Definiendo los temas
Para usar temas en la aplicación deberemos configurar una implementación del interfaz
ThemeSource. El contexto de aplicación web implementa ThemeSource, pero delega sus
responsabilidades a una implementación dedicada. Por defecto, esta será
ResourceBundleThemeSource que leerá los ficheros de propiedades de la raíz del classpath. Se
podrán usar implementaciones personalizadas de ThemeSource. Cuando se quiere configurar
ResourceBundleThemeSource o se quiere definir otro específico se ha de hacer en el contexto
de la aplicación. Si queremos usar ResourceBundleThemeSource habrá que definir un par de
propiedades en un fichero.
Luego utilizaremos el tag <spring:theme en los ficheros JSP que es muy similar al tag
<spring:message.
...
<link rel="stylesheet" href="<spring:theme code='st yleSheet'/>" type="text/css"/> ... <body style="background=<spring:theme code='backgro und'/>"> ...
Por defecto ResourceBundleThemeSource usa un nombre base vacío como prefijo. Así, el
fichero de properties es cargado desde la raíz del classpath. ResourceBundleThemeSource
utiliza el mecanismo de carga del paquete estándar de Java para leer los recursos, siendo así
también internacionalizable.
Resolutores de tema
Después de definir los temas hay que elegir cuál de ellos usar. El DispatcherServlet buscará un
bean llamado themeResolver para saber que implementación de ThemeResolver se usará.
ThemeResolver funciona de manera muy similar a LocaleResolver, se detecta que tema se
usará en una petición en particular y también se puede cambiar el tema. Spring ofrece los
siguientes resolutores de tema:
• FixedThemeResolver, Selecciona un tema fijo usando la propiedad
defaultThemeName.
• SessionThemeResolver, El tema es mantenido en la sesión HTTP del usuario el cuál
permanecerá toda la sesión, pero no estará persistida entre sesiones.
• CookieThemeResolver, El tema se guarda en una cookie en el navegador del cliente.
Spring también tiene la clase ThemeChangeInterceptor que permite los cambios de tema en
cada una de las peticiones con un único parámetro.
Subida de ficheros
Spring permite la subida de ficheros multipart. Se puede activar esta característica con el
objeto MultipartResolver. Spring proporciona un resolutor multiparte para usar Commons
FileUpload de Apache.
El siguiente ejemplo muestra cómo usar CommonsMultipartResolver:
<bean id ="multipartResolver" class ="org.springframework.web.multipart.commons.CommonsM ultipartResolver" > <property name="maxUploadSize" value ="100000" /> </ bean >
Por supuesto también se necesita poner los .jar necesarios en el classpath para que el
resolutor multipart funcione, es decir, el fichero commons-fileupload.jar.
Cuando el DispatcherServlet de Spring detecta una petición multi-part activa el resolutor que
haya sido declarado en el contexto. El resolutor entonces envuelve la petición HTTP actual en
una MultipartHttpServletRequest que permite la subida por partes de ficheros.
113
Subiendo un fichero desde un formulario
Después de que el resolutor complete su trabajo, la petición se procesa como si hubiese sido
cualquier otra. Primero hay que crear un formulario con un input de tipo file. Habrá que definir
el atributo enctype con el valor “multipart/form-data”, eso permitirá al navegador saber cómo
ha de enviar el fichero. El siguiente ejemplo muestra cómo hacerlo:
<html> <head> <title>Upload a file please</title> </head> <body> <h1>Please upload a file</h1> <form method="post" action="/form" enctype= "multipart/form-data"> <input type="text" name="name"/> <input type="file" name="file"/> <input type="submit"/> </form> </body> </html>
El siguiente paso será crear un controlador que maneje la petición de la subida del fichero.
Este controlador es muy similar a cualquier otro, con la salvedad que usa
MultipartHttpServletRequest o MultipartFile como parámetros de método. El siguiente código
muestra cómo hacerlo:
@Controller public class FileUpoadController { @RequestMapping(value = "/form", method = Reque stMethod.POST) public String handleFormUpload(@RequestParam("n ame") String name, @RequestParam("file") MultipartFile file) { if (!file.isEmpty()) { byte[] bytes = file.getBytes(); // store the bytes somewhere return "redirect:uploadSuccess"; } else { return "redirect:uploadFailure"; } } }
Cabe destacar cómo los atributos anotados con @RequestParam son los elementos input del
fichero.
Finalmente, se tendrán que declarar el controlador y su resolutor en el contexto de la
aplicación.
<beans> <bean id="multipartResolver" class="org.springframework.web.multipart.commons.Co mmonsMultipartResolver"/> <bean id="fileUploadController" class="examples.FileUploadController"/> </beans>
Manejo de Excepciones
La clase de Spring HandlerExceptionResolver se encarga de la excepción de excepciones que
puedan ocurrir mientras se hace se ejecuta la petición a un controlador.
HandlerExceptionResolver tiene un comportamiento similar al mapeo que se puede hacer en
el descriptor xml de la aplicación web. Sin embargo, proporciona mayor flexibilidad para
manejar las excepciones. Proporciona información sobre qué manejador se estaba ejecutando
mientras se lanzó la excepción. Por otra parte, una manera programática de manejar
excepciones te da más opciones de responder apropiadamente antes de que la petición sea
pasada a otra URL.
Aunque se pueda implementar la interfaz HandlerExceptionResolver, que se implementa
únicamente con el método resolverException(Exception, Handler) y devolviendo un
ModelAndView. También se puede usar el SimpleMappingExceptionResolver el cual te
permite resolver el nombre de la clase de la excepción que haya podido ser lanzada y mapearla
con el nombre de una vista. Esta funcionalidad es equivalente al mapeo de excepciones del API
de Servlet, pero también se puede implementar de una manera más ajustada mapeos de
excepciones de diferentes controladores.
Por defecto, el DispatcherServlet registra DefaultHandlerExceptionResolver, este resolutor
maneja una serie de excepciones estándar de Spring asignándolas un específico código de
respuesta. La siguiente tabla muestra esta equivalencia:
Excepción Código de estado HTTP ConversionNotSupportedException 500 (Error interno del servidor) HttpMediaTypeNotAcceptableException 406 (No aceptable) HttpMediaTypeNotSupportedException 415 (Tipo de medio no soportado) HttpMessageNotReadableException 400 (Mala petición) HttpMessageNotWritableException 500 (Error interno de servidor) HttpRequestMethodNotSupportedException 405 (Método no soportado) MissingServletRequestParameterException 400 (Mala petición) NoSuchRequestHandlingMethodException 404 (No encontrado) TypeMismatchException 400 (Mala petición)
115
@ExceptionHandler
Otra alternativa al interfaz HandlerExceptionResolver es la anotación @ExceptionHandler. Se
usa @ExceptionHandler como una anotación de método, para especificar qué método será
invocado cuando una excepción en particular sea lanzada mientras la ejecución de algún
método del controlador.
@Controller public class SimpleController { @ExceptionHandler(IOException.class) public String handleIOException(IOException ex, H ttpServletRequest request) { return ClassUtils.getShortName(ex.getClass()); } }
De este modo se invocará este método cuando una excepción sea lanzada.
Convenios
La mejor manera de mantener y crear un proyecto es seguir determinadas pautas y convenios
que harán más sostenible la configuración necesaria, el mapeo de controladores, la resolución
de vistas, las instancias ModelAndView y demás. Esto hará más consistente y mantenible el
código.
117
Capítulo 6:
Spring Security
Cómo hacer de nuestra aplicación un lugar
seguro
Este capítulo se centrará en la seguridad y cómo Spring a través de su proyecto Spring Security se hace cargo de ella.
Javier Sevilla Sánchez
119
Contenido ¿Qué es Spring Security? ........................................................................................................... 121
Autenticación en Spring Security .............................................................................................. 121
AuthenticationManager ............................................................................................................ 122
Seguridad en aplicaciones Web ................................................................................................ 123
Autorización en Spring Security ................................................................................................ 127
Seguridad de la capa de servicio ............................................................................................... 129
Configuración de seguridad a nivel global (namespace)....................................................... 129
Seguridad a nivel de bean ..................................................................................................... 129
Con el objeto MethodSecurityInterceptor ............................................................................ 129
Seguridad a nivel de anotación ............................................................................................. 130
121
¿Qué es Spring Security?
Spring Security se ha convertido en la librería de referencia dentro del mundo Java, para dar
soporte a los servicios de autenticación y autorización de una aplicación. Esto se debe
principalmente a que es un proyecto proyecto enmarcado dentro de Spring y como ya hemos
visto gracias a Spring podremos hacer código mantenible, reutilizable, ágil y robusto. El código
y diseño del componente es inmejorable, es sabido que dentro de la comunidad de
desarrolladores Java tiene un gran uso siendo un referente. Spring Security es un framework
maduro que viene de otro proyecto llamado Acegi. Como la mayoría del framework de Spring,
Spring Security ofrece una gran facilidad de configuración y parametrización, gracias al uso de
mamespace y la inyección de dependencia. Permite la integración con los sistemas legacy de
autenticación más importantes del mercado: BBDD, LDAP, CAS (para single sign-on), gestión de
certificados, etc. Spring Security tiene un gran respaldo de la comunidad con lo que hay una
gran cantidad de documentación y ejemplos.
Autenticación en Spring Security
Un proceso de autenticación consiste en identificar la entidad que realiza una acción sobre la
aplicación y garantizar que es quién dice ser. La entidad que realiza una acción sobre la
aplicación es conocida como principal, en Spring es llamada AuthenticationManager,
encargado de implementar el proceso de autenticación en las aplicaciones.
Antes de describir cómo es el diseño de AutenticationManager, vamos a presentar otros
componentes importantes y necesarios para poder comprender el proceso de autenticación.
Authenticacion, este objeto guardará los datos asociados a AutenticationManager y sus roles.
Así este objeto es en el que sea apoya el AuthenticationManager para implementar el servicio
de autenticación.
SecurityContextHolder, encargado de guardar los datos del principal y sus roles dentro del
contexto de seguridad. Como hemos dicho, la información del principal la alberga
Authentication, así que SecurityContextHolder guarda el objeto Authentication en el contexto
de seguridad. Así la información que reside en el objeto Authentication pueda ser consultada
en todo momento por cualquier componente dentro de la aplicación.
Objeto UserDetailsService, Spring suele delegar responsabilidades en otros objetos, en este
caso el objeto AuthenticationManger delega en el objeto UserDetails las labores de obtención
de la información del usuario sobre el repositorio de seguridad. Así, si los usuarios están en
una base de datos, el objeto UserDetails realizará las consultas necesarias para obtener la
información del AuthenticationManager.
AuthenticationManager
Este interfaz sólo define un método:
Authentication authenticate (Authentication authenticaction) throws AuthenticationException;
El parámetro de entrada tendrá los datos básicos de autenticación. Con este dato se prodecerá
a obtener toda la información de credenciales asociadas con el principal y se comprobará si
dichas credenciales son correctas. En el caso de que las credenciales no sean correctas el
proceso de autenticación lanzará una AuthenticationException. Si la autenticación es correcta
se obtendrán las autoridades asociadas al usuario y serán guardadas en el objeto
Authentication, el cual será devuelto por el método. Éste objeto será guardado por el
componente SecurityContextHolder en el contexto de seguridad.
Como hemos dicho, AuthenticationManager delega responsabilidades, teniendo un conjunto
de componentes ProviderManager los cuales son los encargados de implementar el proceso de
autenticación. Los objetos ProviderManer están más cercanos a la tecnología usada para
impementar el repositorio de seguridad de la aplicación. Así si disponemos de un LDAP,
tendremos el objetoLdapAuthenticationProvider, si accedemos a una base de datos tendremos
un DaoAuthenticationProvider, etc, etc.
En el siguiente diagrama se muestra la colaboración entre componentes para implementar el
proceso de autenticación de la aplicación, en este caso se muestra para un delegado que
buscará en una base de datos gracias al DaoAuthenticationProvider.
123
Spring tiene un gran número de Providers así, podremos dar soporte a multitud de sistemas.
Así podremos adaptar nuestras aplicaciones a distintas particularidades, presentes y futuras.
Una de las grandes ventajas que tiene Spring Security es la inclusión de namespace específicos
que da sopote a la configuración de los distintos componentes que forman parte de la solución
de autenticación y autorización de aplicaciones. Antes, era necesario definir un conjunto de
beans muy extenso y existía la posibilidad de error en la gestión de las dependencias de los
distintos beans. Gracias a la incorporación de namespace, al desarrollar se abstrae de muchas
particularidades internas de la solución, proporcionando un fichero XML de configuración más
sencillo.
Como vemos en el ejemplo el único parámetro de configuración necesario es el gestor de
autenticación donde está el esquema de base de datos donde reside el repositorio de
seguridad.
Este fichero permitirá la configuración de todos los elementos necesarios para realizar la
autenticación y autorización de una aplicación web sobre HTTP.
Seguridad en aplicaciones Web
Lo primero es definir es el ServletFilter DelegatingFilterProxy. Este filtro lo que hace es un
proxy entre los Servlets y el contexto de Spring. Así, este filtro permite que todos los
componentes que forman parte de la arquitectura de Spring Security puedan ser definidos
mediante los ficheros XML.
Spring Security, como ya hemos dicho, se puede configurar mediante el uso de namespace,
que es un fichero XML que cumple un determinado esquema, facilitando la configuración.
Dentro del fichero de configuración incluiremos el siguiente código:
Dentro de la configuración se puede ver como se pueden securizar URL. La etiqueta contiene el
patrón de URL que se quiere securizar y una lista de roles que son necesarios para poder
acceder al recurso, en el caso que se indiquen varios roles, con que se tenga uno de los roles
será suficiente para disponer de permiso para dicha URL.
Dentro de este fichero de configuración se puede configurar las siguientes grandes
funcionalidades:
Remember-me, así el usuarios puede guardar las credenciales en el navegador y así no tener
que volver a introducirlas en el siguiente proceso de autenticación. Usa un soporte de cookies
con aplicación de algoritmos hash para guardar la información.
Selección de canal de seguridad, si usamos el atributo requires-channel dentro de la etiqueta,
se puede exigir que determinadas URL sean seguras dentro de la aplicación. Spring Security
hace las oportunas redirecciones que sean necesarias para el cambio de canal.
Gestión de la sesión, control de timeouts de sesión, y control de la sesión concurrente. El
control de los timeouts de sesión sirve para indicar cuando esta invalidada. El control de sesión
concurrente es para controlar el número de sesiones que están activas para un mismo usuario.
Esto supone una protección contra el ataque de fijación de sesión.
http://en.wikipedia.org/wiki/Session_fixation
Soporte para OpenId
Spring Security mantiene una cadena de filtros que cada uno de ellos da cabida a una
funcionalidad dentro de los procesos de autenticación y autorización entre cada petición y
respuesta. Es posible modificar la cadena de filtros.
Dentro de estos filtros destacan:
125
FilterSecurityInterceeptor, el cual es encargado de manejar la seguridad de los recursos HTTP
manejando los elementos definidos en el namespace.
ExceptionTranslationFilter, encargado de manejar excepciones lanzadas por los interceptores
de seguridad y proveer la respuesta HTTP correspondiente.
SecurityContextPersistenceFilter, responsable de guardar el contexto de seguridad entre
peticiones. Básicamente el contexto de seguridad es guardado a nivel de sesión.
127
Como vemos en la figura, existen numerosos interceptores en la cadena. Toda la cadena de
filtros es creada y aislada del programador mediante el uso de namespace. Sin embargo, es
posible modificar y extender la cadena de filtros.
Autorización en Spring Security
El siguiente diagrama de clases define la arquitectura del módulo de autorización en Spring
Security:
En el centro del diagrama tenemos la clase abstracta AbstractSecurityInterceptor, ésta
representa las capacidades de autorización básicas. Se pueden securizar peticiones HTTP
gracias a FilterSecurityInterceptor e invocaciones a métodos gracias a
MethodSecurityInterceptor y AspectJSecurityInterceptor.
Este interceptor delega en el interfaz AccesDecisionManager la decisión de la autorización
final. Este interfaz implementa un método decide que básicamente lo que hace es recibir un
objeto Authentication representando al principal que accede a la petición, un objeto seguro
(url o método) y una lista de aributos con metadatos de seguridad (la lista de roles para los que
se les puede dar acceso).
El siguiente diseño es el definido para el componente AccesDecisionManager:
El diagrama representa el sistema de autorización de Spring Security. Este sistema está basado
en un sistema de votos. Spring Security tiene tres implementaciones de
AccessDecissionManager:
AffirmativeBased, en el caso de recibir un único voto positivo, se le da acceso al recurso. Se
permite controlar el comportamiento en el caso que todos los votos son de abstención.
ConsensusBased, en este caso será necesario que haya más votos positivos que negativos para
dar acceso al recurso protegido. También se puede controlar el comportamiento si todos los
votos son abstención.
UnanimousBased, en este caso será necesario que todos los votos sean positivos para dar
acceso al recurso. Se permite controlar el comportamiento en el caso que todos los votos son
de abstención.
Los objetos AccessDecissionManager hacen uso de AccessDecissionVoter, actualmente Spring
proporciona dos clases de este objeto:
RoleVoter, comprueba cada rol que protege el recurso comprobándolo con el principal que
realiza la petición. Si se tiene uno de los roles devolverá un AffirmativeBased
AccessDecissionManger, así con uno bastará para dar acceso.
AuthenticatedVoter, Este componente permite diferenciar entre acceso anónimo,
completamente autenticado o autenticado mediante remember-me. Así podrá acceder a
129
páginas que necesitan tener un comportamiento diferente para usuarios autenticados de los
que no.
Todos los componentes del módulo de autorización de Spring Security permiten ser
extendidos para proveer mecanismos de autorización específicos o más profundos.
Seguridad de la capa de servicio
Spring Security permite securizar invocaciones a métodos, comúnmente llamado seguridad a
nivel de capa de servicio. Esto permitirá que tengamos mecanismos de autorización de
invocaciones de componentes de la capa de servicio aportando mayor seguridad.
Spring otorga cuatro configuraciones para la seguridad en la capa de servicio, configuración a
nivel global mediante namespace, mediante el componente MethdoSecurityInterceptor,
basada en anotaciones o a nivel de bean.
En Spring podemos observar cuatro maneras de securizar las ejecuciones de métodos dentro
de Spring Security, Configuración de seguridad a nivel global usando namespace, a nivel de
bean, usando el componente MethodSecurityInterceptor y basada en anotaciones.
Configuración de seguridad a nivel global (namespace)
Spring Security permite una configuración de seguridad a nivel global, esta opción es una
securización centralizada de todos los métodos de la capa de servicio. Adicionalmente,
presenta una expresión AspectJ de definición del pointcut, el cual permite aplicar de manera
flexible seguridad a varios objetos. En el ejemplo expuesto se están securizando todos los
métodos del paquete com.mycompany que acaben por service e independientemente del
número de parámetros que tenga el método.
Seguridad a nivel de bean
Con el objeto MethodSecurityInterceptor
Seguridad a nivel de anotación
131
133
Capítulo 7: Desarrollando con
Spring
Javier Sevilla Sánchez
135
Contenido ¿Qué vamos a hacer? ................................................................................................................ 137
¿Por dónde empiezo? ............................................................................................................... 137
Maven........................................................................................................................................ 137
Web.xml .................................................................................................................................... 138
Ficheros de definición de Beans ................................................................................................ 140
Campus-servlet.xml ................................................................................................................... 140
Controllers.xml .......................................................................................................................... 143
Campus-hibernate.xml .............................................................................................................. 143
Campus-security.xml ................................................................................................................. 147
Objetos de acceso a datos (DAO) y el modelo a persistir ......................................................... 150
Spring MVC ................................................................................................................................ 154
Controllers ............................................................................................................................. 154
Ajax con Spring MVC ............................................................................................................. 155
Vista ........................................................................................................................................... 156
Resolutores............................................................................................................................ 156
Tags JSP ................................................................................................................................. 157
Form tag ............................................................................................................................ 157
Spring tag........................................................................................................................... 158
Security tag ........................................................................................................................ 158
Binding con WebDataBinder ..................................................................................................... 159
Uso y creación de CustomEditor ........................................................................................... 159
Creando CustomEditors con PropertyEditorSupport ............................................................ 160
Uso de WebBindingInitializer: La clase CampusBindingInitializer ........................................ 162
Uso de la anotación @InitBinder .......................................................................................... 163
137
¿Qué vamos a hacer?
A lo largo de este libro, hemos hecho referencias académicas como estudiantes, profesores o
la universidad de Alcalá. De modo que es bastante oportuno hacer una aplicación de campus
virtual que implemente cada una de las funcionalidades explicadas.
Así, esta aplicación tendrá que dar uso de las funcionalidades de Spring en materia de
conexión de beans (gracias al contenedor), aspectos, persistencia de datos, web y seguridad.
¿Por dónde empiezo?
Elegir un IDE para desarrollar puede ser el primer de los pasos que demos, cada desarrollador
tiene entornos preferidos. A pesar de que particularmente prefiero eclipse facilitaremos lo
posible el que el IDE sea independiente de la aplicación.
Maven Vamos a utilizar maven, una herramienta de software para la gestión y construcción de
proyectos Java. Es similar en funcionalidad a Apache Ant, pero tiene un modelo de
configuración de construcción más simple, basado en un formato XML. Maven utiliza un
Project Object Model (POM) para describir el proyecto de software a construir, sus
dependencias de otros módulos y componentes externos, y el orden de construcción de los
elementos. Viene con objetivos predefinidos para realizar ciertas tareas claramente definidas,
como la compilación del código y su empaquetado.
En el fichero pom.xml definiremos, entre otras cosas las dependencias de Spring, el siguiente
código es un extracto del fichero /pom.xml:
<dependency > <groupId >org.springframework </ groupId > <artifactId >spring-context </ artifactId > <version >${org.springframework-version} </ version > <exclusions > <!-- Exclude Commons Logging in favor of SLF4j --> <exclusion > <groupId >commons-logging </ groupId > <artifactId >commons-logging </ artifactId > </ exclusion > </ exclusions > </ dependency > <dependency > <groupId >org.springframework </ groupId > <artifactId >spring-jdbc </ artifactId > <version >${org.springframework-version} </ version > </ dependency > <dependency > <groupId >org.springframework </ groupId > <artifactId >spring-orm </ artifactId > <version >${org.springframework-version} </ version > </ dependency >
<dependency > <groupId >org.springframework </ groupId > <artifactId >spring-webmvc </ artifactId > <version >${org.springframework-version} </ version > </ dependency > <dependency > <groupId >org.springframework.webflow </ groupId > <artifactId >spring-webflow </ artifactId > <version >${org.springwebflow-version} </ version > </ dependency > <dependency > <groupId >org.springframework.webflow </ groupId > <artifactId >spring-js </ artifactId > <version >${org.springwebflow-version} </ version > </ dependency > <dependency > <groupId >org.springframework.security </ groupId > <artifactId >spring-security-web </ artifactId > <version >3.0.5.RELEASE </ version > <exclusions > <!-- Exclude Commons Logging in favor of SLF4j --> <exclusion > <groupId >commons-logging </ groupId > <artifactId >commons-logging </ artifactId > </ exclusion > </ exclusions > <type >jar </ type > </ dependency > <dependency > <groupId >org.springframework.security </ groupId > <artifactId >spring-security-taglibs </ artifactId > <version >${org.springsecurity-version} </ version > </ dependency > <dependency > <groupId >org.springframework.security </ groupId > <artifactId >spring-security-config </ artifactId > <version >${org.springsecurity-version} </ version > <exclusions > <!-- Exclude Commons Logging in favor of SLF4j --> <exclusion > <groupId >commons-logging </ groupId > <artifactId >commons-logging </ artifactId > </ exclusion > </ exclusions > </ dependency >
Con este código tendremos todas las librerías de Spring que utilizaremos más adelante.
Web.xml
El fichero web.xml configura nuestra aplicación, y es el punto en el que Spring arrancará. Para
ello tendremos que incluir tanto el DispatcherServlet del framework MVC, el Listener de
contexto, el filtro de la seguridad así como la configuración del contexto.
El siguiente código muestra cómo configurar el contexto:
139
<context-param > <param-name >contextConfigLocation </ param-name >
<param-value > /WEB-INF/spring/root-context.xml </ param-value > </ context-param >
<listener > <listener-class >
org.springframework.web.context.ContextLoaderListen er </ listener-class >
</ listener >
El siguiente código muestra cómo configurar el DispatcherServlet:
<servlet > <servlet-name >campus</ servlet-name > <servlet-class > org.springframework.web.servlet.DispatcherServ let </ servlet-class > </ servlet > <servlet-mapping > <servlet-name >campus</ servlet-name > <url-pattern >/ </ url-pattern > </ servlet-mapping >
Este Servlet será el punto de partida para la carga de todos los componentes de Spring.
Buscará en la raíz de la aplicación un fichero llamado campus-servlet.xml que contendrá la
definición del contenedor de Spring.
Para poder implementar la seguridad tendremos que incluir los siguientes filtros:
<filter > <filter-name >springSecurityFilterChain </ filter-name > <filter-class >org.springframework.web.filter.DelegatingFilterProx y</ filter-class > </ filter > <filter-mapping > <filter-name >springSecurityFilterChain </ filter-name > <url-pattern >/* </ url-pattern > </ filter-mapping > Uno de los problemas más habituales que existe en las aplicaciones web es la codificación, los
servidores de aplicaciones han de ser configurados explícitamente para que transmitan y
reciban en una determinada codificación. De este modo, Spring hace posible que cada
aplicación tenga su configuración o incluso que se tengan varias configuraciones para una
misma aplicación. El siguiente filtro servirá para forzar la codificación, en este caso UTF-8.
<filter > <filter-name >CharacterEncodingFilter </ filter-name >
<filter-class > org.springframework.web.filter.CharacterEncodingFil ter </ filter-class >
<init-param > <param-name >encoding </ param-name > <param-value >UTF-8 </ param-value >
</ init-param > <init-param > <param-name >forceEncoding </ param-name > <param-value >true </ param-value > </ init-param > </ filter >
Con esta definición creamos las herramientas necesarias para poder empezar nuestra
aplicación. El siguiente paso será crear y configurar nuestro fichero campus-servlet.xml.
Ficheros de definición de Beans
La forma más comúnmente utilizada por la comunidad para definir beans en Spring es a través
de los ficheros XML, ya sea de forma explícita con cada uno de los beans, o definiendo
etiquetas que buscaran anotaciones en nuestras clases. Existen múltiples combinaciones para
realizar esto, pero en la mayoría de los expertos dividen según su finalidad cada uno de los
ficheros. Acorde a nuestras necesidades y basándonos en estas normas de estilo dividiremos
nuestros beans basándonos en si el bean en cuestión es un objeto de persistencia, de
seguridad, de servicio o web. Así, los beans que definan el modelo así como el motor de
persistencia (en nuestro caso hibernate) irán en el fichero campus-hibernate.xml, los objetos
de servicio, irán en service.xml. Usaremos Spring Security y las definiciones de los beans irán
en el fichero campus-security.xml. Por último, El fichero campus-servlet.xml será el fichero en
el que se definan todos los objetos relativos a la parte web, en nuestro caso lo hemos dividido
en un segundo llamado Controllers.xml para tener un código más mantenible y más limpio, en
él definiremos los controladores.
Campus-servlet.xml
Vamos a explicar cuáles son y para qué se utilizan los componentes definidos en este fichero,
que como ya comentamos contendrá los componentes relacionados con la parte web.
El primer elemento que nos encontramos es el siguiente:
<mvc:resources mapping ="/resources/**" location ="/resources/" /> Con este tag lo que estamos diciendo es donde está la ruta que contendrá ficheros estáticos y
cuál será su localización web. En este caso estamos diciendo que estará en la carpeta llamada
resources que está en la raíz de la aplicación y que tendrá la misma localización web.
El siguiente elemento que nos encontramos proporcionará capacidades de anotaciones:
<mvc:annotation-driven /> Los siguientes elemento son indispensables ya que los utilizaremos para resolver las vistas, son
los resolutores de vistas. Vamos a definir dos, ResourceBundleViewResolver e
InternalResourceViewResolver. Utilizaremos dos porque vamos a mezclar dos maneras de
141
resolver las vistas y las ordenaremos. La que va en primer orden utiliza un fichero properties
que define dónde están y qué clase se hace cargo de resolver cada uno de los nombres de las
vistas, lo vamos a hacer así porque es una manera elegante de poder utilizar plantillas de
Apache Tiles. El segundo es más sencillo, simplemente buscará el nombre de la vista
añadiendo el prefijo /WEB-INF/jsp/ y el sufijo .jsp.
Como vemos definimos dos propiedades, basename que será el fichero .properties donde se
encontrará la correspondencia entre vista y clase resolutora y order, que indica el orden.
<bean id ="viewResolver" class ="org.springframework.web.servlet.view.ResourceBund leViewResolver" > <property name="basename" value ="views" /> <property name="order" value ="0" /> </ bean > El siguiente resolutor, como ya hemos comentado hará una correspondencia entre cada
nombre de vista y la clase resolutora encargada. En este caso el orden es 1, posterior al
anterior resolutor definido.
<bean class ="org.springframework.web.servlet.view.InternalResou rceViewResolver" p:prefix ="/WEB-INF/jsp/" p:suffix =".jsp" p:order ="1" />
Gracias a la clase TilesCOnfigurer de Spring podremos integrar tiles como si de otro bean se
tratase, beneficiándonos de la inyección de dependencia. El siguiente código muestra como
hacerlo:
<bean id ="tilesConfigurer" class ="org.springframework.web.servlet.view.tiles2.TilesC onfigurer" p:definitions ="/WEB-INF/tiles/templates.xml" />
Como vemos hemos de asignarle la propiedad del fichero .xml donde estarán las plantillas
definidas.
El siguiente tag sirve para importar los beans de otro fichero como si estuviesen declarados en
este. Como ya dijimos anteriormente, hemos dividiremos los componentes web y los
controladores en dos ficheros, es precisamente este fichero Controllers.xml el que contendrá
los controladores.
<import resource ="spring/controllers.xml" /> En muchas aplicaciones estamos acostumbrados a ver códigos de excepciones ocurridas en la
ejecución de nuestra petición, y por defecto, los servidores de aplicaciones muestran esa
excepción al usuario, que bien podría tener poco que ver con el equipo de desarrollo y poco le
importan las trazas expuestas. De esta forma, la clase SimpleMappingExceptionResolver lo que
hace es redireccionar a distintas vistas cuando ocurra determinada excepción y así mostrar una
vista más amigable al usuario.
<bean class ="org.springframework.web.servlet.handler.SimpleMapp ingExceptionResolver" > <property name="exceptionMappings" >
<props > < prop key = "org.springframework.web.servlet.PageNotFound" >
pageNotFound </ prop > <prop key = "org.springframework.dao.DataAccessException" >
dataAccessFailure </ prop > <prop key = "org.springframework.transaction.TransactionExcepti on" >
dataAccessFailure </ prop >
</ props > </ property > </ bean > La internacionalización es un aspecto crucial en toda aplicación, y más para entornos web.
Gracias a la clase ReloadableResourceBundlemessageSource podemos configurar ficheros de
propiedades que contengan nuestros mensajes internacionalizados. El siguiente código
muestra cómo definir este bean:
<bean id ="messageSource" class ="org.springframework.context.support.ReloadableReso urceBundleMessageSource" > <property name="defaultEncoding" value ="UTF-8" /> <property name="cacheSeconds" value ="10" /> <property name="fallbackToSystemLocale" value ="false" /> <property name="basenames" > <list > <value >/WEB-INF/properties/messages </ value > </ list > </ property > </ bean >
Como vemos existen diversos parámetros como defaultEncoding, para la codificación,
cacheSeconds, que determina cada cuanto se recarga el fichero etc.
Otros beans necesarios en la internacionalización son el LocaleChangeInterceptor y el
SessionLocaleResolver definidos con el siguiente código:
<mvc:interceptors > <bean class ="org.springframework.web.servlet.i18n.LocaleChangeI nterceptor" p:paramName ="locale" /> </ mvc:interceptors > <bean id ="localeResolver" class ="org.springframework.web.servlet.i18n.SessionLocale Resolver" />
143
Una de las funcionalidades de Spring MVC indispensables es el enlace de datos, gracias a
InitBinder podremos enlazar parámetros de petición con objetos completos. La clase
AnnotationMethodHandlerAdapter se sirve de una clase que habremos de implementar ( en
nuestro caso CampusDindingInitializer) en la que podremos definir nuestros enlaces
personalizados, como por ejemplo recibir un identificador y devolver un objeto persistido.
<bean class ="org.springframework.web.servlet.mvc.annotation.Ann otationMethodHandlerAdapter" > <property name="webBindingInitializer" > <bean class ="com.campus.web.binding.campusBindingInitializer" /> </ property > </ bean > </ beans >
Controllers.xml
Como ya hemos comentado, este fichero estará contenido en el campus-servlet y en él
definiremos los controladores. En nuestro caso habrá dos tipos de definición, la que otorga el
tag <mvc:view-controller> que simplemente redirecciona una deterinada ruta a un fichero jsp
o <context:component-scan> que define un paquete base en el que se escanearán los
controladores, en nuestro caso com.campus.web.controllers
El siguiente código es una muestra de cómo estos están definidos:
<mvc:view-controller path ="/login" /> <context:component-scan base-package ="com.campus.web.controllers" />
Campus-hibernate.xml
En este fichero definiremos todos los beans que tengan relación con la persistencia. Como bien
indica el nombre del fichero el motor de persistencia será Hibernate, sin embargo y gracias a la
modularización de Spring, múltiples motores de persistencia podrán ser configurados o
podremos tener varios ficheros, uno para JPA, iBatis, etc e intercambiarlos según nuestras
necesidades. Esto es posible gracias a que los demás beans que esperen los DAO definidos aquí
recibirán la implementación definida, pero ellos operarán con la interfaz, desconociendo cómo
estos DAO operan.
El primer bean definido en este fichero será un property placeholder, cuyo fichero será
jdbc.properties. El contenido de este fichero serán los distintos parámetros de configuración
para la fuente de datos así como para el sessionFactory de hibernate.
El contenido de este fichero es el siguiente:
hibernate.generate_statistics= true hibernate.show_sql= true jpa.showSql= true jdbc.driverClassName= com.mysql.jdbc.Driver jdbc.url= jdbc\ : mysql\://localhost\:3306/campus jdbc.username= campus jdbc.password= campus hibernate.dialect= org.hibernate.dialect.MySQLDialect jpa.databasePlatform= oracle.toplink.essentials.platform.database.MySQL4Platform jpa.database= MYSQL
El siguiente bean definido es el dataSource. En esta aplicación de ejemplo hemos creado un
dataSource básico, si dispusiésemos de un dataSource en nuestro servidor podríamos acceder
a él también definiendo su ruta. Como no es nuestro caso definiremos un dataSource de tipo
BasicDataSource de apache commons.
<context:property-placeholder location ="classpath:jdbc.properties" />
El siguiente bean a definir será el data source, en nuestro caso utilizaremos BasicDataSource
de Apache.
<bean
id ="dataSource" class ="org.apache.commons.dbcp.BasicDataSource" destroy-method ="close"
p:driverClassName ="${jdbc.driverClassName}" p:url ="${jdbc.url}" p:username ="${jdbc.username}" p:password ="${jdbc.password}" />
Una vez definida el data source podremos definir un sessionFactoryBean de Hibernate, en
nuestro caso hemos definido el AnnotationsSessionFactoryBean. Muy cómodo porque
simplemente le indicaremos el paquete del modelo donde escanará los objetos a persistir y el
se encargará de leerlos.
<bean id ="sessionFactory" class ="org.springframework.orm.hibernate3.annotation.Anno tationS
essionFactoryBean" p:dataSource-ref ="dataSource" p:packagesToScan ="com.campus.model" >
<property name="hibernateProperties" > <props >
<prop key ="hibernate.dialect" >${hibernate.dialect} </ prop > <prop key ="hibernate.show_sql" >${hibernate.show_sql} </ prop > <prop k ey = "hibernate.generate_statistics" >${hibernate.generate_statistics}</ prop >
145
<prop key ="hibernate.hbm2ddl.auto" >update </ prop > </ props > </ property > <property name="eventListeners" >
<map> <entry key ="merge" >
<bean class ="org.springframework.orm.hibernate3.support.IdTrans ferringMergeEventListener" /> </ entry > </ map> </ property > </ bean > El siguiente bean es un manager de transacción, en concreto el HibernateTransactionManager.
Este manager nos permitirá hacer operaciones transaccionales asegurando la atomicidad.
<bean id ="transactionManager" class ="org.springframework.orm.hibernate3.HibernateTransa ctionManager" p:sessionFactory-ref ="sessionFactory" />
Como vimos en el capítulo de persistencia, Spring otorga varias soluciones para facilitar la
creación de DAO, una de ellas son las plantillas, el siguiente código configura una plantilla.
<bean id ="hibernateTemplate" class ="org.springframework.orm.hibernate3.HibernateTempla te" p:sessionFactory-ref ="sessionFactory" />
El siguiente tag permite la configuración por anotaciones.
<context:annotation-config />
El siguiente tag indica que utilizaremos transacciones con anotaciones.
<tx:annotation-driven /> Definiremos nuestros DAO de hibernate con anotaciones en el paquete com.campus.orm.dao
y la manera que tenemos de decirle a Spring que busque en ese paquete para encontrar beans
es la siguiente:
<context:component-scan base-package ="com.campus.orm.dao.hibernate" >
<context:include-filter type ="annotation" expression ="org.springframework.stereotype.Repository" />
</ context:component-scan >
Hemos visto como se definen los distintos beans pertenecientes a la parte web así como a la
persistencia. La siguiente parte a declarar será la seguridad.
En este fichero definiremos mediante aspectos la configuración del transactionManager, el
bean necesario para las transacciones.
<bean id ="transactionManager" class ="org.springframework.orm.hibernate3.HibernateTransa ctionManager" p:sessionFactory-ref ="sessionFactory" />
<aop:config > <aop:pointcut id ="all-service-methods" expression ="execution(* com.campus.orm.dao.*Dao.*(..))" /> <aop:advisor advice-ref ="local-tx-advice" pointcut-ref ="all-service-methods" /> </ aop:config > <tx:advice id ="local-tx-advice" transaction-manager ="transactionManager" > <tx:attributes > <tx:method name="get*" propagation ="REQUIRED" isolation ="READ_COMMITTED" read-only ="true" /> <tx:method name="find*" propagation ="REQUIRED" isolation ="READ_COMMITTED" read-only ="true" /> <tx:method name="save*" propagation ="REQUIRED"/> <tx:method name="remove*" propagation ="REQUIRED"/> <tx:method name="add*" propagation ="REQUIRED"/> <tx:method name="update*" propagation ="REQUIRED"/> <tx:method name="modify*" propagation ="REQUIRED"/> <tx:method name="remove*" propagation ="REQUIRED"/> <tx:method name="*" /> </ tx:attributes > </ tx:advice >
Así de simple, con esta configuración diremos a Spring que cada vez que se ejecute un método
de una clase Dao del paquete com.campus.orm.dao y que responda a cada uno de los mapeos
hechos con AOP realizará determinadas operaciones. Por ejemplo, con un método sabe se
hará transacción y propagación.
De nada servirá esta configuración si no tenemos objetos DAO. Ellos serán nuestra conexión
entre base de datos y aplicación. Más tarde veremos cómo se definen estos DAO.
147
Campus-security.xml La seguridad es una parte imprescindible en toda aplicación, especialmente las web. A menudo
vemos una infinidad de filtros tediosos y de difícil compresión cuya función es impedir que
usuarios malintencionados alteren el funcionamiento de la aplicación o que obtengan
información no permitida. Spring security, como ya comentamos en el capítulo dedicado a ello,
facilita todo esto otorgando una gran potencia.
El tag http permite que bajo patrones de petición se precise tener o no un rol específico. En la
siguiente definición vemos que por ejemplo para los recursos no se comprueba ningún
permiso, al igual que para la parte pública o el logeo. Sin embargo para todas las rutas que
comiencen por admin sólo un usuario con rol ADMIN tendrá acceso a ellas, así como teacher si
tiene el rol TEACHER etc.
También podemos definir cuál será nuestro formulario de logeo así como capacidades como la
posibilidad de introducir una cookie para recordarte en una máquina, o que haya un máximo
de sesiones abiertas para un usuario así como se puede configurar cúal será la ruta para salir
de la aplicación y su posterior redirección.
<http auto-config ="true" use-expressions ="true" > <intercept-url pattern ="/resources/**" filters ="none" /> <intercept-url pattern ="/public/**" filters ="none" /> <intercept-url pattern ="/login*" access ="permitAll" /> <intercept-url pattern ="/admin/**" access ="hasRole('ADMIN')" /> <intercept-url pattern ="/teacher/**" access ="hasRole('TEACHER')" /> <intercept-url pattern ="/student/**" access ="hasRole('STUDENT')" /> <intercept-url pattern ="/**" access ="hasRole('ADMIN') or hasRole('TEACHER') or hasRole('STUDENT')" /> <form-login login-page ="/login" login-processing-url ="/loginProcess" default-target-url ="/home" authentication-failure-url ="/login?login_error=1" /> <logout invalidate-session ="true" logout-url ="/logout"
logout-success-url ="/login?loggedout=true" /> <remember-me /> </ http >
En este fichero también definimos el authenticationManager y le asignamos un proveedor, en
nuestro caso el DAO abstractUserDao el cuál implementa UserDetailService. También
definiremos un codificador de contraseña que utilizará el algoritmo de cifrado sha.
<authentication-manager > <authentication-provider user-service-ref ="abstractUserDao" > <password-encoder hash ="sha" /> </ authentication-provider > </ authentication-manager >
Diremos a Spring que él cifrado a utilizar será el algoritmo de autenticación sha gracias al tag
<password-encoder>.
El método a implementar de UserDetailService es loadUserByUsername cuyo código es el
siguiente:
@Override public UserDetails loadUserByUsername( final String username) throws UsernameNotFoundException, DataAccessException { AbstractUser user = (AbstractUser) getHibernateTemplate().execute( new HibernateCallback() { @Override public Object doInHibernate(Session session) throws HibernateException { String hql = "from AbstractUser au " + " where au.username = :username\n" ; Query query = session.createQuery(hql); query.setString( "username" , username); AbstractUser abstractUser = (AbstractUser) query.uniqueResult(); return abstractUser; } }); if (user == null) { throw new UsernameNotFoundException( "User not found with the username of " + username); } return user; }
El objeto AbastractUser tendrá que implementar la clase UserDetails, el código siguiente es la
implementación de los métodos:
public String getPassword() { return password ; }
public String getUsername() { return username ; }
@OneToMany(cascade = CascadeType. ALL, fetch = FetchType. EAGER, targetEntity = Authority. class, mappedBy = "user" ) public Collection<GrantedAuthority> getAuthorities() { return authorities ; }
@Override @Transient public boolean isAccountNonExpired() { return true; }
149
@Override @Transient public boolean isAccountNonLocked() { return true; } @Override @Transient public boolean isCredentialsNonExpired() { return true; }
De igual modo hemos de crear una última clase que implemente GrantedAuthority, esta será
Authority. Esta clase será la encargada de guardar cada uno de los roles, el siguiente código
pertenece a Authority.java:
@Override @Transient public String getAuthority() { return this. value .name(); }
Sólo se ha de implementar este método, el cuál devolverá el nombre del rol en cuestión. La
siguiente clase, AutorityType, muestra cuales son los distintos roles:
package com.campus.model.security; public enum AuthorityType { ADMIN, TEACHER, STUDENT }
En nuestra aplicación sólo tendremos tres tipos de roles, el rol de estudiante, el de profesor y
el de administrador.
El administrador será el encargado de crear usuarios, ya sean estudiantes o profesores, crear
carreras y asignaturas. Al crear una asignatura el administrador asignará ésta a un profesor
para que la imparta.
El profesor será el encargado de crear lecciones para cada una de sus asignaturas así como
preguntas relacionadas con esas lecciones. Tendrá listados de sus alumnos así como podrá ver
estadísticas de las respuestas. También tendrá la posibilidad de responder dudas surgidas
sobre temas.
El estudiante será el encargado de apuntarse a asignaturas, ver y estudiar las lecciones,
apuntar dudas sobre estas y responder preguntas.
Objetos de acceso a datos (DAO) y el modelo a persistir
En toda aplicación es aconsejable definir buenos interfaces que definan cuales serán las
operaciones que se harán con nuestros objetos. En el caso de los DAO si cabe, cobra más
importancia esta buena práctica, ya que gracias a Spring es muy fácil poder hacer uso así como
cambiar entre distintas herramientas de persistencia. Para nuestra aplicación hemos elegido
Hibernate, pero como ya dijimos, Spring es compatible con la mayoría de los framework de
persistencia como iBatis, JPA etc. Así si tenemos bien definidos los interfaces podremos hacer
que el cambio entre los framework sea relativamente sencillo.
Así tendremos dos paquetes:
com.campus.orm.dao.* en el que se declararán los distintos interfaces de cada uno de los
DAO.
com.campus.orm.dao.hibernate.* en el que estarán las implementaciones mediante el uso de
HibernateDaoSupport.
Como explicamos en el capítulo dedicado a la persistencia, HibernateDaoSupport facilita la
creación de DAO con Hibernate. En nuestro caso tendremos una clase base de la que
heredarán el resto de implementaciones. Esta se llamará BaseDao cuya implementación con el
framework de Hibernate será BaseDaoHibernate. En ella definiremos las operaciones más
comunes relativas al guardado, recuperación mediante clave etc de nuestros objetos de
dominio. Todo objeto de dominio tendrá que implementar BaseEntity y heredar
BaseEntityCampus.
El siguiente código pertenece a BaseEntityCampus:
@MappedSuperclass public abstract class BaseEntityCampus implements BaseEntity, Serializable { private Long id ; public BaseEntityCampus() { super(); } public BaseEntityCampus(Long id) { super(); this. id = id; } @Id @GeneratedValue (strategy = GenerationType. TABLE) @Override public Long getId() { return id ; } @Override public void setId(Long id) {
151
this. id = id; } @Override @Transient public boolean isNew() { return this.getId() == null; } }
Con la anotación @MappedSuperclass diremos que es una clase padre que no será mapeada.
Como vemos tan solo contiene un identificador como atributo.
Creda esta clase estamos preparados para ver nuestra clase BaseDao, el siguiente código
pertenece a ella:
public interface BaseDao<T extends BaseEntity> { public Long save(T entity); public T get(Long id); public T load(Long id); public List<T> getAll(); public void update(T entity); public void delete(T entity); public void delete(Long id); public void clear(); public T getFetch(Long id); }
Y su imlementación en con HibernateDaoSupport:
public abstract class BaseDaoHibernate<T extends BaseEntityCampus> extends HibernateDaoSupport implements BaseDao<T> { protected Class<T> entityClass ; public BaseDaoHibernate() { super(); } public BaseDaoHibernate(Class<T> entityClass) { super(); this. entityClass = entityClass; }
@Override public void clear() { getHibernateTemplate().clear(); } @Override public void delete(T entity) { getHibernateTemplate().delete(entity); getHibernateTemplate().flush(); } @SuppressWarnings ( "unchecked" ) @Override public void delete( final Long id) { getHibernateTemplate().execute( new HibernateCallback() { public Object doInHibernate(Session session) throws HibernateException, SQLException { T entity = (T) session.load( entityClass , id); session.delete(entity); return null; } }); } @Override public T get(Long id) { return (T) getHibernateTemplate().get( entityClass , id); } @Override public T load(Long id) { return (T) getHibernateTemplate().load( entityClass , id); } @Override public List<T> getAll() { return getHibernateTemplate().loadAll( entityClass ); } @Override public Long save(T entity) { getHibernateTemplate().saveOrUpdate(entity) ; return entity.getId(); } @Override public void update(T entity) { getHibernateTemplate().update(entity); } public void flush() { getHibernateTemplate().flush(); } }
Como vemos existe un método constructor que tiene como parámetro una clase de tipo
entidad base. Esta será la clase de la cual heredarán el resto de los objetos DAO como
podemos comprobar en el siguiente código con StudentDaoHibernate:
153
@Repository (value = "studentDao" ) public class StudentDaoHibernate extends BaseDaoHibernate<Student> implements StudentDao {
...
}
La etiqueta @Repository dirá a Spring que esta clase será un bean cuyo nombre será
studentDao, más tarde a la hora de utilizar este dao será tan sencillo como utilizar la anotación
@Autowired, el siguiente código muestra a un cómo esta clase será inyectada:
@Autowired private StudentDao studentDao ;
La anotación @Autowired dirá a Spring que el tipo de este objeto es un bean definido en el
contenedor y que es sujeto de ser inyectado.
Spring MVC
Como ya dijimos, esta aplicación será web y hará uso de la herramienta que Spring otorga para
contruir aplicaciones web, Spring MVC.
Controllers
Tras ver cómo damos soporte a los componentes de Spring MVC con el fichero Campus-
Servlet.xml es tiempo de ver el tipo más utilizado en Spring, el Controller.
En anteriores versiones de Spring, los controladores tenían que ser definidos en el fichero xml
y tenían que heredar de alguna de las clases de soporte de Spring. Ahora esto se ha acabado
gracias a la anotación @Controller, con ella cualquier POJO podrá ser un controlador sin
necesidad de heredar clases específicas de Spring. El siguiente código de la aplicación muestra
un ejemplo sencillo de controlador:
@Controller @RequestMapping ( "/subjectProfile" ) public class SubjectProfileController { @Autowired private SubjectDao subjectDao ; @InitBinder public void initBinder(HttpServletRequest request,
ServletRequestDataBinder binder) throws Exception { binder.registerCustomEditor(Subject. class,
new SubjectCustomEditor( this. subjectDao )); } @RequestMapping ( "/{id}" ) public ModelAndView subjectProfile( @PathVariable ( "id" ) Subject subject){ ModelAndView mav = new ModelAndView( "subjectProfile" ); mav.addObject( "subjectProfile" ,subject); return mav; } }
En este ejemplo hay varios conceptos a destacar, el primero sería ver qué sencillo es decirle a
Spring que la clase es un controlador, simplemente con la anotación @Controller. Spring
buscará en los paquetes de escaneo definidos en el fichero xml.
La anotación @RequestMapping hace referencia a qué ruta mapeará este controlador, es
opcional el que esté encima de la definición de la clase, si es así será la raíz para el resto de los
métodos. Cada método que tenga la anotación @RequestMapping() será mapeado por Spring
como un método del controlador.
Los métodos de un controlador serán sujetos de ser utilizados dependiendo de la URL de la
petición y del método de la petición (es decir, POST, GET,..etc).
155
Es decir, puede haber métodos con la misma URL pero con distintos RequestMethod.
En el ejemplo existe otra anotación a destacar, se trata de @InitBinder este método será del
que se sirva para el enlace de los valores de los parámetros de la petición con objetos Java. En
el ejemplo se define un objeto de tipo CustomEditor que esperará un identificador (un id de
una entidad) irá a la base de datos y recuperará la entidad en concreto para nuestro método.
Tras la ejecución del método vemos que devuelve un objeto ModelAndView, este objeto es
básico para comprender el funcionamiento de Spring MVC. ModelAndView definirá qué vista
(por ejemplo un fichero jsp) será la que se le enviará en la respuesta y que modelo es
necesario para mostrar dicha vista. En nuestro caso la vista será subjectProfile y contendrá el
objeto subjectProfile de tipo subject como modelo.
También podremos devolver un objeto String, de este modo simplemente Spring buscará la
vista y la devolverá. Si en los parámetros del método hemos pedido el objeto Model Spring lo
inyetará y si hacemos alguna modificación esta estará presente en la vista designada por la
cadena.
El siguiente código es un ejemplo de lo descrito:
@RequestMapping (method = RequestMethod. GET) public String setupForm(Model model) { model.addAttribute( "lessonDataPicker" , new LessonDataPicker()); return "teacher/createLesson" ; }
En este método se devuelve el nombre lógico de la vista pero la modificación del objeto Model
estará presente en la vista.
Ajax con Spring MVC
En nuestra aplicación haremos uso de la librería de javascript jQuery la cual facilita, entre otras
funcionalidades, la creación de AJAX. Con Spring una manera de tratar estas peticiones es
devolviendo el cuerpo de la petición gracias a @ResponseBody. Con @ResponseBody diremos
que la cadena devuelta (u objeto) será el contenido de la respuesta y no el nombre lógico de la
vista.
El siguiente código muestra cómo hacerlo:
@RequestMapping (method = RequestMethod. POST) @ResponseBody public String processSubmit( @RequestParam(value = "lessonNote" ) String lessonNote, @RequestParam( "lessonId" ) Lesson lesson) { if (lessonNote.trim().equals( "" )) { return "false" ; } Note note = this. noteDao .saveFromActualUser(lesson, lessonNote);
return "{text:'" + note.getText() + "',creator:'" + note.getCreator() + "',creationDate:'" + note.getCreationDate() + "'}" ; }
Vemos como el método devuelve una cadena que será un objeto JSON.
Vista Para explicar la vista en Spring hemos de tener dos conceptos claros, los resolutores de vista y
los tags de Jsp de spring.
Resolutores
Los resolutores de vista será el nexo entre una definición lógica y un física de la vista en
concreto, podremos utilizar distintas tecnologías como Velocity o FreeMarker, pero en nuestra
aplicación de ejemplo utilizaremos la más utilizada por al comunidad, los JSP.
Para la resolución de las vistas de lógico a físico utilizaremos dos clases, definidas en campus-
servlet.xml:
<bean id ="viewResolver" class ="org.springframework.web.servlet.view.ResourceBundl eViewResolver" > <property name="basename" value ="views" /> <property name="order" value ="0" /> </ bean >
E InternalResourceViewResolver:
<bean class ="org.springframework.web.servlet.view.InternalResou rceViewResolver" p:prefix ="/WEB-INF/jsp/" p:suffix =".jsp" p:order ="1" />
He elegido dos debido a que voy a combinar el uso del framework para la vista de Apache Tiles
con ResourceBundleViewResolver y para el resto con InternalResourceViewResolver.
Para poder mapear con ResourceBundleViewResolver necesitaremos mapear el bean
tilesConfigurer, tener un fichero de propiedades donde definiremos las vistas y el fichero de
plantillas de tiles.
La clase tilesConfigurer se define en campus-servlet.xml de la siguiente manera:
<bean id ="tilesConfigurer" class ="org.springframework.web.servlet.view.tiles2.TilesC onfigurer" p:definitions ="/WEB-INF/tiles/templates.xml" />
157
EL siguiente código es un extracto de views.properties y muestra cómo mapearemos cada una
de las vistas lógicas a usar por Spring con cada una de las plantillas de Tiles:
home.(class)= org.springframework.web.servlet.view.tiles2.TilesVi ew home.url= home
El nombre (en este caso home) será el nombre lógico de la vista, el atributo .(class)= hace
referencia a la clase a utilizar por Spring y el atributo url será la plantilla de Tiles, (también
home).
Así en el fichero templates.xml podremos ver las diferentes vistas mapeadas con plantillas de
tiles, como muestra el ejemplo:
<definition name="home" extends =".teacher" > <put-attribute name="url" value ="home" /> <put-attribute name="content" value ="/WEB-INF/jsp/home.jsp" /> </ definition >
De este modo podremos utilizar otras tecnologías y combinarlas con nuestro framework. Este
resolutor se ejecutará en primera posición, es decir a mismos nombres lógicos este será el
encargado de resolver la vista.
El siguiente resolutor es el InternalResourceViewResolver, el comportamiento de este es muy
sencillo, a el nombre lógico de la vista se le añadirán un prefijo y un sufijo, así si se pregunta
por “login” se devolverá la vista “/WEB-INF/jsp/login.jsp”. Este resolutor se ejecutará tras
descartarse la vista en el anterior resolutor.
Tags JSP
Spring MVC y Spring security tiene una serie de tags que nos serán muy útiles a la hora de
construir nuestras aplicaciones. En esta aplicación de ejemplo utilizaremos principalmente las
derivadas de <form: *>, las de <spring:*> así como las de <security:*>.
Form tag
Los tag <form:*> sirven principalmente para el mapeo y enlace de elementos de un formulario
y estarán presentes en cada uno de los formularios de la aplicación. Las principales utilidades
son las siguientes:
• <Form:form> definirá el formulario en cuestión y lo enlazará con un controlador así
como con el objeto comando u objeto del modelo a enlazar.
• <form:input> este junto a otros como <form:select> , <form:checkboxes> o
<form:hidden> serán cada uno de los elementos del formulario los cuales Spring
enlazará con la ruta definida en path.
• <form:errors> este objeto imprimirá los errores encontrados tras la validación del
formulario en la parte servidora.
Spring tag
Con <spring:*> tendremos múltiples funcionalidades como el enlace de objetos , la creación de
URL absolutas, evaluación de expresiones, trasformaciones de variables a cadenas etc.
Security tag
Con los tags que proveee <security:> podremos realizar comprobaciones de roles para el
principal, es decir, para el usuario que haya realizado la petición. Este tag lo utilizaremos
mucho para mostrar u ocultar información dependiendo del tipo de usuario que seas. Por
ejemplo las opciones del menú no serán iguales para un profesor que para un alumno al igual
que el alumno no podrá ver resultados de las preguntas, un profesor no va a responderlas.
Veamos un ejemplo con el siguiente código perteneciente a menu.jsp.
<div id ="tabsQuestionHome" > <security:authorize ifAnyGranted ="STUDENT"> <p><a href =' <spring:url value ="/student/answerQuestion" htmlEscape ="true" /> ' >
<fmt:message key ="ANSWER_QUESTIONS"/></ a></ p> </ security:authorize > <security:authorize ifAnyGranted ="TEACHER"> <p><a href =' <spring:url value ="/teacher/createQuestion" htmlEscape ="true" /> ' >
<fmt:message key ="CREATE_QUESTION"/></ a></ p> <p>
<a href =' <spring:url value ="/teacher/statistics/select" htmlEscape ="true" /> ' > <fmt:message key ="STATISTICS" /></ a></ p>
<p><a href =' <spring:url value ="/teacher/faqs/faqsSelect" htmlEscape ="true" /> ' > <fmt:message key ="ANSWERS"/></ a></ p>
</ security:authorize > </ div >
Este ejemplo es el código de parte del menú, para ser más concreto de las preguntas. Como
hemos dicho un alumno sólo debería poder ver la opción de responder a preguntas y un
profesor sólo debería poder crear preguntas o ver las estadísticas de ellas así como las
respuestas de las preguntas.
Evidentemente de nada sirve que ocultemos su enlace si luego es accesible si lo tecleamos en
la barra de direcciones, para impedir esto el fichero campus-security.xml muestra cuales son
los roles permitidos a según que rutas.
159
Binding con WebDataBinder
Los componentes de Binding o enlace que Spring otorga tienen la finalidad de enlazar
parámetros de la petición con objetos. Estos objetos pueden ser de diversos tipos, desde tipos
simples como Long, Boolean, String, Intege..etc hasta objetos complejos como en nuestro caso
Student, Teacher, Subject, Lesson así como datapickers y comandos. Todo ello será gestionado
por WebDataBinder.
Por lo general esta aplicación se sirve de objetos datapickers para recoger los datos de los
formularios. Estos datapicker pueden contener objetos de tipo complejo como Student,
Teacher etc…La manera que tiene Srping de enlazar estos objetos es ver qué tipo se espera
buscar en sus WebBindingInitializer definidos y en los métodos marcados con @InitBinder de
los controladores.
Las siguientes propiedades pertenecen a LessonDataPicker:
private Subject subject ; private String name; private String description ; private String content ; private Boolean active ; private boolean edit = false; private Long id ; private Date beginDate ; private Date endDate ;
Como vemos existen propiedades de diversos tipos, pero llama la atención cómo Spring podrá
enlazar desde un formulario web un objeto complejo como pueda ser Subject.
Para tipos comunes como Date nos podemos servir de CustomEditors del framework de
Spring, que intentarán hacer un casting de los objetos y nos devolverá o el objeto esperado o
errores al forzar el casting (visibles posteriormente con <form:errors).
La respuesta a cómo hace Spring para traerse objetos complejos es tan simple como pasarle el
identificador de base de datos al que hace referencia y si existe un customEditor definido para
Subject este tendrá que buscar en la BBDD y devolver el objeto. Evidentemente el enlace
puede ser tan complejo como el desarrollador quiera y no tiene por qué servirse de DAO.
Uso y creación de CustomEditor
Los custom editors son los objetos de los que se sirve Spring para realizar este enlace, como ya
hemos dicho se pueden basar en todo tipo de estrategias para convertir un texto enviado en la
petición a un objeto java.
El siguiente código muestra el uso de CustomDateEditor de Spring:
SimpleDateFormat dateFormat = new SimpleDateFormat( "dd/MM/yyyy" ); dateFormat.setLenient( false); binder.registerCustomEditor(Date. class, newCustomDateEditor(dateFormat, false));
En el podemos ver que se crea un objeto de tipo CustomEditor para la clase Date. Así cuando
Spring espere un objeto de tipo Date en un datapicker o como cualquier tipo de parámetro de
la petición Spring buscará y utilizará esta clase para su enlace.
Es posible enlazar nuestras clases, así con la ayuda y herencia de PropertyEditorSupport se
podrá crear clases para ello.
Creando CustomEditors con PropertyEditorSupport
Cuando heredamos de PropertyCustomEditor tenemos que implementar el método setAsText
el cuál tiene como parámetro el texto enlazado desde la petición. El siguiente ejemplo es un
ejemplo de nuestro objeto CustomerEditor base para las entidades de nuestro modelo:
public class CampusCustomEditor extends PropertyEditorSupport { @SuppressWarnings ( "unchecked" ) private final BaseDao dao ; @SuppressWarnings ( "unchecked" ) protected CampusCustomEditor(BaseDao dao) { super(); this. dao = dao; } @Override public void setAsText(String text) { Long id = null; if (!StringUtils. hasLength (text.trim())) { setValue( null); return; } id = new Long(text); BaseEntity entity = dao .get(id); if (entity == null) { return; } setValue(entity); } }
Como vemos el constructor precisa de la implementación del objeto de tipo abstracto BaseDao
el cual hará uso para obtener la entidad esperada, así simplemente pasándole el identificador
de base de datos enlazará la entidad completa.
Luego la creación de un CustomEditor para un objeto del modelo será tan sencillo como el
siguiente código:
161
public class LessonCustomEditor extends CampusCustomEditor {
public LessonCustomEditor(LessonDao dao) { super(dao); } }
Como vemos simplemente se hace una llamada al constructor con el Dao correspondiente.
Una vez definidos nuestros CustomerEditors podremos utilizarlos de dos maneras, con objetos
BindingInitializer o con los métodos anotados con @InitBinder de nuestros controladores.
Uso de WebBindingInitializer: La clase CampusBindingInitializer
Para nuestra aplicación definiremos una clase con los CustomEditor más comunes. Esta clase
tendrá que heredar de WebBindingInitializer e implementar el método initBinder.
El siguiente código pertenece a CampusBindingInitializer:
public class CampusBindingInitializer implements WebBindingInitializer { public CampusBindingInitializer() { } @Override public void initBinder(WebDataBinder binder, WebRequest reques t) { SimpleDateFormat dateFormat = new SimpleDateFormat( "dd/MM/yyyy" ); dateFormat.setLenient( false); binder.registerCustomEditor(Date. class, new CustomDateEditor(dateFormat, false)); binder.registerCustomEditor(String. class, new StringTrimmerEditor( false)); binder.registerCustomEditor(Long. class, new CustomNumberEditor(Long. class, true)); } }
Asi cuando se esperen determinados objetos Spring los enlazará sirviéndose de estos
CustomEditor.
163
Uso de la anotación @InitBinder
Otra forma que Spring tiene para enlazar es mediante métodos anotados con @InitBinder. El
siguiente códig muestra cómo hacerlo:
@InitBinder public void initBinder(HttpServletRequest request, ServletRequestDataBinder binder) throws Exception {
binder.registerCustomEditor( Lesson. class, new LessonCustomEditor( this. lessonDao )); }
Como vemos en el código, la manera de definirlo es igual que dentro del método initBinder de
los WebInitializer.
165
Apéndice A:
Hibernate Configuración y puesta en marcha del
framework En este apéndice veremos las características básicas del marco de trabajo, veremos cómo crear objetos persistentes y definir el mapeo ORM.
Javier Sevilla Sánchez
167
Contenido Introducción .............................................................................................................................. 168
Creación del esquema ............................................................................................................... 168
Con un esquema existente .................................................................................................... 169
Sin que exista un esquema .................................................................................................... 169
Con un esquema Parcial ........................................................................................................ 169
Mapeo de Objetos ..................................................................................................................... 169
Atributo Id ............................................................................................................................. 170
Propiedades........................................................................................................................... 170
Formas de mapear objetos dentro de objetos ......................................................................... 171
Relaciones one-to-one .......................................................................................................... 171
Relaciones many-to-one ....................................................................................................... 171
Relaciones one-to-many ....................................................................................................... 172
Relación many-to-many ........................................................................................................ 173
Configuración de Hibernate: Fichero .cfg.xml ........................................................................... 173
Inicializar el objeto Session ....................................................................................................... 174
Operaciones típicas con el objeto Session ................................................................................ 175
Inserción ................................................................................................................................ 175
Actualización ......................................................................................................................... 175
Consulta ................................................................................................................................. 176
Borrado.................................................................................................................................. 176
Introducción
Hibernate es una herramienta de Mapeo objeto-relacional que facilita el mapeo de atributos
entre una base de datos relacional tradicional y el modelo de objetos de nuestra aplicación,
mediante archivos declarativos (XML) que permiten establecer estas relaciones. Fue
desarrollada inicialmente por la iniciativa de un grupo de desarrolladores dispersos por el
mundo. Más tarde JBoss financiaría el proyecto. Con la información que otorgamos a
Hibernate relativa a las tablas, relaciones, etc. le permite a la aplicación manipular los datos de
la base operando sobre objetos, con todas las características de la POO. Hibernate convertirá
los datos entre los tipos utilizados por Java y los definidos por SQL. Hibernate genera las
sentencias SQL y libera al desarrollador del manejo manual de los datos que resultan de la
ejecución de dichas sentencias, manteniendo la portabilidad entre todos los motores de bases
de datos con un ligero incremento en el tiempo de ejecución. Éste es un framework de
software libre bajo licencia GNU-LGPL.
Hibernate está diseñado para ser flexible en cuanto al esquema de tablas utilizado, para poder
adaptarse a su uso sobre una base de datos ya existente. También tiene la funcionalidad de
crear la base de datos a partir de la información disponible. Hibernate otorga un lenguaje de
consulta de datos llamado HQL (Hibernate Query Language) y una API para construir las
consultas programáticamente criteria.
Hibernate para Java puede ser utilizado en aplicaciones Java independientes o en aplicaciones
Java EE, mediante el componente Hibernate Annotations que implementa el estándar JPA, que
es parte de esta plataforma.
Con lo que las principales características son:
• Framework Open Source.
• Independencia del motor de BBDD.
• Carga perezosa de objetos iniciándolos bajo demanda.
• Capacidad de generar el modelo de datos a partir de modelo de objetos.
• Compatible con la especificación JPA.
• Lenguaje propio unificado para las consultas (Hibernate Query Language)
• Api para realizar consultas Criteria.
• Utiliza la Programación Orientada a Aspectos (AOP), interceptando los objetos y
creando proxys.
• No incluye nuevos atributos a los POJOs simplemente genera interceptores.
Creación del esquema Hibernate es capaz de facilitar cual será el modelo de persistencia con simples POJOs y un
buen mapeo XML o anotaciones JPA. Con esos ingredientes Hibernate será capaz de poder
crear el esquema de diferentes maneras.
169
Con un esquema existente
Cuando el esquema ya existe debemos crear nuestros objetos en consonancia con el esquema
ya existente. Realizaremos el mapeo de esos objetos y atributos de manera manual o también
de forma automática con una herramienta de Hibernate.
Gracias a Hibernate Tools podremos pasar grandes bases de datos con un gran número de
tablas de una forma sencilla.
Sin que exista un esquema
Cuando esto ocurre, Hibernate permite que una vez realizado el diseño orientado a objetos se
genere el modelo de datos relacional asociado. Es decir, de forma automática Hibernate creará
las tablas asociadas a nuestros POJOs. Esto lo hará de manera independiente a la base de
datos elegida.
Con un esquema Parcial
También Hibernate puede ampliar la parte que nuestra aplicación precise. Es útil cuando parte
del esquema ya existe pero es necesario ampliarlo con los objetos creados. Para ello
tendremos que generar nuestro modelo de dominio persistente, teniendo en cuenta el modelo
relacional y posteriormente podremos utilizar Hibernate pare generar el modelo de datos
relacional a partir de nuestro dominio.
Hibernate se sirve de un gran número de interceptores que crearán objetos de tipo Proxy. El
típico problema que surge al utilizar un único modelo de dominio persistente con Hibernate es
consultar los datos de nuestros objetos fuera de la sesión de Hibernate. Cuando esto ocurre
nos dará una excepción de sesión cerrada. Se recomienda utilizar el dominio de persistencia
sólo con los objetos que sigan el patrón DAO y generar un nuevo modelo de dominio para el
resto de la aplicación.
Utilizando Hibernate Tools podremos personalizar la creación de POJOs a partir del esquema
de la base de datos.
Mapeo de Objetos En Hibernate podremos mapear los objetos con ficheros xml o con anotaciones JPA. El nombre
que reciben los ficheros xml en Hibernate es HBM. En los ficheros de mapeo se señala la
relación de la clase con la tabla del modelo entidad-relación, el identificador del objeto que
corresponderá con la clave primaria, el mapeo de atributos del objeto con los campos de la
tabla y el mapeo de los objetos relacionados existentes dentro del propio objeto grafo de
objetos.
El siguiente fichero corresponde a la descripción HBM del objeto estudiante.
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hi bernate Mapping DTD 3.0//EN" "http://hibernate.sourceforge.net/hibernat e-mapping-3.0.dtd"> <hibernate-mapping> <class dynamic-insert="false" dynamic-update="f alse" mutable="true" name="es.uah.hibernateaddendum.model.Student" o ptimistic-lock="version" polymorphism="implicit" select-before-update="f alse" table="STUDENTS"> <id name="id" type="integer"> <column name="STUDENT_ID"/> </id> <property name="name"> <column name="STUDENT_NAME"/> </property> <set lazy="true" name="exams" table="STUDEN TS_HAS_EXAMS"> <key> <column name="STUDENT_ID"/> </key> <many-to-many class="es.uah.hibernatead dendum.model.Exam" column="EXAM_ID"/> </set> <set lazy="true" name="subjects" table="STU DENTS_HAS_SUBJECTS"> <key> <column name="STUDENT_ID"/> </key> <many-to-many class="es.uah.hibernatead dendum.model.Subject" column="SUBJECT_ID"/> </set> </class> </hibernate-mapping>
En este fichero observamos diversos tags, en ellos se definen cual será la correspondencia
entre el objeto y las base de datos relacional. En el tag class vemos como se define la clase
que será es.uah.hibernateaddendum.model.Student, definimos cuál será la clave primaria su
identificador así como la columna correspondiente.
Atributo Id
Para indicar el mapeo entre el/los atributos del objeto y la clave primaria de la tabla se utiliza
el elemento “id”.
En el anterior ejemplo el atributo id se le especifica la columna de la base de datos la cual está
definida como primary key. Hibernate tiene varias implementaciones para la generación de
identificadores como increment, identity, native, assigned etc. Estos se definirán dentro del
cuerpo de id con el tag genarator.
Propiedades
Para mapear los atributos utilizaremos el elemento “property”. Como vemos en la definición
del xml, la columna STUDENT_NAME tiene correspondencia con el atributo name del objeto.
<property name="name"> <column name="STUDENT_NAME"/> </property>
171
Como vemos los nombres del atributo de la clase y la columna de la tabla no han de coincidir.
Aún que en el ejemplo no lo especificamos, se pueden añadir restricciones como por ejemplo
no permitir valores nulos o el tamaño.
Formas de mapear objetos dentro de objetos Las relaciones entre tablas pueden ser de uno a uno (1-1), de uno a muchos (1-n) y de muchos
a muchos (m-n). En Hibernate podemos mapear estas relaciones con cuatro etiquetas, ono-to-
one, one-to-many, many-to-many y many-to-one. Para explicar todas estas relaciones
hagamos un ejemplo consistente en cómo la información de una carrera, sus asigunaturas
exámenes y estudiantes está relacionada.
Figura 5 Diagrama relacional de estudiantes, exámenes, asignaturas y temas
En el diagrama vemos como un estudiante tiene n exámenes, que a su vez son realizados por
m estudiantes. Un examen se compone de una única asignatura, pero una asignatura puede
tener varios exámenes. Cada asignatura tiene n temas que pertenecen exclusivamente a cada
asignatura.
Relaciones one-to-one
Las relaciones one-to-one son aquellas que se producen cuando dos objetos se relacionan uno
a uno. Cada tabla dispone de la clave primaria de la otra haciendo una referencia mutua. Este
tipo de relación no es habitual, debido a que la mayoría de la información relacionada de esta
forma estaría en una sola tabla. En el ejemplo de la figura 1 vemos que no existe esta relación.
Esta relación sí se podría dar por ejemplo entre marido y esposa en culturas monógamas, ya
que una persona sólo puede estar casado con otra.
Relaciones many-to-one
Esta relación se da cuando la tabla de la parte del muchos tiene referencias a la clave primaria
de la tabla del uno. En esta relación podremos incluir la instancia de otro objeto persistente
que puede estar dado de alta en varios objetos persistentes. En nuestro ejemplo vemos como
varios temas pertenecen a una única asignatura o que existen varios exámenes para cada
asignatura.
En el modelo de clases vemos como cada examen tiene una asignatura el fichero de definición
hbm sería:
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hi bernate Mapping DTD 3.0//EN" "http://hibernate.sourceforge.net/hibernat e-mapping-3.0.dtd"> <hibernate-mapping> <class dynamic-insert="false" dynamic-update="fal se" mutable="true" name="es.uah.hibernateaddendum.model.Exam" optimist ic-lock="version" polymorphism="implicit" select-before-update="false " table="EXAMS"> <id name="id" type="integer"> <column name="EXAM_ID"/> </id> <property name="date" type="date"> <column name="EXAM_DATE"/> </property> <many-to-one class="es.uah.hibernateaddendum.mo del.Subject" fetch="join" name="subject"> <column name="SUBJECT_ID" not-null="true"/> </many-to-one> </class> </hibernate-mapping>
La relación muchos a uno del examen indica que para una misma asignatura existirán varios
exámenes.
Relaciones one-to-many
Esta relación se da cuando el objeto que mapeamos contiene una colección, mapa, etc que
hace que se hagan múltiples referencias. En el esquema relacional esto se convierte en una
tabla es referenciada múltiples veces por otra, la parte del uno es referenciada por la parte de
muchos. En nuestro ejemplo una asignatura está compuesta de varios temas, pero un tema es
sólo puede pertenecer a una asignatura, es decir la asignatura es la parte del uno y los temas
serán la parte del muchos.
El siguiente fichero mapea la clase asignatura y vemos como los objetos de la clase tema
forman parte de un objeto que implementa el interfaz Set.
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hi bernate Mapping DTD 3.0//EN" "http://hibernate.sourceforge.net/hibernat e-mapping-3.0.dtd"> <hibernate-mapping> <class name="es.uah.hibernateaddendum.model.Subje ct" table="SUBJECTS" > <id name="id" type="integer" > <column name="SUBJECT_ID"/> </id> <property name="name" type="string"> <column name="SUBJECT_NAME"/> </property> <set inverse="true" lazy="true" name="content"> <key> <column name="SUBJECT_ID"/> </key> <one-to-many class="es.uah.hibernateaddendum. model.SubjectUnit"/> </set> </class> </hibernate-mapping>
Como vemos dentro del tag set hemos dado el valor true al atributo lazy. Este atributo ya lo
habíamos visto también en el hbm del estudiante. Hemos de tener cuidado con este tipo de
173
relación ya que esta propiedad determinará cómo se cargan los objetos dentro de la sesión de
Hibernate. Es decir cómo Hibernate instanciará los objetos. Si este atributo lo ponemos a false
Hibernate cargará todos los objetos cuando se instancie el objeto lo que puede ser terrible si
existen miles o millones de referencias. Un ejemplo podría ser que necesitásemos un
estudiante para saber su nombre y tuviésemos lazy=”false” en el atributo de los exámenes
realizados, esto haría que Hibernate instanciase todos los objetos Examen sin que fuesen
necesarios.
Relación many-to-many
Estas relaciones hacen que cada ocurrencia, en cualquiera de las dos entidades de la relación,
puede estar asociada con muchas (n) de la otra y viceversa. Es decir en caso un alumno realiza
muchos exámenes y un examen es realizado por muchos alumnos. A la hora de pasar eso al
modelo de objetos se traduce a una colección de exámenes en el objeto alumno y una
colección de alumnos en la clase examen.
<set lazy="true" name="exams" table="STUDEN TS_HAS_EXAMS"> <key> <column name="STUDENT_ID"/> </key> <many-to-many class="es.uah.hibernatead dendum.model.Exam" column="EXAM_ID"/> </set> <set lazy="true" name="subjects" table="STU DENTS_HAS_SUBJECTS"> <key> <column name="STUDENT_ID"/> </key> <many-to-many class="es.uah.hibernatead dendum.model.Subject" column="SUBJECT_ID"/> </set>
Una vez visto como mapear los objetos con Hibernate es preciso explicar cómo configurar
Hibernate con todo aquello que necesite para hacerlo funcionar.
Configuración de Hibernate: Fichero .cfg.xml
En los anteriores puntos hemos visto como crear un esquema y como mapear los distintos
objetos con ficheros hbm.xml. Lo que haremos ahora es unir todo ello para que cobre un
sentido así como definir propiedades relativas a la base de datos como la URL, el usuario, el
password, el driver, los distintos hbm y el dialecto que usaremos. El dialecto es el encargado
de trasformar las sentencias HQL al SQL de una base en cuestión. Así tendremos distintas
clases dialecto en medida de la base de datos que estemos usando. Para nuestro ejemplo
usaremos la clase MySQLDialect contenida en el paquete org.hibernate.dialect.*. Esta clase
extiende de la clase abstracta org.hibernate.dialect.Dialect. Al igual que el dialecto, tal y como
hemos comentado, también tendremos que definir la URL, el usuario, el password, el driver y
todos aquellos ficheros hbm que necesitemos para mapear nuestra base de datos. El siguiente
fichero muestra cómo lo hemos realizado en nuestro ejemplo hibernateAddendum.cfg.xml.
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE hibernate-configuration PUBLIC "-//Hibern ate/Hibernate Configuration DTD 3.0//EN" "http://hibernate.sourceforge.net/hibe rnate-configuration-3.0.dtd"> <hibernate-configuration> <session-factory> <property name="hibernate.dialect">org.hibernate.dialect.MySQ LDialect</property> <property name="hibernate.connection.driver_class">com.mysql. jdbc.Driver</property> <property name="hibernate.connection.url">jdbc:mysql://127.0. 0.1:3306/HIBERNATE_ADDENDUM2</property> <property name="hibernate.connection.userna me">ADMIN</property> <property name="hibernate.connection.passwo rd">ADMIN</property> <!--descomentar la siguiente linea si no ex isten las tablas--> <!--<property name="hibernate.hbm2ddl.auto" >create</property>--> <mapping resource="es/uah/hibernateaddendum /model/Exam.hbm.xml"/> <mapping resource="es/uah/hibernateaddendum /model/Student.hbm.xml"/> <mapping resource="es/uah/hibernateaddendum /model/Subject.hbm.xml"/> <mapping resource="es/uah/hibernateaddendum/model/SubjectUni t.hbm.xml"/> </session-factory> </hibernate-configuration>
La propiedad hibernate.hbm2ddl.auto es la que usaremos para crear el esquema definido en
los hbm, hacer un update o incluso crear y borrar las tablas lo cuál será útil para cuando
hagamos pruebas.
Inicializar el objeto Session
En hibernate el objeto sesión es indispensable para cualquier proceso relativo a la persistencia.
Este objeto lo crearemos gracias una factoría org.hibernate.SessionFactory en la que
podremos obtener gracias al objeto org.hibernate.cfg.Configuration. El siguiente código
muestra una forma útil de realizar todo lo comentado.
private static final SessionFactory sessionFactory; static { try { sessionFactory = new Configuration().co nfigure( "hibernateAddendum.cfg.xml").bu ildSessionFactory(); } catch (Throwable ex) { System.err.println("Initial sessionFact ory creation failed." + ex); throw new ExceptionInInitializerError(e x); } }
Tras obtener la sesión existe una serie de pasos para poder realizar toda operación persistente.
Tras la obtención de la sesión crearemos la transacción, operaremos en la base de datos y
finalizaremos la transacción y después la sesión Hibernate. Todo ello lo haremos a través del
175
objeto sesión. Como se habrá percatado el lector, Spring nos facilita todo proceso reiterativo,
es decir, todo proceso en el cuál existan una serie de pasos fijos.
Operaciones típicas con el objeto Session
Existen una serie de operaciones típicas que se suelen hacer con las bases de datos como
puedan ser la inserción, actualización, borrado o consulta.
Inserción
Como ya hemos contado, existen un número de pasos que hemos de dar con las operaciones
con el objeto Session. Así primero recuperaremos el objeto sesión de la factoría, empezaremos
la transacción, ejecutaremos el método save() al cuál le pasaremos el objeto que queremos
persistir y después forzaremos la inserción con el método flush(), haremos un commit en la
base de datos y cerraremos la sesión. El código siguiente muestra los pasos descritos.
public void insert(Exam exam) throws ExamsException { try { Session session = getSessionFactory().o penSession(); Transaction transaction = session.begin Transaction(); session.save(exam); session.flush(); transaction.commit(); session.close(); } catch (HibernateException hibernateExcept ion) { throw new ExamsException(INSERT, exam, hibernateException); } }
Actualización
Cuando hayamos modificado objetos existentes previamente el método update() será el
encargado de realizar un upadate en la base datos. Es muy similar al método save comentado,
siguiendo los mismos pasos. Un código de ejemplo sería el siguiente.
public void update(Exam exam) throws ExamsExcep tion { try { Session session = getSessionFactory().o penSession(); Transaction transaction = session.begin Transaction(); session.update(exam); session.flush(); transaction.commit(); session.close(); } catch (HibernateException hibernateExcept ion) { throw new ExamsException(INSERT, exam, hibernateException); } }
A pesar de que la manera más clara de actualizar un objeto es esta, existe también la
posibilidad de recuperar un objeto con el método get de la sesión, hacer los cambios
necesarios y después cerrar la sesión. Esto hará el mismo efecto que el método update.
Consulta
Disponemos de varias formas de obtener objetos de la base de datos. Podremos utilizar el
método get de la sesión pasándole la clase y el identificador o podemos hacer consultas ya sea
en lenguaje HQL o mediante el API de criteria. El siguiente código hace uso del método del
objeto sesión get.
public Exam getExam(int id) throws ExamsExcepti on { Exam exam = null; try { Session session = getSessionFactory().o penSession(); exam = (Exam) session.get(Exam.class, i d); session.close(); } catch (HibernateException he) { throw new ExamsException(GET_STUDENT, e xam, he); } if (exam == null) { throw new ExamNotFoundException(id); } return exam; }
Para poder hacer consultas más complejas Hibernate dispone de un lenguaje unificado de
consulta llamado HQL. Gracias a este lenguaje podemos crear consultas que más tarde
Hibernate interpretará pasándolas al lenguaje de la base de datos utilizada gracias al dialecto
seleccionado. El objeto Query es el encargado de la personalización de la consulta teniendo
diversos métodos que nos ayudaran a perfilar la consulta.
Con HQL podremos generalizar las consultas a la base de datos haciendo que no tengamos que
aprender cada lenguaje SQL de cada base de datos, pero, por el contrario, tendremos que
aprender HQL. Criteria es un API que nos permite realizar consultas dentro del ORM utilizando
objetos, así no necesitaremos saber HQL.
Un ejemplo de cómo utilizamos criteria es el siguiente:
public Collection<Exam> getAllExams() { Session session = getSessionFactory().openS ession(); Criteria criteria = session.createCriteria( Exam.class); Collection<Exam> exams = criteria.list(); session.close(); return exams; }
Borrado
Para eliminar de una entrada de la tabla utilizaremos el método delete. Para eliminar primero
deberemos recuperar el objeto y después borrarlo. El siguiente código muestra cómo hacerlo:
public void delete(int id) throws ExamsExceptio n { try { Session session = getSessionFactory().o penSession(); Transaction transaction = session.begin Transaction();
177
Exam exam = (Exam) session.get(Exam.cla ss, id); transaction.commit(); session.close(); } catch (HibernateException exception) { throw new ExamsException(DELETE); } }
179
CONCLUSIONES
181
Contenido Experiencia personal ................................................................................................................. 183
Bibliografía ................................................................................................................................ 184
183
Experiencia personal
Como tantos otros Informáticos, empecé a trabajar antes de terminar mi carrera, Ingeniería Técnica en Informática de Gestión. Mi primer trabajo lo conseguí como programador Java y fui poco a poco dándome cuenta de la importancia que tiene hacer aplicaciones mantenibles, desacopladas, con módulos reutilizables y entendibles. Tras estos años he utilizado diversos componentes, marcos de trabajo y herramientas para el desarrollo de aplicaciones pero ninguno tan extenso y tan útil como Spring.
Cuando empecé con este trabajo me gustó la idea de poder involucrarme más con una
herramienta que ya había utilizado y que en ese momento veía práctica. Más tarde, según me
fui documentando, caí en la cuenta de lo extenso, versátil y potente que es Spring Framework
y entendí cómo era uno de los requisitos más valorados dentro del mundo Java en las ofertas
de trabajo. Comprobé que para entender Spring y cómo este interactúa tenía que conocer
otras herramientas ya que la magia de Spring consiste en ello. De este modo este estudio no
solo me sirvió para conocer el framework de Spring, sino para conocer más sobre el mundo
Java.
Según fui acumulando conocimientos en Spring en particular y en Java en general pude
cambiar de puesto de trabajo y comprobar que Spring es una herramienta muy valorada.
Posteriormente decidí crear una empresa junto a otro compañero en la cual desarrollaríamos
una aplicación web. Todo ese desarrollo ha sido creado haciendo uso de Spring.
Bibliografía
• Spring. Craig Walls. Anaya multimedia 2009.
• Java 6. Felipe Lima Díaz. Anaya Multimedia.
• http://www.springsource.org/
• Spring Web Flow 2 Web Development. Sven Lüppken, Markus Stäuble 2009.
• Spring Security 3. Peter Mularien, Apress 2011.
• Spring Recipes: A Problem-Solution Approach Gary Mark, Ken Sipe, Josh Long Apress.
• http://refcardz.dzone.com/
185
Recommended