Descripción: Guía Dipusevilla Historia Antigua Sevilla
Agenda sobre los eventos, fiestas, y actividad cultural de Sevilla Valle del Cauca, municipio al oriente de este departamento, incluido por la UNESCO en el paisaje cultural cafetero colombia…Descripción completa
teatru
En el contexto por el dominio del mediterráneo los romanos aterrizan en Sevilla tras vencer a los cartagineses en la batalla de Ilipa cerca de Alcalá del Río. Fundarán Italica y no refundará…Descripción completa
digestFull description
Full description
Springback merupakan gaya balik yang ditimbulkan akibat pengaruh elastisitas bahan sheet metal pada proses metal forming dalam hal ini proses bending. Besarnya gaya balik ditentukan oleh nilai modu...Full description
single degree of freedom
Appalachian SpringFull description
bar
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
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.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; public class HolaMundo {
public static void main(String[] args) { ApplicationContext ctx = new ClassPathXmlApplicationContext("holamundo.xml"); Saludo saludoConstructor = (Saludo) ctx.getBean("saludoConstructor");
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 public public public
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 universityName); }
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 universityName) { this.universityName = universityName; } public Certificate graduate() { return new UniversityCertificate(carrerName, universityName); } }
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 Certificate{ private String carrerName; private String universityName; public UniversityCertificate(String carrerName, String 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:
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.
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 ClassPathXmlApplicationContext(“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. 2. 3. 4.
5.
6. 7.
Instanciar: Spring instancia el bean. Informar de las propiedades: Spring hace la inyección a las propiedades. Asignación del nombre: Spring pasa el nombre del bean si este implementa BeanNameAware. 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. 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. Estado de listo para su uso: Tras esto, el bean ya estaría listo para usarse. 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) {
Veamos cuál sería la implementación más básica de camarero:
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í:
Con la etiqueta 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, Restaurant 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.
Lo que Spring internamente ejecutaría sería algo similar a esto: Local restaurant = new Restaurant(); Worker alberto = new WaiterDustman(coffeesServedDaily, 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 valenciana"; public void develop() { fryTheMeat(); sauteVegetables(); pourWater(); addRice(); } public String getName() { return NAME; } public void fryTheMeat(){ System.out.println("La carne se está friendo"); } public void sauteVegetables(){ System.out.println("las verduras se están rehogando"); } 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.
Como vemos en el código podemos asignar valores mediante el tag . 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.
31
Si la clase cocinero pudiera configurarse por constructor también se podría crear un bean interno de la siguiente manera:
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 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 cocinar " + recipe.getName()); recipe.develop(); } System.out.println("Todo ello para " + diners + " comensales"); } }
Raimundo es un buen Chef, ha estudiado varias recetas. Veamos su definición.
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 en vez de , 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 conversación a sus clientes: "); for (Iterator it = kindSpeach.keySet().iterator(); it.hasNext();) { String speachType = (String) it.next(); System.out.println(" - para " + speachType + " dice \"" + kindSpeach.getProperty(speachType) + "\""); } } }
Para poder definirlo en el contenedor de Spring incluiremos el siguiente código en el fichero xml. Buenos días, que bonita mañana hace hoyBuenas y maravillosas tardesAquí está su caféLe traigo su cuenta enseguidaMuchas gracias y vuelva otra vez
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 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 employees) { this.employees = employees; } }
Su definición en el fichero xml sería la siguiente
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 , 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 autoconectará 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:
35
Por el siguiente código:
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.
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 autoconectar si hay más de uno del mismo tipo.
Auto-conexión autodetect
La conexión por auto-detección hace que Spring primero intente conectar mediante constructor y luego mediante tipo.
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. …
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.
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
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.
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 + " comensales"); } 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:
init-method="init" destroy-method="clean">
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:
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, InitializingBean, 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 + " comensales"); } 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 Exception { 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. 2. 3. 4.
AOP clásico, basado en proxy. Notación AspectJ (sólo en Spring 2.x) Aspectos POJO puros (sólo en Spring 2.x) 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 org.springframework.aop.ThrowsAdvice una introducción
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 import import import
public class WatchmanAdvice implements MethodBeforeAdvice, 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, Throwable 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.
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í:
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.
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.
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.
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.
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:
Así crearemos la base del bean abstracto con el que luego instanciaremos los proxy de una manera más sencilla.
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:
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.
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 import import import import
@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 " + teacher.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 " + teacher.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.getName() + " 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:
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:
También AnnotationAwareAspectJAutoProxyCreator hace lo mismo que DefaultAdvisorAutoProxyCreator. 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.
Elemento de configuración AOP
Utilidad
Configura un notificador
Configura una notificación antes de la ejecución.
Configura una notificación posterior (haya habido éxito en la ejecución o no)
Configura una notificación alrededor
Configura una notificación de excepción
Configura una notificación después del retorno
Punto de corte definido
Definición de un aspecto, etiqueta contenida en
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 BatchUpdateException DataTruncation SQLException SQLWarning
Excepciones de DB Spring CannotAcquierLockException CannotSerializeTtransactionException CleanupFailureDataAccessException ConcurrencyFailureDataAccessException DataAccessException DataAccesResourceFailureException DataIntegrityViolationException DataRetrievalFailureException DeadlockLoserDataAccessException
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 JdbcTemplate NamedParameterJdbcTemplate SimpleJdbcTemplate HibernateTemplate HibernateTemplate (…orm.hibernate3.*) SqlMapClientTemplate JdoTemplate JpaTemplate TopLinkTemplate
Conexiones JCA CCI Conexiones JDBC JDBC con soporte para nombrados JDBC simplificadas Hibernate 2 Hibernate 3 iBATIS SqlMap JDO JPA Java Persistence Api TopLink de Oracle
parámetros
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 CciDaoSupport JdbcDaoSupport NamedParameterJdbcDaoSupport HibernateDaoSupport HibernateDaoSupport (orm.hibernate3.support) SqllMapClientDaoSupport JdoDaoSupport JpaDaoSupport TopLinkDaoSupport
TIPO JCA CCI JDBC JDBC con nombrados Hibernate 2 Hibernate 3
soporte
para
parámetros
iBatis JDO JPA 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:
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:
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 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) " + "values (?,?)"; 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 jdbcTemplate) { this.jdbcTemplate = jdbcTemplate; } public JdbcTemplate getJdbcTemplate() { return jdbcTemplate; } public Person getPerson(int personID) { List matches = jdbcTemplate.query(PERSON_SELECT_ID, new Object[]{Long.valueOf(personID)}, new RowMapper() {
69
public Object mapRow(ResultSet rs, int rowNum) throws SQLException, DataAccessException { Person person = new PersonImp(); person.setId(rs.getInt(1)); person.setName(rs.getString(2)); return person; } }); return matches.size() > 0 ? (Person) matches.get(0) : null; } public void savePerson(Person person) { jdbcTemplate.update(PERSON_INSERT, new Object[]{ new Integer(person.getId()), person.getName()}); } public List getAllPerson() { List matches = jdbcTemplate.query(PERSON_SELECT, new RowMapper() { public Object mapRow(ResultSet rs, int rowNum) throws SQLException, DataAccessException { Person person = new PersonImp(); 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:
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 = "insert 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.
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 PersonDao { private SimpleJdbcTemplate simpleJdbcTemplate; private static final String PERSON_INSERT = "insert into person (id, name) " + "values (?,?)"; 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 simpleJdbcTemplate) { this.simpleJdbcTemplate = simpleJdbcTemplate;
71
} public void setJdbcTemplate(SimpleJdbcTemplate simpleJdbcTemplate) { this.simpleJdbcTemplate = simpleJdbcTemplate; } public SimpleJdbcTemplate getSimpleJdbcTemplate() { return simpleJdbcTemplate; } public Person getPerson(int personID) { List matches = simpleJdbcTemplate.query(PERSON_SELECT_ID, new ParameterizedRowMapper() { public Person mapRow(ResultSet rs, int rowNum) throws SQLException { Person person = new PersonImp(); person.setId(rs.getInt(1)); person.setName(rs.getString(2)); return person; } }, personID); return matches.size() > 0 ? (Person) matches.get(0) : null; } public void savePerson(Person person) { simpleJdbcTemplate.update(PERSON_INSERT, person.getId(), person.getName()); } public List getAllPerson() { List matches = simpleJdbcTemplate.query(PERSON_SELECT, new ParameterizedRowMapper() { public Object mapRow(ResultSet rs, int rowNum) throws SQLException, DataAccessException { Person person = new PersonImp(); 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:
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 implements PersonDao{ private static final String PERSON_INSERT = "insert into person (id, name) " + "values (?,?)"; 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(PERSON_SELECT_ID, new Object[]{Long.valueOf(personID)}, new RowMapper() { public Object mapRow(ResultSet rs, int rowNum) throws SQLException, DataAccessException { Person person = new PersonImp(); person.setId(rs.getInt(1)); person.setName(rs.getString(2)); return person; } }); return matches.size() > 0 ? (Person) matches.get(0) : null; } public void savePerson(Person person) { getJdbcTemplate().update(PERSON_INSERT, new Object[]{ new Integer(person.getId()), person.getName()}); } public List getAllPerson() { List matches = getJdbcTemplate().query(PERSON_SELECT, new RowMapper() { public Object mapRow(ResultSet rs, int rowNum) throws SQLException, DataAccessException { Person person = new PersonImp(); person.setId(rs.getInt(1));
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: es/uah/hibernateaddendum/model/Exam.hbm.xmles/uah/hibernateaddendum/model/Student.hbm.xmles/uah/hibernateaddendum/model/Subject.hbm.xmles/uah/hibernateaddendum/model/SubjectUnit.hbm.xml${hibernate.dialect}
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. es.uah.hibernateaddendum.model.Exames.uah.hibernateaddendum.model.Studentes.uah.hibernateaddendum.model.Subjectes.uah.hibernateaddendum.model.SubjectUnit
75
${hibernate.dialect}
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:
Y el siguiente sería para utilizar el annotationSessionFactoryBean definido anteriormente.
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 private private private private
static final String INSERT = "insert"; static final String DELETE = "delete"; static final String GET_EXAM = "getExam"; static final String EXAM = Exam.class.getName(); 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(HibernateTemplate hibernateTemplate) { this.hibernateTemplate = hibernateTemplate; } public void update(Exam exam) { getHibernateTemplate().saveOrUpdate(exam); } public void insert(Exam exam) { getHibernateTemplate().saveOrUpdate(exam); } public void insert(Set exams) throws ExamsException { for (Iterator it = exams.iterator(); it.hasNext();) { this.insert(it.next()); } } public Exam getExam(int id) throws ExamsException { List results = getHibernateTemplate().find(SELECT_ID, id); if (results.size() == 0) { return (Exam) results.get(0); } throw new ExamNotFoundException(id); } public Collection getAllExams() { return getHibernateTemplate().find(SELECT_ALL); } public void delete(int id) throws ExamsException { 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:
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 HibernateDaoSupport { private static final String STUDENT = Student.class.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 students) throws StudentsException { for (Iterator it = students.iterator(); it.hasNext();) { this.insert(it.next()); } } public Student getStudent(int id) throws StudentsException { List results = getHibernateTemplate().find(SELECT_ID, id); if (results.size() == 0) { return (Student) results.get(0); } throw new StudentNotFoundException(id); } public Collection getAllStudents() { return getHibernateTemplate().find(SELECT_ALL); } public void delete(int id) throws StudentsException { getHibernateTemplate().delete(this.getStudent(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.
Más tarde definiremos el bean en el fichero de Spring y le deberemos pasar la unidad de persistencia como parámetro.
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 LocalContainerEntityManagerFactoryBean.
cómo
configurar
un
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.
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:
Una manera de usar la plantilla sería así. public void saveUser(User user){ getJpaTemplate().persist(user); } public List 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 extends JpaDaoSupport { … }
La definición será tan sencilla como:
83
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
84
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.
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: contextConfigLocation /WEB-INF/exampleapp-service.xml /WEB-INF/exampleapp-data.xml /WEB-INF/exampleapp-security.xml org.springframework.web.context.ContextLoaderListener
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 preprocesos 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 import import import import
/** * * @author javiersevilla */ public class WelcomeController extends AbstractController { public WelcomeController(){ } protected ModelAndView handleRequestInternal(HttpServletRequest 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:
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.
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-88591"%> WELCOME ${dateMessage}
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.RequestMapping; 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:
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.RequestMapping; 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 org.springframework.ui.ModelMap para guardar los objetos de modelo.
o
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 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(); } } }
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 = RequestMethod.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 answersId) { List answersIdLongs = new ArrayList(); if (answersId != null) { for (String answerId : answersId) { answersIdLongs.add(new Long(answerId)); } } QuestionAnswered questionAnswered = this.questionDao.saveQuestionAnsweredByUser(questionId, answersIdLongs); 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 handle(HttpEntity requestEntity) throws UnsupportedEncodingException { String requestHeader = requestEntity.getHeaders().getFirst("MyRequestHeader")); byte[] requestBody = requestEntity.getBody(); HttpHeaders responseHeaders = new HttpHeaders(); responseHeaders.set("MyResponseHeader", "MyValue"); return new ResponseEntity("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 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("JSESSIONID") 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("Accept-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, ServletRequestDataBinder 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.
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.
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: • • • • •
Con UrlBasedViewResolver las traducciones de resolución de vista se hacen desde una URL
107
Cuando se devuelve ejemplo como vista lógica se traducirá el fichero físico /WEBINF/jsp/ejemplo.jsp.
Cuando
se
combinan
varias tecnologías de ResourceBundleViewResolver del siguiente modo:
vista
se
pueden
usar
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
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 RedirectView
es
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.
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. /**/*.view=someController
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
... ...
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:
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: Upload a file please
Please upload a file
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 = RequestMethod.POST) public String handleFormUpload(@RequestParam("name") 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.
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 ConversionNotSupportedException HttpMediaTypeNotAcceptableException HttpMediaTypeNotSupportedException HttpMessageNotReadableException HttpMessageNotWritableException HttpRequestMethodNotSupportedException MissingServletRequestParameterException NoSuchRequestHandlingMethodException TypeMismatchException
Código de estado HTTP 500 (Error interno del servidor) 406 (No aceptable) 415 (Tipo de medio no soportado) 400 (Mala petición) 500 (Error interno de servidor) 405 (Método no soportado) 400 (Mala petición) 404 (No encontrado) 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, HttpServletRequest 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:
Javier Sevilla Sánchez
Desarrollando con Spring
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: org.springframeworkspring-context${org.springframework-version}commons-loggingcommons-loggingorg.springframeworkspring-jdbc${org.springframework-version}org.springframeworkspring-orm${org.springframework-version}
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:
El siguiente código muestra cómo configurar el DispatcherServlet: campus org.springframework.web.servlet.DispatcherServlet campus/
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: springSecurityFilterChainorg.springframework.web.filter.DelegatingFilterProxyspringSecurityFilterChain/*
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. CharacterEncodingFilter org.springframework.web.filter.CharacterEncodingFilter encodingUTF-8
forceEncodingtrue
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:
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:
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.
>
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.
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:
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.
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.
pageNotFound dataAccessFailure dataAccessFailure
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: /WEB-INF/properties/messages
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:
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.
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 que simplemente redirecciona una deterinada ruta a un fichero jsp o 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:
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
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.
El siguiente bean a definir será el data source, en nuestro caso utilizaremos BasicDataSource de Apache.
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. ${hibernate.dialect}${hibernate.show_sql}${hibernate.generate_statistics}
145
update
El siguiente bean es un manager de transacción, en concreto el HibernateTransactionManager. Este manager nos permitirá hacer operaciones transaccionales asegurando la atomicidad.
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.
El siguiente tag permite la configuración por anotaciones.
El siguiente tag indica que utilizaremos transacciones con anotaciones.
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:
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.
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.
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.
Diremos a Spring que él cifrado a utilizar será el algoritmo de autenticación sha gracias al tag .
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:
@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) {
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 { public Long save(T entity); public T get(Long id); public T load(Long id); public List 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 extends HibernateDaoSupport implements BaseDao { protected Class entityClass; public BaseDaoHibernate() { super(); } public BaseDaoHibernate(Class 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 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 implements StudentDao {
extends
BaseDaoHibernate
... }
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 CampusServlet.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);
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 campusservlet.xml:
E InternalResourceViewResolver:
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:
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.TilesView 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:
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 , las de así como las de . Form tag
Los tag 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: • •
•
definirá el formulario en cuestión y lo enlazará con un controlador así como con el objeto comando u objeto del modelo a enlazar. este junto a otros como , o serán cada uno de los elementos del formulario los cuales Spring enlazará con la ruta definida en path. este objeto imprimirá los errores encontrados tras la validación del formulario en la parte servidora.