<%@ page language="java" contentType="text/html; charset=ISO-8859-1" pageEncoding="ISO-8859-1"%> <%@ include file="/WEB-INF/jsp/include.jsp" %> Internacionalización con Spring

MVC de Spring (II)

Ramiro Lago Bagüés (Enero 2008)


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:

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"?>
<web-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">
	<display-name>Ejemplo de MVC en Spring</display-name>

	<listener>
		<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
	</listener>

	<servlet>
		<servlet-name>spring21</servlet-name>
		<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
		<load-on-startup>1</load-on-startup>
	</servlet>

	<servlet-mapping>
		<servlet-name>spring21</servlet-name>
		<url-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 -->
<beans>
	<!-- Controlador de index (consulta, insert, etc) -->
	<bean id="indexController" class="com.controlador.IndexController">
		<property name="servicioCliente" ref="servicioCliente" />
	</bean>
	<!-- Controlador de index (borrar) -->
	<bean id="borrarController" class="com.controlador.BorrarController">
		<property name="servicioCliente" ref="servicioCliente" />
	</bean>

	<!--  Indico que las vistas se toman de /WEB-INF/jsp/ y que tendrán extensión jsp  -->
	<bean id="viewResolver"
			class="org.springframework.web.servlet.view.InternalResourceViewResolver">
		<property name="viewClass"
			value="org.springframework.web.servlet.view.JstlView" />
		<property name="prefix" value="/WEB-INF/jsp/"/>
		<property name="suffix" value=".jsp"/>
	</bean>

	<!-- Las llamadas a .do se dirigen a su controlador correspondiente -->
    <bean id="urlMapping" class="org.springframework.web.servlet.handler.SimpleUrlHandlerMapping">
    	<property name="mappings">
			<value>
            	/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">

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

	<!-- El Servicio de Cliente tiene como atributo el DAO -->
	<bean id="servicioCliente" class="com.servicio.ServicioCliente">
		<property name="dao" ref="DAOCliente"/>
	</bean>

	<!-- Las propiedades para el dataSource -->
	<bean id="propertyConfigurer"
		class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
		<property name="locations">
			<list>
		        <value>WEB-INF/configuracion.properties</value>
			</list>
		</property>
	</bean>
</beans>

Algunas cosas que merece la pena destacar:


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:

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.

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


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

	....
	
	<c:if test="${empty clienteList}">
		<p>No hay clientes</p>
	</c:if>

	....
	
	<c:forEach items="${clienteList}" var="cliente">
		<tr>
			<td>${cliente.codigo}</td>
			<td>${cliente.nombre}</td>
			<td>${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"%>

<html xmlns="http://www.w3.org/1999/xhtml">

<%@ include file="cabeceraDePagina.jsp" %>

<CENTER><H4><b>Gestión de clientes</H4></b></CENTER>

<!-- INICIO DE TABLA PRINCIPAL -->
<TABLE BORDER=1 align='center' width='900'>
<TR><TD>

	<c:if test="${empty clienteList}">
		<p>No hay clientes</p>
	</c:if>

	<!-- TABLA SECUNDARIA -->
	<TABLE BORDER=1 align='center' width='300'>
		<thead>
			<tr>
				<th>Codigo</th>
				<th>Nombre</th>
				<th>Edad</th>
			</tr>
		</thead>
		<c:forEach items="${clienteList}" var="cliente">
			<tr>
				<td>${cliente.codigo}</td>
				<td>${cliente.nombre}</td>
				<td>${cliente.edad}</td>
			</tr>
		</c:forEach>
	</TABLE>

<!-- CAMBIA DE COLUMNA EN LA TABLA PRINCIPAL -->
</TD>
<TD>

	<%@ include file="formulario.jsp" %>

</TD></TR></TABLE>

</font></body></html>

Interesa destacar que:


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:


	<form action="inicio.do" method="post" >
	....

Otro es el "pequeño", orientada al borrado:


	<form action="borrar.do" method="post" >
	<blockquote>
		<P>Código:     
		<INPUT TYPE="text" NAME="codigo" size="20">
		  <input 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"%>

<p>${mensaje}</p>

<form action="inicio.do" method="post" >
<blockquote>
	<P>Código:     
	<INPUT TYPE="text" NAME="codigo" size="20" value="${unCliente.codigo}"></p>
	<P>Nombre:    
	<INPUT TYPE="text" NAME="nombre" size="30" value="${unCliente.nombre}"></P>
	<P>Apellidos:  
	<INPUT TYPE="text" NAME="ape1" size="30" value="${unCliente.ape1}">
	<INPUT TYPE="text" NAME="ape2" size="30" value="${unCliente.ape2}"></P>
	<P>Edad:       
	<INPUT TYPE="text" NAME="edad" size="10" value="${unCliente.edad}"></P>
	<blockquote>
		<p>
			<input type="submit" name="operacion" value="Insertar">
			  <input type="submit" name="operacion" value="Actualizar">
			  <input type="submit" name="operacion" value="Consultar">
		</p>
		<p>
			<input type="submit" name="operacion" value="Refrescar">
		</p>	
	</blockquote>
</blockquote>
</form>

<br><BR>
<HR>

<form action="borrar.do" method="post" >
<blockquote>
	<P>Código:     
	<INPUT TYPE="text" NAME="codigo" size="20">
	  <input 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


<head>
	<title>Ejemplo de Spring MVC</title>
	<meta name="author" content="Ramiro Lago">
	<meta name="organization" content="Ramiro Lago">
	<meta name="locality" content="Spain">
	<meta name="lang" content="es">
	<meta name="description" content="JSP">
	<meta name="keywords" content="Java, Spring, MVC, JSTL">
	<meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1">
	<meta http-equiv="Pragma" content="no-cache">
	<meta http-equiv="Expires" content="-1">
</head>
<body bgcolor="#FFFF9D"><FONT color="#000080" FACE="Arial,Helvetica,Times" SIZE=2>
<CENTER><H3>Ejemplo de Spring MVC</H3></CENTER>
<HR>

error.jsp


<%@ page language="java" contentType="text/html; charset=ISO-8859-1"
    pageEncoding="ISO-8859-1"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">

<%@ page isErrorPage="true" %>

<html>
<%@ include file="cabeceraDePagina.jsp" %>

    <h3>Se ha producido un error</h3>
    <p>Error: <%= exception%></p>

<%@ include file="pieDePagina.jsp" %>
</html>

La capa de servicio

ServicioCliente es descrito en applicationContext.xml, conteniendo al atributo dataSource. Los controladores tienen este servicio como atributo (ver spring21-servlet.xml). En resumen:

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



Volver