Sie sind auf Seite 1von 62

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/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 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));
}

return str.toString();
}
}

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 );
}

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 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

&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( "");

try {
return invocacion.proceed(); // Llamada al
objeto destino (joint point)
}
finally {
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 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" destroy-
method="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):
o El dao está en com.persistencia. Se instancia en index.jsp
o 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

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" destroy-
method="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 java.sql.ResultSet;
import java.sql.SQLException;
import org.springframework.jdbc.core.simple.ParameterizedRowMapper;
import 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 com.persistencia.query.ImagenSetter;
import org.springframework.jdbc.support.lob.LobHandler;
import org.springframework.jdbc.support.lob.DefaultLobHandler;
import java.io.IOException;
import 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>
&ltlistener-
class>org.springframework.web.context.ContextLoaderListener</listener-
class>
</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, WEB-
INF/abc.xml</param-value>
</context-param>

Lo siguiente es señalar el servlet que actuará como controlador frontal:

&ltservlet>
&ltservlet-name&gtspring21</servlet-name>
&ltservlet-
class&gtorg.springframework.web.servlet.DispatcherServlet</servlet-
class>
&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" destroy-
method="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&gtWEB-
INF/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:
o
o jdbc.driverClassName=com.mysql.jdbc.Driver
o
jdbc.url=jdbc:mysql://localhost:3306/proactiv_prueba?
autoReconnect=true
o jdbc.username=usuario
o jdbc.password=password

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 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:

<?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>
&ltlistener-
class&gtorg.springframework.web.context.ContextLoaderListener</listene
r-class>
</listener>

&ltservlet>
&ltservlet-name>spring21</servlet-name>
&ltservlet-
class&gtorg.springframework.web.servlet.DispatcherServlet</servlet-
class>
&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" destroy-
method="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&gtWEB-
INF/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:
o
o jdbc.driverClassName=com.mysql.jdbc.Driver
o
jdbc.url=jdbc:mysql://localhost:3306/proactiv_prueba?
autoReconnect=true
o jdbc.username=usuario
o jdbc.password=password

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 "WEB-
INF/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)


{
this.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 );
//// 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 );

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:


o
o //// Añade lista de clientes al modelo
o List clientes =
servicioCliente.getTodosClientes();
o mav.addObject( clientes );

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 cliente =
servicioCliente.getCliente(request.getParameter("codigo"))
;
o ...
o 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 Se introduce un objeto String mensaje:


o
o 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 com.dominio.Cliente;
import java.util.List;
import org.springframework.dao.EmptyResultDataAccessException;
import 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.

Das könnte Ihnen auch gefallen