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:
This document is posted to help you gain knowledge. Please leave a comment to let me know what you think about it! Share it to your friends and learn new things together.
Transcript
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:
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:
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
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:
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/spring-framework-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 Factura tiene que hacer referencia a un cliente y una lista de productos:
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 );
/**************************************************************************************** * 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/application-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 --><beans>
<!-- Producto. La tercera propiedad es del tipo Properties y la manejamos con la etiqueta value --> <bean id="prod_01" class="org.ejemplo.Producto"> <property name="codigo" value="FZX" /> <property name="precio" value="37.95" /> <property name="caracteristicas"> <value> 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 --> <bean id="prod_02" class="org.ejemplo.Producto"> <constructor-arg value="AK" /> <constructor-arg value="600" /> <property name="caracteristicas"> <props> <prop key="capacidad">5</prop> <prop key="caballos de vapor">130</prop> <prop key="cilindrada">2200</prop> </props> </property> </bean>
PRODUCTO. Código: AK. Precio: 600.0capacidad: 5caballos de vapor: 130cilindrada: 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 WEB-INF/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
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: <property 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"));
//// 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 SpringLos argumentos "Arg1" y "Arg2" son requeridosWelcome to use the Spring Bundle
Podríamos conseguir el MessageSource de manera más directa:
MessageSource fuenteDeMensajes = new org.springframework.context.support.ClassPathXmlApplicationContext("org/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);
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.
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">
<beans>
<!-- El proxy recibe llamadas que deriva al"target object" GestorRecursos. El Advice es del tipo
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).
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
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 892Estamos 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: trueEstamos en org.ejemplo.GestorRecursos y libero Paquete x09 de Camión 892Estamos 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">
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">
System.out.println( "Ya se ha ejecutado el Joint Point");
}}
}
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 892Ya 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:
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):
o El dao está en com.persistencia. Se instancia en index.jspo Como auxiliar del dao está el mapper (com.persistencia.mappers) que asocia
atributos del objeto Cliente con columnas de la tablao 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 Springo 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
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):
/************************************************************************************ * Carga del archivo de contexto y del archivo de properties *************************************************************************************/public class GestorCarga {
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:
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 -->
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:
/************************************************************************* * DAO que emplea la clase JdbcDaoSupport de Spring *************************************************************************/public class DAOClienteSpring extends JdbcDaoSupport {
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).
/************************************************************************* * 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) {
/*************************************************************************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{
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" %>
<html>
<%@ include file="cabeceraDePagina.jsp" %>
<!-- El objeto gestorCarga realiza la carga del contexto (xml),propiedades y DAO -->
<p>Las operaciones de Insertar, Actualizar, Borrar y Consultar se refieren siempre a un <b>código de registro</b>, que es clave primaria.</p><p><b>Actualizar</b>: update del registro cuyo código corresponda con el de la página.</p><p><b>Consultar</b>: select del registro cuyo código corresponda con el de la página.</p><p><b>Refrescar</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..
/*********************************************************************************** * 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 DAOInteger edadMinima; // Para un where
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
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:
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.
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 --><beans>
<!-- Controlador de index (consulta, insert, etc) --><bean id="indexController"
<!-- Las llamadas a .do se dirigen a su controlador correspondiente --> <bean id="urlMapping" class="org.springframework.web.servlet.handler.SimpleUrlHandlerMapping"> <property name="mappings">
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: --><!--<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
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:
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 WEB-INF/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 WEB-INF/src/com/persistencia/DAOClienteSpring y por un mapper: WEB-INF/src/com/persistencia/mappers/ClienteMapper
o Dominio. La representación del cliente está en WEB-INF/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:
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">
<beans><!-- Las propiedades del dataSource tienen como valor
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:
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.
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 );
return mav;}
}
Conviene entender la secuencia de acciones para resolver una petición:
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 url-mapping 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 Una lista de todos los clientes, llamando al servicio: oo //// Añade lista de clientes al modeloo List clientes =
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 Se introduce un objeto String mensaje: oo 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">
....
<c:if test="${empty clienteList}"><p>No hay clientes</p>
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"%>
<html xmlns="http://www.w3.org/1999/xhtml">
<%@ include file="cabeceraDePagina.jsp" %>
<CENTER><H4><b>Gestión de clientes</H4></b></CENTER>
<!-- INICIO DE TABLA PRINCIPAL --><TABLE BORDER=1 align='center' width='900'><TR><TD>
<c:if test="${empty clienteList}"><p>No hay clientes</p>
</c:if>
<!-- TABLA SECUNDARIA --><TABLE BORDER=1 align='center' width='300'>
<!-- CAMBIA DE COLUMNA EN LA TABLA PRINCIPAL --></TD><TD>
<%@ 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:
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"%>
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
<head><title>Ejemplo de Spring MVC</title><meta name="author" content="Ramiro Lago"><meta name="organization" content="Ramiro Lago">
</head><body bgcolor="#FFFF9D"><FONT color="#000080" FACE="Arial,Helvetica,Times" SIZE=2><CENTER><H3>Ejemplo de Spring MVC</H3></CENTER><HR>
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" %>
<html><%@ include file="cabeceraDePagina.jsp" %>
<h3>Se ha producido un error</h3> <p>Error: <%= 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
/************************************************************************************ * 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(); }
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