jueves, 12 de diciembre de 2013

Spring MVC: Proyecto Real + MYSQL + GENERIC + JUNIT + HIBERNATE + MVC + AJAX + CRUD PARTE 1

Creo que una de las mejores formas de termina el estudio de MVC con Spring es presentar un escenario real, con este objeto esta guía abordara  Hitos Tecnológicos que de seguro podremos encontrar en cualquier desarrollo real.

Nuestro ejemplo abordara la administración de cuentas. Como podrás observar es una pantalla que nos posibilita realizar las operaciones CRUD de manera tradicional que realizaremos sobre toda base de datos.




La pantalla de administración podemos observar que cuenta con un listado de cuentas muy basico donde por cada registro contamos con un checkbox que al marcar los registros que pretendemos eliminar con el link "Eliminar Cuentas" realizamos la eliminación en la base de datos de los registros seleccionados.





Por otro lado el listado para cada registro proporciona un link para editar el registro 



Al seleccionado  nos traslada a un formulario de edición de datos de la base de datos, que al confirmar actualizara la información del registro de acuerdo a los cambios realizados por el usuario.



Por otro lado la pantalla de administración nos proporciona un link para cargar nuevos registros en la base de datos.



Debemos destacar que tanto el formulario de carga, como el de edicion de datos contienen las validaciones correspondientes al formulario.



De mas esta decir que tanto al momento de realizar un alta como al editar un registro de manera satisfactoria nos traslada a una pantalla para indicarnos que la operación se a realizados con exito como las siguientes.

operación realizada con exito en el alta.




Operación realizada con exito en la edición.



En este ejemplo utilizare muchos hitos explicados en mayor detalle en entregas anteriores por lo que recomiendo que mires los anteriores post.

Nota: por supuesto con la interfaz mostrada tenemos claro las funcionalidades buscadas en una asignación real de desarrollo.

Definiendo mi modelo de datos


Como modelo de datos vamos a utilizar a nuestro amigo MYSQL que tantas alegrias nos brinda y no necesitamos mucho para poder trabajar con el. En lo personal para un ejemplo practico utilizo XAMMP que es un paquete que facilita la vida por que instala una serie de herramientas como apache, mysql entre otros.

Nuestro modelo de datos lo llamaremos springbanco en esta oportunidad solo tendrá una tabla muy simple con algunos campos.




Pero podríamos utilizar el siguiente script para crear nuestra base de datos.



/*
SQLyog Enterprise - MySQL GUI v6.0
Host - 5.1.37 : Database - springbanco
*********************************************************************
Server version : 5.1.37
*/


/*!40101 SET NAMES utf8 */;

/*!40101 SET SQL_MODE=''*/;

create database if not exists `springbanco`;

USE `springbanco`;

/*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */;
/*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */;

/*Table structure for table `account` */

DROP TABLE IF EXISTS `account`;

CREATE TABLE `account` (
  `Id` int(11) NOT NULL AUTO_INCREMENT,
  `Numero` varchar(50) DEFAULT NULL,
  `Nombre` varchar(100) DEFAULT NULL,
  `Balance` varchar(100) DEFAULT NULL,
  `Estado` varchar(50) DEFAULT NULL,
  PRIMARY KEY (`Id`)
) ENGINE=InnoDB AUTO_INCREMENT=46 DEFAULT CHARSET=latin1;

/*Data for the table `account` */

insert  into `account`(`Id`,`Numero`,`Nombre`,`Balance`,`Estado`) values (42,'10','Pedro Herrera','150','Active'),(43,'123','diego herrera','10000','Inactive'),(44,'345','Walter Herrera','4500','Active'),(45,'1278','diego','4455','Active');

/*!40101 SET SQL_MODE=@OLD_SQL_MODE */;
/*!40014 SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS */;


Nota: podemos observar algo simple una tabla y un par de insert como para tener datos cargados en nuestro ejemplo.

Definamos nuestro proyecto 

Como en la mayoría de nuestras entregas utilizaremos Maven para la gestión de proyectos y dependencias 



Seleccionamos el Archetype general para desarrollo web que nos proporcionara la estructura de trabajo necesaria y suficiente para abordar nuestro proyecto.



Siguiente paso es proporcionar un nombre a nuestro proyecto.



Nota: En nuestro ejemplo el proyecto se llamara mvc_proyectofinal.

Repositorio de código fuente 


Si bien en otras oportunidades en el blog ya aborde este tema, es necesario enlazar nuestro proyecto a un repositorio de código fuente por todas las bondades que tiene el hecho de trabajar enlazado a un repositorio.

En esta ocacion utilizaremos SVN conectado a googlecode como alternativa free, pero existen otros con iguales y mejores prestaciones, si quiere mirar un poco este articula aborda este conocimiento. 

Definir la estructura del proyecto 

Como el proyecto se va poner un poco grande vamos a analizar en resumen el contenido del mismo por partes.




Librerías y Dependencias 

Hace mucho tiempo la inclusión de librerias en los proyectos para los programadores generaba grandes dolores de cabeza por diversos motivos, por suerte Maven nos facilita la vida con la gestión automática de estas y por supuesto M2Eclipse facilita el trabajo desde eclipse por lo que lo recomiendo si no quieres trabajar por consola.

En esta oportunidad no voy a dedicar mucho tiempo a detallar mi pom.xml dado que en entregas anteriores ya hable mucho de cada una de las librerias necesarias para trabajar con Spring.

pom.xml: archivo de dependencias para proyectos Maven.


<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
  <modelVersion>4.0.0</modelVersion>
  <groupId>com.company</groupId>
  <artifactId>mvc_proyectoFinal</artifactId>
  <packaging>war</packaging>
  <version>0.0.1-SNAPSHOT</version>
  <name>mvc_proyectoFinal Maven Webapp</name>
  <url>http://maven.apache.org</url>
  <properties>
   <spring.version>3.0.5.RELEASE</spring.version>
   <log4j.version>1.2.16</log4j.version>
  </properties>
  <dependencies>
    <dependency>
      <groupId>junit</groupId>
      <artifactId>junit</artifactId>
      <version>4.6</version>
    </dependency>
    <dependency>
   <groupId>org.springframework</groupId>
      <artifactId>spring-test</artifactId>
      <version>${spring.version}</version>
      <scope>test</scope>
 </dependency>
    <dependency>
   <groupId>log4j</groupId>
   <artifactId>log4j</artifactId>
   <version>${log4j.version}</version>
  </dependency>
    <dependency>
  <groupId>commons-fileupload</groupId>
  <artifactId>commons-fileupload</artifactId>
  <version>1.2.2</version>
 </dependency>

 <!-- Apache Commons Upload --> 
 <dependency>
  <groupId>commons-io</groupId>
  <artifactId>commons-io</artifactId>
  <version>1.3.2</version>
 </dependency>
 <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-orm</artifactId>
            <version>${spring.version}</version>
        </dependency>
    <dependency>
   <groupId>org.springframework</groupId>
   <artifactId>spring-core</artifactId>
   <version>${spring.version}</version>
  </dependency>
  <dependency>
   <groupId>org.springframework</groupId>
   <artifactId>spring-context</artifactId>
   <version>${spring.version}</version>
  </dependency>
  <dependency>
   <groupId>org.springframework</groupId>
   <artifactId>spring-beans</artifactId>
   <version>${spring.version}</version>
  </dependency>
  <dependency>
   <groupId>org.springframework</groupId>
   <artifactId>spring-web</artifactId>
   <version>${spring.version}</version>
  </dependency> 
  <dependency>
   <groupId>org.springframework</groupId>
   <artifactId>spring-webmvc</artifactId>
   <version>${spring.version}</version>
  </dependency>
  <dependency>
  <groupId>javax.servlet</groupId>
  <artifactId>jstl</artifactId>
  <version>1.1.2</version>
 </dependency>
 <dependency>
  <groupId>javax.servlet</groupId>
  <artifactId>servlet-api</artifactId>
  <version>2.5</version>
 </dependency>            
 <dependency>
  <groupId>taglibs</groupId>
  <artifactId>standard</artifactId>
  <version>1.1.2</version>
 </dependency>
 <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>5.1.17</version>
            <type>jar</type>
            <scope>compile</scope>
        </dependency>  
        <!-- Hibernate framework -->
    <dependency>
   <groupId>org.hibernate</groupId>
   <artifactId>hibernate-core</artifactId>
   <version>3.6.3.Final</version>
  </dependency>
 <dependency>
   <groupId>javassist</groupId>
   <artifactId>javassist</artifactId>
   <version>3.12.1.GA</version>
  </dependency>  
 
 <!-- Hibernate library dependecy start -->
 <dependency>
  <groupId>dom4j</groupId>
  <artifactId>dom4j</artifactId>
  <version>1.6.1</version>
 </dependency>
 
 <dependency>
  <groupId>commons-logging</groupId>
  <artifactId>commons-logging</artifactId>
  <version>1.1.1</version>
 </dependency>
 <dependency>
  <groupId>antlr</groupId>
  <artifactId>antlr</artifactId>
  <version>2.7.7</version>
 </dependency>
 <!-- Hibernate library dependecy end -->
  </dependencies>
  <build>
    <finalName>mvc_proyectoFinal</finalName>
  </build>
</project>

Pero si vamos a indicar que para este proyecto las librerías necesarias serán:

Junit que posibilitaran realizar pruebas automatizadas con Spring
Log4j que posibilitara la gestión de log en mi aplicación.
Las librerias de Spring necesarias para el esquema de trabajo que presentamos.

  • ORM que facilita la integracion con Hibernate
  • CORE, CONTEXT, BEANS necesarias en todo proyecto basado en Spring.
  • WEB, WEBMVC necesarias para la integracion con MVC 
  • JSTL, TAGLIB, SERVLET necesarias para el trabajo con JSP.

El conector MYSQL para poder interaccionar con el Motor de base de datos.
Las Librerias de Hibernate y sus dependencias por supuesto para enlazarnos al modelo de datos.

Nota: Si miras un poco las entradas que dedique a Spring podras encontrar mayor detalle sobre estas librerías, como ejemplos prácticos de la utilizacion.

Archivos de Recursos 

En esta sección vamos a hablar de los archivos de recursos necesarios del proyecto, que en este proyecto no vamos a tener muchos dado que solo es un proyecto de estudio.

src\main\resources\SQL\basededatos.sql : es simplemente el script para crear la base de datos que lo puse para poder replicarlo, pero la realidad es que muchas veces queremos trasladar el proyecto a otra computadora y no damos con que no tenemos lo necesario para crear el modelo de datos de esta forma lo tengo dentro del proyecto por lo que si lo descargo de SVN lo voy  a poder crear sin problema y ustedes no insultaran cuando olvide subir el script.

Moraleja 1: Colocar los script de la base de datos a la que nos conectamos dentro del proyecto ahorra grandes dolores de cabeza, pero convengamos que no es muy seguro. Pero para fines de estudio sirve.

src\main\resources\message.properties: a quien no le paso que tenia que cambiar algunas etiquetas de la aplicación y dedico días a realizar los cambios en todo el proyecto buscando uno a uno los JSP y cuando tiene todo cocinado llega el cliente y dice no mejor lo cambiemos por ....plin plin plin.  Esta es la parte donde aprendemos la lengua de jesus para insultarlo groso. Pero esto pasa por que a la hora de desarrollar no planteamos una solución que posibilite el desacoplamiento del contenido estatico del sitio centralizando en un archivo de recurso que posibilita una fácil actualización del mismo. En la mayoría de casos tomamos esta buena practica solo en proyectos que tendrán internacionalización o múltiples idiomas por que tendremos archivos de recurso para cada uno de los idiomas disponibles, permitiendo switch del contenido estático según el idioma seleccionado, pero lo cierto es que deberíamos intentar aplicarlo en todos los proyectos por que nos ahorrara grandes dolores de cabeza a la hora de modificar contenido estático de nuestro sitio.

Moraleja 2: Desacoplar el contenido estático de los JSP ahorra muchas horas de cambios gramaticales a la hora de implementaciones y toparnos con clientes que no tienen claro la forma en que quieren escribir un mensaje en el proyecto.


Construyendo nuestro Modelo Hibernate

Si tenemos mas de 2 proyectos desarrollados vamos a saber que todo proyecto tiene operaciones Crud comunes para las diferentes tablas de un modelo de datos y que si no tomamos la buena practica de la utilización de operaciones genéricas que puedan ser replicadas en todo el modelo, vamos a pasar horas generando código repetitivo.

Hay un post dedicado a la contruccion de DAO genericas para implementaciones CRUD en proyectos con Hibernate Aqui

Nota: Ahora diras flaco si ya lo explicaste en otra entrada por que vas a dedicar tiempo ahora, pasa que esta ocacion vamos a utilizasar Spring para la construccion de DAO Genericos con la ayuda  HibernateDaoSupport y Generic. Si quieres entender en mayor detalle lo que mejora Hibernate Spring con alguna de las template definidas para facilitar el trabajo,  lo poder ver en un post dedicado al tema.

En esta ocasión no vamos a dedicar tiempo explicando la utilización de Generic dado que lo pueden ver en detalle en la entrada del blog dedicada al tema. 

Pero si tenemos que tener claro que esta es una buena practica y que se implementa en grandes proyectos ahorrando muchas horas de desarrollo de código repetitivo.

Moraleja 3: Debemos a tender a evitar código repetitivo en nuestros proyecto, objetivo reutilizar código , generación de implementaciones genéricas. Por que ? por que ahorramos tiempo de desarrollo, por que logramos código escalable y limpio mas manejable ..etc.

src\main\java\com\company\modelo\generic\GenericHibernateDao.java: Define la interfaz generica.


package com.company.modelo.generic;

import java.io.Serializable;
import java.util.Collection;
import java.util.List;

import org.hibernate.criterion.DetachedCriteria;
//import org.hibernate.mapping.Map;

import org.hibernate.mapping.Map;

import com.company.modelo.Entity.AccountEntity;

public interface GenericHibernateDao<T extends Serializable, E> {
 public void deleteAll(Collection<T> instances) throws Exception;
    public int bulkUpdate(String query) throws Exception;
    public E save(T instance) throws Exception;
    public void saveOrUpdateAll(Collection<T> instances) throws Exception;
    public void saveOrUpdate(T instance) throws Exception;
    public void persist(T transientInstance) throws Exception;
    public void attachDirty(T instance) throws Exception;
    public void attachClean(T instance) throws Exception;
    public void delete(T persistentInstance) throws Exception;
    public List<T> findByExample(T instance) throws Exception;
    public List<T> findByQuery(String query) throws Exception;
   // public List<Map<String, Object>> findMapByQuery(String queryString) throws Exception;
    public List<T> findByCriteria(DetachedCriteria criteria) throws Exception;
    public T merge(T detachedInstance) throws Exception;
    public List<T> findAll() throws Exception;
    public T findById(E id) throws Exception;
}

Al ser un objeto de estudio podemos ver solo es el contrato donde definimos los métodos, como podrás observar define muchos métodos comunes utilizados en la interacción con Hibernate con cualquier modelo de datos que en la practica podremos heredar para futuros dao que interacciones con otras tablas.

Vamooo !! tranqui esto no quiere decir que no vamos a hacer nada. solo que lo genérico sera re utilizable, las particularidades de cada tabla las tendremos que picar linea por linea, pero lo bueno es que nos centramos en logica diferencial y lo genérico ya fue atacado.

src\main\java\com\company\modelo\generic\GenericHibernateDaoImpl.java: define la implementacion de operaciones genéricas para trabajar con Hibermate.


package com.company.modelo.generic;

import java.io.Serializable;
import java.util.Collection;
import java.util.List;

import org.apache.log4j.Logger;
import org.hibernate.LockMode;
import org.hibernate.criterion.DetachedCriteria;
import org.springframework.orm.hibernate3.support.HibernateDaoSupport;

import com.company.modelo.Entity.AccountEntity;
import com.company.modelo.service.AccountServiceImpl;

public abstract class GenericHibernateDaoImpl<T extends Serializable, E> extends HibernateDaoSupport implements GenericHibernateDao<T, E> {

 private static final Logger logger = Logger.getLogger(GenericHibernateDaoImpl.class);
 
 public void deleteAll(Collection<T> instances) throws Exception {
  try {
   logger.info("Ejecutando metodo deleteAll");
            getHibernateTemplate().deleteAll(instances);
            logger.info("Fin del metodo deleteAll");
        } catch (final Exception e) {
         logger.error(Thread.currentThread().getStackTrace()[1].getMethodName(), e);
            throw e;
        }
 }

 public int bulkUpdate(String query) throws Exception {
  try {
   logger.info("Ejecutando metodo " + Thread.currentThread().getStackTrace()[1].getMethodName() );
            return getHibernateTemplate().bulkUpdate(query);
        } catch (final Exception e) {
         logger.error(Thread.currentThread().getStackTrace()[1].getMethodName(), e);
            throw e;
        }
 }

 public E save(T instance) throws Exception {
  try {
   logger.info("Ejecutando metodo " + Thread.currentThread().getStackTrace()[1].getMethodName() );
            return (E) getHibernateTemplate().save(instance);
        } catch (final Exception e) {
         logger.error(Thread.currentThread().getStackTrace()[1].getMethodName(), e);
            throw e;
        }
 }

 public void saveOrUpdateAll(Collection<T> instances) throws Exception {
  try {
   logger.info("Ejecutando metodo " + Thread.currentThread().getStackTrace()[1].getMethodName() );
            getHibernateTemplate().saveOrUpdateAll(instances);
        } catch (final Exception e) {
         logger.error(Thread.currentThread().getStackTrace()[1].getMethodName(), e);
            throw e;
        } 
 }

 public void saveOrUpdate(T instance) throws Exception {
  try {
   logger.info("Ejecutando metodo " + Thread.currentThread().getStackTrace()[1].getMethodName() );
            getHibernateTemplate().saveOrUpdate(instance);
        } catch (final Exception e) {
         logger.error(Thread.currentThread().getStackTrace()[1].getMethodName(), e);
            throw e;
        }
  
 }

 public void persist(T transientInstance) throws Exception {
  try {
   logger.info("Ejecutando metodo " + Thread.currentThread().getStackTrace()[1].getMethodName() );
            getHibernateTemplate().persist(transientInstance);
        } catch (final Exception e) {
         logger.error(Thread.currentThread().getStackTrace()[1].getMethodName(), e);
            throw e;
        }
  
 }

 public void attachDirty(T instance) throws Exception {
  try {
   logger.info("Ejecutando metodo " + Thread.currentThread().getStackTrace()[1].getMethodName() );
            getHibernateTemplate().saveOrUpdate(instance);
        } catch (final Exception e) {
         logger.error(Thread.currentThread().getStackTrace()[1].getMethodName(), e);
            throw e;
        }
  
 }

 public void attachClean(T instance) throws Exception {
  try {
   logger.info("Ejecutando metodo " + Thread.currentThread().getStackTrace()[1].getMethodName() );
            getHibernateTemplate().lock(instance, LockMode.NONE);
        } catch (final Exception e) {
         logger.error(Thread.currentThread().getStackTrace()[1].getMethodName(), e);
            throw e;
        }
  
 }

 public void delete(T persistentInstance) throws Exception {
  try {
   logger.info("Ejecutando metodo " + Thread.currentThread().getStackTrace()[1].getMethodName() );
            getHibernateTemplate().delete(persistentInstance);
        } catch (final Exception e) {
         logger.error(Thread.currentThread().getStackTrace()[1].getMethodName(), e);
            throw e;
        }
  
 }

 public List<T> findByExample(T instance) throws Exception {
  try {
   logger.info("Ejecutando metodo " + Thread.currentThread().getStackTrace()[1].getMethodName() );
            final List<T> results = getHibernateTemplate().findByExample(instance);
            return results;
        } catch (final Exception e) {
         logger.error(Thread.currentThread().getStackTrace()[1].getMethodName(), e);
            throw e;
        }
 }

 public List<T> findByQuery(String query) throws Exception {
  try {
   logger.info("Ejecutando metodo " + Thread.currentThread().getStackTrace()[1].getMethodName() );
            final List<T> results = getHibernateTemplate().find(query);
            return results;
        } catch (final Exception e) {
         logger.error(Thread.currentThread().getStackTrace()[1].getMethodName(), e);
            throw e;
        }
 }

 public List<T> findByCriteria(DetachedCriteria criteria) throws Exception {
  try {
   logger.info("Ejecutando metodo " + Thread.currentThread().getStackTrace()[1].getMethodName() );
            return getHibernateTemplate().findByCriteria(criteria);
        } catch (final Exception e) {
         logger.error(Thread.currentThread().getStackTrace()[1].getMethodName(), e);
            throw e;
        }
 }

 public T merge(final T detachedInstance) throws Exception {
        try {
         logger.info("Ejecutando metodo " + Thread.currentThread().getStackTrace()[1].getMethodName() );
            final T result = getHibernateTemplate().merge(detachedInstance);
            return result;
        } catch (final Exception e) {
         logger.error(Thread.currentThread().getStackTrace()[1].getMethodName(), e);
            throw e;
        }
    }
 
 public abstract List<T> findAll() throws Exception;
    public abstract T findById(E id) throws Exception; 

}

En primera instancia en una implementacion tan genérica es necesario registrar sus actividades para lo que utilizamos Log4j para registrar en log y por consola para ir siguiendo la interacción de la capa de modelo que en el futuro consumirá estas implementaciones.

Ahora si observamos por un lado implementamos la interface antes mencionada y por otro lado extendemos de un soporte hibernate que proporciona Spring para facilitar el trabajo como HibernateDaoSupport.   

Ahora si notamos esta clase por si sola no hace nada por que necesita de una sessionfactory que por supuesto es inyectada por la clase dao que vaya a consumirla,  man esto no es magia si nunca inyectamos la información de la base de datos el código no sabe para donde ir.

por consiguiente los dao que vayan a consumir estas clases genéricas de seguro deberán incluir un código como el siguiente 

@Autowired
public AccountDaoImpl(SessionFactory sessionFactory) {
logger.info("Inyeccion SessionFactory en clase AccountDaoImpl");
super.setSessionFactory(sessionFactory);

}

Pero pero por que no ponemos la inyección del sessionfactory en mis clases genéricas asi me olvido de este tema ?  por la virtud mas grande son clases genéricas si lo colocara de seguro todos mis dao van a interaccionar con el mismo motor de base de datos.  Al brindarle a los dao la responsabilidad de indicar la conexión desacoplamos de este nivel de interacción genérico la necesidad de entender con quien va interaccionar. 

 Moraleja 4: Debemos tener claro el nivel de responsabilidades que le damos a las diferentes capas de nuestro proyecto manteniendo desacoplamiento y facilidad de adaptación al cambio. 

src\main\java\com\company\modelo\Entity\AccountEntity.java: definimos la clases o pojo con sus getter y setter que se mapeara con la tabla de MYSQL.


package com.company.modelo.Entity;

public class AccountEntity implements java.io.Serializable {
    private Integer Id;
    private String Numero;
    private String Nombre;
    private String Balance;
    private String Estado;
    
 public Integer getId() {
  return Id;
 }
 public void setId(Integer id) {
  this.Id = id;
 }
 public String getNumero() {
  return Numero;
 }
 public void setNumero(String numero) {
  Numero = numero;
 }
 public String getNombre() {
  return Nombre;
 }
 public void setNombre(String nombre) {
  Nombre = nombre;
 }
 public String getBalance() {
  return Balance;
 }
 public void setBalance(String balance) {
  Balance = balance;
 }
 public String getEstado() {
  return Estado;
 }
 public void setEstado(String estado) {
  Estado = estado;
 }
    
    
    
}

Nota: toda interacción con Hibernate necesita de la clase en la que se realiza el mapeo, en nuestro caso vamos a trabajar con una sola tabla por lo que la clase entidad Account nos ayudara con este propósito.  Si observamos de seguro nos vamos a dar cuenta de la correspondencia con los campos de la tabla MYSQL.


src\main\java\com\company\modelo\hbm\Account.hbm.xml: Es nuestro archivo de mapeo xml donde en lazamos los campos de la tabla de la base de datos a la clase antes mencionada.


<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
<!-- Generated 13-sep-2013 15:59:20 by Hibernate Tools 3.4.0.CR1 -->
<hibernate-mapping>
    <class name="com.company.modelo.Entity.AccountEntity" table="account" catalog="springbanco">
        <id name="Id" type="java.lang.Integer">
            <column name="Id" />
            <generator class="identity" />
        </id>
        <property name="Numero" type="string">
            <column name="Numero" length="50" />
        </property>
        <property name="Nombre" type="string">
            <column name="Nombre" length="100" />
        </property>
        <property name="Balance" type="string">
            <column name="Balance" length="100" />
        </property>
        <property name="Estado" type="string">
            <column name="Estado" length="50" />
        </property>
    </class>
</hibernate-mapping>

Moraleja 5:  Por dios prestar atención a los nombres de los campos de la tabla y lo de la clase por que podemos perder muchas horas de nuestra vida insultando hibernate hasta que nos damos cuenta que mapeamos mal.

Arquitectura de trabajo

Ahora tenemos casi todo para comenzar a trabajar en nuestra logica de negocio una buena practica es la utilización de los patrones DAO  y Facade Service.


"Data Access Object" un patrón de diseño muy común que se usa para diseñar como los objetos de negocio de una aplicación deben usar los objetos encargados del acceso a los datos.



Como pueden ver los objetos del negocio acceden a los DAO a través de sus interfaces, de esta manera no dependen de una implementación en específico lo que hace que esta implementación se pueda cambiar fácilmente. También facilita las pruebas que se pueden realizar sobre esta implementación, ya que se pueden crear implementaciones falsas menos complejas que las implementaciones reales con fines de prueba.

Por ultimo la exposicion de los DAO para ser consumidos se realiza por medio de la capa de servicio que sera la encargada de mediante las implementaciones la utilización de 1 o N objetos DAO para alcanzar su objetivo funcional.

Moraleja 6: Debemos tender a posibilita una arquitectura desacoplada que permite un modelo escalable y facilita que las transacciones puedan ser tratadas de manera centralizada por tener un punto de exposición por medio de las capas de servicio.


Esto quiere decir que como buena practica toda mi CORE de interacción con la base de datos estarán atrás de la exposición de clases servicios que respondan las necesidades funcionales del frontend ?  Yes..

Avisa!!  ahora se como debo seguir en primera instancia debemos definir nuestro Layer Dao y Services.


Definiendo nuestro DAO 

En un proyecto siempre vamos a tener N dao que realice interacción con la base de datos, en esta ocasión solo tendremos 1 dado que hay una tabla en estudio, pero por otro lado en esta sección nuestra clase dao se relacionan con las clases Dao GENERIC que definimos en el apartado anterior por que es la forma en que podremos adquirir las operaciones genérica que definimos.

src\main\java\com\company\modelo\dao\AccountDao.java: Contrato que definimos para los métodos que implementara el dao.


package com.company.modelo.dao;

import com.company.modelo.Entity.AccountEntity;
import com.company.modelo.generic.GenericHibernateDao;



public interface AccountDao extends GenericHibernateDao<AccountEntity , Integer> {
 public AccountEntity getAccountDetails(String accountNumber);
}

Podemos notar que extiende del la interfaz dao generic que definimos con anterioridad, pasando por parámetro los tipos con lo que trabajara la interfaz generic.

src\main\java\com\company\modelo\dao\AccountDaoImpl.java: Implementacion de los metodos definidos en el contrato.


package com.company.modelo.dao;

import java.util.List;

import org.apache.log4j.Logger;
import org.hibernate.SessionFactory;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.orm.hibernate3.support.HibernateDaoSupport;
import org.springframework.stereotype.Repository;

import com.company.modelo.Entity.AccountEntity;
import com.company.modelo.generic.GenericHibernateDaoImpl;



@Repository
public class AccountDaoImpl extends GenericHibernateDaoImpl<AccountEntity , Integer> implements AccountDao {

 private static final Logger logger = Logger.getLogger(AccountDaoImpl.class);
 
 @Autowired
 public AccountDaoImpl(SessionFactory sessionFactory) {
  logger.info("Inyeccion SessionFactory en clase AccountDaoImpl");
  super.setSessionFactory(sessionFactory);
 }
 
 public AccountEntity getAccountDetails(String accountNumber) {
  logger.info("Llamando al metodo getAccountDetails con parametro accountNumber " + accountNumber);
  return (AccountEntity)getHibernateTemplate().get(AccountEntity.class, new Integer(accountNumber));
  
 }

 @Override
 public List<AccountEntity> findAll() throws Exception {
  logger.info("Llamando al metodo findAll");
  return getHibernateTemplate().loadAll(AccountEntity.class);
 }

 
 @Override
 public AccountEntity findById(Integer id) throws Exception {
  logger.info("Llamando al metodo findById con parametro id " + id.toString());
  return (AccountEntity)getHibernateTemplate().get(AccountEntity.class, id);
 }

 

}

Podemos notar que extiende de la implementacion dao generic donde le pasamos los tipos con los que trabajara. Por otro lado implementa los metodos de la clase antes mencionada.

Como ya lo mencionamos en entregas anteriores para que mi clase dao que extiende de hibernatedaosupport pueda tener una gestion de conexion es necesario la inyeccion del session factory que gestiona las conexiones de manera automatica y en esta oportunidad no sera diferente si notamos inyectamos la session por el contructor de la clase.

Por otro lado podemos notar que impelementa un metodo getAccountDetails que es propio de la clase dado que no esta disponible en la implementacion generica, por lo que podremos definir N metodos diferenciales de ser necesarios que no se encuentra en la template de operaciones genericas definidas. 

también definimos 2 métodos que si se encuentran en generic y que sobrescribiremos en la clase propiamente mencionada. 

Nota Final: con esto logramos que nuestra clase dao tendrá funcional las operaciones generic definidas mas las que definamos en esta clase dao.

Moraleja 7: implementaciones genéricas evitan código repetitivo y mayor atención en el código de logica diferencial y muy propio de la tabla. En español le brindamos mayor tiempo al loco que va picar la logica que no es generica.


Definiendo nuestro Service Facade

Por decirlo de una forma simple y practica para todos, a este nivel vamos a exponer funcionalidades que respondan de manera directa a los casos de usos definidos por el usuario o bien el frondend aprobado, pero en la practica real la layer service podria utilizar uno o N objetos dao previamente definidos para alcanzar una funcionalidad de caso de uso definidos por el usuario. 

Ejemplo Caso funcional
Eliminación de Cliente en sistema de facturación

ClienteService: podría ser una clase de servicio que casualmente realiza la eliminación del cliente en el sistema. Pero la pregunta es como ?  Mediante la interacción de diferentes DAO que interaccionan con tablas de la base de datos.

Se entendió ?

Moraleja 8: definir el nivel de responsabilidad de las capas es importante el front-end solo debe tener acceso a las funcionalidades necesarias y suficiente para cumplir con su fin, es decir lo que necesita para atender el caso de uso y nada mas.

src\main\java\com\company\modelo\service\AccountService.java:  Es la definición del contrato que tendrá los métodos que expondrá.


package com.company.modelo.service;

import java.util.List;

import com.company.modelo.Entity.AccountEntity;



public interface AccountService {
    public void DeleteAccount(Integer id) throws Exception;
 public List<AccountEntity> AccountFinAll();
 public AccountEntity getAccountDetails(String accountNumber);  
 public void SaveAccountAdd(AccountEntity obj);
 public void UpdateAccount(AccountEntity obj);
 public AccountEntity findById(Integer id);
 
}

por lo general estos métodos son los que necesita la aplicación a nivel funcional.

src\main\java\com\company\modelo\service\AccountServiceImpl.java: Es la implementacion de los métodos que expondrá la clase.


package com.company.modelo.service;

import java.util.List;

import org.apache.log4j.Logger;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Component;
import org.springframework.stereotype.Service;

import com.company.modelo.Entity.AccountEntity;
import com.company.modelo.dao.AccountDao;
import com.company.modelo.dao.AccountDaoImpl;



@Service
public class AccountServiceImpl implements AccountService {
 
 private static final Logger logger = Logger.getLogger(AccountServiceImpl.class);
 
  @Autowired
  private AccountDao accountdao;
 
 public AccountEntity getAccountDetails(String accountNumber) {
  logger.info("Llamada al metodo getAccountDetails con parametro accountNumber="+accountNumber);
  System.out.println("paso por servicio");   
   return accountdao.getAccountDetails(accountNumber);    
   
 }

 public List<AccountEntity> AccountFinAll() {
  try {
   return accountdao.findAll();
  } catch (Exception e) {
   // TODO Auto-generated catch block
   e.printStackTrace();
   return null;
  }
  
 }

 public void DeleteAccount(Integer id) throws Exception {
  accountdao.delete(accountdao.findById(id));
  
 }

 public void SaveAccountAdd(AccountEntity obj) { 
  Integer result = null;
  try {
   result = accountdao.save(obj);
   ///System.out.println("id grabado " + obj.getId());
  } catch (Exception e) {
   // TODO Auto-generated catch block
   e.printStackTrace();
  }
  
  
 }

 public void UpdateAccount(AccountEntity obj) {   
   try {   
   accountdao.saveOrUpdate(obj);   
  } catch (Exception e) {
   // TODO Auto-generated catch block
   e.printStackTrace();
  }
  
 }

 public AccountEntity findById(Integer id) {
  try {
   return accountdao.findById(id);
  } catch (Exception e) {
   // TODO Auto-generated catch block
   e.printStackTrace();
   return null;
  }
 }


}

ahora si observamos podremos notar que a este nivel inyectaremos los objetos DAO necesarios para atender la necesidad funcional de los métodos definidos, en nuestro caso solo inyectamos un DAO pero si tuviéramos un proyecto grande de seguro las clase de servicio tendran N objetos DAO definidos. 

Moraleja 9: facilitar el desacoplamiento por completo de la capa de servicio de la capa DAO, algo en lo que spring nos facilita la vida. Si el dia de mañana las implementaciones de nuestro DAO cambia, la clase de servicio ni se entera y es indistinto.

Definiendo nuestros Test Automáticos.

Una buena practica es definir test automaticos con JUnit a nivel Dao y a Nivel servicio. En que nos ayuda ?  en primera instancia en la prueba de partes y pruebas de integración, en segunda instancia que cuando realicemos cambios sobre alguna de estas capas podremos verificar su funcionamiento de manera inmediata garantizando el éxito del funcionamiento general tras el cambio realizado.

Mi opinion: o sea ya se que hacer todo esto lleva su tiempo pero de verdad es que si tenemos un proyecto muy grande es tiempo que luego ganaremos por que no tendremos que probar todo el sistema nuevamente dado que podremos garantizar que nuestros cambios sobre por ejemplo una capa dao funciona perfecto por la prueba unitaria. Es decir si realice un cambio y la prueba de parte es satisfactoria, puedo garantizar que el todo esta funcional.

Moraleja 10: Ahora si un desarrollo de 4 horas se transforma en 7 por hacer test unitarios de seguro tengo que pegar un tiro al programador, lo que indica que manera clara que si bien es una buena practica muchas veces por una cuestión de tiempo no es aplicable. 

src\main\java\com\company\modelo\test\AccountDaoTest.java: Es nuestro clase de test utilizando JUNIT 4 y su integración con Spring. Dado que no es objeto de estudio solo se realiza test sobre algunos métodos pero alcanzara para entender un poco los beneficios y la forma de realizarlo.


package com.company.modelo.test;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;

import java.util.List;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

import com.company.modelo.Entity.AccountEntity;
import com.company.modelo.dao.AccountDao;


@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("file:src/main/webapp/WEB-INF/mvc-dispatcher-servlet.xml")
public class AccountDaoTest {

 final Logger logger = LoggerFactory.getLogger(AccountDaoTest.class);
 
 @Autowired
  private AccountDao accountdao;
 
 public void ImprimirListado(List<AccountEntity> lista)
 {
  System.out.println("Listado");
  for (AccountEntity item : lista) {
         System.out.println(item.getId() + " - " + item.getNumero() + " - " + item.getNombre()+ " - " + item.getBalance() + " - " + item.getEstado());   
        }
 }
 
 @Test
    public void testFindAll() throws Exception {
        List<AccountEntity> objaccount = accountdao.findAll();
        System.out.println("testFindAll");   
        assertNotNull("Account list is null.", objaccount);
        ImprimirListado(objaccount); 
    }
 
 @Test
    public void testfindByQuery() {
        List<AccountEntity> objaccount = null;
        System.out.println("testfindByQuery");
  try {
   objaccount = accountdao.findByQuery("from AccountEntity dt order by dt.Id DESC");
  } catch (Exception e) {
   // TODO Auto-generated catch block
   e.printStackTrace();
  }
           
        assertNotNull("Account list is null.", objaccount);             
        ImprimirListado(objaccount); 
    }
 
 
 @Test
    public void testsaveOrUpdate() {
   logger.info("Probando Metodo saveOrUpdate");
   
   String Numero = "1054";
   String Nombre = "Diego Herrera Modificado";
   String Balance = "25.000";
   String Estado = "Inactive";   
  
   AccountEntity objAccountSearch = null;
   try {
   objAccountSearch = accountdao.findById(44);
   objAccountSearch.setNumero(Numero);
   objAccountSearch.setNombre(Nombre);
   objAccountSearch.setBalance(Balance);
   objAccountSearch.setEstado(Estado);
   accountdao.saveOrUpdate(objAccountSearch);   
   objAccountSearch = accountdao.findById(44);
   
  } catch (Exception e) {
   // TODO Auto-generated catch block
   e.printStackTrace();
  }
   System.out.println("Identificador" + objAccountSearch.getId().toString());
   //realizo el control de la salida de buscqueda con los datos cargados
   assertNotNull("AccountEntity is null.", objAccountSearch);   
   assertEquals("Numero", Numero,objAccountSearch.getNumero());
   assertEquals("Nombre", Nombre,objAccountSearch.getNombre());
   assertEquals("Balance", Balance,objAccountSearch.getBalance());
   assertEquals("Estado", Estado,objAccountSearch.getEstado());
   
    }
 
 @Test
    public void testSave() {
   logger.info("Probando Metodo Save");
   
   String Numero = "1054";
   String Nombre = "Diego Herrera";
   String Balance = "15.000";
   String Estado = "Active";
   
   //Creo el objeto en base a la informacion proporcionada
   AccountEntity ObjAccount = createObjeto(Numero, Nombre, Balance, Estado);
   System.out.println("Paso");
   
   AccountEntity objAccountSearch = null;
   try {
    System.out.println("Identificador" + ObjAccount.getId().toString());
    //busco el objeto en base al identificador
   objAccountSearch = accountdao.findById(ObjAccount.getId());
  } catch (Exception e) {
   // TODO Auto-generated catch block
   e.printStackTrace();
  }
   System.out.println("Identificador" + objAccountSearch.getId().toString());
   //realizo el control de la salida de buscqueda con los datos cargados
   assertNotNull("AccountEntity is null.", ObjAccount);   
   assertEquals("Numero", Numero,objAccountSearch.getNumero());
   assertEquals("Nombre", Nombre,objAccountSearch.getNombre());
   assertEquals("Balance", Balance,objAccountSearch.getBalance());
   assertEquals("Estado", Estado,objAccountSearch.getEstado());
   
    }
 
 
 private AccountEntity createObjeto(String Numero, String Nombre, String Balance, String Estado) 
  {
  
  AccountEntity obj=new AccountEntity();
  obj.setNumero(Numero);
  obj.setNombre(Nombre);
  obj.setBalance(Balance);
  obj.setEstado(Estado);
  
  Integer result = null;
  try {
   result = accountdao.save(obj);
   ///System.out.println("id grabado " + obj.getId());
  } catch (Exception e) {
   // TODO Auto-generated catch block
   e.printStackTrace();
  }
  
  return obj;
  }
 
}

Ahora pero todo nuestro modelo hasta ahora usa inyección de spring para diferentes cosas como vamos a realizar test sin levantar los beans ?  no podríamos por eso JUnit se integra con spring de manera fácil. 

A nuestra clase test debemos indicarle en su encabezado:

Primero para que ejecute test unitario debemos indicar la notacion  @RunWith(SpringJUnit4ClassRunner.class) que utilice la extencion Spring para Junit4. El valor de la clase SpringJUnit4ClassRunner  hereda de la clase Runner de junit4 que declara un método abstracto run que Spring implementa para la operatoria.

Para poder inyectar los beans de spring lo hacemos mediante la anotación 'ContextConfiguration' que espera la ruta o classpath donde se encuentran los contenedores de beans(*.xml).

Luego inyectamos el bean dao sobre el cual vamos a realizar test con @Autowired.

Luego definimos los métodos test sobre los cuales realizaremos las pruebas. 

Como en todo desarrollo serio generamos log para entender que esta pasando en la ejecución por consola o por archivo.

Metodos test ??  me parece que si a este nivel nunca utilizaste Junit vamos a tocar un poco el tema para que todos estemos al mismo nivel.

Tenemos algunas notaciones interesantes para cada método que definamos en una clase test:

@BeforeClass: Los métodos anotados con @BeforeClass serán ejecutados una vez por cada instancia de esta clase al crearse la clase.

Ejemplo 
@BeforeClass
public static void inicializacionClass(){
logger.info("Inicialización de la clase de Test...!");
}

@AfterClass: Los métodos anotados con @AfterClass serán ejecutados una vez por cada instancia de esta clase al terminar la ejecución de todos los métodos de prueba.

Ejemplo 

@AfterClass
public static void finalizacionClass(){
logger.info("Finalización de la clase de Test...!");

}

@Before: La anotación @Before pertenece a junit4.4 y le indica a la clase que el mètodo anotado se ejecute antes de la ejecución de cada método de prueba.Si en la clase de test hubiera varios métodos anotados con @Before entonces todos ellos se ejecutaran al iniciar cada método de prueba.

Ejemplo 

@Before
public void inicializacion(){
logger.info("Inicialización de método de prueba ...!");

}

@After: La anotación @After pertenece a junit4.4 y le indica a la clase que el método anotado se ejecute después de la ejecución de cada método de prueba.Si en la clase de test hubiera varios métodos anotados con @After entonces todos ellos se ejecutaran al terminar cada método de prueba.

Ejemplo 


@After
public void finalizacion2(){
logger.info("Finalización de método de prueba 2...!");
}

@Test: Método que realizará una prueba unitaria, no es necesario que el nombre del método empiece con test, simplemente que esté anotado con @Test.

Ejemplo


@Test
public void testSumar(){
logger.info("Inicio de test testSumar()");
Long operador1 = 1L;
Long operador2 = 2L;
Long resultado = calculadoraService.sumar(operador1, operador2);
assertEquals(3L, resultado.longValue());
logger.info("Fin de test testSumar()");
}

Ahora sobre esta notaciones tenemos algunas variantes que deberíamos conocer:

@Test(expected=ArithmeticException.class): utiliza un parámetro de la anotación @Test que indica la excepción esperada por el método, si la excepción es lanzada entonces la prueba es correcta, si el método terminasin lanzar una excepción entonces la prueba falla.

@Test(timeout=1000): otro parámetro de la anotación @Test es 'timeout' que indica que la prueba fallará si la ejecución del método se demora más del valor del parámetro 'timeout' en milisegundos.

@Ignore: con esto le indicamos que ignore en la prueba unitaria la ejecución del metodo.

El resto son evaluaciones que realizamos sobre los datos obtenidos resultante de logica de prueba como:


assertEquals(), que en general admite dos parámetros: El primero es el valor esperado y el segundo el valor que hemos obtenido. 
assertNotNull() que indica que el resultado esperado no debe ser null
assertTrue(), que indica que el resultado esperado debe ser true.
assertFalse(expresión) que indica que el resultado esperado debe ser false.
assertNull(objeto) que indica que el resultado esperado debe ser null
assertSame(objeto_esperado,objeto_real) comprueba que objeto_esperado y objeto_real sean el mismo objeto
assertNotSame(objeto_esperado,objeto_real) comprueba que objeto_esperado no sea el mismo objeto que objeto_real
fail() hace que el test termine con fallo

Pero como en toda mi vida me fui de tema lo siguiente es mostrar el resultado de la ejecución de esta clase test. Para lo que nos paramos sobre la clase le damos boton derecho......



Podremos visualizar la automatización de pruebas y el resultado obtenido.


Otra vista que me gusta mucho es la de consola, donde podremos visualizar el resultado de la ejecución.


"Como digo siempre hasta ahora todo lindo bonito barato" jaajja 

Por que ?

Por que hasta donde trabajamos es lo básico en todo desarrollo J2EE, ahora quien va consumir este CORE lo define la necesidad. Si si por que si bien el ejemplo tenemos claro que lo va consumir un front-end realizado en MVC, bajo otro escenario podría convertirse en un servicio para exponer esta funcionalidad, podría ser consumido por una interfaz flex o hasta quizás por tan solo una Lanzadera de un proceso batch.

Moraleja: sea cual sea tu desarrollo siempre:
  • Definirías un arquitectura que permita desacoplamiento entre las capas DAO+SERVICE.
  • Utilizaras una forma de acceso a datos que en nuestro caso Hibernate.
  • Buscaras reducir el código repetitivo por medio de Generic o alguna template aplicable en el proyecto.
  • Buscara facilitar las pruebas unitarias de las diferentes partes de tu proyecto, en nuestro caso JUNIT.

Quien lo consumirá ?

Podría ser cualquier cosa y no seria relevante por que trabajamos sobre un modelo escalable.

En la siguiente entrega trabajaremos sobre el front-end utilizando MVC. 

Hasta la proxima.

sábado, 30 de noviembre de 2013

Spring MVC: Formularios + Validator

Si bien venimos lento pero seguro en el entendimiento de MVC de spring, me pareció que ya estamos en condiciones de hacer un poco de desarrollo un tanto mas real. Algo que de seguro vamos a tener que trabajar son los formularios.

Nuestro ejemplo es simplemente un formulario con una serie de controles los cuales en primera instancia deben ser inicializados y posterior al submite deben ser visualizados en otra pagina.

Formulario inicializado



Realizado el Submit del formulario




Validación sobre datos del formulario





Cuales son los objetos de estudio de esta guía:

  • Aprenderemos a manejar diferentes controles para construcción del formulario.
  • Aprenderemos como inicializar valores en controles.
  • Aprenderemos a realizar validaciones sobre los datos cargados del formulario.
  • Aprenderemos a trasladar información a otra vista.

Como en todo los ejemplo en este caso utilizaremos 2 vistas  con las cuales vamos a trabajar , primero la vista del formulario y segundo la vista que utilizaremos para mostrar los datos que enviamos desde el formulario.


Buenisimo !!! ahora como hacemos esto. jajajaja


Pero como trabajamos con MVC 

Inicia con un Request



Finaliza con Response

Cuando se envía una solicitud a la Spring Framework MVC la siguiente secuencia de los acontecimientos suceden.

El DispatcherServlet recibe por primera vez la solicitud.

La consulta a la DispatcherServlet Handler Mapping e invoca el controlador asociado a la solicitud.

El proceso regulador de la solicitud llamando a los métodos apropiados de servicio y devuelve un objeto ModeAndView a la DispatcherServlet. 

El objeto ModeAndView contiene los datos del modelo y el nombre de vista.

El DispatcherServlet envía el nombre de la vista a un ViewResolver para encontrar la visión real de invocar.

Ahora, el DispatcherServlet pasará el modelo de objetos a la vista para hacer que el resultado.

La vista con la ayuda de los datos del modelo hará que el resultado de vuelta al usuario.


Paso 0: Definir nuestro proyecto y archivo POOM.

Definamos nuestro proyecto 












El siguiente paso es definir nuestro archivo pom para la gestión de dependencias.

pom.xml: definiremos todas las librerías necesarias para trabajar con nuestro ejemplo.


<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
  <modelVersion>4.0.0</modelVersion>
  <groupId>com.company</groupId>
  <artifactId>mvc_MultiActionControllerAnnotation</artifactId>
  <packaging>war</packaging>
  <version>0.0.1-SNAPSHOT</version>
  <name>mvc_MultiActionControllerAnnotation Maven Webapp</name>
  <url>http://maven.apache.org</url>
  <properties>
   <spring.version>3.0.5.RELEASE</spring.version>
  </properties>
  <dependencies>
    <dependency>
      <groupId>junit</groupId>
      <artifactId>junit</artifactId>
      <version>3.8.1</version>
      <scope>test</scope>
    </dependency>
    <dependency>
   <groupId>org.springframework</groupId>
   <artifactId>spring-core</artifactId>
   <version>${spring.version}</version>
  </dependency>
  <dependency>
   <groupId>org.springframework</groupId>
   <artifactId>spring-context</artifactId>
   <version>${spring.version}</version>
  </dependency>
  <dependency>
   <groupId>org.springframework</groupId>
   <artifactId>spring-beans</artifactId>
   <version>${spring.version}</version>
  </dependency>
  <dependency>
   <groupId>org.springframework</groupId>
   <artifactId>spring-web</artifactId>
   <version>${spring.version}</version>
  </dependency> 
  <dependency>
   <groupId>org.springframework</groupId>
   <artifactId>spring-webmvc</artifactId>
   <version>${spring.version}</version>
  </dependency>
  <dependency>
  <groupId>javax.servlet</groupId>
  <artifactId>jstl</artifactId>
  <version>1.1.2</version>
 </dependency>
 <dependency>
  <groupId>javax.servlet</groupId>
  <artifactId>servlet-api</artifactId>
  <version>2.5</version>
 </dependency>            
 <dependency>
  <groupId>taglibs</groupId>
  <artifactId>standard</artifactId>
  <version>1.1.2</version>
 </dependency>
  </dependencies>
  <build>
    <finalName>mvc_MultiActionControllerAnnotation</finalName>
  </build>
</project>

En esta ocacion no utilizaremos ninguna libreria rara, solo spring y las taglib para trabajar con JSP un poquito.

Paso 1: Definir el dispacher

web.xml: Primero debemos modificar nuestro archivo web.xml de base para definir el dispachservlet de spring y la locacion del spring bean configuration file.


<web-app id="WebApp_ID" version="2.4" 
 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">

  <display-name>Spring Web MVC Application</display-name>
  
  <servlet>
   <servlet-name>mvc-dispatcher</servlet-name>
    <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
    <load-on-startup>1</load-on-startup>
  </servlet>
  
  <servlet-mapping>
  <servlet-name>mvc-dispatcher</servlet-name>
    <url-pattern>*.htm</url-pattern>
  </servlet-mapping>

    <context-param>
 <param-name>contextConfigLocation</param-name>
 <param-value>/WEB-INF/mvc-dispatcher-servlet.xml</param-value>
  </context-param>
  
  <listener>
    <listener-class>
      org.springframework.web.context.ContextLoaderListener
    </listener-class>
  </listener>
  
</web-app>

En este archivo lo que realizamos es mendiante definimos el dispatchservlet y el orden de prioridad para las solicitudes. La tarea de este recurso es procesar las llamadas realizadas a la aplicación y determinar que Controlador sera el encargado de atenderla para su resolución.

Luego mediante definimos el patron de filtro para la escucha de solicitudes si por ejemplo agregamos *.htm le estamos indicando que escuche todo las llamadas que hagan referencia a archivos htm.

Mediante definimos donde buscara el spring bean configuration file que utilizara nuestro proyecto. Ahora si esta información no es proporcionada en el archivo de analisis, spring por si solo buscara el archivo en WEB-INF  con el nombre resultante de una concatenacion simple.

lo que pusimos en servlet-name + "-servlet.xml", por consiguiente si tuviera una definición como  <servlet-name>mvc-dispatcher</servlet-name>  la busqueda del archivo se realizaria a mvc-dispatcher-servlet.xml.

 Por ultimo tendremos la definición de nuestro ContextLoaderListener es el encargado de inicializar todo el contexto del framework para el funcionamiento de la mecanica MVC.

Paso 2: Definir controlador,  modelo,  vista y Validator 

Antes de comenzar a trabajar con nuestro controlador es necesario afirmar algunos conceptos que creo necesarios.

@ModelAttribute es una notacion de Spring Mvc y por lo general es usada en 2 escenarios:

Primero puede ser usado para inyectar objetos del modelo de datos a la vista, esto hace que sea especialmente útil para asegurar que una JSP tiene todos los datos que necesita.

La segunda es utilizarlo para leer datos de un modelo de datos existentes y asignarlo al controlador por medio de los parametros de un metodo.

mmmmm de verdad macho no me queda claro!! Me lo cuentas mas fácil ? Si !!

Por ejemplo tenemos un combo en un formulario y en la inicializacion es necesario cargarlos con valores,  que hacemos ¿  A ver en primera instancia nuestra vista tiene que contener la variable sobre la que trabajaremos, por eso imaginemos un pequeño fragmento de codigo jsp para cargar un combo.

<form:select path="country">
<form:option value="NONE" label="--- Select ---"/>
<form:options items="${countryList}" />

</form:select>

Nota: Si observamos no es un codigo completado definimos el control select y posterior definimos una opcion, la primera por default para indicarle al usuario que debe seleccionar, posterior define una opcion que se encuentra asociado a una variable ${countryList}

Ahora necesito el metodo que se encargue de cargar esta variable que por supuesto tendra en su cabecera la notacion @ModelAttribute que debe hacer referencia a la variable antes mencionada ${countryList} para que spring enlace de manera automática esta definición a la necesidad de la vista, miremos un pequeño fragmento de codigo java que servira a los fines.

@ModelAttribute("countryList")
public Map<String,String> populateCountryList() {

//Data referencing for java skills list box
Map<String,String> country = new LinkedHashMap<String,String>();
country.put("US", "United Stated");
country.put("CHINA", "China");
country.put("SG", "Singapore");
country.put("MY", "Malaysia");

return country;

}

Nota: a nivel código no es mas que un método que gestiona una colección y la retorna.

Si si amigo acabamos de realizar la inicializacion de un control de un formulario de una manera facil y desacoplada, por lo que acabamos de abordar el primer escenario.

Ahora el segundo escenario y lo podríamos plantear en un ejemplo de los mas comunes, tengo un formulario que se cargo y se realiza un submit y por supuesto quiero operar con los datos del formulario desde mi controlador para luego realizar otras acciones. 

Primero vamos a tener que definir un método que por supuesto escuche las solicitudes del cliente con @RequestMapping  al cual deberemos indicar que en nuestro caso el metodo debe ser por POST.

@RequestMapping(method = RequestMethod.POST)
public String processSubmit(
@ModelAttribute("customer") Customer customer,
BindingResult result, SessionStatus status) {

...................

}

Nota: Ahora debemos observar que recibimos un modelo que existe por parametros del metodo  para poder trabajar por el desde el controlador, la particularidad es que lo realizamos con @ModelAttribute.

Por otro lado en la mayoría de las veces en un formulario vamos a necesitar una logica de validación para el usuario, para esto spring proporciona la interface Validator que nos va ayudar con este fin.

Hay algunas cosas que necesitamos hacer para implementar nuestra validacion:

  • Crear una clase de validador para algún modelo de dominio y implementar la interfaz Validator.
  • La clase Sobre cargar el metodo supports donde proporcionamos la clase en la que el validator se apoya.
  • La sobrecarga al método Validate que es quien valida la información e informa los errores por medio del objeto error.


Primero vamos a mirar nuestro formulario para entender algunos conceptos.


<%@ taglib prefix="form" uri="http://www.springframework.org/tags/form"%>
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
<html>
<head>
<style>
.error {
 color: #ff0000;
}
.errorblock{
 color: #000;
 background-color: #ffEEEE;
 border: 3px solid #ff0000;
 padding:8px;
 margin:16px;
}

</style>
</head>

<body>
<h2>Formulario de validacion con Spring</h2>

<form:form method="POST" commandName="customer">

<form:errors path="*" cssClass="errorblock" element="div"/>

<table>
<tr>
<td>Nombre de Usuario : </td>
<td><form:input path="userName" /></td>
<td><form:errors path="userName" cssClass="error" /></td>
</tr>
<tr>
<td>Direccion : </td>
<td><form:textarea path="address" /></td>
<td><form:errors path="address" cssClass="error" /></td>
</tr>
<tr>
<td>Password : </td>
<td><form:password path="password" /></td>
<td><form:errors path="password" cssClass="error" /></td>
</tr>
<tr>
<td>Confirmacion Password : </td>
<td><form:password path="confirmPassword" /></td>
<td><form:errors path="confirmPassword" cssClass="error" /></td>
</tr>
<tr>
<td>Suscribir newsletter? : </td>
<td><form:checkbox path="receiveNewsletter" /></td>
<td><form:errors path="receiveNewsletter" cssClass="error" /></td>
</tr>
<tr>
<td>Frameworks Favorito: </td>
<td>
 <form:checkboxes items="${webFrameworkList}" path="favFramework" /> 
</td>
<td><form:errors path="favFramework" cssClass="error" /></td>
</tr>
<tr>
<td>Sexo: </td>
<td>
<form:radiobutton path="sex" value="M"/>Male 
<form:radiobutton path="sex" value="F"/>Female 
</td>
<td><form:errors path="sex" cssClass="error" /></td>
</tr>
<tr>
<td>Seleccione number : </td>
<td>
 <form:radiobuttons path="favNumber" items="${numberList}"  /> 
</td>
<td><form:errors path="favNumber" cssClass="error" /></td>
</tr>

<tr>
<td>Country : </td>
<td>
<form:select path="country">
 <form:option value="NONE" label="--- Select ---"/>
 <form:options items="${countryList}" />
</form:select>
</td>
<td><form:errors path="country" cssClass="error" /></td>
</tr>
 
<tr>
<td>Java Skills : </td>
<td>
<form:select path="javaSkills" items="${javaSkillsList}" multiple="true" />
</td>
<td><form:errors path="javaSkills" cssClass="error" /></td>
</tr>


<form:hidden path="secretValue" />

<tr>
<td colspan="3"><input type="submit" /></td>
</tr>
</table>
</form:form>

</body>
</html>

Lo primero que debemos destacar es que en nuestro formulario vamos a encontrar:

form:form : hace referencia por medio de commandName al modelo que sera expuesto desde el controlador por medio de modelAttribute en nuestro caso se llama "customer",  un poco mas adelante veremos la definición del modelo.

Por otro lado vamos a encontrar controles de usuario y su correspondiente objeto de error que son los que utiliza spring junto a la clase validator para generar una respuesta de error.

Ejemplo 

<tr>
<td>UserName : </td>
<td><form:input path="userName" /></td>
<td><form:errors path="userName" cssClass="error" /></td>

</tr>

Nota: un tema donde coloquemos el form:error asociado al campos es donde en tiempos de ejecucion aparecer por ejemplo la leyenda de validacion por dar un ejemplo "El campo username es requerido".

Por otro parte vamos a encontrar codigo por ejemplo la generacion de N controles 

 checkbox

<form:checkboxes items="${webFrameworkList}" path="favFramework" />

Radiobutton

<form:radiobuttons path="favNumber" items="${numberList}"  />

Nota:  donde debemos prestar atención son en las variables ${webFrameworkList} , ${numberLista}  por que de seguro es una colección que cargaremos desde nuestra controladora por medio de metodos.

Por otro lado vamos a encontrar el codigo para la carga de opciones de un combobox que la unica diferencia en comparación con la generacion de N controles es sintaxis.

<form:select path="country">
<form:option value="NONE" label="--- Select ---"/>
<form:options items="${countryList}" />

</form:select>

Creo que la siguiente parte es definir nuestro modelo que sera el que utilizaremos para la comunicación de datos entre la vista y el controlador.


package com.company.customer.model;

public class Customer{
 
 //textbox
 String userName;
 
 //textarea
 String address;
 
 //password
 String password;
 String confirmPassword;
 
 //checkbox
 boolean receiveNewsletter;
 String [] favFramework;
 
 //radio button
 String favNumber;
 String sex;
 
 //dropdown box
 String country;
 String javaSkills;
 
 //hidden value
 String secretValue;
 
 public String getSecretValue() {
  return secretValue;
 }
 public void setSecretValue(String secretValue) {
  this.secretValue = secretValue;
 }
 public String getUserName() {
  return userName;
 }
 public void setUserName(String userName) {
  this.userName = userName;
 }
 public String getAddress() {
  return address;
 }
 public void setAddress(String address) {
  this.address = address;
 }
 public String getPassword() {
  return password;
 }
 public void setPassword(String password) {
  this.password = password;
 }
 public String getConfirmPassword() {
  return confirmPassword;
 }
 public void setConfirmPassword(String confirmPassword) {
  this.confirmPassword = confirmPassword;
 }
 public boolean isReceiveNewsletter() {
  return receiveNewsletter;
 }
 public void setReceiveNewsletter(boolean receiveNewsletter) {
  this.receiveNewsletter = receiveNewsletter;
 }
 public String[] getFavFramework() {
  return favFramework;
 }
 public void setFavFramework(String[] favFramework) {
  this.favFramework = favFramework;
 }
 public String getFavNumber() {
  return favNumber;
 }
 public void setFavNumber(String favNumber) {
  this.favNumber = favNumber;
 }
 public String getSex() {
  return sex;
 }
 public void setSex(String sex) {
  this.sex = sex;
 }
 public String getCountry() {
  return country;
 }
 public void setCountry(String country) {
  this.country = country;
 }
 public String getJavaSkills() {
  return javaSkills;
 }
 public void setJavaSkills(String javaSkills) {
  this.javaSkills = javaSkills;
 }
}

Nota: A  nivel codigo nos vamos a dar cuenta que no es otra cosa que una clase con sus correspondientes getter y setter.

Ahora debemos entender el validattor que generamos para nuestra vista.


package com.company.customer.validator;

import org.springframework.validation.Errors;
import org.springframework.validation.ValidationUtils;
import org.springframework.validation.Validator;

import com.company.customer.model.Customer;

public class CustomerValidator implements Validator{

 public boolean supports(Class clazz) {
  //just validate the Customer instances
  return Customer.class.isAssignableFrom(clazz);

 }

 public void validate(Object target, Errors errors) {
  
  ValidationUtils.rejectIfEmptyOrWhitespace(errors, "userName",
    "required.userName", "El campo Nombre de Usuario es Necesario.");
  
  ValidationUtils.rejectIfEmptyOrWhitespace(errors, "address",
    "required.address", "El campo direccion es Necesarios.");
  
  ValidationUtils.rejectIfEmptyOrWhitespace(errors, "password",
    "required.password", "El campo clave es Necesarios.");
   
  ValidationUtils.rejectIfEmptyOrWhitespace(errors, "confirmPassword",
    "required.confirmPassword", "El campo clave es Necesarios.");
  
  ValidationUtils.rejectIfEmptyOrWhitespace(errors, "sex", 
    "required.sex", "El campo sexo es Necesarios.");
  
  ValidationUtils.rejectIfEmptyOrWhitespace(errors, "favNumber", 
    "required.favNumber", "El campo Numero es Necesarios.");
  
  ValidationUtils.rejectIfEmptyOrWhitespace(
    errors, "javaSkills", "required.javaSkills","El campo Skill es Necesarios.");
  
  Customer cust = (Customer)target;
  
  if(!(cust.getPassword().equals(cust.getConfirmPassword()))){
   errors.rejectValue("password", "notmatch.password");
  }
  
  if(cust.getFavFramework().length==0){
   errors.rejectValue("favFramework", "required.favFrameworks");
  }

  if("NONE".equals(cust.getCountry())){
   errors.rejectValue("country", "required.country");
  }
  
 }
 
}

Donde en el metodo supports asignamos la clase sobre la que se apoya en validattor.

Para el metodo Validate tiene 2 parametros, target hace referencia al objeto que sera validado y el parametro error hace referencia a los objetos definidos en la vista.

Para la validación de algo tan típico como determinar si el campo de entrada está vacío podemos utilizar la clase ValidationUtils de Spring, donde podemos determinar el nombre del campo afectado, que mensaje internacionalizado e incluso parametrizado queremos mostrar, y en caso de no encontrar la clave del mensaje, poder mostrar un texto por defecto, para nuestro ejemplo validados como campo requerido la mayoria de los campos.

Pero como podemos ver es posible realizar validaciones manuales accediendo al objeto por metodo del parametro target  e informando el error asociado a un campo por medio de rejectValue.

Por ejemplo validamos que el combo no tenga seleccionado NONE y si hay error retornamos el error asociado al campo.

if("NONE".equals(cust.getCountry())){
errors.rejectValue("country", "required.country");
}


Ahora es momento de trabajar con nuestro controlador donde lograremos integrar todos los hitos mencionados.

package com.company.customer.controller;

import java.sql.Date;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.propertyeditors.CustomDateEditor;
import org.springframework.stereotype.Controller;
import org.springframework.ui.ModelMap;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.WebDataBinder;
import org.springframework.web.bind.annotation.InitBinder;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.support.SessionStatus;

import com.company.customer.model.Customer;
import com.company.customer.validator.CustomerValidator;

@Controller
@RequestMapping("/customer.htm")
public class CustomerController{
 
 CustomerValidator customerValidator;
 
 @Autowired
 public CustomerController(CustomerValidator customerValidator){
  this.customerValidator = customerValidator;
 }
 
 @RequestMapping(method = RequestMethod.POST)
 public String processSubmit(
   @ModelAttribute("customer") Customer customer,
   BindingResult result, SessionStatus status) {
  
  customerValidator.validate(customer, result);
  
  if (result.hasErrors()) {
   //if validator failed
   return "CustomerForm";
  } else {
   status.setComplete();
   //form success
   return "CustomerSuccess";
  }
 }
 
 @RequestMapping(method = RequestMethod.GET)
 public String initForm(ModelMap model){
  
  Customer cust = new Customer();
  //Make "Spring MVC" as default checked value
  cust.setFavFramework(new String []{"Spring MVC"});
  
  //Make "Make" as default radio button selected value
  cust.setSex("M");
  
  //make "Hibernate" as the default java skills selection
  cust.setJavaSkills("Hibernate");
  
  //initilize a hidden value
  cust.setSecretValue("I'm hidden value");
  
  //command object
  model.addAttribute("customer", cust);
  
  //return form view
  return "CustomerForm";
 }
 
 
 @ModelAttribute("webFrameworkList")
 public List<String> populateWebFrameworkList() {
  
  //Data referencing for web framework checkboxes
  List<String> webFrameworkList = new ArrayList<String>();
  webFrameworkList.add("Spring MVC");
  webFrameworkList.add("Struts 1");
  webFrameworkList.add("Struts 2");
  webFrameworkList.add("JSF");
  webFrameworkList.add("Apache Wicket");
  
  return webFrameworkList;
 }
 
 @InitBinder
 public void initBinder(WebDataBinder binder) {
  SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd");
  
  binder.registerCustomEditor(Date.class, new CustomDateEditor(dateFormat, true));
  
 }
 
 @ModelAttribute("numberList")
 public List<String> populateNumberList() {
  
  //Data referencing for number radiobuttons
  List<String> numberList = new ArrayList<String>();
  numberList.add("Number 1");
  numberList.add("Number 2");
  numberList.add("Number 3");
  numberList.add("Number 4");
  numberList.add("Number 5");
  
  return numberList;
 }
 
 @ModelAttribute("javaSkillsList")
 public Map<String,String> populateJavaSkillList() {
  
  //Data referencing for java skills list box
  Map<String,String> javaSkill = new LinkedHashMap<String,String>();
  javaSkill.put("Hibernate", "Hibernate");
  javaSkill.put("Spring", "Spring");
  javaSkill.put("Apache Wicket", "Apache Wicket");
  javaSkill.put("Struts", "Struts");
  
  return javaSkill;
 }

 @ModelAttribute("countryList")
 public Map<String,String> populateCountryList() {
  
  //Data referencing for java skills list box
  Map<String,String> country = new LinkedHashMap<String,String>();
  country.put("US", "United Stated");
  country.put("CHINA", "China");
  country.put("SG", "Singapore");
  country.put("MY", "Malaysia");
  
  return country;
 }
 
}

Ahora de seguro notamos que en primera instancia la clase controladora lleva un Annotation @controller y que por cada metodo mapeamos el metodo a una url con @RequestMapping.

Avisaaa!!  me quieres decir que con esto voy a poder escuchar en los métodos cuando realicen una solicitud URL como las que mapeamos ?

Ahora los siguientes metodos utilizaran @ModelAttribute haciendo referencia a las variables definidas del lado de la vista para cargar su contenido y decir verdad no presentan grandes problematicas en el codigo, simplemente son metodos que retornan colecciones que precargamos pero podriamos levantar datos de un modelo de dato o archivos fisicos y tendria la misma mecanica.

  • @ModelAttribute("webFrameworkList")
  • @ModelAttribute("numberList")
  • @ModelAttribute("javaSkillsList")
  • @ModelAttribute("countryList")


Por otro lado podemos notar que realizaremos al parecer una inyeccion por contructor para el validator del formulario para poder ser usado en el controlador.

Luego contamos con 2 metodos imporntantes 

@RequestMapping(method = RequestMethod.GET): metodo que se dispara cuando llamamos al formulario. 
@RequestMapping(method = RequestMethod.POST): metodo que se dispara cuando realizamos un post desde el formulario, si observamos es una logica muy simple dispara el validator si hay error retorna a la pagina de formulario para manifestar los errores que encontro, en caso contrario direcciona a una pagina que solo muestra los datos cargados.


Que jodido el tema hasta ahora siempre vimos que el retorno de los metodos era un un ModelAndView pero en este caso son string. Si observamos retornamos String pero no cualquiera sino el nombre de las vistas a la que retornaremos. O sea el ViewResolver redirecciona con string ? Yesss.

Cuando en anteriores entregas el retorno era un ModelAndView que como su nombre lo indica es una composicion del modelo logico de datos y vista o interfaz de usuario. En el contructor le mandamos el nombre de la vista JSP  y luego con el metodo addObjetc le agregamos un valor a key para pasar objetos. 

Ahora ?  con el @ModelAttribute por parametro del metodo invocado le transferimos a la siguiente vista, esto lo  podremos ver en el metodo que realiza el post luego de realizar la validacion nos direcciona a la pagina que mostrara los datos cargados.

<%@ taglib prefix="form" uri="http://www.springframework.org/tags/form"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>

<html>
<body>
<h2>Spring's form tags example</h2>

<table>
<tr>
<td>UserName :</td><td>${customer.userName}</td>
</tr>
<tr>
<td>Address :</td><td>${customer.address}</td>
</tr>
<tr>
<td>Password :</td><td>${customer.password}</td>
</tr>
<tr>
<td>Confirm Password :</td><td>${customer.confirmPassword}</td>
</tr>
<tr>
<td>Receive Newsletter :</td><td>${customer.receiveNewsletter}</td>
</tr>
<tr>
<td>Favourite Web Frameworks :</td>
<td>
<c:forEach items="${customer.favFramework}" var="current">
   [<c:out value="${current}" />]
</c:forEach>
</td>
</tr>
<tr>
<td>Sex :</td><td>${customer.sex}</td>
</tr>
<tr>
<td>Favourite Number :</td><td>${customer.favNumber}</td>
</tr>
<tr>
<td>Country :</td><td>${customer.country}</td>
</tr>
<tr>
<td>Java Skills :</td><td>${customer.javaSkills}</td>
</tr>
<tr>
<td>Hidden Value :</td><td>${customer.secretValue}</td>
</tr>

</table>

</body>
</html>

Nota: esto podríamos realizarlo con un objeto ModelAndView  transfiriendo el objeto del modelo a la vista y obtendríamos un resultado similar.


Paso 4: Definir nuestro Spring Bean Configuration File

src/main/webapp/WEB-INF/mvc-dispacher-servlet.xml: me voy a contradecir un poco en mis comentarios, aqui es donde comienza la magia, por que definiendo los aspectos necesarios la maquina spring comienza a funcionar.

<beans xmlns="http://www.springframework.org/schema/beans"
 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
 xmlns:context="http://www.springframework.org/schema/context"
 xsi:schemaLocation="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">

 <context:component-scan base-package="com.company.customer.controller" />
 
 <bean class="com.company.customer.validator.CustomerValidator" />
 
  <!-- Register the Customer.properties -->
 <bean id="messageSource"
  class="org.springframework.context.support.ResourceBundleMessageSource">
  <property name="basename" value="Customer" />
 </bean>
  
 <bean id="viewResolver"
       class="org.springframework.web.servlet.view.InternalResourceViewResolver" >
          <property name="prefix">
              <value>/WEB-INF/pages/</value>
           </property>
          <property name="suffix">
             <value>.jsp</value>
          </property>
    </bean>

</beans>

Pero no veo que definamos el  handler que permite que dispatchservlet pueda interpretar los controladores y pueda  mapear una URL. 

Exacto!!

Con component-scan logramos este objetivo, si con esto buscara las Annotation en el paquete que le indicamos y posibilitara que el mecanismo spring escuche las solicitudes.

Pero en esta ocacion contamos con un nuevo bean que hace referecia a la clase  ResourceBundleMessageSource que nos posibilita leer label de un archivo de recurso que en este caso se llama Customer, si miramos nuestro proyecto encontraremos un archivo de recurso con los label que utiliza nuestro proyecto.

required.userName = El campo Username es Necesario!
required.address = El campo Direccion es Necesario!
required.password = El campo Password es Necesario!
required.confirmPassword = El campo Password es Necesario!
required.favFrameworks = Debe seleccionar Framework!
required.sex = Debe seleccionar el Sexo!
required.favNumber = Debe Seleccionar un Numero!
notmatch.password = El campo Password y confirmar Password no son iguales!
required.country = Debe Seleccionar Country!
required.javaSkills = Debe Seleccionar Skill!

Como ultima opción y no menos importante es la definicion del ViewResolver que es utilizado por el controlador para realizar el redireccionamiento adecuado,  en nuestra definicion le decimos que dentreo del directorio /pages vamos a encontrar nuestras vistas y van a ser de extencion jsp.

Paso 5:  Entendiendo la ejecución

 Para facilitar la prueba definimos un index que haga referencia a URL que posibiliten entender el mapeo que realizamos en el contenedor bean.

Primero ejecutemos en tomcat.





Tendremos una index que nos proporciona el link a nuestro formulario. 


El link nos lleva al formulario de carga.


En primera instancia podremos ver como nuestro formulario se encuentra generado y sus controles tienen los valores que definimos desde la clase controladora. 

Si no ingresamos valores y realizamos el post podremos ver la utilización de los validattor.


Si pasamos la validación correspondiente podremos ver la vista resultado de los datos cargados.



El código del proyecto lo puedes descargar desde aquí.

codigofuente.zip