Framework Spring

¿Qué es Spring?
Algunas características que hacen interesante el framework Spring: • La inicial motivación era facilitar el desarrollo de aplicaciones J2EE, promoviendo buenas prácticas de diseño y programación. En concreto se trata de manejar patrones de diseño como Factory, Abstract Factory, Builder, Decorator, Service Locator, etc; que son ampliamente reconocidos dentro de la industria del desarrollo de software. Es código abierto Enfoque en el manejo de objetos de negocio, dentro de una arquitectura en capas Una ventaja de Spring es su modularidad, pudiendo usar algunos de los módulos sin comprometerse con el uso del resto: o El Core Container o Contenedor de Inversión de Control (Inversion of Control, IoC) es el núcleo del sistema. Responsable de la creación y configuración de los objetos. o Aspect-Oriented Programming Framework, que trabaja con soluciones que son utilizadas en numerosos lugares de una aplicación, lo que se conoce como asuntos transversales (cross-cutting concerns). o Data Access Framework, que facilita el trabajo de usar un API com JDBC, Hibernate, etc. o Transaction Management Framework. o Remote Access framework. Facilita la existencia de objetos en el servidor que son exportados para ser usados como servicios remotos. o Spring Web MVC. Maneja la asignación de peticiones a controladores y desde estos a las vistas. Implica el manejo y validación de formularios. o Spring Web Flow. o Spring Web Services. o Etc Una característica de Spring es que puede actuar como pegamento de integración entre diferentes APIs (JDBC, JNDI, etc.) y frameworks (por ejemplo entre Struts e iBatis).

• • •

Core Container
Una aclaración previa: un bean, en el contexto de Spring, es un objeto que es creado y manejado por el contenedor Spring. Es importante destacar la diferencia con respecto al uso clásico de 'bean' en J2EE: en Spring el bean no es una clase que cumple una serie de normas o restricciones, sino que es un objeto. Los paquetes org.springframework.beans y org.springframework.context proporcionan la base para el contenedor IoC (Spring Framework's IoC container). En el primer paquete tenemos el interfaz BeanFactory, que proporciona la capacidad de gestionar cualquier tipo de objeto. En el segundo paquete tenemos el subinterfaz ApplicationContext, construido sobre la base del anterior. Añade a BeanFactory una mejor integración con AOP, manejo de internacionalización, propagación de eventos, manejo de contextos web con WebApplicationContext, etc. La implementación más utilizada de BeanFactory es la clase XmlBeanFactory. Esta implementación toma de un archivo XML la definición de instancias o beans, así como sus dependencias. Por ejemplo:

&ltbean id="springappController" class="spring03.SpringappController"> &ltproperty name="libreria"> &ltref bean="libs"/> </property> </bean> &ltbean id="libs" class="spring03.negocio.Libreria"> &ltproperty name="libros"> &ltlist> &ltref bean="libro1"/> &ltref bean="libro2"/> &ltref bean="libro3"/> &ltref bean="libro4"/> </list> </property> </bean> ... En este ejemplo el bean springappController (de la clase spring03.SpringappController) tiene un atributo (librería) que es una referencia al bean libs, de la clase spring03.negocio.Libreria. La libreria tiene la propiedad libros, que es una lista de libros (los bean para los libros no aparecen en este resumen del ejemplo). Un ejemplo de instancia del contenedor podría ser el siguiente, donde al constructor se le pasa como argumento el archivo XML donde se definen los beans y sus dependencias:

XmlBeanFactory factory = new XmlBeanFactory(new ClassPathResource("org/xml/application-context.xml")); Si no se quiere tener toda la definición de beans en un único archivo XML se puede utilizar la etiqueta import. Ejemplo:

&ltbeans> &ltimport resource="services.xml"/> &ltimport resource="resources/messageSource.xml"/> &ltimport resource="/resources/themeSource.xml"/> &ltbean id="bean1" class="..."/> &ltbean id="bean2" class="..."/> </beans> El directorio de referencia de las importaciones es aquel en el que se encuentra el archivo base. Los archivos importados deben tener la etiqueta 'beans' y hacer referencia al DTD o esquema.

Inversión de Control (IoC)

Dentro del modo de programación imperativa estamos acostumbrados a pensar en un flujo de control predefinido por el programador. Sin embargo hay contextos (el caso típico son los modernos interfaces gráficos de usuario) en los que la aplicación cede el control a un API o sistema externo. Es dicho sistema el que determina los eventos que incidirán en el ciclo de vida de nuestra aplicación. Un ejemplo de inversión de control se encuentra en el modelo de programación orientado a eventos de Swing o AWT. De manera informal, la IoC viene representada por la frase "No me llames, yo te llamaré". Es el sistema externo el que llama a nuestra aplicación. Veamos diferentes tipos de IoC: • • Elevación de dependencia (Dependency Lookup) Inyección de dependencia (Dependency Injection)

Un ejemplo de "Dependency lookup" puede ser el que sucede en RMI

String url = "rmi://localhost/"; String nombreObjetoRemoto = "ob1" ... ClaseRemota ob = (ClaseRemota) Naming.lookup( url + nombreObjetoRemoto); System.out.println( ob.getString() ); El objeto cliente para acceder al servicio primero lo identifica y después lo localiza por medio de una referencia (en el ejemplo la referencia es "ob"); a continuación hace la llamada al procedimiento. El inconveniente es el acoplamiento entre la capa cliente (front), la capa de acceso al servicio y la implementación del servicio. Spring se identifica con una forma de IoC denominada Inyección de Dependencia, en la que el servicio se identifica y localiza por medio de mecanismos no programáticos, externos al código, como por ejemplo un archivo XML. Las dependencias con respecto a los servicios son explicitas y no están en el código. Con ello se gana en facilidad de test y mantenimiento. Algunas formas de inyección de dependencia son: • • • • • • • Inyección por medio de métodos Setter. En nuestro ejemplo anterior de Spring la clase SpringappController tiene un método setLibreria(), que es el que utiliza el framework para inyectar la propiedad "libreria". Inyección por medio de constructor. Por ejemplo, el bean "prod_02" tiene un constructor que admite dos argumentos: &ltbean id="prod_02" class="org.ejemplo.Producto"> &ltconstructor-arg value="AK" /> &ltconstructor-arg value="600" /> </bean>

Descarga de Spring
La descarga del framework se puede hacer en la web de Spring. Para utilizar Spring en nuestro proyecto simplemente tenemos que instalar la librería spring.jar, que exige además la librería de log básico: commons-logging-api.jar, normalmente incluida en la distribución de Tomcat. Si utilizamos Eclipse tenemos que hacer Project - Properties - Java Build Path - Add Jars. Además se puede señalar el "JavaDoc Location" (donde se encuentran los JavaDoc de spring.jar), estos JavaDocs se incluyen

también en el archivo descargado. Ejemplo de "JavaDoc Location": file:/C:/DOC/Spring/springframework-2.0.7/docs/api/.

Ejemplo de Spring

Introducción
Vamos a poner en práctica los conceptos fundamentales del "Core Container" de Spring. Empezaremos creando el proyecto con Eclipse e instalando las librerías. Para simplificar vamos a empezar con un proyecto Java no Web (una sencilla aplicación "standalone") al que se añaden las librerías necesarias (ver la Introducción). Los conceptos fundamentales de nuestro ejemplo son: • • • Producto: incluye precio, código y características. Cliente: NIF. Factura: incluye un número, una referencia al cliente y otra referencia a una lista de productos.

La estructura del proyecto es sencilla:

Las clases del dominio de problema tienen los típicos métodos set y get. Además tienen un método toString() que devuelve una cadena que representa los atributos de cada objeto. El cliente tiene unicamente un NIF:

package org.ejemplo;

public class Cliente { protected String nif; public Cliente( String nif ) { this.nif = nif; } public Cliente() {} public String getNif() { return nif; } public void setNif(String nif) { this.nif = nif; } public String toString() { return "NIF: " + getNif(); } } La clase Producto:

package org.ejemplo; import java.util.Enumeration; import java.util.Properties; public class Producto { protected String codigo; protected float precio; protected Properties caracteristicas; public Producto( String codigo, float precio ) { setCodigo( codigo ); setPrecio( precio ); } public Producto() {} public String getCodigo() { return codigo; } public void setCodigo(String codigo) { this.codigo = codigo; } public float getPrecio() { return precio; } public void setPrecio(float precio) { this.precio = precio; }

public Properties getCaracteristicas() { return caracteristicas; } public void setCaracteristicas(Properties caracteristicas) { this.caracteristicas = caracteristicas; } public String toString() { StringBuffer str = new StringBuffer(); str.append(" PRODUCTO. Código: " + getCodigo() + ". Precio: " + getPrecio()); //// Itero para recorrer la lista de propiedades Enumeration it = caracteristicas.propertyNames(); while ( it.hasMoreElements()) { String clave = (String) it.nextElement(); str.append("\n\t" + clave + ": " + caracteristicas.getProperty(clave)); } } } La Factura tiene que hacer referencia a un cliente y una lista de productos: return str.toString();

package org.ejemplo; import java.util.*; / ********************************************************************** ******************** * La factura tiene un cliente y una lista de productos. * Lanza excepción si no hay un cliente definido o si la lista de productos está vacia. * Ver métodos set correspondientes. ********************************************************************* ********************/ public class Factura { protected Cliente cliente; protected int numero; protected List productos; public Factura( Cliente cliente, int numero ) { setNumero( numero ); setCliente( cliente ); } public Factura( Cliente cliente, int numero, List productos ) { setNumero( numero ); setCliente( cliente ); setProductos( productos ); } public Cliente getCliente() {

}

return cliente;

public void setCliente(Cliente cliente) { if (cliente == null || cliente.getNif() == null || cliente.getNif().length() == 0 ) throw new IllegalArgumentException( "No puede asignar a la factura " + numero + " un cliente no definido"); this.cliente = cliente; } public int getNumero() { return numero; } public void setNumero(int numero) { this.numero = numero; } public List getProductos() { return productos; } public void setProductos(List productos) { if (productos == null || productos.size() == 0 ) throw new IllegalArgumentException( "No puede asignar a la factura " + numero + " una lista vacia de productos"); this.productos = productos; } public String toString() { StringBuffer str = new StringBuffer(); str.append( "FACTURA Numero: " + getNumero() + ". " + cliente.toString()); //// Itero para recorrer la lista de productos Iterator it = productos.iterator(); while ( it.hasNext()) { Producto p = (Producto) it.next(); str.append("\n" + p.toString()); } return str.toString(); } }

En el método main() creamos el contenedor. Le pasamos como argumento el archivo XML en el que se definen los objetos y sus dependencias:

package org.ejemplo; import org.springframework.beans.factory.xml.XmlBeanFactory; //import org.springframework.core.io.FileSystemResource; import org.springframework.core.io.ClassPathResource;; / ********************************************************************** ******************

* Clases del dominio: Factura, Producto y Cliente. La factura tiene asociado un cliente y * una lista de productos. * Son necesarias las librerias spring.jar y commons-logging-api.jar ********************************************************************* ******************/ public class Inicio { public static void main(String[] args) { try { XmlBeanFactory factory = new XmlBeanFactory(new ClassPathResource("org/xml/application-context.xml")); // Otra forma: XmlBeanFactory factory = new XmlBeanFactory(new FileSystemResource("c:/DOC/Java_Eclipse/spring05_main/src/org/xml/appl ication-context.xml")); Factura fac = (Factura) factory.getBean( "factura_alfa"); System.out.println( fac.toString()); } catch (Exception e) { e.printStackTrace(); } } }

Con el metodo getBean() se puede obtener un bean del contenedor. Como argumento se pasa el id del bean, definido en el archivo XML. En este ejemplo se obtiene la factura y se muestran por pantalla sus propiedades, obtenidas por medio del método toString().

<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" "http://www.springframework.org/dtd/spring-beans.dtd"> <!-- Beans de aplicación &ltbeans> -->

<!-- Producto. La tercera propiedad es del tipo Properties y la manejamos con la etiqueta value --> &ltbean id="prod_01" class="org.ejemplo.Producto"> &ltproperty name="codigo" value="FZX" /> &ltproperty name="precio" value="37.95" /> &ltproperty name="caracteristicas"> &ltvalue> peso=12.34 tension=125 </value> </property> </bean> Producto. Usamos el constructor. La tercera propiedad es del tipo Properties y la manejamos con la etiqueta props --> &ltbean id="prod_02" class="org.ejemplo.Producto"> &ltconstructor-arg value="AK" /> <!--

&ltconstructor-arg value="600" /> &ltproperty name="caracteristicas"> &ltprops> &ltprop key="capacidad"&gt5</prop> &ltprop key="caballos de vapor"&gt130</prop> &ltprop key="cilindrada"&gt2200</prop> </props> </property> </bean> <!-- Cliente --> &ltbean id="Pedro" class="org.ejemplo.Cliente"> &ltproperty name="nif" value="1111G" /> </bean> <!-Factura. Usamos el primer constructor para indicar el bean del tipo Cliente y el número. Por medio de método set asignamos la lista de productos. Nota: con el atributo type podriamos identificar el tipo. Ejemplo: type="java.lang.Integer". --> &ltbean id="factura_alfa" class="org.ejemplo.Factura"> &ltconstructor-arg ref="Pedro"/> &ltconstructor-arg value="20080045" /> &ltproperty name="productos"> &ltlist> <ref bean="prod_01"/> <ref bean="prod_02"/> </list> </property> </bean> </beans> El resultado de ejecutar la aplicación es:

FACTURA Numero: 20080045. NIF: 1111G PRODUCTO. Código: FZX. Precio: 37.95 tension: 125 peso: 12.34 PRODUCTO. Código: AK. Precio: 600.0 capacidad: 5 caballos de vapor: 130 cilindrada: 2200

Internacionalización

Introducción
Resulta interesante tener todos los mensajes (normales o de error) en un archivo properties, además sería útil que tuviésemos un archivo properties por cada idioma, de tal forma que se cargase de manera automática el archivo properties de mensajes en función del idioma

escogido en el sistema. En nuestro ejemplo pondremos en el directorio bin los siguientes archivos: • • • errores.properties, que contiene una línea: argumento.requerido=Los argumentos "{0}" y "{1}" son requeridos textos.properties, que contiene la línea mensaje.bienvenida=Bienvenido al manejo de Bundle de Spring textos_en.properties, que contiene la traducción al inglés del anterior.

Si estuvieramos en un proyecto web, el directorio activo para los archivos properties es WEBINF/classes. En nuestro ejemplo, como es una sencilla aplicación de consola, el directorio activo es bin. La idea es usar el interface MessageSource para obtener de manera agil mensajes.

Definir el bean del tipo MessageSource
En el archivo xml de definición de contexto tenemos que indicar la creación de un bean del tipo MessageSource

&ltbean id="messageSource" class="org.springframework.context.support.ResourceBundleMessageSource "> &ltproperty name="basenames"> &ltlist> &ltvalue&gtmensajes/textos</value> &ltvalue&gtmensajes/errores</value> </list> </property> </bean> Algunos aspectos a resaltar: • • • La implementación más manejada del interface MessageSource es ResourceBundleMessageSource En la propiedad basenames indicamos los nombres de los archivos de mensajes. En nuestro ejemplo tenemos dos. Si utilizásemos uno podría ser: &ltproperty name="baseName" value="WEB-INF/test-messages"/> Como en nuestro ejemplo no estamos en una aplicación web, sino una sencilla aplicación por consola, hemos creado en Eclipse un directorio src/mensajes, que al compilar Eclipse coloca en bin/mensajes.

Obtener los mensajes
En este ejemplo conseguimos el bean del tipo org.springframework.context.MessageSource a partir de una XmlBeanFactory. El método que utilizamos para conseguir el mensaje es getMessage(), que está sobrecargado. En la versión que utilizamos tenemos los siguientes argumentos: • El primero es la clave del archivo properties.

• • •

El segundo es un array de Strings, que indica argumentos (ver errores.properties). Si es null, lo se usan. El tercero es el mensaje que se devuelve en caso de no encontrar el mensaje buscado. El cuarto indica idioma (del tipo java.util.Locale). Tenemos diversos atributos static que nos pueden facilitar la vida,por ejemplo: Locale.ENGLISH

XmlBeanFactory factory = new XmlBeanFactory(new ClassPathResource("org/xml/application-context.xml")); MessageSource fuenteDeMensajes = (MessageSource) factory.getBean( "messageSource"); //// Obtengo un mensaje del properties por defecto (archivo textos.properties) String mensaje = fuenteDeMensajes.getMessage("mensaje.bienvenida", null, "No hay mensaje de bienvenida", null); System.out.println( mensaje ); //// Obtengo un mensaje del properties por defecto, usando argumentos (archivo errores.properties) String str[] = {"Arg1","Arg2"}; mensaje = fuenteDeMensajes.getMessage("argumento.requerido", str, "No hay mensaje de error", null); System.out.println( mensaje ); //// Obtengo un mensaje del properties en inglés (archivo textos_en.properties), definiendo el idioma con el 4º arg Locale loc = new Locale( "en"); mensaje = fuenteDeMensajes.getMessage("mensaje.bienvenida", null, "No hay mensaje de bienvenida", loc); System.out.println( mensaje ); En el primer uso de getMessage() realizamos la llamada más habitual: no hay argumentos ni idioma. En el segundo caso indicamos un array de argumentos. En el tercer caso indicamos el idioma inglés, que lo toma de textos_en.properties. La salida por pantalla sería:

Bienvenido al manejo de Bundle de Spring Los argumentos "Arg1" y "Arg2" son requeridos Welcome to use the Spring Bundle Podríamos conseguir el MessageSource de manera más directa:

MessageSource fuenteDeMensajes = new org.springframework.context.support.ClassPathXmlApplicationContext("or g/xml/application-context.xml"); En resumen, MessageSource cumple con las reglas del ResourceBundle del JDK.

Spring AOP

1. Introducción
La Programación Orientada a Aspectos, Aspect-Oriented Programming (AOP), es un nuevo modelo de entender la programación, tanto su estructura como el flujo de control. Se centra en aspectos, entendiendo por tal un servicio que es transversal, es decir, que es utilizado en gran cantidad de clases de nuestra aplicación. En inglés se dice que un aspecto es un asunto de corte transversal o "cruzado" (crosscutting concern). Ejemplos: uso de log, gestión de transacciones, etc. El contenedor IoC de Spring no depende de la implementación AOP, por tanto el uso de AOP no es obligado. Las capacidades AOP que vamos a utilizar están cubiertas por la librería spring.jar.

2. Un sencillo ejemplo
Supongamos una empresa que quiere gestionar la asignación de recursos (por ejemplo paquetes) a otros recursos (por ejemplo camiones). Tenemos el interfaz Recurso, implementado por RecursoGeneral. Las ordenes de asignar o liberar recursos llegan a GestorRecursos, que implementa el interface Gestor. Con esta situación podemos hacer algo como:

Gestor gestor = new GestorRecursos(); // Creamos gestor //// Creamos recursos Recurso camion = new RecursoGeneral(); camion.setIdentificador("Camión 892"); Recurso paq = new RecursoGeneral(); paq.setIdentificador("Paquete x09"); //// El gestor asigna y libera recursos gestor.asignar( paq, camion); gestor.liberar( paq, camion); Las clases involucradas en el problema son:

public interface Recurso { public void setIdentificador( String identificador ); public String getIdentificador(); public String toString(); } public class RecursoGeneral implements Recurso { private String identificador; public void setIdentificador( String identificador ) { this.identificador = identificador; } public String getIdentificador() { return identificador; }

public String toString() { return getIdentificador(); } } public interface Gestor { public boolean asignar( Recurso herramienta, Recurso receptor); public boolean liberar( Recurso herramienta, Recurso receptor); } public class GestorRecursos implements Gestor { public boolean asignar( Recurso herramienta, Recurso receptor) { System.out.println( "Estamos en " + this.getClass().getName() + " y asigno " + herramienta.toString() + " a " + receptor.toString()); return true; } public boolean liberar( Recurso herramienta, Recurso receptor) { System.out.println( "Estamos en " + this.getClass().getName() + " y libero " + herramienta.toString() + " de " + receptor.toString()); return true; } } Se puede ver que el ejemplo es desde el punto de vista de implementación muy sencillo.

3. Complicando el ejemplo
Supongamos que queremos añadir un aspecto (cross-cutting concern): un control de operaciones: de tal forma que cada vez que se asigna, libera o da de baja un recurso, se dispare una llamada al Control de Operaciones (ControlOperaciones). Algunas consideraciones: • • • • El objeto destino (target object: GestorRecursos) no podemos o queremos cambiarlo. Puede interesar no cambiarlo porque forme parte del nucleo de negocio o resulte costoso o arriesgado su cambio. Además no queremos abusar de la herencia, haciendo una clase hija de GestorRecursos que tenga las nuevas funcionalidades. Las nuevas funcionalidades van a ser invocadas desde bastantes lugares del código, es decir, son aspectos (cross-cutting concerns). Además queremos que sea una única clase la que realice la nueva funcionalidad del control de operaciones. Se trata de hacer software cohesivo (o coherente), de tal forma que sea una clase responsable de un rol (y no de varios). Por ello, tenemos que hacer una nueva clase y no poner el nuevo comportamiento en la clase ya existente GestorRecursos.

4. Primeros conceptos AOP

Una funcionalidad que aparezca en numerosos lugares de nuestro código y no sea añadida de una forma fácil utilizando herencia o el patrón Observador, puede ser denominado aspecto o cross-cutting concern. De esta forma añadiremos una nueva funcionalidad sin tener que tocar los objetos de negocio. En Spring AOP la forma de implementar un aspecto es crear un Advice. Una forma de implementar un aspecto en AOP es crear un Advice, que se dispara cada vez que hay una llamada al objeto destino (target object). En nuestro caso, después de cada llamada a GestorRecursos se invocará a ControlOperaciones.afterReturning(). ControlOperaciones implementa el interface de Spring AOP AfterReturningAdvice, cuyo único método es afterReturning(). El Advice se comporta "como un" interceptor posterior a la invocación al objeto destino. Definición Spring: un Advice es una acción que realiza un aspecto. Cuando hablamos de joint point nos referimos a la unión que se establece entre un punto de ejecución (un método) del objeto destino (por ejemplo, el método liberar()) y un Advice.

5. Aplicando AOP al ejemplo
Necesitamos que después de disparar el método GestorRecursos.asignar() o GestorRecursos.liberar() se puedan hacer determinadas operaciones en ControlOperaciones. ControlOperaciones se comporta como un Advice, concretamente como un AfterReturningAdvice.

import java.lang.reflect.Method; import org.springframework.aop.AfterReturningAdvice; public class ControlOperaciones implements AfterReturningAdvice { public void afterReturning(Object returnValue, Method method, Object[] args, Object target) throws Throwable { ... } } Diagrama de secuencia:

El diagrama de secuencia muestra lo siguiente: • • • • El cliente llama al proxy. El proxy es creado por una factoria de Spring AOP. El proxy delega la llamada el objeto destino (target object). Hasta aquí no se ha producido ningún cambio desde el punto de vista del comortamiento, el proxy Spring se ha convertido en el intermediario que delega el comportamiento en el objeto destino. A partir de aquí si que hay un cambio en el comportamiento: al terminar la llamada al método asignar() del objeto destino, el proxy invoca a ControlOperaciones.afterReturning().

6. Application-context y cliente
El primer objeto es el proxy, que hemos denominado proxyGestor. Una de sus propiedades es el target object, del tipo GestorRecursos, que es creado por Spring. Más adelante, cuando veamos el código fuente del cliente podremos observar que el cliente no instancia ni al proxy ni al target object. Otra propiedad del proxy es el Advice (interceptorNames), que también es instanciado por Spring. Existen dos tipos de proxy. En este ejemplo se usa el proxy Spring. Si queremos un proxy JDK, que utiliza java.lang.reflect.Proxy, debemos indicar la propiedad proxyInterfaces, que en nuestro ejemplo la hemos puesto en comentario.

<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" "http://www.springframework.org/dtd/spring-beans.dtd"> &ltbeans> El proxy recibe llamadas que deriva al "target object" GestorRecursos. El Advice es del tipo ControlOperaciones --> &ltbean id="proxyGestor" class="org.springframework.aop.framework.ProxyFactoryBean"> &ltproperty name="target"> &ltbean class="org.ejemplo.GestorRecursos"> <!--

</bean> </property> <!-- Usa un Proxy JDK (por medio de java.lang.reflect.Proxy) &ltproperty name="proxyInterfaces"> &ltlist> &ltvalue&gtorg.ejemplo.Gestor</value> </list> </property> --> &ltproperty name="interceptorNames"> &ltlist> &ltidref bean="controlador" /> </list> </property> </bean> &ltbean id="controlador" class="org.ejemplo.ControlOperaciones"> </bean> </beans> El proxy es instanciado por Spring y lo obtiene el cliente por medio de factory.getBean( "proxyGestor"). Es interesante observar que el proxy es referenciado usando el interface Gestor, que es el interface que implementa el objeto destino GestorRecursos. Las llamadas a asignar() y liberar() desencadenan las invocaciones al Advice (al terminar cada una de las llamadas, ya que estamos utilizando un afterReturningAdvice).

import org.springframework.beans.factory.xml.XmlBeanFactory; import org.springframework.core.io.ClassPathResource; public class Inicio { public static void main(String[] args) { try { XmlBeanFactory factory = new XmlBeanFactory(new ClassPathResource("org/xml/application-context.xml")); Gestor proxy = (Gestor) factory.getBean( "proxyGestor"); Recurso camion = new RecursoGeneral(); camion.setIdentificador("Camión 892"); Recurso paq = new RecursoGeneral(); paq.setIdentificador("Paquete x09"); proxy.asignar( paq, camion); proxy.liberar( paq, camion); } catch (Exception e) { e.printStackTrace(); }

} }

7. Advice: implementando el aspecto

El resto de clases del ejemplo siguen siendo válidas. Lo único que ha cambiado es la inclusión del Advice, que tiene una sencilla salida por pantalla. El método afterReturning recibe como argumentos: • • • • El objeto que retorna el método del objeto destino El método del objeto destino (asignar() o liberar()) Los argumentos enviados al método del objeto destino (los recursos de nuestro ejemplo) El objeto destino (el bean controlador)

import java.lang.reflect.Method; import org.springframework.aop.AfterReturningAdvice; public class ControlOperaciones implements AfterReturningAdvice { public void afterReturning(Object returnValue, Method method, Object[] args, Object target) throws Throwable { mostrar( "Estamos en " + this.getClass().getName() + ". En afterReturning()", returnValue, method, args, target ); } public void mostrar( String mensaje, Object returnValue, Method method, Object[] args, Object target) { System.out.println( mensaje ); System.out.println( " Target: " + (target==null ? "Null" : target.getClass().getName())); System.out.print( " Argumentos: " ); for ( Object obj: args ) System.out.print( obj.toString() + "("+ obj.getClass().getName()+") "); System.out.println( ""); System.out.println( " Método: " + method.getName()); System.out.println( " Retorna: " + returnValue.toString()); } } Los tipos de Advice: • • • • Around advice: también conocido como método interceptor, recibe el control tanto antes como después del punto de ejecución (o joint point, por ejemplo asignar()) Before advice, recibe el control antes de la ejecución del joint point After advice, ya lo hemos visto en el ejemplo, después del punto de ejecución Throws advice, después del punto de ejecución, si hay excepción

8. Salida por pantalla con el AfterReturningAdvice
La salida por pantalla muestra que primero realiza el método correspondiente (liberar() o asignar()) y después afterReturning:

Estamos en org.ejemplo.GestorRecursos y asigno Paquete x09 a Camión 892 Estamos en org.ejemplo.ControlOperaciones. En afterReturning() Target: org.ejemplo.GestorRecursos

Argumentos: Paquete x09(org.ejemplo.RecursoGeneral) Camión 892(org.ejemplo.RecursoGeneral) Método: asignar Retorna: true Estamos en org.ejemplo.GestorRecursos y libero Paquete x09 de Camión 892 Estamos en org.ejemplo.ControlOperaciones. En afterReturning() Target: org.ejemplo.GestorRecursos Argumentos: Paquete x09(org.ejemplo.RecursoGeneral) Camión 892(org.ejemplo.RecursoGeneral) Método: liberar Retorna: true

Spring AOP

Nota previa
Los siguientes ejemplos de código siguen el ejemplo anterior basado en un objeto destino (target object) denominado RecursoGeneral, que implementa el interface Recurso.

Filtrado de métodos y puntos de corte
Un aspecto establece joint points (puntos de unión), que son los puntos de ejecución del objeto destino (por ejemplo, GestorRecursos.liberar()) que entran dentro del aspecto. O dicho de otro modo, los joint point son los puntos de ejecución del objeto destino que producen una invocación a un Advice. En nuestro caso anterior todas las invocaciones al proxy se derivan en invocaciones al objeto destino, que a su vez acaban en invocaciones al Advice. Este es el comportamiento por defecto: todos los puntos de ejecución del objeto destino son interceptados por el Advice. Pero podemos filtrarlos. Un punto de corte no es más que una etiqueta o predicado que asocia (match): • • Advice Puntos de unión (Joint points)

Un jointcut no es mas que una etiqueta que establece una relación entre puntos de ejecución del objeto destino y un Advice. Por tanto con un punto de corte somos selectivos, podemos escoger los puntos de ejecución en el objeto destino y su correspondiente Advice. En el ejemplo podriamos cambiar nuestro application-context para que tuviese un punto de corte llamado controladorAsignacion. Que está proyectado (matched) contra el método asignar() del objeto destino, excluyendo el método liberar(). El nuevo application-context sería:

<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" "http://www.springframework.org/dtd/spring-beans.dtd">

&ltbeans> &ltbean id="proxyGestor" class="org.springframework.aop.framework.ProxyFactoryBean"> &ltproperty name="target"> &ltbean class="org.ejemplo.GestorRecursos"> </bean> </property> &ltproperty name="interceptorNames"> &ltlist> &ltidref bean="controladorAsignacion" /> </list> </property> </bean> &ltbean id="controladorAsignacion" class="org.springframework.aop.support.NameMatchMethodPointcutAdvisor" > &ltproperty name="mappedName" value="asignar"/> &ltproperty name="advice" ref="controlador"/> </bean> &ltbean id="controlador" class="org.ejemplo.ControlOperaciones"> </bean> </beans> NameMatchMethodPointcutAdvisor es un tipo de punto de corte basado en el nombre de los métodos. Usa una notación parecida al estilo Ant. Por ejemplo los patrones (mappedNames) *Recurso y set* encajan con el método setRecurso. Esto hace que el cliente (main) tenga un comportamiento diferente: • • Tanto la llamada proxy.asignar() como proxy.liberar() llegan al objeto destino (GestorRecursos). Pero sólo asignar() es en un join point, es decir, tan solo asignar() genera una invocación al Advice (afterReturning) por parte del proxy.

Around Advice
Around advice: también conocido como método interceptor, recibe el control tanto antes como después del punto de ejecución (o joint point, por ejemplo nuestro método asignar()). Para implementar un around advice no hay más que modificar el appplication-context.xml e implementar el interface MethodInterceptor:

public interface MethodInterceptor { public Object invoke( MethodInvocation invocacion) throws Throwable; } La secuencia del ejemplo es:

1. El cliente invoca al proxy Spring (como antes con afterReturning).

2. El proxy Spring llama al invoke() del Advice (en nuestro ejemplo es
ControlOperacionesAround). Como argumento pasa una forma o método de invocación (una clase que representa la forma en que se llemará al objeto destino o target object).

3. El método invoke() del Advice debe llamar o invocar al objeto destino con:
4. 5. invocation.proceed();

Donde invocation es el argumento recibido por el advice, el modo de invocación. proceed() acaba en una llamada al objeto destino, concretamente al método señalado en el application-context. proceed() retorna un Object, que es el objeto retornado por el objeto destino. Ejemplo:

... antes de llamar al objeto destino ... try { return invocacion.proceed(); // Llamada al objeto destino (joint point) } finally { System.out.println( "Ya se ha ejecutado el Joint Point"); }

6. El Advice realiza en finally las operaciones posteriores a la invocación al objeto
destino.

7. El Advice devuelve el Object al proxy

8. El proxy devuelve el control al cliente En el application-context tenemos dos advices, uno para el Joint Point liberar() y otro para el Joint Point asignar(). El advice de liberar() es un Around Advice, que implementa MethodInterceptor:

<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" "http://www.springframework.org/dtd/spring-beans.dtd"> &ltbeans> &ltbean id="proxyGestor" class="org.springframework.aop.framework.ProxyFactoryBean"> &ltproperty name="target"> &ltbean class="org.ejemplo.GestorRecursos"> </bean> </property>

&ltproperty name="interceptorNames"> &ltlist> &ltidref bean="controladorAsignacion" /> &ltidref bean="controladorLiberacion" /> </list> </property> </bean> &ltbean id="controladorAsignacion" class="org.springframework.aop.support.NameMatchMethodPointcutAdvisor" > &ltproperty name="mappedName" value="asignar"/> &ltproperty name="advice" ref="controlador1"/> </bean> &ltbean id="controlador1" class="org.ejemplo.ControlOperaciones"> </bean> &ltbean id="controladorLiberacion" class="org.springframework.aop.support.NameMatchMethodPointcutAdvisor" > &ltproperty name="mappedName" value="liberar"/> &ltproperty name="advice" ref="controlador2"/> </bean> &ltbean id="controlador2" class="org.ejemplo.ControlOperacionesAround"> </bean> </beans>

Implementando el MethodInterceptor (Around Advice)
public class ControlOperacionesAround implements MethodInterceptor { public Object invoke( MethodInvocation invocacion) throws Throwable { System.out.println( "Estamos en " + this.getClass().getName() + ". En invoke()" ); System.out.println( " Joint Point (clase interceptada): " + invocacion.getThis().getClass().getName() ); System.out.println( " Clase que representa la invocación: " + invocacion.getClass().getName() ); System.out.println( " Método interceptado: " + invocacion.getMethod().getName() ); System.out.print( " Argumentos: " ); for ( Object obj: invocacion.getArguments() ) System.out.print( obj.toString() + "("+ obj.getClass().getName()+") "); System.out.println( ""); return invocacion.proceed(); // Llamada al objeto destino (joint point) } finally { try {

Joint Point"); } } }

System.out.println( "Ya se ha ejecutado el

Salida por pantalla con el MethodInterceptor (Around Advice)
Estamos en org.ejemplo.ControlOperacionesAround. En invoke() Joint Point (clase interceptada): org.ejemplo.GestorRecursos Clase que representa la invocación: org.springframework.aop.framework.ReflectiveMethodInvocation Método interceptado: liberar Argumentos: Paquete x09(org.ejemplo.RecursoGeneral) Camión 892(org.ejemplo.RecursoGeneral) Estamos en org.ejemplo.GestorRecursos y libero Paquete x09 de Camión 892 Ya se ha ejecutado el Joint Point

Persistencia en Spring (I)

Introducción
JDBC es el API Java para el acceso a base de datos. Es un solución difícil debido varios inconvenientes o limitaciones: • • • • • Trabaja a bajo nivel. Como consecuencia de lo anterior, la gestión del ciclo de vida de los recursos (conexiones, resultSet, etc.) no esta exenta de dificultades. La gestión de transacciones puede resultar pesada. La gestión de excepciones. El cambio del modelo de datos afecta al cambio de sentencias, que obligan a modificar diversos archivos de código fuente y a la recompilación. Es necesario centrarse en estrategias más declarativas y menos programáticas.

Todo lo anterior hace que una sólución basada en el puro JDBC resulte difícil de mantener. Por ello se ha popularizado el uso de frameworks como iBatis o Hibernate. Spring puede trabajar con otros frameworks, fiel a su filosofía no invasiva.

El pool de conexiones
Ya sabemos la importancia de que una aplicación no gestione de forma directa la creación y liberación de conexiones, sino que debe utilizar un pool de conexiones que actúa como un sistema de caché de conexiones. La solución clásica en JDBC implica definir la fuente de datos (DataSource) en server.xml y definir su correspondiente entrada en web.xml. La aplicación usa JNDI para buscar y acceder a la fuente de datos, es decir, al pool de conexiones.

Con Spring la fuente de datos se puede definir como un bean de nuestro contexto de aplicación. En nuestro siguiente ejemplo usaremos el BasicDataSource de Apache:

<?xml version="1.0" encoding="UTF-8"?> &ltbeans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:p="http://www.springframework.org/schema/p" xmlns:aop="http://www.springframework.org/schema/aop" xmlns:context="http://www.springframework.org/schema/context" xmlns:jee="http://www.springframework.org/schema/jee" xmlns:tx="http://www.springframework.org/schema/tx" xsi:schemaLocation=" http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.5.xsd http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-2.5.xsd http://www.springframework.org/schema/jee http://www.springframework.org/schema/jee/spring-jee-2.5.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-2.5.xsd"> <!-- Las propiedades del dataSource tienen como valor properties --> &ltbean id="dataSource" class="org.apache.tomcat.dbcp.dbcp.BasicDataSource" destroymethod="close"> &ltproperty name="driverClassName" value="$ {jdbc.driverClassName}"/> &ltproperty name="url" value="${jdbc.url}"/> &ltproperty name="username" value="${jdbc.username}"/> &ltproperty name="password" value="${jdbc.password}"/> </bean> ... </beans> En este ejemplo hay que destacar: • • Necesitamos tener commons-logging-api.jar (ver Tomcat) Las propiedades de la fuente de datos hacen referencia a un archivo properties, por ejemplo:

jdbc.driverClassName=com.mysql.jdbc.Driver jdbc.url=jdbc:mysql://localhost:3306/basedatos? autoReconnect=true jdbc.username=user jdbc.password=pass

El archivo de propiedades se carga como un bean ("location" hace referencia al lugar del archivo properties):

&ltbean id="propertyConfigurer" class="org.springframework.beans.factory.config.PropertyPlaceholderCon figurer"> &ltproperty name="location"> &ltvalue>/WEB-INF/jdbc.properties</value> </property> </bean> Existe la posibilidad de usar properties de forma programática:

public GestorCarga() throws Exception { XmlBeanFactory factory = new XmlBeanFactory(new ClassPathResource("../application-context.xml")); PropertyPlaceholderConfigurer cfg = new PropertyPlaceholderConfigurer(); cfg.setLocation(new ClassPathResource("../configuracion.properties")); cfg.postProcessBeanFactory(factory); } Este código inyecta las properties (cfg) en la factoria, cuando se han cargado las definiciones de bean (pero todavía no se han instanciado).

Persistencia en Spring (II)

Introducción al ejemplo
El manejo de persistencia en Spring es un recubrimiento sobre JDBC para dotarlo de un mayor nivel de abstracción. Lo explicaremos por medio de un ejemplo, una sencilla página que realiza operaciones sobre los clientes de una empresa. La estructura del ejemplo aparece a continuación, destacando cuatro grandes bloques: vista (html y jsp), dominio, persistencia, etiquetas y recursos.

ADVERTENCIA: Las JSPs se han colocado fuera de WEB-INF. Más adelante, aprenderemos con Spring MVC a situarlas en WEB-INF/jsp, que es un lugar más protegido. Funciones sobre la tabla cliente: • • Muestra página jsp/index.jsp. Esta página realiza select de clientes e incluye jsp/formulario.jsp Formulario.jsp: delete, insert, update y select de cliente.

Lo que se requiere conocer: • • • • JSP y Java Beans. Manejamos request y no usa sesion. JDBC y pool de conexiones. Spring Core Container. Patrón DAO.

Arquitectura: • Vista: o

HTML y JSP: index.html enlaza con jsp/index.jsp, esta incluye jsp/formulario.jsp. En index.jsp se invoca a la carga del contexto Spring y de las properties. Las JSP usan Java Beans o Java Beans: se utilizan para generar HTML y sirven de intermediarios con el DAO. Se encuentran en com.etiquetas. Cuando trabajemos con Spring MVC estos Java Beans no serán necesarios. Acceso a datos (con Spring):

El dao está en com.persistencia. Se instancia en index.jsp Como auxiliar del dao está el mapper (com.persistencia.mappers) que asocia atributos del objeto Cliente con columnas de la tabla o Como auxiliar del dao para la construcción de query tenemos el paquete com.persistencia.query Servicios de carga de contexto y recursos (Spring): o com.recursos.GestorCarga es la clase que en su constructor realiza esta labor. o GestorCarga crea la factoria Spring o GestorCarga se maneja como Java Bean desde index.jsp. En Spring MVC la carga de contexto se automatiza y no resulta necesario este servicio de carga Dominio: o com.dominio.Cliente o o

Nota sobre los jar: • • spring.jar es la librería básica de Spring e incluimo commons-logging-api.jar porque la utiliza Si usamos como DataSource org.apache.tomcat.dbcp.dbcp.BasicDataSource necesitaremos que en el CLASSPATH este naming-factory-dbcp.jar (se encuentra en el common/lib de Tomcat).

A continuación una muestra de la página:

Este ejemplo muestra una página muy densa (diversos SELECT, formulario, etc.), por razones pedagógicas. Un caso real trataría de separar las consultas más costosas (SELECTS) del formulario.

Contexto de aplicación y pool de conexiones
La creación de la factoría y la carga de propiedades se realiza en una clase, con la finalidad de recubrir la implementación. El código tiene una particularidad: el archivo de properties no se especifica en el application-context.xml (que es lo más habitual), sino que se especifica en el el código Java, por medio de la clase PropertyPlaceholderConfigurer (por razones puramente pedagógicas, para aprender otra forma de cargar properties):

package com.recursos; import org.springframework.beans.factory.config.PropertyPlaceholderConfigurer ; import org.springframework.beans.factory.xml.XmlBeanFactory; import org.springframework.core.io.ClassPathResource; import com.persistencia.*; / ********************************************************************** ************** * Carga del archivo de contexto y del archivo de properties ********************************************************************* ****************/ public class GestorCarga { private XmlBeanFactory factory; private PropertyPlaceholderConfigurer cfg; private DAOClienteSpring dao; /************************************************************** ***************** * Inyecta las properties (cfg) en la factoria, cuando se han cargado las * definiciones de bean, pero todavía no se han instanciado ********************************************************************** *********/ public GestorCarga() throws Exception { factory = new XmlBeanFactory(new ClassPathResource("../application-context.xml")); cfg = new PropertyPlaceholderConfigurer(); cfg.setLocation(new ClassPathResource("../configuracion.properties")); cfg.postProcessBeanFactory(factory); dao = (DAOClienteSpring) factory.getBean( "DAOCliente"); } public DAOClienteSpring getDao() { return dao; } public PropertyPlaceholderConfigurer getCfg() { return cfg; } public void setCfg(PropertyPlaceholderConfigurer cfg) { this.cfg = cfg; } public XmlBeanFactory getFactory() { return factory; } public void setFactory(XmlBeanFactory factory) { this.factory = factory; }

}

Otra particularidad es que además de crear la factoría e inyectarle las properties, crea el DAO del cliente en el constructor. Puesto que es un ejemplo con un único DAO esto puede servir. Pero si hubiese numerosos DAOS se podría probar con otra estrategia. Por ejemplo, el correspondiente método getDaoXXX() devuelve el DAO y si no se ha cargado (si es null) entonces invoca a getBean(). El archivo index.jsp instancia GestorCarga como si fuera un Java Bean:

&ltjsp:useBean id="gestorCarga" scope="application" class="com.recursos.GestorCarga"/> El archivo de definición del contexto de aplicación tiene únicamente dos bean: uno para la fuente de datos (Data Base Common Pooling, dbcp, de Apache) y otro para el DAO (observar que un atributo del DAO es la fuente de datos):

<!-- Las propiedades del dataSource tienen como valor properties --> &ltbean id="dataSource" class="org.apache.tomcat.dbcp.dbcp.BasicDataSource" destroymethod="close"> &ltproperty name="driverClassName" value="$ {jdbc.driverClassName}"/> &ltproperty name="url" value="${jdbc.url}"/> &ltproperty name="username" value="${jdbc.username}"/> &ltproperty name="password" value="${jdbc.password}"/> </bean> <!-- El DAO que tiene como atributo el dataSource --> &ltbean id="DAOCliente" class="com.persistencia.DAOClienteSpring"> &ltproperty name="dataSource" ref="dataSource"/> </bean>

El dominio
Es algo sencillo, la clase que representa al cliente (el típico bean asociado a tabla):

package com.dominio; public class Cliente { private String codigo; private String nombre; private String ape1; private String ape2; private Integer edad; public String getApe1() { return ape1; } public void setApe1(String ape1) { this.ape1 = ape1; } public String getApe2() { return ape2; }

public void setApe2(String ape2) { this.ape2 = ape2; } public String getCodigo() { return codigo; } public void setCodigo(String codigo) { this.codigo = codigo; } public Integer getEdad() { return edad; } public void setEdad(int edad) { this.edad = new Integer(edad); } public void setEdad(Integer edad) { if (edad == null) this.edad = 0; else this.edad = edad; } public String getNombre() { return nombre; } public void setNombre(String nombre) { this.nombre = nombre; }

}

Persistencia
El DAO hereda de JdbcDaoSupport de Spring, que contiene el atributo "dataSource". En el application-context.xml se indica el bean de nuestra clase DAO y su atributo "dataSource". Si se hecha un rápido vistazo al DAO se puede ver que se usa con frecuencia org.springframework.jdbc.core.simple.SimpleJdbcTemplate. Una de las clases más importantes de Spring 2.0 para encapsular la complejidad de usar JDBC. En un ejemplo típico creamos el SimpleJdbcTemplate (pasandole el DataSource de JdbcDaoSupport) y ejecutamos una SELECT por medio de query().

SimpleJdbcTemplate template = new SimpleJdbcTemplate(getDataSource()); return template.query("SELECT * FROM cliente", new ClienteMapper()); El segundo argumento de query() es un mapper, del que hablaremos más adelante. El código del DAO:

package com.persistencia; import org.springframework.jdbc.core.support.JdbcDaoSupport; import org.springframework.jdbc.core.simple.SimpleJdbcTemplate; import java.util.List;

import com.persistencia.mappers.ClienteMapper; import com.persistencia.query.ClientePorEdadSetter; import com.dominio.Cliente; / ********************************************************************** *** * DAO que emplea la clase JdbcDaoSupport de Spring ********************************************************************* ****/ public class DAOClienteSpring extends JdbcDaoSupport { /************************************************************** *********** * Inicializador de DAO ********************************************************************** ***/ protected void initDao() throws Exception { super.initDao(); } /************************************************************** *********** * Count de clientes. Usa SimpleJdbcTemplate (Spring 2.0) ********************************************************************** ***/ public int getNumeroClientes() { return new SimpleJdbcTemplate(getDataSource()).queryForInt("SELECT COUNT(*) FROM cliente"); } /************************************************************** *********** * Count de clientes entre un rango de edades. Usa SimpleJdbcTemplate (Spring 2.0) ********************************************************************** ***/ public int getNumeroClientes(Integer edadMinima, Integer edadMaxima) { return new SimpleJdbcTemplate(getDataSource()).queryForInt( "SELECT count(*) FROM cliente WHERE edad >= ? AND edad <= ?", edadMinima, edadMaxima ); } /************************************************************** *********** * Select de todos los clientes. Usa un mapper. Usa SimpleJdbcTemplate (Spring 2.0) ********************************************************************** ***/ public List&ltCliente> selectAll() { SimpleJdbcTemplate template = new SimpleJdbcTemplate(getDataSource());

return template.query("SELECT * FROM cliente", new ClienteMapper()); } /************************************************************** *********** * Select de los clientes entre un rango de edades. Usa mapper * Usa PreparedStatement ********************************************************************** ***/ public List&ltCliente> selectAllPreparedStatement(int edadMinima, int edadMaxima) { return getJdbcTemplate().query( "SELECT * FROM cliente WHERE edad >= ? AND edad <= ?", new ClientePorEdadSetter(edadMinima, edadMaxima),new ClienteMapper()); } /************************************************************** *********** * Select de UN cliente. Usa un mapper. Usa SimpleJdbcTemplate (Spring 2.0) ********************************************************************** ***/ public Cliente selectCliente( String codigo ) { SimpleJdbcTemplate template = new SimpleJdbcTemplate(getDataSource()); return (Cliente) template.queryForObject("SELECT * FROM cliente WHERE codigo=?", new ClienteMapper(), codigo); } /************************************************************** *********** * Select para funciones mátemáticas básicas ********************************************************************** ***/ public long getEdadTotal() { return getJdbcTemplate().queryForLong("SELECT SUM(edad) FROM cliente"); } public long getEdadMedia() { return getJdbcTemplate().queryForLong("SELECT AVG(edad) FROM cliente"); } public long getEdadMaxima() { return getJdbcTemplate().queryForLong("SELECT MAX(edad) FROM cliente"); } public long getEdadMinima() { return getJdbcTemplate().queryForLong("SELECT MIN(edad) FROM cliente"); } /************************************************************** ***********

* Insert de cliente con SimpleJdbcTemplate (Spring 2.0) * Usa ClienteMapper para inyectar los datos en la sentencia SQL ********************************************************************** ***/ public int insertarCliente(Cliente cliente) { SimpleJdbcTemplate template = new SimpleJdbcTemplate(getDataSource()); return template.update("INSERT INTO cliente (codigo, nombre, ape1, ape2, edad) " + "VALUES (?, ?, ?, ?, ?)", ClienteMapper.mapAributos(cliente) ); } /************************************************************** *********** * Actualiación de cliente con SimpleJdbcTemplate (Spring 2.0) * Usa ClienteMapper para inyectar los datos en la sentencia SQL ********************************************************************** ***/ public int actualizarCliente(Cliente cliente) { SimpleJdbcTemplate template = new SimpleJdbcTemplate(getDataSource()); return template.update("UPDATE cliente SET codigo=?, nombre=?, ape1=?, "+ "ape2=?, edad=? WHERE codigo='" + cliente.getCodigo() + "'", ClienteMapper.mapAributos(cliente)); } /************************************************************** *********** * Borrado de cliente con SimpleJdbcTemplate (Spring 2.0) ********************************************************************** ***/ public int borrarCliente(Cliente cliente) { SimpleJdbcTemplate template = new SimpleJdbcTemplate(getDataSource()); return template.update("DELETE FROM cliente WHERE codigo=?", cliente.getCodigo()); } /************************************************************** *********** * En función del argumento realiza una operación de actualización de datos ********************************************************************** ***/ public int updateHub(String operacion, Cliente cliente) { if ( operacion.compareTo( "Insertar") == 0 ) return insertarCliente( cliente ); if ( operacion.compareTo( "Actualizar") == 0 ) return actualizarCliente( cliente ); if ( operacion.compareTo( "Borrar") == 0 ) return borrarCliente( cliente ); return 0; }

} Se puede observar que:

En getNumeroClientes(): new SimpleJdbcTemplate(getDataSource()).queryForInt ("SELECT COUNT(*) FROM cliente"); es una forma bastante sencilla de obtener una cuenta (count).

• • • • •

En getNumeroClientes(Integer edadMinima, Integer edadMaxima): return new SimpleJdbcTemplate(getDataSource()).queryForInt( "SELECT count(*) FROM cliente WHERE edad >= ? AND edad <= ?", edadMinima, edadMaxima );

Aprovechamos la capacidad de Java 1.5 de manejar un número variable de argumentos.

En insertarCliente(Cliente cliente) tenemos un caso típico de manejar la modificación de datos, tan sólo es necesario especificar la sentencia SQL y un array de Object que define los parámetros de la sentencia, es decir, los datos del cliente para insertar (INSERT). Para UPDATE es la misma lógica.

• • • •

En selectAllPreparedStatement(int edadMinima, int edadMaxima) se maneja una PreparedStatement de JDBC: return getJdbcTemplate().query( "SELECT * FROM cliente WHERE edad >= ? AND edad <= ?", new ClientePorEdadSetter(edadMinima, edadMaxima),new ClienteMapper());

En el método query() se envia la sentencia, un setter y el mapper. El setter tiene una función muy sencilla: inyectar los párametros la sentencia. Veremos más adelante al mapper.

Dentro de la persistencia: los mapper
Un mapper es algo realmente sencillo. Simplemente realiza dos tareas: • La más importante es implementar el método mapRow() del interface ParameterizedRowMapper. El método "mapea" un ResultSet sobre una instancia de la clase Cliente. Este método es del tipo callback: es llamado por Spring cada vez que

un ResultSet pasa a la siguiente fila. Lo que hace es devolver el cliente, cuyos atributos han sido definidos a partir del ResultSet.

El método mapAributos() no esta obligado por ningún interface. Es nuestra solución para devolver un array de objetos que representa los atributos del cliente que se pasa como argumento. Ver el método actualizarCliente(Cliente cliente).

package com.persistencia.mappers; import import import import java.sql.ResultSet; java.sql.SQLException; org.springframework.jdbc.core.simple.ParameterizedRowMapper; com.dominio.Cliente;

/ ********************************************************************** *** * Mapea atributos del objeto y columnas de la base de datos * * El metodo mapRow() inyecta en el resultSet los datos del objeto Cliente. * Es un callback: llamado por Spring por cada fila del resultSet. * Tiene como argumentos dicho resultSet y el nº de fila * * El método mapAtributos devuelve los atributos del objeto cliente, * en la forma de un array de objetos ********************************************************************* ****/ public class ClienteMapper implements ParameterizedRowMapper { public Cliente mapRow(ResultSet resultSet, int row) throws SQLException { Cliente cli = new Cliente(); cli.setCodigo(resultSet.getString("codigo")); cli.setNombre(resultSet.getString("nombre")); cli.setApe1(resultSet.getString("ape1")); cli.setApe2(resultSet.getString("ape2")); cli.setEdad(resultSet.getInt("edad")); return cli; } static public Object[] mapAributos( Cliente cliente) { return new Object[] { cliente.getCodigo(), cliente.getNombre(), cliente.getApe1(), cliente.getApe2(), cliente.getEdad()}; } }

Setter
La clase ClientePorEdadSetter es muy breve. Simplemente define los parámetros de una PreparedStament:

package com.persistencia.query; import java.sql.SQLException; import org.springframework.jdbc.core.PreparedStatementSetter; import java.sql.PreparedStatement; / ********************************************************************** *** Clase que inyecta parametros en un PreparedStatement ********************************************************************* ****/ public class ClientePorEdadSetter implements PreparedStatementSetter { private int edadMinima; private int edadMaxima; public ClientePorEdadSetter( int edadMinima, int edadMaxima) { this.edadMinima = edadMinima; this.edadMaxima = edadMaxima; } public void setValues (PreparedStatement ps) throws SQLException{ ps.setInt(1, edadMinima); ps.setInt(2, edadMaxima); } } Al implementar el interface PreparedStatementSetter tenemos que definir el método setValues(), que define los parámetros de la sentencia.

La vista: index.jsp y Java Beans
La página principal tiene dos caracteristicas esenciales: • • Apenas tiene Java (scriptlets). Los objetos utilizados (instancias de GestorCarga y ClienteEtiquetaSelect) se manejan como Java Beans. Con Spring MVC veremos que el uso de Java Beans no es (casi) necesario. Incluye formulario.jsp

<%@ page language="java" contentType="text/html; charset=ISO-8859-1" pageEncoding="ISO-8859-1"%> <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"> <%@ page errorPage="error.jsp"%> <%@ page import="com.recursos.GestorCarga" %> <%@ page import="com.persistencia.DAOClienteSpring" %> &lthtml> <%@ include file="cabeceraDePagina.jsp" %> <!-El objeto gestorCarga realiza la carga del contexto (xml),

propiedades y DAO --> &ltjsp:useBean id="gestorCarga" scope="application" class="com.recursos.GestorCarga"/> <% %> int edadMinima = 25; int edadMaxima = 35;

&ltCENTER>&ltH4>&ltb&gtGestión de clientes</H4></b></CENTER> <!-- INICIO DE TABLA --> &ltTABLE BORDER=1 align='center' width='900'> &ltTR>&ltTD> <!-El objeto select muestra resultados de consultas de clientes. Para ello usa el DAO. Se le pasa un rango de edades para los select con where

--> &ltjsp:useBean id="select" scope="request" class="com.etiquetas.ClienteEtiquetaSelect"> &ltjsp:setProperty name="select" property="dao" value="< %=gestorCarga.getDao()%>"/> &ltjsp:setProperty name="select" property="edadMinima" value="25"/> &ltjsp:setProperty name="select" property="edadMaxima" value="35"/> </jsp:useBean> <!-- Listado de todos los clientes --> &ltp>&ltb&gtListado de clientes: </b>&ltol> &ltjsp:getProperty name="select" property="select" /> </ol></p> <!-- Count de todos los clientes --> &ltp>&ltb&gtNúmero de clientes: </b> &ltjsp:getProperty name="select" property="cuenta" /> </p>

<!-- Listado de los clientes que tienen una edad --> &ltp>&ltb&gtListado de clientes entre <%=edadMinima%> y <%=edadMaxima %>: </b>&ltol> &ltjsp:getProperty name="select" property="selectPorEdad" /> </ol></p> <!-- Count de los clientes que tienen una edad --> &ltp>&ltb&gtNúmero de clientes entre <%=edadMinima%> y <%=edadMaxima %>: </b> &ltjsp:getProperty name="select" property="cuentaSelectiva" /> </p> <!-- CAMBIA DE COLUMNA EN LA TABLA --> </TD> &ltTD> <%@ include file="formulario.jsp" %> </TD></TR></TABLE>

&ltp&gtLas operaciones de Insertar, Actualizar, Borrar y Consultar se refieren siempre a un &ltb&gtcódigo de registro</b>, que es clave primaria.</p> &ltp>&ltb&gtActualizar</b>: update del registro cuyo código corresponda con el de la página.</p> &ltp>&ltb&gtConsultar</b>: select del registro cuyo código corresponda con el de la página.</p> &ltp>&ltb&gtRefrescar</b>: vuelve a presentar la página con listados actualizados.</p> <%@ include file="pieDePagina.jsp" %> </html> Código fuente de formulario.jsp. El Java Bean "select" se usa para aligerar de Java las páginas JSP. El bean devuelve un String que se vuelca en la página. De esta forma se obtienen los listados de clientes. Más adelante, cuando estudiemos Spring MVC veremos que este uso de los JavaBeans no es necesario; ya que los controladores devolverán a las páginas los objetos que deben mostrar..

package com.etiquetas; import com.dominio.Cliente; import com.persistencia.DAOClienteSpring; / ********************************************************************** ************* * JavaBean generador de código html. * Es un intermediario entre la página jsp y el dao: la página llama a este JavaBean * y el JavaBean devuelve el resultado de las consultas como tipo String ********************************************************************* *************/ public class ClienteEtiquetaSelect { DAOClienteSpring dao; // El DAO Integer edadMinima; // Para un where por edad Integer edadMaxima; // Para un where por edad String inicioLinea = "&ltli>"; // Etiqueta para iniciar linea de select String finLinea = "</li>"; // Etiqueta para terminar linea de select /************************************************************** ********************* * Devuelve el nº total de clientes ********************************************************************** ************/ public String getCuenta() { return ""+dao.getNumeroClientes(); }

/************************************************************** ********************* * Devuelve el nº de clientes entre las edades ********************************************************************** ************/ public String getCuentaSelectiva() { return ""+dao.getNumeroClientes(edadMinima,edadMaxima); } /************************************************************** ********************* * Devuelve todos los clientes. Para separar líneas usa los atributos * incioLinea y finLinea ********************************************************************** ************/ public String getSelect() { StringBuffer str = new StringBuffer(); for ( Cliente cliente : dao.selectAll() ) str.append( inicioLinea + cliente.getCodigo() + ", " + cliente.getNombre() + " (" + cliente.getEdad() +")"+finLinea); return str.toString(); } /************************************************************** ********************* * Devuelve los clientes entre las edades. Para separar líneas usa los atributos * incioLinea y finLinea ********************************************************************** ************/ public String getSelectPorEdad() { StringBuffer str = new StringBuffer(); for ( Cliente cliente : dao.selectAllPreparedStatement(edadMinima,edadMaxima) ) str.append( inicioLinea + cliente.getCodigo() + ", " + cliente.getNombre() + " (" + cliente.getEdad() + ")"+finLinea); return str.toString(); } /************************************************************** ********************* * Definir periodo de edad ********************************************************************** ************/ public void setEdadMaxima(Integer edadMaxima) { this.edadMaxima = edadMaxima; } public void setEdadMinima(Integer edadMinima) { this.edadMinima = edadMinima; } /************************************************************** ********************* * Definir inicio y fin de linea

********************************************************************** ************/ public void setFinLinea(String finLinea) { this.finLinea = finLinea; } public void setInicioLinea(String inicioLinea) { this.inicioLinea = inicioLinea; } public void setDao(DAOClienteSpring dao) { this.dao = dao; } }

Persistencia en Spring (III): LOBs

Introducción
Algunas aplicaciones requieren el manejo de documentos. Por ejemplo una Web que gestiona ofertas de trabajo puede solicitar al candidato que cargue (upload) un archivo con su CV y le permite a la empresa ofertante descargarse (download) dicho archivo. Los documentos se almacenan en campos de tipo LOB (Large Object) en nuestra base de datos. Si el campo soporta gran cantidad de datos en modo caracter hablamos de CBLOB y si soporta datos binarios nos refereimos BLOB. Los LOBs son campos que exigen un tratamiento especial en JDBC y por tanto en Spring.

Insert
Spring exige • • Un LobHandler (para la versión 9.0 de Oracle hay un OracleLobHandler) En execute() se usa una clase setter que hereda de AbstractLobCreatingPreparedStatementCallback, especial para manejar LOBs con PreparedStatement

En nuestro DAO tendremos:

import import import import import ...

com.persistencia.query.ImagenSetter; org.springframework.jdbc.support.lob.LobHandler; org.springframework.jdbc.support.lob.DefaultLobHandler; java.io.IOException; java.io.InputStream;

public void insertarImagen(Integer id, final InputStream in) throws IOException { LobHandler lobHandler = new DefaultLobHandler(); getJdbcTemplate().execute( "INSERT INTO cliente (id, imagen) VALUES (?, ?)", new ImagenSetter(lobHandler, id, in) ); } ... Nuestra clase ImagenSetter hereda de AbstractLobCreatingPreparedStatementCallback:

import org.springframework.jdbc.core.support.AbstractLobCreatingPreparedState mentCallback; import org.springframework.jdbc.support.lob.LobHandler; import org.springframework.jdbc.support.lob.LobCreator; import java.io.IOException; import org.springframework.dao.DataAccessException; import java.io.InputStream; import java.sql.PreparedStatement; import java.sql.SQLException; public class ImagenSetter extends AbstractLobCreatingPreparedStatementCallback { int tamanioImagen; Integer id; InputStream in; public ImagenSetter(LobHandler lobHandler, final Integer id, final InputStream in) throws IOException { super(lobHandler); tamanioImagen = in.available(); this.in = in; this.id = id; } protected void setValues(PreparedStatement ps, LobCreator lobCreator) throws SQLException, DataAccessException { ps.setInt(1, id); lobCreator.setBlobAsBinaryStream(ps, 2, in, tamanioImagen); } } El método setValues() ya lo hemos visto como la solución Spring para trabajar con PreparedStatement. Es un método CallBack, es decir, será llamado por Spring, no por nuestra aplicación, cuando sea necesario inyectar los parámetros en la sentencia INSERT: • Usamos el típico setInt() de JDBC para el primer parámetro que inyectaremos a la sentencia INSERT (el id del cliente en nuestro ejemplo).

Para inyectar el segundo parámetro usamos un objeto del tipo LobCreator, que nos da Spring como una utilidad para el manejo de LOBs. Este objeto tiene el método

setBlobAsBinaryStream para que un stream de entrada se vuelque sobre un campo BLOB. Otros métodos de LobCreator: setClobAsString(), setBlobAsBytes(), etc. Argumentos de setBlobAsBinaryStream: • • • • El PreparedStament que nos pasa Spring como argumento de setValue(). El número de parámetro en la sentencia INSERT. En nuestro ejemplo es el segundo (con setInt hemos inyectado el primer parámetro). El InputStream donde se encuentran los datos (el archivo que nos da el usuario). El tamaño de los datos que vamos a insertar.

Select
public void getImagen(Integer id, final OutputStream out) { getJdbcTemplate().query("SELECT image FROM member_image WHERE id = ?", new AbstractLobStreamingResultSetExtractor() { protected void streamData(ResultSet rs) throws SQLException, IOException { FileCopyUtils.copy(lobHandler.getBlobAsBinaryStream(rs, 1), out); } } ); }

MVC de Spring (I) Ramiro Lago Bagüés (Enero 2008)

Introducción
Para aplicar el patrón MVC ya contamos con: • • Una capa de vista, formada de jsp, html, css, etiquetas personalizadas, etc. Una capa de modelo, que cuenta con las subcapas de servicios, persistencia (daos, etc.) y dominio (beans). Se forma mediante clases e interfaces Java.

Pero necesitamos una capa de control, que se compone de: • DispatcherServlet: es el controlador frontal, que recibe y gestiona todas las peticiones (request). Resulta oculto al programador y es instanciado por Spring.

Interface HandlerMapping: analiza cada petición y determina el controlador que la gestiona. Podemos contar con varios manejadores, en función de las diversas estrategias de "mapeo" (basado en cookies, variables de sesión, etc.). En la mayor parte de los casos nos sirve el manejador por defecto de Spring: BeanNameUrlHandleMapping.

Controladores: manejan las peticiones de cada página. Cada controlador recibe las peticiones de su página correspondiente, delega en el dominio y recoge los resultados. Lo que hace es devolver un modelo a la vista que ha seleccionado (por medio del controlador frontal).

Lo que devuelve cada controlador es un objeto del tipo ModelAndView. Este objeto se compone de: • • Una referencia a la vista destino El modelo: un conjunto de objetos se se utilizan para componer (render) la vista destino. Por ejemplo, un bean Cliente o una lista de beans (clientes) que se ha obtenido de un DAO.

Configuración: web.xml
Empezaremos con el clásico web.xml. En el que se empieza definiendo el Listener que ante el evento contextInitialized cargará el contexto de aplicación:

&ltlistener> &ltlistenerclass>org.springframework.web.context.ContextLoaderListener</listenerclass> </listener> La localización de los archivos de definición de contexto de aplicación se puede hacer por medio del parámetro contextConfigLocation. Si no se indica nada, Spring buscará un archivo con nombre applicationContext.xml en WEB-INF.

&ltcontext-param> &ltparam-name>contextConfigLocation</param-name> &ltparam-value&gtWEB-INF/xyz.xml, WEBINF/abc.xml</param-value> </context-param> Lo siguiente es señalar el servlet que actuará como controlador frontal:

&ltservlet> &ltservlet-name&gtspring21</servlet-name> &ltservletclass&gtorg.springframework.web.servlet.DispatcherServlet</servletclass> &ltload-on-startup&gt1</load-on-startup> </servlet> &ltservlet-mapping> &ltservlet-name&gtspring21</servlet-name> &lturl-pattern>*.do</url-pattern> </servlet-mapping> Dos aspectos importantes de la configuración del DispatcherServlet:

1. El nombre que se da al servlet-mapping no es casual. Spring buscará el archivo
spring21-servlet.xml, que sirve para configurar el resto de controladores, viewResolvers, urlMappings, etc.

2. El url-pattern indica los tipos de peticiones que aceptará, en nuestro ejemplo con las extensiones .do.

Configuración: spring-servlet.xml
A continuación interesa conocer la configuración básica de los servlets, en nuestro ejemplo utilizamos spring21-servlet.xml en WEB-INF, ya que el servlet-name de web.xml es spring21.

<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" "http://www.springframework.org/dtd/spring-beans.dtd"> <!-- Definición de contexto de aplicación para Controlador frontal y resto de controladores --> &ltbeans> <!-- Controlador de index (consulta, insert, etc) --> &ltbean id="indexController" class="com.controlador.IndexController"> &ltproperty name="servicioCliente" ref="servicioCliente" /> </bean> <!-- Controlador de index (borrar) -->

&ltbean id="borrarController" class="com.controlador.BorrarController"> &ltproperty name="servicioCliente" ref="servicioCliente" /> </bean> <!-- Indico que las vistas se toman de /WEB-INF/jsp/ y que tendrán extensión jsp --> &ltbean id="viewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolv er"> &ltproperty name="viewClass" value="org.springframework.web.servlet.view.JstlView" /> &ltproperty name="prefix" value="/WEB-INF/jsp/"/> &ltproperty name="suffix" value=".jsp"/> </bean> <!-- Las llamadas a .do se dirigen a su controlador correspondiente --> &ltbean id="urlMapping" class="org.springframework.web.servlet.handler.SimpleUrlHandlerMapping "> &ltproperty name="mappings"> &ltvalue> /inicio.do=indexController /borrar.do=borrarController </value> </property> </bean> </beans> En nuestro ejemplo tenemos dos controladores. Uno que hace las inserciones, actualizaciones (update) y consultas; otro que hace el borrado. Lo veremos más adelante. Normalmente un formario está asociado a un controlador, señalando la asociación en el action del formulario. En nuestro ejemplo cada controlador tiene como atributo un bean servicioCliente, que es definido en el applicationContext.xml. El viewResolver indica donde están las vistas (normalmente JSPs) que son invocadas por el controlador frontal. En el ejemplo las vistas se toman de /WEB-INF/jsp y tienen la extensión jsp. El bean urlMapping indica el mapeo de peticiones y controladores. En nuestro ejemplo cada petición .do tiene su correspondiente bean controlador definido más arriba. Observar que esto debe ser coherente con los url-mapping del servlet frontal en el web.xml. Podriamos evidentemente usar otras extensiones, como *.html o *.form, o indicar un directorio (app/*.do) o varios (*/*.do).

Configuración: applicationContext.xml
Para terminar con esto de la configuración es necesario tener en cuenta el tradicional application context de Spring. En nuestro ejemplo se llama applicationContext.xml y está en WEB-INF; esto hace que (recordando lo que hemos dicho antes) no sea necesario utilizar contextConfigLocation en el web.xml:

<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE beans SYSTEM "spring-beans.dtd"> <!-- CUANDO HAYA CONEXION A INTERNET: --> <!-&ltbeans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:p="http://www.springframework.org/schema/p" xmlns:aop="http://www.springframework.org/schema/aop" xmlns:context="http://www.springframework.org/schema/context" xmlns:jee="http://www.springframework.org/schema/jee" xmlns:tx="http://www.springframework.org/schema/tx" xsi:schemaLocation=" http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.5.xsd http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-2.5.xsd http://www.springframework.org/schema/jee http://www.springframework.org/schema/jee/spring-jee-2.5.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-2.5.xsd"> --> &ltbeans> <!-- Las propiedades del dataSource tienen como valor properties --> &ltbean id="dataSource" class="org.apache.tomcat.dbcp.dbcp.BasicDataSource" destroymethod="close"> &ltproperty name="driverClassName" value="$ {jdbc.driverClassName}"/> &ltproperty name="url" value="${jdbc.url}"/> &ltproperty name="username" value="${jdbc.username}"/> &ltproperty name="password" value="${jdbc.password}"/> </bean> <!-- El DAO que tiene como atributo el dataSource --> &ltbean id="DAOCliente" class="com.persistencia.DAOClienteSpring"> &ltproperty name="dataSource" ref="dataSource"/> </bean> <!-- El Servicio de Cliente tiene como atributo el DAO --> &ltbean id="servicioCliente" class="com.servicio.ServicioCliente"> &ltproperty name="dao" ref="DAOCliente"/> </bean> <!-- Las propiedades para el dataSource --> &ltbean id="propertyConfigurer" class="org.springframework.beans.factory.config.PropertyPlaceholderCon figurer"> &ltproperty name="locations"> &ltlist> &ltvalue&gtWEBINF/configuracion.properties</value>

</list> </property> </bean> </beans> Algunas cosas que merece la pena destacar:

Hemos puesto en comentario la referencia al XML Schema, por si falla la conexión a Internet. Ya que si no hay conexión nos devolverá un mensaje de error que indica que no encuentra sentido a la etiqueta beans. Cuando hay estos fallos lo mejor es usar el dtd spring-beans.dtd y ponerlo en el mismo directorio donde está el appplicationContext.xml. ¿De dónde sacarlo? Lo tenemos en la descarga (download) de Spring.

La organización de los beans en spring21-servlet.xml y applicationContext.xml refleja la arquitectura de nuestro proyecto.

o

En spring21-servlet.xml los controladores tenían como atributo el bean servicioCliente. Este bean es un intermediario entre el controlador y los DAOs. El controlador llama al servicio y este delega en el DAO.

o

En applicationContext.xml se puede ver que el servicioCliente tiene como atributo el DAO.

o

El DAO tiene como atributo el dataSource.

o

El dataSource tiene como atributos datos que son definidos por un archivo properties. Por ejemplo, jdbc.url se refiere a jdbc:mysql://localhost:3306/proactiv_prueba?autoReconnect=true. En el archivo configuracon.properties del directorio WEB-INF se encuentra la siguiente definición: jdbc.driverClassName=com.mysql.jdbc.Driver jdbc.url=jdbc:mysql://localhost:3306/proactiv_prueba? autoReconnect=true jdbc.username=usuario jdbc.password=password

o o o o o

En resumen:

• • •

Los controladores (en spring21-servlet.xml) hacen referencia al servicio servicioCliente. En applicationContext.xml se puede ver que el servicioCliente tiene como atributo el DAO El DAO tiene como atributo el dataSource, que se configura por un archivo properties.

Secuencia de configuración inicial
Veamos la secuencia de acciones que se desencdenan cuando el servidor de aplicaciones inicializa la aplicación:

1. El servidor de aplicaciones dispara el evento ContextInitilized.

2. Este evento invoca al ContextLoaderListener (señalado en web.xml) y crea el
contexto de aplicación (applicationContext.xml).

3. Inicialización del servlet frontal (DispatcherServlet) y creación de su contexto
(spring21-servlet.xml). Los dos contextos se unen.

4. El controlador central busca e inicializa componentes como ViewResolver o
HandlerMapping. Si no los encuentra, inicializa versiones por defecto. Con esto la web ya está preparada para recibir peticiones.

MVC de Spring (II)

Introducción al ejemplo
Para aprender de forma práctica utilizaremos un ejemplo de gestión de la tabla de clientes.

La arquitectura sigue el modelo MVC: • Vista. Se compone de JSPs en WEB-INF/jsp.

Contrladores. Los controladores son clases Java que se encuentran en WEBINF/src/com/contrlador. No incluye el controlador frontal (DispatcherServlet) que es una clase Spring. Hay dos controladores (IndexController y BorrarController), cada uno de los cuales es invocado por el action de un formulario.

Modelo. El modelo se compone de subcapas: o Servicio. WEB-INF/src/com/servicio/ServicioCliente, que es el intermediario entre los controladores y el DAO.

o

Persistencia. Compuesta por el DAO WEBINF/src/com/persistencia/DAOClienteSpring y por un mapper: WEBINF/src/com/persistencia/mappers/ClienteMapper

o

Dominio. La representación del cliente está en WEBINF/src/com/dominio/Cliente.

La imagen de la estructura en Eclipse es:

Archivos de configuración
Ya los hemos comentado en el capítulo dedicado a la configuración, aquí simplemente los mostramos y haremos alguna breve nota. web.xml:

<?xml version="1.0" encoding="ISO-8859-1"?> &ltweb-app xmlns="http://java.sun.com/xml/ns/j2ee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd" version="2.4"> &ltdisplay-name&gtEjemplo de MVC en Spring</display-name> &ltlistener>

&ltlistenerclass&gtorg.springframework.web.context.ContextLoaderListener</listene r-class> </listener> &ltservlet> &ltservlet-name>spring21</servlet-name> &ltservletclass&gtorg.springframework.web.servlet.DispatcherServlet</servletclass> &ltload-on-startup&gt1</load-on-startup> </servlet> &ltservlet-mapping> &ltservlet-name>spring21</servlet-name> &lturl-pattern>*.do</url-pattern> </servlet-mapping> </web-app> Puesto que el servlet-name es spring21, el archivo de configuración de los controladores será spring21-servlet.xml.

<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" "http://www.springframework.org/dtd/spring-beans.dtd"> <!-- Definición de contexto de aplicación para Controlador frontal y resto de controladores --> &ltbeans> <!-- Controlador de index (consulta, insert, etc) --> &ltbean id="indexController" class="com.controlador.IndexController"> &ltproperty name="servicioCliente" ref="servicioCliente" /> </bean> <!-- Controlador de index (borrar) --> &ltbean id="borrarController" class="com.controlador.BorrarController"> &ltproperty name="servicioCliente" ref="servicioCliente" /> </bean> <!-- Indico que las vistas se toman de /WEB-INF/jsp/ y que tendrán extensión jsp --> &ltbean id="viewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolv er"> &ltproperty name="viewClass" value="org.springframework.web.servlet.view.JstlView" /> &ltproperty name="prefix" value="/WEB-INF/jsp/"/> &ltproperty name="suffix" value=".jsp"/> </bean> <!-- Las llamadas a .do se dirigen a su controlador correspondiente -->

&ltbean id="urlMapping" class="org.springframework.web.servlet.handler.SimpleUrlHandlerMapping "> &ltproperty name="mappings"> &ltvalue> /inicio.do=indexController /borrar.do=borrarController </value> </property> </bean> </beans> En nuestro ejemplo tenemos dos controladores. Uno que hace las inserciones, actualizaciones (update) y consultas; otro que hace el borrado. Lo veremos más adelante. Normalmente un formario está asociado a un controlador, señalando la asociación en el action del formulario. En nuestro ejemplo cada controlador tiene como atributo un bean servicioCliente, que es definido en el applicationContext.xml. Para terminar con esto de la configuración es necesario tener en cuenta el tradicional application context de Spring. En nuestro ejemplo se llama applicationContext.xml y está en WEB-INF; esto hace que (recordando lo que hemos dicho antes) no sea necesario utilizar contextConfigLocation en el web.xml:

<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE beans SYSTEM "spring-beans.dtd"> &ltbeans> <!-- Las propiedades del dataSource tienen como valor properties --> &ltbean id="dataSource" class="org.apache.tomcat.dbcp.dbcp.BasicDataSource" destroymethod="close"> &ltproperty name="driverClassName" value="$ {jdbc.driverClassName}"/> &ltproperty name="url" value="${jdbc.url}"/> &ltproperty name="username" value="${jdbc.username}"/> &ltproperty name="password" value="${jdbc.password}"/> </bean> <!-- El DAO que tiene como atributo el dataSource --> &ltbean id="DAOCliente" class="com.persistencia.DAOClienteSpring"> &ltproperty name="dataSource" ref="dataSource"/> </bean> <!-- El Servicio de Cliente tiene como atributo el DAO --> &ltbean id="servicioCliente" class="com.servicio.ServicioCliente"> &ltproperty name="dao" ref="DAOCliente"/> </bean> <!-- Las propiedades para el dataSource --> &ltbean id="propertyConfigurer" class="org.springframework.beans.factory.config.PropertyPlaceholderCon figurer"> &ltproperty name="locations"> &ltlist>

&ltvalue&gtWEBINF/configuracion.properties</value> </list> </property> </bean> </beans> Algunas cosas que merece la pena destacar:

La organización de los beans en spring21-servlet.xml y applicationContext.xml refleja la arquitectura de nuestro proyecto.

o

En spring21-servlet.xml los controladores tenían como atributo el bean servicioCliente. Este bean es un intermediario entre el controlador y los DAOs. El controlador llama al servicio y este delega en el DAO.

o

En applicationContext.xml se puede ver que el servicioCliente tiene como atributo el DAO.

o

El DAO tiene como atributo el dataSource.

o

El dataSource tiene como atributos datos que son definidos por un archivo properties. Por ejemplo, jdbc.url se refiere a jdbc:mysql://localhost:3306/proactiv_prueba?autoReconnect=true. En el archivo configuracon.properties del directorio WEB-INF se encuentra la siguiente definición: jdbc.driverClassName=com.mysql.jdbc.Driver jdbc.url=jdbc:mysql://localhost:3306/proactiv_prueba? autoReconnect=true jdbc.username=usuario jdbc.password=password

o o o o o

Los controladores
Empezaremos con el controlador principal IndexController que implementa el interface de Spring Conroller. Hay un atributo servicioCliente, que es inicializado por Spring cuando carga spring21-servlet.xml, este servicio se encarga de invocar al DAO para obtener los datos de la base de datos. El segundo atributo es el logger, una referencia a log4j (el archivo de configuración está en el directorio log). El tercer atributo es el nombre de la vista: index.

package com.controlador; import org.springframework.web.servlet.mvc.Controller; import org.springframework.web.servlet.ModelAndView; import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; import java.util.List; import org.apache.log4j.Logger; import org.apache.log4j.PropertyConfigurator; import com.servicio.ServicioCliente; import com.dominio.Cliente; / ********************************************************************** ************* * Controlador para index.jsp (listado, consulta, inserción, etc) ********************************************************************* *************/ public class IndexController implements Controller { private ServicioCliente servicioCliente; private Logger logger = Logger.getLogger( this.getClass() ); // En spring-servlet.xml se indica que las vistas se toman de /WEB-INF/jsp/ // y tienen extensión jsp. Por tanto aqui no se pone "WEBINF/jsp/index.jsp" private String nombreVista = "index"; public IndexController() throws Exception { PropertyConfigurator.configure( "c:/DOC/Java_Eclipse/spring21_MVC/log/ log4j.properties"); logger.info("spring21_MVC. Iniciado loger de " + this.getClass().getName()); } public void setServicioCliente(ServicioCliente servicioCliente) { } /************************************************************** ********************* * Conducta básica: devuelve listado de cliente * Conducta opcional: en función de param de request (consulta, etc) ********************************************************************** ************/ public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { Cliente cliente = null; String mensaje = null; ModelAndView mav = new ModelAndView( nombreVista ); this.servicioCliente = servicioCliente;

//// Añade lista de clientes al modelo List clientes = servicioCliente.getTodosClientes(); mav.addObject( clientes ); String paramOperacion = request.getParameter("operacion"); logger.info("spring21_MVC. Devolviendo la vista " + nombreVista + ". Operación: " + paramOperacion);

if ( paramOperacion != null ) { //// Si es una consulta ... if ( paramOperacion.equals("Consultar") ) { try { cliente = servicioCliente.getCliente(request.getParameter("codigo")); } catch (org.springframework.dao.EmptyResultDataAccessException e) { mensaje = "Cliente no encontrado"; } } //// Si es una inserción ... if ( paramOperacion.equals("Insertar") ) { try { cliente = servicioCliente.getClienteFromRequest( request ); int resultado = servicioCliente.insertarCliente( cliente); if (resultado == 0) mensaje = "Cliente no insertado"; } catch (Exception e) { mensaje = "Cliente no insertado"; } } //// Si es una actualización ... if ( paramOperacion.equals("Actualizar") ) { try { cliente = servicioCliente.getClienteFromRequest( request ); int resultado = servicioCliente.actualizarCliente( cliente); if (resultado == 0) mensaje = "Cliente no actualizado"; } catch (Exception e) { mensaje = "Cliente no actualizado"; } } }

mav.addObject( "unCliente", cliente ); mav.addObject( "mensaje", mensaje ); } } Conviene entender la secuencia de acciones para resolver una petición: return mav;

1. El usuario ha realizado la petición
http://localhost:8060/http://localhost:8060/spring21_MVC/inicio.do

2. El controlador frontal (DispatcherServlet) recoge esta petición, ya que en el urlmapping tenemos un filtro para todas las peticiones que tengan el patrón *.do

3. Se asocia el controlador correspondiente (IndexController) a esta petición, ya que
spring21-servlet.xml indica en el urlMappings que una petición inicio.do está asociada a dicho controlador: /inicio.do=indexController

4. Spring invoca el método ModelAndView handleRequest(). En este método en función
del parámetro "Operacion" de la request se realiza una cosa u otra: inserción, consulta, etc. Lo que interesa es que este método debe devolver al controlador frontal un ModelAndView. Lo que devuelve cada controlador es un objeto del tipo ModelAndView. Este objeto se compone de: • • • Una referencia a la vista destino (WEB-INF/jsp/index.jsp). Por medio de: ModelAndView mav = new ModelAndView( nombreVista );

El modelo: un conjunto de objetos se utilizan para componer (render) la vista destino. Por ejemplo una lista de beans (Clientes) que se ha obtenido de un DAO. En nuesro caso introducimos en ModelAndView tres objetos:

o o o o o

Una lista de todos los clientes, llamando al servicio: //// Añade lista de clientes al modelo List clientes = servicioCliente.getTodosClientes(); mav.addObject( clientes );

o o

Además hay que introducir en el modelo el cliente, en caso de consulta, actualización o inserción. También se usa el servicio:

o o o

cliente = servicioCliente.getCliente(request.getParameter("codigo")) ; ... mav.addObject( "unCliente", cliente );

En caso de que no haya que hacer ninguna operación sobre cliente, entonces el cliente que se añade al modelo es null.

o o o

Se introduce un objeto String mensaje: mav.addObject( "mensaje", mensaje );

mensaje puede ser null Código fuente de BorrarController.

index.jsp y JSTL
Lo interesante es observar como recoge la página index.jsp todos estos datos (cliente, lista de clientes y mensaje) que devuelve IndexController. • Cuando en el controlador usamos la versión addObjet( String, Object) que permite especificar un nombre de objeto en el primer argumento, por ejemplo mav.addObject( "mensaje", mensaje ), entonces la solución es sencilla: en la JSP recogemos el objeto mediante la expresión ${mensaje}.

Cuando en el controlador usamos la versión addObject( Object ) el nombre que maneja la JSP es el nombre del tipo (clase) que se ha añadido, pero poniendo la primera letra en minúscula y añadiendo "List" si es un array o collection. Dos ejemplos: o Si añadimos un objeto del tipo Persona, la JSP lo nombrará como ${persona}. o Si añadimos una lista de objetos del tipo Persona, la JSP lo nombrará como $ {personaList}. Como en nuestro ejemplo es del tipo Cliente, el nombre en la JSP es ${clienteList}. o Si añadimos un array de objetos del tipo Persona, la JSP lo nombrará como $ {personaList}.

Manejando JSTL (ver al principio de la JSP el taglib prefix="c" podemos realizar condicionales e iteraciones en estilo XML, sin necesidad de recurrir a Java o Javascript. Por ejemplo:

<%@ page language="java" contentType="text/html; charset=ISO-8859-1" pageEncoding="ISO-8859-1"%> <%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">

.... &ltc:if test="${empty clienteList}"> &ltp&gtNo hay clientes</p> </c:if> .... &ltc:forEach items="${clienteList}" var="cliente"> &lttr> &lttd>${cliente.codigo}</td> &lttd>${cliente.nombre}</td> &lttd>${cliente.edad}</td> </tr> </c:forEach> Para el uso de JSTL necesitamos las librerias jstl.jar y standard.jar. Dentro de la iteración se realizan llamadas a métodos getXXX() de la clase Cliente para el código, etc. El código completo de la JSP:

<%@ page language="java" contentType="text/html; charset=ISO-8859-1" pageEncoding="ISO-8859-1"%> <%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> <%@ page errorPage="error.jsp"%> &lthtml xmlns="http://www.w3.org/1999/xhtml"> <%@ include file="cabeceraDePagina.jsp" %> &ltCENTER>&ltH4>&ltb&gtGestión de clientes</H4></b></CENTER> <!-- INICIO DE TABLA PRINCIPAL --> &ltTABLE BORDER=1 align='center' width='900'> &ltTR>&ltTD> &ltc:if test="${empty clienteList}"> &ltp&gtNo hay clientes</p> </c:if> <!-- TABLA SECUNDARIA --> &ltTABLE BORDER=1 align='center' width='300'> &ltthead> &lttr> &ltth&gtCodigo</th> &ltth&gtNombre</th> &ltth&gtEdad</th> </tr> </thead> &ltc:forEach items="${clienteList}" var="cliente"> &lttr> &lttd>${cliente.codigo}</td> &lttd>${cliente.nombre}</td> &lttd>${cliente.edad}</td> </tr> </c:forEach>

</TABLE> <!-- CAMBIA DE COLUMNA EN LA TABLA PRINCIPAL --> </TD> &ltTD> <%@ include file="formulario.jsp" %> </TD></TR></TABLE> </font></body></html> Interesa destacar que: • • Gracias al uso de controladores y JST no hay una sola línea de Java en la JSP. Por la misma razón no ha sido necesario recurrir a Java Beans (estilo usabean).

formulario.jsp
Puede observar que index.jsp incluye a formulario.jsp mediante:

<%@ include file="formulario.jsp" %> El código de formulario.jsp contiene dos fromularios, uno es el "grande", donde se trabaja la inserción, actualización y consulta de datos:

&ltform action="inicio.do" method="post" > .... Otro es el "pequeño", orientada al borrado:

&ltform action="borrar.do" method="post" > &ltblockquote> &ltP&gtCódigo: &ltINPUT TYPE="text" NAME="codigo" size="20"> &ltinput type="submit" name="operacion" value="Borrar"> </p> </blockquote> </form> La acción borrar.do de este formulario invoca al controlador BorrarController. La asociación de borrar.do con BorrarController se puede ver en el mapping de spring21-servlet.xml. A continuación el código de formulario.jsp. El formulario empieza haciendo una referencia al objeto "mensaje" que ha sido devuelto por el controlador. Además hace referencia al objeto "unCliente" devuelto por el controlador:

<%@ page language="java" contentType="text/html; charset=ISO-8859-1" pageEncoding="ISO-8859-1"%>

<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"> <%@ page errorPage="error.jsp"%> &ltp>${mensaje}</p> &ltform action="inicio.do" method="post" > &ltblockquote> &ltP&gtCódigo: &ltINPUT TYPE="text" NAME="codigo" size="20" value="$ {unCliente.codigo}"></p> &ltP&gtNombre: &ltINPUT TYPE="text" NAME="nombre" size="30" value="$ {unCliente.nombre}"></P> &ltP&gtApellidos: &ltINPUT TYPE="text" NAME="ape1" size="30" value="$ {unCliente.ape1}"> &ltINPUT TYPE="text" NAME="ape2" size="30" value="$ {unCliente.ape2}"></P> &ltP&gtEdad: &ltINPUT TYPE="text" NAME="edad" size="10" value="$ {unCliente.edad}"></P> &ltblockquote> &ltp> &ltinput type="submit" name="operacion" value="Insertar"> &ltinput type="submit" name="operacion" value="Actualizar"> &ltinput type="submit" name="operacion" value="Consultar"> </p> &ltp> &ltinput type="submit" name="operacion" value="Refrescar"> </p> </blockquote> </blockquote> </form> &ltbr>&ltBR> &ltHR> &ltform action="borrar.do" method="post" > &ltblockquote> &ltP&gtCódigo: &ltINPUT TYPE="text" NAME="codigo" size="20"> &ltinput type="submit" name="operacion" value="Borrar"> </p> </blockquote> </form> El action del primer form llama de nuevo al controlador IndexController que devuelve la vista index.jsp. El action del segundo llama a BorrarController que devuelve la vista index.jsp. El controlador perfectamente podría devolver otra vista (es lo más habitual), pero en nuestro ejemplo volvemos de nuevo a index.jsp.

Otros JSPs

cabeceraDePagina.jsp

&lthead> &lttitle&gtEjemplo de Spring MVC</title> &ltmeta name="author" content="Ramiro Lago"> &ltmeta name="organization" content="Ramiro Lago"> &ltmeta name="locality" content="Spain"> &ltmeta name="lang" content="es"> &ltmeta name="description" content="JSP"> &ltmeta name="keywords" content="Java, Spring, MVC, JSTL"> &ltmeta http-equiv="Content-Type" content="text/html; charset=iso-8859-1"> &ltmeta http-equiv="Pragma" content="no-cache"> &ltmeta http-equiv="Expires" content="-1"> </head> &ltbody bgcolor="#FFFF9D">&ltFONT color="#000080" FACE="Arial,Helvetica,Times" SIZE=2> &ltCENTER>&ltH3&gtEjemplo de Spring MVC</H3></CENTER> &ltHR> error.jsp

<%@ page language="java" contentType="text/html; charset=ISO-8859-1" pageEncoding="ISO-8859-1"%> <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"> <%@ page isErrorPage="true" %> &lthtml> <%@ include file="cabeceraDePagina.jsp" %> &lth3&gtSe ha producido un error</h3> &ltp&gtError: <%= exception%></p> <%@ include file="pieDePagina.jsp" %> </html>

La capa de servicio
ServicioCliente es descrito en applicationContext.xml, conteniendo al atributo dataSource. Los controladores tienen este servicio como atributo (ver spring21-servlet.xml). En resumen: • • • El servicio es instanciado por Spring El servicio es una atributo de cada controlador El servicio es un intermediario entre el controlador y el DAO; se puede decir que el servicio delega en el DAO el acceso a datos.

El código del ServicioCliente:

package com.servicio; import com.persistencia.*;

import import import import

com.dominio.Cliente; java.util.List; org.springframework.dao.EmptyResultDataAccessException; javax.servlet.http.HttpServletRequest;

/ ********************************************************************** ************** * Clase de servicio. Intermediaria entre vista y DAO ********************************************************************* ****************/ public class ServicioCliente { private DAOClienteSpring dao; public DAOClienteSpring getDao() { return dao; } public void setDao(DAOClienteSpring dao) { this.dao = dao; } // @Transactional(readOnly=true) public List getTodosClientes() { return dao.selectAll(); } // @Transactional(readOnly=true) public Cliente getCliente( String codigo ) throws EmptyResultDataAccessException { return dao.selectCliente(codigo); } public int insertarCliente( Cliente cliente ) { return dao.insertarCliente(cliente); } public int borrarCliente( String codigo ) { Cliente cliente = new Cliente(); cliente.setCodigo(codigo); return dao.borrarCliente(cliente); } public int actualizarCliente( Cliente cliente ) { return dao.actualizarCliente(cliente); } /***************************************************************** ******************* * Mapea una request sobre un objeto. Devuelve el objeto ***************************************************************** ********************/ public Cliente getClienteFromRequest( HttpServletRequest request ) { Cliente cliente = new Cliente(); cliente.setCodigo(request.getParameter("codigo")); cliente.setNombre(request.getParameter("nombre")); cliente.setApe1(request.getParameter("ape1")); cliente.setApe2(request.getParameter("ape2"));

cliente.setEdad( new Integer( Integer.parseInt(request.getParameter("edad") ) ) ); return cliente; } }

El DAO se puede ver en el ejemplo de persistencia Spring.

Master your semester with Scribd & The New York Times

Special offer for students: Only $4.99/month.

Master your semester with Scribd & The New York Times

Cancel anytime.