Descargar como pdf o txt
Descargar como pdf o txt
Está en la página 1de 79

Versin de Spring utilizada: 4.2.

Tabla de Contenidos
Descripcin
1. Contenido
2. Software requerido
3. La aplicacin que vamos a construir
1. Aplicacion Base y Configuracion del Entorno
1.1. Crear la estructura de directorios del proyecto
1.2. Crear 'index.jsp'
1.3. Desplegar la aplicacin en el servidor
1.4. Comprobar que la aplicacin funciona
1.5. Descargar Spring Framework
1.6. Modicar 'web.xml' en el directorio 'src/main/webapp/WEB-INF'
1.7. Crear el Controlador
1.8. Escribir un test para el Controlador
1.9. Crear la Vista
1.10. Compilar, desplegar y probar la aplicacin
1.11. Resumen
2. Desarrollando y Configurando la Vista y el Controlador
2.1. Configurar JSTL y aadir un archivo de cabecera JSP
2.2. Mejorar el controlador
2.3. Separar la vista del controlador
2.4. Resumen
3. Desarrollando la Lgica de Negocio
3.1. Revisar la regla de negocio del Sistema de Mantenimiento de Inventario
3.2. Aadir algunas clases a la lgica de negocio
3.3. Resumen
4. Desarrollando la Interface Web
4.1. Aadir una referencia a la lgica de negocio en el controlador
4.2. Modificar la vista para mostrar datos de negocio y aadir soporte para archivos de mensajes
4.3. Aadir datos de prueba para rellenar algunos objetos de negocio
4.4. Aadir una ubicacin para los mensajes
4.5. Aadir un formulario
4.6. Aadir un controlador de formulario
4.7. Resumen
5. Implementando Persistencia en Base de Datos
5.1. Creacin y rellenado de la base de datos
5.2. Crear una implementacion para JPA de un Objeto de Acceso a Datos (DAO)
5.3. Implementar tests para la implementacion DAO sobre JPA
5.4. Resumen
6. Integrando la Aplicacin Web con la Capa de Persistencia
6.1. Modificar la Capa de Servicio
6.2. Resolver los tests fallidos
6.3. Crear un nuevo contexto de aplicacion para configurar la capa de servicio
6.4. Test final de la aplicacion completa
6.5. Resumen
Descripcin
Este documento es una gua paso a paso sobre cmo desarrollar una aplicacin web,
partiendo de cero, usando Spring Framework.

Se asume que posees un conocimiento superficial de Spring, por lo que este tutorial
te sera til si estas aprendiendo o investigando Spring. Durante el tiempo que
trabajes a travs del material presentado en el tutorial, podrs ver cmo encajan
diversas partes de Spring Framework dentro de una aplicacin web Spring MVC, como
Inversin de Control (Inversion of Control - IoC), Programacin Orientada a Aspectos
(Aspect-Oriented Programming - AOP) y la integracin con las diversas libreras de
servicios (como JPA).

Spring provee diversas opciones para configurar tu aplicacin. La forma ms popular


es usando archivos XML. sta es la forma ms tradicional, soportada desde la primera
versin de Spring. Con la introduccin de Anotaciones en Java 5, ahora disponemos
de una manera alternativa de configurar nuestras aplicaciones Spring. La nueva
versin Spring 4.2.5 introduce un amplio soporte para configurar una aplicacin web
mediante anotaciones. Este documento usa el estilo ms moderno de configuracin
mediante anotaciones.

Ten en cuenta que no se tratar ninguna informacin en profundidad en este tutorial,


as como ningn tipo de teora; hay multitud de libros disponibles que cubren Spring
en profundidad; siempre que una nueva clase o caracterstica sea usada en el tutorial,
se mostrarn enlaces a la seccin de documentacin de Spring, donde la clase o
caracterstica es tratada en profundidad.
1. Contenido
La siguiente lista detalla todas las partes de Spring Framework que son cubiertas a
lo largo del tutorial.

Inversin de Control (IoC)

El framework Spring Web MVC

Acceso a Datos mediante JPA

Integracin mediante tests


2. Software Requerido
Se requiere el siguiente software y su adecuada configuracin en el entorno. Deberas
sentirte razonablemente confortable usando las siguientes tecnologas.

Java SDK 1.7+

SpringSource Tool Suite 3.7.3 (Recomendado, pero no necesario)

Maven 2
El proyecto SpringSource Tool Suite (http://www.springsource.com/products/sts)
proporciona un excelente entorno para el desarrollo de aplicaciones que utilicen
Spring Framework. SpringSource Tool Suite integra sobre la plataforma Eclipse
(http://www.eclipse.org/), entre otras cosas, el plugin de Spring IDE as como tambin
da soporte al servidor Pivotal tc Server (Tomcat). Por supuesto, puedes usar
cualquier variacin de las versiones de software anteriores. En concreto, los pasos
de este tutorial son reproducibles sobre el servidor GlassFish v3.1.2
(https://glassfish.java.net/). Si quieres usar NetBeans o IntelliJ en lugar de Eclipse, o
Jetty en lugar de Tomcat o GlassFish, ciertos pasos de este tutorial no se
correspondern directamente con tu entorno, pero deberas ser capaz de seguir
adelante de todas maneras.
3. La aplicacin que vamos a construir
La aplicacin que vamos a construir desde cero a lo largo de este tutorial es un
sistema de mantenimiento de inventario muy bsico. Este sistema de mantenimiento
de inventario est muy limitado en alcance y funcionalidad; abajo puedes ver un
diagrama de casos de uso ilustrando los sencillos casos de uso que implementaremos.
La razn por la que la aplicacin es tan limitada es que puedas concentrarte en las
caractersticas especficas de Spring Web MVC y Spring, y olvidar los detalles ms
sutiles del mantenimiento de inventario.
Diagrama de casos de uso de un sistema de mantenimiento de inventario

Comenzaremos configurando la estructura bsica de los directorios para nuestra


aplicacion, descargando las librerias necesarias, etc. El primer paso nos
proporcionar una base slida sobre la que desarrollar de forma adecuada las
secciones 2, 3, y 4.

Una vez terminada la configuracin bsica, introduciremos Spring, comenzando con


el framework Spring Web MVC. Usaremos Spring Web MVC para mostrar el stock
inventariado, lo que implicar escribir algunas clases simples en Java y algunos JSP.
Entonces introduciremos acceso de datos y persistencia en nuestra aplicacin, usando
para ello el soporte que ofrece Spring para JPA.

Una vez hayamos finalizado todos los pasos del tutorial, tendremos una aplicacin
que realiza un mantenimiento bsico de stock, incluyendo listados de stock y el
incremento de precios de los productos de dicho stock.
Captulo 1. Aplicacin Base y Configuracin del Entorno
1.1. Crear la estructura de directorios del proyecto
Necesitamos crear un directorio donde alojar todos los archivos que vayamos
creando, as que comenzaremos creando un proyecto en el Workspace
llamado 'springapp'. Utilizaremos Maven2 para facilitar la creacin de la estructura
de directorios del proyecto as como la inclusin de las libreras necesarias.

Creacin del nuevo proyecto usando Maven

Tras seleccionar el proyecto de tipo Maven y pinchar 'Next' para mantener la


localizacin del proyecto dentro del Workspace, debemos seleccionar el arquetipo del
proyecto. Los arquetipos son patrones o modelos genricos a partir de los cuales se
pueden crear proyectos de un determinado tipo. Maven proporciona un conjunto de
estructuras de proyectos (esto es, el rbol de directorios, ficheros que aparecern en
el proyecto, etc.) entre los cuales podemos elegir. De acuerdo con la naturaleza del
proyecto que se crear en este tutorial, debemos seleccionar el arquetipo 'maven-
archetype-webapp'.
Seleccin del arquetipo del proyecto

Maven, entre otras funcionalidades, es til como herramienta para la gestin de


dependencias entre proyectos y permite definir diferentes componentes (llamados
artifacts) que forman parte de un grupo de trabajo con una entidad mayor. De
momento, slo disponemos de nuestra simple aplicacin para la gestin de
inventario.
Definicin del nombre del proyecto

Si apareciera un warning en el proyecto causado por el uso de diferentes versiones


de la JDK, ajustarlas conveniente configurando el Build Path del proyecto. En las
ltimas versiones de STS tambin aparece un error en el fichero JSP porque todav
no se ha incorporado la dependencia 'servlet-api'. Se puede seguir adelante con
el tutorial a pesar de este error, ya que lo solucionaremos en el paso 1.5.

Una vez que el proyecto se ha creado, podemos identificar la siguiente estructura:

El subdirectorio 'src/main/resources' que contendr todos los recursos


utilizados por la aplicacin.

El subdirectorio 'src/main/webapp' que alojar todos los archivos que no


sean cdigo fuente Java, como archivos JSP y de configuracin.
El directorio 'target' donde se generar el archivo WAR que usaremos para
almacenar y desplegar rpidamente nuestra aplicacin.

El fichero 'pom.xml' que contiene las dependencias Maven.

A continuacin puedes ver una captura de pantalla que muestra como quedara la
estructura de directorios si has seguido las instrucciones correctamente. (La imagen
muestra dicha estructura desde el SpringSource Tool Suite (STS): no se necesita usar
STS para completar este tutorial, pero usndolo podrs hacerlo de manera mucho
ms sencilla).

La estructura de directorios del proyecto


1.2. Crear 'index.jsp'

Puesto que estamos creando una aplicacin web, Maven ya ha creado un archivo JSP
muy simple llamado 'index.jsp' en el directorio 'src/main/webapp'. El
archivo 'index.jsp' es el punto de entrada a nuestra aplicacin. Para
familiarizarnos con el entorno, podemos cambiarlo por el que se muestra a
continuacin:

'springapp/src/main/webapp/index.jsp':
<html>
<head><title>Example :: Spring Application</title></head>
<body>
<h1>Example - Spring Application</h1>
<p>This is my test.</p>
</body>
</html>

Asimismo, Maven tambin ha creado un archivo llamado 'web.xml' dentro del


directorio 'src/main/webapp/WEB-INF' con la configuracin bsica para ejecutar la
aplicacin por primera vez. Proponemos modificarlo por el que se muestra a
continuacin para utilizar una especificacin ms moderna de JavaEE.

'springapp/src/main/webapp/WEB-INF/web.xml':
<?xml version="1.0" encoding="UTF-8"?>

<web-app version="2.5" xmlns="http://java.sun.com/xml/ns/javaee"


xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:web="http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee
http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd">

<display-name>Springapp</display-name>

</web-app>
1.3. Desplegar la aplicacin en el servidor
Para compilar, construir y desplegar la aplicacin automticamente slo es necesario
seleccionar 'Run as > Run on Server' sobre el men contextual que aparece
cuando se pincha el botn derecho sobre el nombre del proyecto. A continuacin,
debemos seleccionar el servidor desde el cuadro de dilogo que ofrece los servidores
dados de alta en el entorno. Por ejemplo: Pivotal tc Server, Tomcat, GlassFish, etc.
1.4. Comprobar que la aplicacin funciona
Los pasos anteriores abrirn una pestaa en el entorno de desarrollo STS donde se
puede ver el contenido de la pgina 'index.jsp'. De manera alternativa, se puede
abrir un navegador y acceder a la pgina de inicio de la aplicacin en la siguiente
URL: http://localhost:8080/springapp.
(La imagen muestra la visualizacin sobre STS: el nmero de puerto puede variar
dependiendo del servidor utilizado).
La pgina de inicio de la aplicacin

Se recomienda detener el servidor para evitar la recarga automtica mientras se


sigue desarrollando el proyecto.
1.5. Descargar Spring Framework
Utilizaremos Maven para gestionar las dependencias del proyecto con Spring
Framework as como para descargar otras libreras adicionales necesarias. Se puede
consultar MVNrepository para obtener los datos concretos de las dependencias que
incluiremos en el fichero 'pom.xml'. En este fichero, hemos introducido una
propiedad llamada 'org.springframework.version' y le hemos dado el
valor '4.2.5.RELEASE'. Este valor corresponde con la ltima versin disponible de
Spring Framework en el momento en el que ha escrito el tutorial. Para consultar las
versiones disponibles, se puede hacer una bsqueda en Sonatype de la dependencia
(artifact) 'spring-core'. La introduccin de propiedades con el valor de la versin
de cada dependencia facilitar la actualizacin del proyecto a nuevas versiones.
Propiedades del fichero 'pom.xml'

A continuacin, crearemos las siguientes dependencias para el proyecto desde la


pestaa 'Dependencies' del fichero 'pom.xml'. Las libreras seran descargadas y
aadidas automticamente al proyecto en el momento en el que guardemos el
fichero 'pom.xml'
Group Id Artifact Id

junit junit 4.12

org.springframework spring-core ${org.spr

org.springframework spring-webmvc ${org.spr

javax.servlet servlet-api 2.5

Ntese como la dependencia 'junit' (incluida por defecto en el fichero 'pom.xml')


ha sido actualizada a la versin '4.12'. Por otra parte, la dependencia 'servlet-
api' ha sido marcada como 'Provided', ya que ser proporcionada por el servidor
sobre el que se despliegue la aplicacin.
Dependencias del fichero 'pom.xml'

Segn el reporte de Roberto Rodrguez, si se est usando WebLogic 11G, es necesario


aadir la dependencia de 'joda-time' para evitar un error del tipo
'java.lang.ClassNotFoundException: org.joda.time.LocalDate'. Los detalles de esta
dependencia se muestran a continuacin.
Group Id Artifact Id

joda-time joda-time

1.6. Modificar 'web.xml' en el directorio 'src/main/webapp/WEB-


INF'

Sitate en el directorio 'src/main/webapp/WEB-INF'. Modifica el


archivo 'web.xml' del que hemos hablado anteriormente. Vamos a definir
un DispatcherServlet (tambin llamado 'Controlador Frontal' (Crupi et al)). Su
misin ser controlar hacia dnde sern enrutadas todas nuestras solicitudes
basndose en informacin que introduciremos posteriormente. La definicin del
servlet tendr como acompaante una entrada <servlet-mapping/> que mapear
las URL que queremos que apunten a nuestro servlet. Hemos decidido permitir que
cualquier URL con una extensin de tipo '.htm' sea enrutada hacia el
servlet 'springapp' ( DispatcherServlet).

'springapp/src/main/webapp/WEB-INF/web.xml':
<?xml version="1.0" encoding="UTF-8"?>

<web-app version="2.5" xmlns="http://java.sun.com/xml/ns/j2ee"


xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:web="http://java.sun.com/xml/ns/j2ee/web-app_2_5.xsd"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee
http://java.sun.com/xml/ns/j2ee/web-app_2_5.xsd">

<display-name>Springapp</display-name>

<servlet>
<servlet-name>springapp</servlet-name>
<servlet-
class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/spring/app-config.xml</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>

<servlet-mapping>
<servlet-name>springapp</servlet-name>
<url-pattern>*.htm</url-pattern>
</servlet-mapping>

</web-app>
A continuacin, creamos el subdirectorio 'src/main/webapp/WEB-INF/spring' y
dentro el archivo llamado 'app-config.xml'. Este archivo contendr las definiciones
de beans (POJO's) usados por el DispatcherServlet. Es decir, este archivo es
el WebApplicationContext donde situaremos todos los componentes. Por tanto,
utilizaremos el asistente para la creacin de ficheros de tipo 'Spring Bean
Configuration File'. (Si no se utiliza STS y no se dispone del plugin Spring IDE de
Eclipse, basta crear un fichero xml como el que se mostrar a continuacin).

Creacin del fichero 'app-config.xml'

Tras haber introducido el nombre del fichero, es necesario introducir los namespaces
que se utilizarn. Seleccionamos los namespaces 'beans', 'context' y 'mvc' en las
versiones que corresponden con la versin '4.0' de Spring Framework.
Seleccin de los namespaces del fichero 'app-config.xml'

Por ltimo, activamos la deteccin automtica de componentes a travs del uso de


anotaciones. El fichero 'app-config.xml', por tanto, deber tener el siguiente
aspecto.

'springapp/src/main/webapp/WEB-INF/spring/app-config.xml':
<?xml version="1.0" encoding="UTF-8"?>

<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"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/mvc
http://www.springframework.org/schema/mvc/spring-mvc.xsd">

<!-- Scans the classpath of this application for @Components to


deploy as beans -->
<context:component-scan base-
package="com.companyname.springapp.web" />

<!-- Configures the @Controller programming model -->


<mvc:annotation-driven/>

</beans>
1.7. Crear el Controlador

Vamos a crear una clase anotada con la etiqueta @Controller (a la que


llamaremos HelloController.java) y que estar definida dentro del
paquete 'com.companyname.springapp.web' del directorio 'src/main/java'

'springapp/src/main/java/com/companyname/springapp/web/HelloController
.java':
package com.companyname.springapp.web;

import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.servlet.ModelAndView;

@Controller
public class HelloController {

protected final Log logger = LogFactory.getLog(getClass());

@RequestMapping(value="/hello.htm")
public ModelAndView handleRequest(HttpServletRequest request,
HttpServletResponse response)
throws ServletException, IOException {

logger.info("Returning hello view");

return new ModelAndView("hello.jsp");


}
}

Esta implementacin del controlador, mediante la anotacin @Controller, es muy


bsica. Ms tarde la iremos expandiendo. En Spring Web MVC, los
componentes @Controller manejan las solicitudes y devuelven normalmente un
objeto ModelAndView. En este caso, uno llamado 'hello.jsp', el cual hace
referencia al nombre del archivo JSP que vamos a crear a continuacin. El modelo
que esta clase devuelve es resuelto a travs del ViewResolver. Puesto que no hemos
definido explictamente un ViewResolver, vamos a obtener uno por defecto de
Spring que simplemente redigir a una direccin URL que coincida con el nombre de
la vista especificada. Ms tarde modificaremos este comportamiento. Adems, hemos
especificado un logger de manera que podemos verificar que pasamos por el
manejador en cada momento. Usando STS, estos mensajes de log deben mostrarse
en la pestaa 'Console'.
1.8. Escribir un test para el Controlador

Los tests son una parte vital del desarrollo del software. El mejor momento para
escribir los tests es durante el desarrollo, no despus, de manera que aunque nuestro
controlador no contiene lgica demasiado compleja vamos a escribir un test para
probarlo. Esto nos permitir hacer cambios en el futuro con total seguridad. Vamos
a crear (si no existiera) un nuevo directorio de tipo 'Source
Folder' llamado 'src/test/java'. Aqu es donde alojaremos todos nuestros tests,
en una estructura de paquetes que ser idntica a la estructura de paquetes que
tenemos en 'src/main/java'.

Creamos una clase de test llamada 'HelloControllerTests' dentro del


paquete 'com.companyname.springapp.web'. Para ello seleccionamos el
tipo 'JUnit Test Case' del asistente de creacin de ficheros.

'springapp/src/test/java/com/companyname/springapp/web/HelloController
Tests.java':
package com.companyname.springapp.web;

import static org.junit.Assert.*;

import org.junit.Test;
import org.springframework.web.servlet.ModelAndView;

public class HelloControllerTests {

@Test
public void testHandleRequestView() throws Exception{
HelloController controller = new HelloController();
ModelAndView modelAndView = controller.handleRequest(null,
null);
assertEquals("hello.jsp", modelAndView.getViewName());
}

Para ejecutar el test (y todos los tests que escribamos en el futuro), basta con que
seleccionemos 'Run as > JUnit Test' sobre el men contextual que aparece
cuando se pincha el botn derecho sobre la clase de test. Si el test se ejecuta de
forma satisfactoria podrs ver una barra verde que lo indica.
1.9. Crear la Vista

Ahora es el momento de crear nuestra primera vista. Como hemos mencionado antes,
estamos redirigiendo hacia una pgina JSP llamada 'hello.jsp'. Para empezar,
crearemos este fichero en el directorio 'src/main/webapp'.

'springapp/src/main/webapp/hello.jsp':
<html>
<head><title>Hello :: Spring Application</title></head>
<body>
<h1>Hello - Spring Application</h1>
<p>Greetings.</p>
</body>
</html>
1.10. Compilar, desplegar y probar la aplicacin
Para ejecutar la aplicacin de nuevo, seleccionamos de nuevo 'Run as > Run on
Server' sobre el men contextual que aparece cuando se pincha el botn derecho
sobre el nombre proyecto. (En algunos casos, es necasario reiniciar el servidor para
asegurar que la aplicacin se actualiza correctamente).

Probemos esta nueva versin de la aplicacin. Desde el navegador que ofrece STS,
o desde cualquier otro navegador, abrir la URL http://localhost:8080/springapp/hello.htm.
La aplicacin actualizada
1.11. Resumen
Echemos un vistazo rpido a las partes de nuestra aplicacion que hemos creado
hasta ahora.

Una pgina de inicio, 'index.jsp', la pgina de bienvenida de nuestra


aplicacin. Fue usada para comprobar que nuestra configuracin era correcta.
Ms tarde la cambiaremos para proveer un enlance a nuestra aplicacin.

Un controlador frontal, DispatcherServlet, con el correspondiente archivo de


configuracin 'app-config.xml'.

Un controlador de pgina, HelloController, con funcionalidad limitada


simplemente devuelve un objeto ModelAndView. Actualmente tenemos un
modelo vaco, ms tarde proveeremos un modelo completo.

Una unidad de test para la pgina del controlador, HelloControllerTests,


para verificar que el nombre de la vista es el que esperamos.

Una vista, 'hello.jsp', que de nuevo es extremadamente sencilla. Las


buenas noticias son que el conjunto de la aplicacin funciona y que estamos
listos para aadir ms funcionalidad.
A continuacin puedes ver una captura de pantalla que muestra el aspecto que
debera tener la estructura de directorios del proyecto despus de seguir todas las
instrucciones anteriores.

La estructura de directorios del proyecto al final de la parte 1


Captulo 2. Desarrollando y Configurando la Vista y el
Controlador
sta es la Parte 2 del tutorial paso a paso sobre cmo desarrollar una aplicacin web
desde cero usando Spring Framework. En la Parte 1 hemos configurado el entorno y
montado una aplicacin bsica que ahora vamos a desarrollar.

Esto es lo que hemos implementado hasta ahora:

Una pgina de inicio, 'index.jsp', la pgina de bienvenida de nuestra


aplicacin. Fue usada para comprobar que nuestra configuracin era correcta.
Ms tarde la cambiaremos para proveer un enlace a nuestra aplicacin.

Un controlador frontal, DispatcherServlet, con el correspondiente archivo de


configuracin 'app-config.xml'.

Un controlador de pgina, HelloController, con funcionalidad limitada


simplemente devuelve un objeto ModelAndView. Actualmente tenemos un
modelo vaco, ms tarde proveeremos un modelo completo.

Una unidad de test para la pgina del controlador, HelloControllerTests,


para verificar que el nombre de la vista es el que esperamos.

Una vista, 'hello.jsp', que de nuevo es extremadamente sencilla. Las


buenas noticias son que el conjunto de la aplicacin funciona y que estamos
listos para aadir ms funcionalidad.
2.1. Configurar JSTL y aadir un archivo de cabecera JSP
Vamos a usar la Librera Estandar JSP (JSP Standard Tag Library - JSTL), as que
comencemos definiendo la dependencia que necesitamos en el fichero 'pom.xml'.

Group Id

javax.servlet.jsp.jstl jstl-api

org.apache.taglibs taglibs-standard-impl

Vamos a crear un archivo de 'cabecera' que ser embebido en todas las paginas JSP
que escribamos despus. As estaremos seguros de que las mismas definiciones son
incluidas en todos nuestros JSP al insertar el archivo de cabecera. Tambin vamos a
poner todos nuestros archivos JSP en un directorio llamado 'views' bajo el
directorio 'src/main/webapp/WEB-INF'. Esto asegurar que nuestras vistas slo
puedan ser accedidas a travs del controlador, por lo que no ser posible acceder a
estas pginas a travs de una direccin URL. Esta estrategia podra no funcionar en
algunos servidores de aplicaciones; si se es tu caso, mueve el directorio 'views' un
nivel hacia arriba y usa 'src/main/webapp/views' como el directorio de JSP en
todos los ejemplos que encontrars a lo largo del tutorial, en lugar
de 'src/main/webapp/WEB-INF/views'.

Primero creamos un archivo de cabecera para incluir en todos los archivos JSP que
crearemos con posterioridad.

'springapp/src/main/webapp/WEB-INF/views/include.jsp':
<%@ page session="false"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt" %>

Ahora podemos actualizar 'index.jsp' para que incluya este archivo, y puesto que
estamos usando JSTL, podemos usar la etiqueta <c:redirect/> para redireccionar
hacia nuestro controlador frontal: Controller. Esto significa que todas nuestras
solicitudes a 'index.jsp' se resolvern a travs de dicho controlador. Elimina los
contenidos actuales de 'index.jsp' y reemplzalos con los siguientes:

'springapp/src/main/webapp/index.jsp':
<%@ include file="/WEB-INF/views/include.jsp" %>

<%-- Redirected because we can't set the welcome page to a virtual URL. --
%>
<c:redirect url="/hello.htm"/>

Mueve 'hello.jsp' al directorio 'src/main/webapp/WEB-INF/views'. Aade la


misma directiva include que hemos aadido en 'index.jsp' a 'hello.jsp'. Vamos
a aadir tambin la fecha y hora actual, que sern ledas desde el modelo que
pasaremos a la vista, y que mostraremos usando la etiqueta JSTL <c:out/>.

'springapp/src/main/webapp/WEB-INF/views/hello.jsp':
<%@ include file="/WEB-INF/views/include.jsp" %>

<html>
<head><title>Hello :: Spring Application</title></head>
<body>
<h1>Hello - Spring Application</h1>
<p>Greetings, it is now <c:out value="${now}"/></p>
</body>
</html>
2.2. Mejorar el controlador
Antes de actualizar la localizacin del JSP en nuestro controlador, actualicemos
nuestra unidad de test. Sabemos que necesitamos actualizar la referencia a la vista
con su nueva localizacin, 'WEB-INF/views/hello.jsp'. Tambin sabemos que
debera haber un objeto en el modelo mapeado a la clave "now".

'springapp/src/test/java/com/companyname/springapp/web/HelloController
Tests.java':
package com.companyname.springapp.web;

import static org.junit.Assert.*;

import org.junit.Test;
import org.springframework.web.servlet.ModelAndView;

public class HelloControllerTests {

@Test
public void testHandleRequestView() throws Exception{
HelloController controller = new HelloController();
ModelAndView modelAndView = controller.handleRequest(null,
null);
assertEquals("WEB-INF/views/hello.jsp",
modelAndView.getViewName());
assertNotNull(modelAndView.getModel());
String nowValue = (String) modelAndView.getModel().get("now");
assertNotNull(nowValue);

A continuacin, ejecutamos el test y debera fallar.

Ahora actualizamos HelloController configurando la referencia a la vista con su


nueva localizacin, 'WEB-INF/views/hello.jsp', as como la pareja clave/valor con
la fecha y hora actual con la clave "now" y el valor: 'now'.

'springapp/src/main/java/com/companyname/springapp/web/HelloController
.java':
package com.companyname.springapp.web;

import java.io.IOException;
import java.util.Date;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.servlet.ModelAndView;

@Controller
public class HelloController {

protected final Log logger = LogFactory.getLog(getClass());

@RequestMapping(value="/hello.htm")
public ModelAndView handleRequest(HttpServletRequest request,
HttpServletResponse response)
throws ServletException, IOException {

String now = (new Date()).toString();


logger.info("Returning hello view with " + now);

return new ModelAndView("WEB-INF/views/hello.jsp", "now", now);

}
}

Ejecutamos de el test y ahora debera pasar.

Estamos ahora listos para probar nuestras mejoras despus sobre el servidor. Cuando
introduzcamos la direccin http://localhost:8080/springapp/ en un navegador, debera
ejecutarse la pgina de bienvenida 'index.jsp', la cual debera redireccionarnos
a 'hello.htm' que es manejada por el DispatcherServlet, quien a su vez delega
nuestra solicitud al controlador HelloController, que inserta la fecha y hora en el
modelo y las pone a disposicin de la vista 'hello.jsp'.
La aplicacin actualizada
2.3. Separar la vista del controlador
Ahora el controlador especifica la ruta completa a la vista, lo cual crea una
dependencia innecesaria entre el controlador y la vista. Idealmente queremos
referirnos a la vista usando un nombre lgico, permitindonos intercambiar la vista
sin tener que cambiar el controlador. Puedes crear este mapeo en un archivo de
propiedades si estas
usando ResourceBundleViewResolver y SimpleUrlHandlerMapping. Otra opcin
para el mapeo bsico entre una vista y una localizacin, consiste en simplemente
configurar un prefijo y sufijo en InternalResourceViewResolver. Esta solucin es
la que vamos a implantar ahora, por lo que modificamos 'app-config.xml' y
declaramos una entrada 'viewResolver'. Eligiendo JstlView tendremos la
oportunidad de usar JSTL en combinacin con paquetes de mensajes de idioma, los
cuales nos ofreceran soporte para la internacionalizacin de la aplicacin.

'springapp/src/main/webapp/WEB-INF/spring/app-config.xml':
<?xml version="1.0" encoding="UTF-8"?>

<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"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-
3.0.xsd
http://www.springframework.org/schema/mvc
http://www.springframework.org/schema/mvc/spring-mvc-3.0.xsd">

<!-- Scans the classpath of this application for @Components to


deploy as beans -->
<context:component-scan base-
package="com.companyname.springapp.web" />

<!-- Configures the @Controller programming model -->


<mvc:annotation-driven/>

<bean id="viewResolver"
class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="viewClass"
value="org.springframework.web.servlet.view.JstlView"></property>
<property name="prefix" value="/WEB-INF/views/"></property>
<property name="suffix" value=".jsp"></property>
</bean>
</beans>

Actualizamos el nombre de la vista en la clase de pruebas del


controlador HelloControllerTests por 'hello' y relanzamos el test para
comprobar que falla.

'springapp/src/test/java/com/companyname/springapp/web/HelloController
Tests.java':
package com.companyname.springapp.web;

import static org.junit.Assert.*;

import org.junit.Test;
import org.springframework.web.servlet.ModelAndView;

public class HelloControllerTests {

@Test
public void testHandleRequestView() throws Exception{
HelloController controller = new HelloController();
ModelAndView modelAndView = controller.handleRequest(null,
null);
assertEquals("hello", modelAndView.getViewName());
assertNotNull(modelAndView.getModel());
String nowValue = (String) modelAndView.getModel().get("now");
assertNotNull(nowValue);

}
}

Ahora eliminamos el prefijo y sufijo del nombre de la vista en el controlador, dejando


que el controlador se refiera a la vista por su nombre lgico "hello".

'springapp/src/main/java/com/companyname/springapp/web/HelloController
.java':
package com.companyname.springapp.web;

import java.io.IOException;
import java.util.Date;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.servlet.ModelAndView;

@Controller
public class HelloController {

protected final Log logger = LogFactory.getLog(getClass());

@RequestMapping(value="/hello.htm")
public ModelAndView handleRequest(HttpServletRequest request,
HttpServletResponse response)
throws ServletException, IOException {

String now = (new Date()).toString();


logger.info("Returning hello view with " + now);

return new ModelAndView("hello", "now", now);

}
}

Relanzamos el test y ahora debe pasar. Compilamos y desplegamos la aplicacin, y


verificamos que todava funciona.
2.4. Resumen
Echemos un vistazo rpido a lo que hemos creado en la Parte 2.

Un archivo de cabecera 'include.jsp', el archivo JSP que contiene la


directiva taglib que usaremos en todos nuestros archivos JSPs.

Estos son los componentes de la aplicacin que hemos cambiado en la Parte 2.


HelloControllerTests ha sido actualizado repetidamente para hacer al
controlador referirse al nombre lgico de la vista en lugar de a su localizacin
y nombre completo.

El controlador de pgina, HelloController, ahora hace referencia a la vista


por su nombre lgico mediante el uso
del 'InternalResourceViewResolver' definido en 'app-config.xml'.

A continuacin puedes ver una captura de pantalla que muestra el aspecto que
debera tener la estructura de directorios del proyecto despus de seguir todas las
instrucciones anteriores.

La estructura de directorios del proyecto al final de la parte 2


Captulo 3. Desarrollando la Lgica de Negocio
sta es la Parte 3 del tutorial paso a paso para desarrollar una aplicacin Spring MVC.
En esta seccin, adoptaremos un acercamiento pragmtico al Test-Driven
Development (TDD o Desarrollo Conducido por Tests) para crear los objetos de
dominio e implementar la lgica de negocio para nuestro sistema de mantenimiento de
inventario. Esto significa que "escribiremos un poco de cdigo, lo testearemos,
escribiremos un poco ms de cdigo, lo volveremos a testear..." En la Parte 1 hemos
configurado el entorno y montado la aplicacin bsica. En la Parte 2 hemos refinado
la aplicacin desacoplando la vista del controlador.

Spring permite hacer las cosas simples fciles y las difciles posibles. La estructura
fundamental que hace esto posible es el uso de Plain Old Java Objects (POJOs u
Objetos Normales Java) por Spring. Los POJOs son esencialmente clases nomales
Java libres de cualquier contrato (normalmente impuesto por un framework o
arquitectura a traves de subclases o de la implementacin de interfaces). Los POJOs
son objetos normales Java que estn libres de dichas obligaciones, haciendo la
programacin orientada a objetos posible de nuevo. Cuando trabajes con Spring, los
objetos de dominio y los servicios que implementes seran POJOs. De hecho, casi todo
lo que implementes debera ser un POJO. Si no es as, deberas preguntarte a ti
mismo porqu no ocurre esto. En esta seccin, comenzaremos a ver la simplicidad y
potencia de Spring.
3.1. Revisando la regla de negocio del Sistema de Mantenimiento
de Inventario
En nuestro sistema de mantenimiento de inventario tenemos dos conceptos: el de
producto, y el de servicio para manejarlo. Supongamos en este tutorial que el negocio
solicita la capacidad de incrementar precios sobre todos los productos. Cualquier
decremento ser hecho sobre cada producto en concreto, pero esta caracterstica
est fuera de la funcionalidad de nuestra aplicacin. Las reglas de validacin para
incrementar precios son:

El incremento mximo esta limitado al 50%.

El incremento mnimo debe ser mayor del 0%.

A continuacin puedes ver el diagrama de clases de nuestro sistema de


mantenimiento de inventario.
El diagrama de clases para el sistema de mantenimiento de inventario
3.2. Aadir algunas clases a la lgica de negocio
Aadamos ahora ms lgica de negocio en la forma de una clase Product y un
servicio al que llamaremos ProductManager que gestionar todos los productos. Para
separar la lgica de la web de la lgica de negocio, colocaremos las clases
relacionadas con la capa web en el paquete 'web' y crearemos dos nuevos paquetes:
uno para los objetos de servicio, al que llamaremos 'service', y otro para los
objetos de dominio al que llamaremos 'domain'.

Primero implementamos la clase Product como un POJO con un constructor por


defecto (que es provisto si no especificamos ningun constructor explcitamente), as
como mtodos getters y setters para las propiedades 'description' y 'price'.
Adems haremos que la clase implemente la interfaz Serializable, aspecto no
necesario en este momento para nuestra aplicacin, pero que ser necesario ms
tarde cuando persistamos y almacenemos su estado. Esta clase es un objeto de
dominio, por lo tanto pertenece al paquete 'domain'.

'springapp/src/main/java/com/companyname/springapp/domain/Product.java
':
package com.companyname.springapp.domain;

import java.io.Serializable;

public class Product implements Serializable {

private static final long serialVersionUID = 1L;


private String description;
private Double price;

public String getDescription() {


return description;
}

public void setDescription(String description) {


this.description = description;
}

public Double getPrice() {


return price;
}

public void setPrice(Double price) {


this.price = price;
}

public String toString() {


StringBuffer buffer = new StringBuffer();
buffer.append("Description: " + description + ";");
buffer.append("Price: " + price);
return buffer.toString();
}
}

Escribamos ahora una unidad de test para nuestra clase Product. Algunos
programadores no se molestan en escribir tests para los getters y setters, tambin
llamado cdigo 'auto-generado'. Normalmente, supone demasiado tiempo
enfrascarse en el debate (como este prrafo demuestra) sobre si los getters y setters
necesitan ser testeados, ya que son mtodos demasiado 'triviales'. Nosotros
escribiremos los tests debido a que: a) son triviales de escribir; b) tendremos siempre
los tests y preferimos pagar el precio de perder un poco de tiempo por la sola ocasin
entre cien en que nos salvemos de un error producido por un getter o setter; y c)
porque mejoran la cobertura de los tests. Creamos un stub de Product y testeamos
cada mtodo getter y setter como una pareja en un test simple. Normalmente,
escribirs uno o ms mtodos de test por cada mtodo de la clase, donde cada uno
de estos mtodos compruebe una condicion particular en el mtodo de la clase (como
por ejemplo, verificar un valor null pasado al mtodo, etc.).

'springapp/src/test/java/com/companyname/springapp/domain/ProductTests
.java':
package com.companyname.springapp.domain;

import static org.junit.Assert.*;

import org.junit.Before;
import org.junit.Test;
public class ProductTests {

private Product product;

@Before
public void setUp() throws Exception {
product = new Product();
}

@Test
public void testSetAndGetDescription() {
String testDescription = "aDescription";
assertNull(product.getDescription());
product.setDescription(testDescription);
assertEquals(testDescription, product.getDescription());
}

@Test
public void testSetAndGetPrice() {
double testPrice = 100.00;
assertEquals(0, 0, 0);
product.setPrice(testPrice);
assertEquals(testPrice, product.getPrice(), 0);
}

A continuacin, creamos el servicio ProductManager. ste es el servicio responsable


de gestionar los productos. Contiene dos mtodos: un mtodo de
negocio, increasePrice(), que incrementa el precio de todos los productos, y un
mtodo getter, getProducts(), para recuperar todos los productos. Hemos decidido
disearlo como una interface en lugar de como una clase concreta por algunas
razones. Primero, es ms fcil escribir tests unitarios para los Controllers (como
veremos en el prximo capitulo). Segundo, el uso de interfaces implica que JDK
Proxying (una caracterstica del lenguaje Java) puede ser usada para hacer el servicio
transaccional, en lugar de usar CGLIB (una librera de generacin de cdigo).

'springapp/src/main/java/com/companyname/springapp/service/ProductMana
ger.java':
package com.companyname.springapp.service;

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

import com.companyname.springapp.domain.Product;

public interface ProductManager extends Serializable {

public void increasePrice(int percentage);

public List<Product> getProducts();


}

Vamos a crear ahora la clase SimpleProductManager que implementa la


interface ProductManager.

'springapp/src/main/java/com/companyname/springapp/service/SimpleProdu
ctManager.java':
package com.companyname.springapp.service;

import java.util.List;

import com.companyname.springapp.domain.Product;

public class SimpleProductManager implements ProductManager {

private static final long serialVersionUID = 1L;

public List<Product> getProducts() {


throw new UnsupportedOperationException();
}

public void increasePrice(int percentage) {


throw new UnsupportedOperationException();
}

public void setProducts(List<Product> products) {


throw new UnsupportedOperationException();
}
}

Antes de implementar los mtodos en SimpleProductManager, vamos a definir


algunos tests. La definicin ms estricta de Test Driven Development (TDD)
implica escribir siempre los tests primero, y a continuacin el cdigo. Una
interpretacin aproximada se conoce como Test Oriented Development (TOD -
Desarrollo Orientado a Tests), donde se alternan las tareas de escribir el cdigo y los
tests como parte del proceso de desarrollo. En cualquier caso, lo ms importante es
tener para el cdigo base el conjunto ms completo de tests que sea posible. La
forma de alcanzar este objetivo es ms teora que prctica. Muchos programadores
TDD, sin embargo, estn de acuerdo en que la calidad de los tests es siempre mayor
cuando son escritos al mismo tiempo que el cdigo, por lo que sta es la aproximacin
que vamos a tomar.

Para escribir test efectivos, tienes que considerar todas las pre- y post-condiciones
del mtodo que va a ser testeado, as como lo que ocurre dentro del mtodo.
Comencemos testeando una llamada a getProducts() que devuelve null.

'springapp/src/test/java/com/companyname/springapp/service/SimpleProdu
ctManagerTests.java':
package com.companyname.springapp.service;

import static org.junit.Assert.*;

import org.junit.Before;
import org.junit.Test;

public class SimpleProductManagerTests {

private SimpleProductManager productManager;

@Before
public void setUp() throws Exception {
productManager = new SimpleProductManager();
}

@Test
public void testGetProductsWithNoProducts() {
productManager = new SimpleProductManager();
assertNull(productManager.getProducts());
}
}

Si ejecutamos ahora los tests de SimpleProductManagerTests fallarn; ya que, por


ejemplo, getProducts() todava no ha sido implementado. Normalmente, es una
buena idea marcar los mtodos an no implementados haciendo que lancen una
excepcin de tipo UnsupportedOperationException.

A continuacin, vamos a implementar un test para recuperar una lista de objectos de


respaldo en los que han sido almacenados datos de prueba. Sabemos que tenemos
que almacenar la lista de productos en la mayoria de nuestros tests
de SimpleProductManagerTests, por lo que definimos la lista de objetos de respaldo
en el metodo setUp() de JUnit. Este mtodo, anotado como @Before, ser invocado
previamente a cada llamada a un mtodo de test.

'springapp/src/test/java/com/companyname/springapp/service/SimpleProdu
ctManagerTests.java':
package com.companyname.springapp.service;

import java.util.ArrayList;
import java.util.List;

import static org.junit.Assert.*;

import org.junit.Before;
import org.junit.Test;

import com.companyname.springapp.domain.Product;

public class SimpleProductManagerTests {


private SimpleProductManager productManager;

private List<Product> products;

private static int PRODUCT_COUNT = 2;

private static Double CHAIR_PRICE = new Double(20.50);


private static String CHAIR_DESCRIPTION = "Chair";

private static String TABLE_DESCRIPTION = "Table";


private static Double TABLE_PRICE = new Double(150.10);

@Before
public void setUp() throws Exception {
productManager = new SimpleProductManager();
products = new ArrayList<Product>();

// stub up a list of products


Product product = new Product();
product.setDescription("Chair");
product.setPrice(CHAIR_PRICE);
products.add(product);

product = new Product();


product.setDescription("Table");
product.setPrice(TABLE_PRICE);
products.add(product);

productManager.setProducts(products);

@Test
public void testGetProductsWithNoProducts() {
productManager = new SimpleProductManager();
assertNull(productManager.getProducts());
}

@Test
public void testGetProducts() {
List<Product> products = productManager.getProducts();
assertNotNull(products);
assertEquals(PRODUCT_COUNT, productManager.getProducts().size());

Product product = products.get(0);


assertEquals(CHAIR_DESCRIPTION, product.getDescription());
assertEquals(CHAIR_PRICE, product.getPrice());

product = products.get(1);
assertEquals(TABLE_DESCRIPTION, product.getDescription());
assertEquals(TABLE_PRICE, product.getPrice());
}

}
Si volvemos a lanzar los test de SimpleProductManagerTests seguirn fallando.Para
solucionarlo, volvemos a SimpleProductManager e implementamos los mtodos
getter y setter de la propiedad products.

'springapp/src/main/java/com/companyname/springapp/service/SimpleProdu
ctManager.java':
package com.companyname.springapp.service;

import java.util.List;

import com.companyname.springapp.domain.Product;

public class SimpleProductManager implements ProductManager {

private static final long serialVersionUID = 1L;

private List<Product> products;

public List<Product> getProducts() {


return products;
}

public void increasePrice(int percentage) {


throw new UnsupportedOperationException();
}

public void setProducts(List<Product> products) {


this.products = products;
}
}

Relanza los test de nuevo y ahora todos ellos deben pasar.

Ahora procedemos a implementar los siguientes test para el


mtodo increasePrice():

La lista de productos es null y el mtodo se ejecuta correctamente.

La lista de productos esta vaca y el mtodo se ejecuta correctamente.

Fija un incremento de precio del 10% y comprueba que dicho incremento se


ve reflejado en los precios de todos los productos de la lista.

'springapp/src/test/java/com/companyname/springapp/service/SimpleProdu
ctManagerTests.java':
package com.companyname.springapp.service;

import java.util.ArrayList;
import java.util.List;
import static org.junit.Assert.*;

import org.junit.Before;
import org.junit.Test;

import com.companyname.springapp.domain.Product;

public class SimpleProductManagerTests {

private SimpleProductManager productManager;

private List<Product> products;

private static int PRODUCT_COUNT = 2;

private static Double CHAIR_PRICE = new Double(20.50);


private static String CHAIR_DESCRIPTION = "Chair";

private static String TABLE_DESCRIPTION = "Table";


private static Double TABLE_PRICE = new Double(150.10);

private static int POSITIVE_PRICE_INCREASE = 10;

@Before
public void setUp() throws Exception {
productManager = new SimpleProductManager();
products = new ArrayList<Product>();

// stub up a list of products


Product product = new Product();
product.setDescription("Chair");
product.setPrice(CHAIR_PRICE);
products.add(product);

product = new Product();


product.setDescription("Table");
product.setPrice(TABLE_PRICE);
products.add(product);

productManager.setProducts(products);

@Test
public void testGetProductsWithNoProducts() {
productManager = new SimpleProductManager();
assertNull(productManager.getProducts());
}

@Test
public void testGetProducts() {
List<Product> products = productManager.getProducts();
assertNotNull(products);
assertEquals(PRODUCT_COUNT, productManager.getProducts().size());
Product product = products.get(0);
assertEquals(CHAIR_DESCRIPTION, product.getDescription());
assertEquals(CHAIR_PRICE, product.getPrice());

product = products.get(1);
assertEquals(TABLE_DESCRIPTION, product.getDescription());
assertEquals(TABLE_PRICE, product.getPrice());
}

@Test
public void testIncreasePriceWithNullListOfProducts() {
try {
productManager = new SimpleProductManager();
productManager.increasePrice(POSITIVE_PRICE_INCREASE);
}
catch(NullPointerException ex) {
fail("Products list is null.");
}
}

@Test
public void testIncreasePriceWithEmptyListOfProducts() {
try {
productManager = new SimpleProductManager();
productManager.setProducts(new ArrayList<Product>());
productManager.increasePrice(POSITIVE_PRICE_INCREASE);
}
catch(Exception ex) {
fail("Products list is empty.");
}
}

@Test
public void testIncreasePriceWithPositivePercentage() {
productManager.increasePrice(POSITIVE_PRICE_INCREASE);
double expectedChairPriceWithIncrease = 22.55;
double expectedTablePriceWithIncrease = 165.11;

List<Product> products = productManager.getProducts();


Product product = products.get(0);
assertEquals(expectedChairPriceWithIncrease, product.getPrice(),
0);

product = products.get(1);
assertEquals(expectedTablePriceWithIncrease, product.getPrice(),
0);
}
}

Por ltimo, volvemos a SimpleProductManager para implementar el


mtodo increasePrice().
'springapp/src/main/java/com/companyname/springapp/service/SimpleProdu
ctManager.java':
package com.companyname.springapp.service;

import java.util.List;

import com.companyname.springapp.domain.Product;

public class SimpleProductManager implements ProductManager {

private static final long serialVersionUID = 1L;

private List<Product> products;

public List<Product> getProducts() {


return products;
}

public void increasePrice(int percentage) {


if (products != null) {
for (Product product : products) {
double newPrice = product.getPrice().doubleValue() *
(100 + percentage)/100;
product.setPrice(newPrice);
}
}
}

public void setProducts(List<Product> products) {


this.products = products;
}
}

Si volvemos a lanzar los tests de SimpleProductManagerTests ahora debern pasar


todos. HURRA!. JUnit tiene un dicho: keep the bar green to keep the code clean
(mantn la barra verde para mantener el cdigo limpio).

Ahora estamos listos para movernos a la capa web y para poner una lista de
productos en nuestro modelo Controller.
3.3. Resumen
Echemos un rpido vistazo a lo que hemos hecho en la Parte 3.

Hemos implementado el objeto de dominio Product, la interface de


servicio ProductManager y la clase concreta SimpleProductManager, todos
como POJOs.

Hemos escrito tests unitarios para todas las clases que hemos implementado.
No hemos escrito ni una sola linea de cdigo de Spring. ste es un ejemplo de
lo no-intrusivo que es realmente Spring Framework. Uno de sus propsitos
principales es permitir a los programadores centrarse en la parte ms
importante de todas: modelar e implementar requerimientos de negocio. Otro
de sus propsitos es hacer seguir las mejores prcticas de programacin de
una manera sencilla, como por ejemplo implementar servicios usando
interfaces y usar tests unitarios ms all de las obligaciones prgmaticas de
un proyecto dado. A lo largo de este tutorial, vers como los beneficios de
disear interfaces cobran vida.

A continuacin puedes ver una captura de pantalla que muestra el aspecto que
debera tener la estructura de directorios del proyecto despus de seguir todas las
instrucciones anteriores.
La estructura de directorios del proyecto al final de la parte 3
Captulo 4. Desarrollando la Interface Web
sta es la Parte 4 del tutorial paso a paso para desarrollar una aplicacin web desde
cero usando Spring Framework. En la Parte 1 hemos configurado el entorno y montado
la aplicacin bsica. En la Parte 2 hemos mejorado la aplicacin que habamos
construido hasta entonces. La Parte 3 aade toda la lgica de negocio y los tests
unitarios. Ahora es el momento de construir la interface web para la aplicacin.
4.1. Aadir una referencia a la lgica de negocio en el
controlador
Para empezar, renombramos el controlador HelloController a algo ms
descriptivo, como por ejemplo InventoryController, puesto que estamos
construyendo un sistema de inventario. Aqu es donde un IDE con opcin de
refactorizar es de valor incalculable.
Renombramos HelloController a InventoryController as
como HelloControllerTests a InventoryControllerTests. A continuacin,
modificamos InventoryController para que almacene una referencia a la
clase ProductManager. Anotaremos la referencia con @Autowired para que Spring la
pueda inyectar automticamente cuando detecte el componente. Tambin aadimos
cdigo para permitir al controlador pasar la informacin sobre los productos a la vista.
El mtodo getModelAndView() ahora devuelve tanto un Map con la fecha y hora
como una lista de productos.

'springapp/src/main/java/com/companyname/springapp/web/InventoryContro
ller.java':
package com.companyname.springapp.web;

import java.io.IOException;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.servlet.ModelAndView;

import com.companyname.springapp.service.ProductManager;

@Controller
public class InventoryController {

protected final Log logger = LogFactory.getLog(getClass());


@Autowired
private ProductManager productManager;

@RequestMapping(value="/hello.htm")
public ModelAndView handleRequest(HttpServletRequest request,
HttpServletResponse response)
throws ServletException, IOException {

String now = (new Date()).toString();


logger.info("Returning hello view with " + now);

Map<String, Object> myModel = new HashMap<String, Object>();


myModel.put("now", now);
myModel.put("products", this.productManager.getProducts());

return new ModelAndView("hello", "model", myModel);


}

public void setProductManager(ProductManager productManager) {


this.productManager = productManager;
}
}

Antes de que los test puedan ser pasados de nuevo, tambin necesitaremos
modificar InventoryControllerTest para que proporcione un ProductManager y
extraiga el valor de 'now' desde el modelo Map.

'springapp/src/test/java/com/companyname/springapp/web/InventoryContro
llerTests.java':
package com.companyname.springapp.web;

import java.util.Map;

import static org.junit.Assert.*;

import org.junit.Test;
import org.springframework.web.servlet.ModelAndView;

import com.companyname.springapp.service.SimpleProductManager;

public class InventoryControllerTests {

@Test
public void testHandleRequestView() throws Exception{
InventoryController controller = new InventoryController();
controller.setProductManager(new SimpleProductManager());
ModelAndView modelAndView = controller.handleRequest(null,
null);
assertEquals("hello", modelAndView.getViewName());
assertNotNull(modelAndView.getModel());
@SuppressWarnings("unchecked")
Map<String, Object> modelMap = (Map<String, Object>)
modelAndView.getModel().get("model");
String nowValue = (String) modelMap.get("now");
assertNotNull(nowValue);
}
}
4.2. Modificar la vista para mostrar datos de negocio y aadir
soporte para archivos de mensajes
Usando la etiqueta JSTL <c:forEach/>, aadimos una seccin que muestra
informacin de cada producto. Tambin vamos a reemplazar el ttulo, la cabecera y
el texto de bienvenida con una etiqueta JSTL <fmt:message/> que extrae el texto a
mostrar desde una ubicacin 'message' veremos esta ubicacin un poco ms
adelante.

'springapp/src/main/webapp/WEB-INF/views/hello.jsp':
<%@ include file="/WEB-INF/views/include.jsp" %>

<html>
<head><title><fmt:message key="title"/></title></head>
<body>
<h1><fmt:message key="heading"/></h1>
<p><fmt:message key="greeting"/> <c:out value="${model.now}"/></p>
<h3>Products</h3>
<c:forEach items="${model.products}" var="prod">
<c:out value="${prod.description}"/> <i>$<c:out
value="${prod.price}"/></i><br><br>
</c:forEach>
</body>
</html>
4.3. Aadir datos de prueba para rellenar algunos objetos de
negocio
Es el momento de aadir un SimpleProductManager a nuestro archivo de
configuracin, el cual se inyectar automticamente en el InventoryController.
Todava no vamos a aadir ningun cdigo para cargar los objetos de negocio desde
una base de datos. En su lugar, podemos reemplazarlos con unas cuantas instancias
de la clase Product usando beans Spring en el fichero de configuraci de la
aplicacin. Para ello, simplemente pondremos los datos que necesitamos en un
puado de entradas bean en el archivo 'app-config.xml'. Tambin aadiremos el
bean 'messageSource' que nos permitir recuperar mensajes desde la
ubicacin 'messages.properties', que crearemos en el prximo paso.

'springapp/src/main/webapp/WEB-INF/spring/app-config.xml':
<?xml version="1.0" encoding="UTF-8"?>

<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"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/mvc
http://www.springframework.org/schema/mvc/spring-mvc.xsd">

<bean id="productManager"
class="com.companyname.springapp.service.SimpleProductManager">
<property name="products">
<list>
<ref bean="product1"/>
<ref bean="product2"/>
<ref bean="product3"/>
</list>
</property>
</bean>

<bean id="product1"
class="com.companyname.springapp.domain.Product">
<property name="description" value="Lamp"/>
<property name="price" value="5.75"/>
</bean>

<bean id="product2"
class="com.companyname.springapp.domain.Product">
<property name="description" value="Table"/>
<property name="price" value="75.25"/>
</bean>

<bean id="product3"
class="com.companyname.springapp.domain.Product">
<property name="description" value="Chair"/>
<property name="price" value="22.79"/>
</bean>

<bean id="messageSource"
class="org.springframework.context.support.ResourceBundleMessageSource">
<property name="basename" value="messages"/>
</bean>

<!-- Scans the classpath of this application for @Components to


deploy as beans -->
<context:component-scan base-
package="com.companyname.springapp.web" />

<!-- Configures the @Controller programming model -->


<mvc:annotation-driven/>

<bean id="viewResolver"
class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="viewClass"
value="org.springframework.web.servlet.view.JstlView"></property>
<property name="prefix" value="/WEB-INF/views/"></property>
<property name="suffix" value=".jsp"></property>
</bean>
</beans>
4.4. Aadir una ubicacin para los mensajes
Creamos un archivo llamado 'messages.properties' en el
directorio 'src/main/webapp/WEB-INF/classes'. Este archivo de propiedades
contiene tres entradas que coinciden con las claves especificadas en las
etiquetas <fmt:message/> que hemos aadido a 'hello.jsp'.

'springapp/src/main/webapp/WEB-INF/classes/messages.properties':
title=SpringApp
heading=Hello :: SpringApp
greeting=Greetings, it is now

Si ahora compilamos y desplegamos de nuevo la aplicacin en el servidor,


deberamos ver lo siguiente en el navegador:

La aplicacin actualizada
4.5. Aadir un formulario
Para proveer de una interface a la aplicacin web que muestre la funcionalidad para
incrementar los precios, vamos a aadir un formulario que permitir al usuario
introducir un valor de porcentaje. Para ello, creamos el archivo
JSP 'priceincrease.jsp' en el directorio 'src/main/webapp/WEB-INF/views'.

'springapp/src/main/webapp/WEB-INF/views/priceincrease.jsp':
<%@ include file="/WEB-INF/views/include.jsp" %>
<%@ taglib prefix="form" uri="http://www.springframework.org/tags/form" %>

<html>
<head>
<title><fmt:message key="title"/></title>
<style>
.error { color: red; }
</style>
</head>
<body>
<h1><fmt:message key="priceincrease.heading"/></h1>
<form:form method="post" commandName="priceIncrease">
<table >
<tr>
<td align="right" width="20%">Increase (%):</td>
<td width="20%">
<form:input path="percentage"/>
</td>
<td width="60%">
<form:errors path="percentage" cssClass="error"/>
</td>
</tr>
</table>
<br>
<input type="submit" value="Execute">
</form:form>
<a href="<c:url value="hello.htm"/>">Home</a>
</body>
</html>

A continuacin, debemos incluir las siguientes dependencias en el fichero 'pom.xml':

Group Id

javax.validation validation-api

org.hibernate hibernate-validator
Group Id

org.slf4j slf4j-api

org.slf4j slf4j-log4j12

Para configurar adecuadamente la librera log4j y evitar as algunos Warnings,


recomendamos aadir (si no existiera) una nueva carpeta de recursos de tipo Source
folder en nuestro proyecto, a la que llamaremos 'src/main/resources'. Dentro de
esta carpeta crearemos los ficheros 'log4j.dtd' y 'log4j.xml' que se muestran a
continuacin:

'springapp/src/main/resources/log4j.dtd':
<?xml version="1.0" encoding="UTF-8" ?>

<!-- Authors: Chris Taylor, Ceki Gulcu. -->

<!-- Version: 1.2 -->

<!-- A configuration element consists of optional renderer


elements,appender elements, categories and an optional root
element. -->

<!ELEMENT log4j:configuration (renderer*,


appender*,(category|logger)*,root?,
categoryFactory?)>

<!-- The "threshold" attribute takes a level value such that all -->
<!-- logging statements with a level equal or below this value are -->
<!-- disabled. -->

<!-- Setting the "debug" enable the printing of internal log4j logging -
->
<!-- statements. -
->

<!-- By default, debug attribute is "null", meaning that we not do touch -


->
<!-- internal log4j logging settings. The "null" value for the threshold -
->
<!-- attribute can be misleading. The threshold field of a repository
-->
<!-- cannot be set to null. The "null" value for the threshold attribute -
->
<!-- simply means don't touch the threshold field, the threshold field -
->
<!-- keeps its old value. -
->

<!ATTLIST log4j:configuration
xmlns:log4j CDATA #FIXED "http://jakarta.apache.org/log4j/"
threshold (all|debug|info|warn|error|fatal|off|null) "null"
debug (true|false|null) "null"
>

<!-- renderer elements allow the user to customize the conversion of -->
<!-- message objects to String. -->

<!ELEMENT renderer EMPTY>


<!ATTLIST renderer
renderedClass CDATA #REQUIRED
renderingClass CDATA #REQUIRED
>

<!-- Appenders must have a name and a class. -->


<!-- Appenders may contain an error handler, a layout, optional parameters
-->
<!-- and filters. They may also reference (or include) other appenders. --
>
<!ELEMENT appender (errorHandler?, param*, layout?, filter*, appender-
ref*)>
<!ATTLIST appender
name ID #REQUIRED
class CDATA #REQUIRED
>

<!ELEMENT layout (param*)>


<!ATTLIST layout
class CDATA #REQUIRED
>

<!ELEMENT filter (param*)>


<!ATTLIST filter
class CDATA #REQUIRED
>

<!-- ErrorHandlers can be of any class. They can admit any number of -->
<!-- parameters. -->

<!ELEMENT errorHandler (param*, root-ref?, logger-ref*, appender-ref?)>


<!ATTLIST errorHandler
class CDATA #REQUIRED
>

<!ELEMENT root-ref EMPTY>

<!ELEMENT logger-ref EMPTY>


<!ATTLIST logger-ref
ref IDREF #REQUIRED
>

<!ELEMENT param EMPTY>


<!ATTLIST param
name CDATA #REQUIRED
value CDATA #REQUIRED
>

<!-- The priority class is org.apache.log4j.Level by default -->


<!ELEMENT priority (param*)>
<!ATTLIST priority
class CDATA #IMPLIED
value CDATA #REQUIRED
>

<!-- The level class is org.apache.log4j.Level by default -->


<!ELEMENT level (param*)>
<!ATTLIST level
class CDATA #IMPLIED
value CDATA #REQUIRED
>

<!-- If no level element is specified, then the configurator MUST not -->
<!-- touch the level of the named category. -->
<!ELEMENT category (param*,(priority|level)?,appender-ref*)>
<!ATTLIST category
class CDATA #IMPLIED
name CDATA #REQUIRED
additivity (true|false) "true"
>

<!-- If no level element is specified, then the configurator MUST not -->
<!-- touch the level of the named logger. -->
<!ELEMENT logger (level?,appender-ref*)>
<!ATTLIST logger
name ID #REQUIRED
additivity (true|false) "true"
>

<!ELEMENT categoryFactory (param*)>


<!ATTLIST categoryFactory
class CDATA #REQUIRED>

<!ELEMENT appender-ref EMPTY>


<!ATTLIST appender-ref
ref IDREF #REQUIRED
>

<!-- If no priority element is specified, then the configurator MUST not -


->
<!-- touch the priority of root. -->
<!-- The root category always exists and cannot be subclassed. -->
<!ELEMENT root (param*, (priority|level)?, appender-ref*)>
<!-- ====================================================================
-->
<!-- A logging event -
->
<!-- ====================================================================
-->
<!ELEMENT log4j:eventSet (log4j:event*)>
<!ATTLIST log4j:eventSet
xmlns:log4j CDATA #FIXED "http://jakarta.apache.org/log4j/"
version (1.1|1.2) "1.2"
includesLocationInfo (true|false) "true"
>

<!ELEMENT log4j:event (log4j:message, log4j:NDC?, log4j:throwable?,


log4j:locationInfo?) >

<!-- The timestamp format is application dependent. -->


<!ATTLIST log4j:event
logger CDATA #REQUIRED
level CDATA #REQUIRED
thread CDATA #REQUIRED
timestamp CDATA #REQUIRED
>

<!ELEMENT log4j:message (#PCDATA)>


<!ELEMENT log4j:NDC (#PCDATA)>

<!ELEMENT log4j:throwable (#PCDATA)>

<!ELEMENT log4j:locationInfo EMPTY>


<!ATTLIST log4j:locationInfo
class CDATA #REQUIRED
method CDATA #REQUIRED
file CDATA #REQUIRED
line CDATA #REQUIRED
>

'springapp/src/main/resources/log4j.xml':
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE log4j:configuration SYSTEM "log4j.dtd">

<log4j:configuration xmlns:log4j="http://jakarta.apache.org/log4j/">

<!-- Appenders -->


<appender name="console" class="org.apache.log4j.ConsoleAppender">
<param name="Target" value="System.out" />
<layout class="org.apache.log4j.PatternLayout">
<param name="ConversionPattern" value="%-5p: %c -
%m%n" />
</layout>
</appender>
<!-- Application logger -->
<logger name="springapp">
<level value="info" />
</logger>

<!-- 3rdparty Loggers -->


<logger name="org.springframework.beans">
<level value="warn" />
</logger>

<logger name="org.springframework.jdbc">
<level value="warn" />
</logger>

<logger name="org.springframework.transaction">
<level value="warn" />
</logger>

<logger name="org.springframework.orm">
<level value="warn" />
</logger>

<logger name="org.springframework.web">
<level value="warn" />
</logger>

<logger name="org.springframework.webflow">
<level value="warn" />
</logger>

<!-- Root Logger -->


<root>
<priority value="warn" />
<appender-ref ref="console" />
</root>

</log4j:configuration>

La siguiente clase que crearemos es un JavaBean muy sencillo que solamente


contiene una propiedad, con sus correspondientes mtodos getter y setter. ste es
el objeto que el formulario rellenar y desde el que nuestra lgica de negocio extraer
el porcentaje de incremento que queremos aplicar a los precios. La
clase PriceIncrease utiliza las anotaciones @Min y @Max para definir el intervalo de
valores vlido para el incremento de precios del stock.

'springapp/src/main/java/com/companyname/springapp/service/PriceIncrea
se.java':
package com.companyname.springapp.service;

import javax.validation.constraints.Max;
import javax.validation.constraints.Min;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

public class PriceIncrease {

/** Logger for this class and subclasses */


protected final Log logger = LogFactory.getLog(getClass());

@Min(0)
@Max(50)
private int percentage;

public void setPercentage(int i) {


percentage = i;
logger.info("Percentage set to " + i);
}

public int getPercentage() {


return percentage;
}
}
4.6. Aadir un controlador de formulario
A continuacin, creamos la clase PriceIncreaseFormController que actuar como
controlador de las peticiones de incremento de precio realizadas desde el formulario.
Spring inyectar automticamente al controlador del formulario la referencia al
servicio ProductManager gracias a la anotaci @Autowired. El
mtodo formBackingObject(..) ser invocado antes de que el formulario se
muestre al usuario (peticin GET) y rellenar el campo con un incremento por defecto
de un 15%. El mtodo onSubmit(..) ser invocado cuando el usuario enve del
formulario a travs del mtodo POST. El uso de la anotacin @Valid permitir validar
el incremento introducido y volver a mostrar el formulario en caso de que ste no
sea vlido.

'springapp/src/main/java/com/companyname/springapp/web/PriceIncreaseFo
rmController.java':
package com.companyname.springapp.web;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.validation.Valid;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import com.companyname.springapp.service.PriceIncrease;
import com.companyname.springapp.service.ProductManager;

@Controller
@RequestMapping(value="/priceincrease.htm")
public class PriceIncreaseFormController {

/** Logger for this class and subclasses */


protected final Log logger = LogFactory.getLog(getClass());

@Autowired
private ProductManager productManager;

@RequestMapping(method = RequestMethod.POST)
public String onSubmit(@Valid PriceIncrease priceIncrease,
BindingResult result)
{
if (result.hasErrors()) {
return "priceincrease";
}

int increase = priceIncrease.getPercentage();


logger.info("Increasing prices by " + increase + "%.");

productManager.increasePrice(increase);

return "redirect:/hello.htm";
}

@RequestMapping(method = RequestMethod.GET)
protected PriceIncrease formBackingObject(HttpServletRequest request)
throws ServletException {
PriceIncrease priceIncrease = new PriceIncrease();
priceIncrease.setPercentage(15);
return priceIncrease;
}

public void setProductManager(ProductManager productManager) {


this.productManager = productManager;
}

public ProductManager getProductManager() {


return productManager;
}

Para mostrar los distintos mensajes de error, vamos a aadir tambin algunos
mensajes al archivo de mensajes 'messages.properties'.

'springapp/src/main/webapp/WEB-INF/classes/messages.properties':
title=SpringApp
heading=Hello :: SpringApp
greeting=Greetings, it is now
priceincrease.heading=Price Increase :: SpringApp
error.not-specified=Percentage not specified!!!
error.too-low=You have to specify a percentage higher than {0}!
error.too-high=Don''t be greedy - you can''t raise prices by more than {0}%!
required=Entry required.
typeMismatch=Invalid data.
typeMismatch.percentage=That is not a number!!!

Finalmente, vamos a aadir un enlace a la pgina de incremento de precio


desde 'hello.jsp'.
<%@ include file="/WEB-INF/views/include.jsp" %>

<html>
<head><title><fmt:message key="title"/></title></head>
<body>
<h1><fmt:message key="heading"/></h1>
<p><fmt:message key="greeting"/> <c:out value="${model.now}"/></p>
<h3>Products</h3>
<c:forEach items="${model.products}" var="prod">
<c:out value="${prod.description}"/> <i>$<c:out
value="${prod.price}"/></i><br><br>
</c:forEach>
<br>
<a href="<c:url value="priceincrease.htm"/>">Increase Prices</a>
<br>
</body>
</html>

Compila, despliega y despus de recargar la aplicacin podemos probarla. El


formulario mostrar los errores siempre que no se introduzca un valor vlido de
porcentaje.
La aplicacin actualizada
4.7. Resumen
Vamos a ver lo que hemos hecho en la Parte 4.

Hemos renombrado nuestro controlador a InventoryController y le hemos


dado una referencia a ProductManager por lo que ahora podemos recuperar
una lista de productos para mostrar.

Entonces hemos definido algunos datos de prueba para rellenar objetos de


negocio.

A continuacin hemos modificado la pgina JSP para usar una ubicacin de


mensajes y hemos aadido un loop forEach para mostrar una lista dinmica
de productos.

Despus hemos creado un formulario para disponer de la capacidad de


incrementar los precios.

Finalmente hemos creado un controlador de formulario que valida los datos


introducidos, hemos desplegado y probado las nuevas caractersticas.

A continuacin puedes ver una captura de pantalla que muestra el aspecto que
debera tener la estructura de directorios del proyecto despus de seguir todas las
instrucciones anteriores.
La estructura de directorios del proyecto al final de la parte 4
Captulo 5. Implementando Persistencia en Base de Datos
Esta es la Parte 5 del tutorial paso a paso sobre sobre cmo desarrollar una aplicacin
web desde cero usando Spring Framework. En la Parte 1 hemos configurado el entorno
y puesto en marcha una aplicacin bsica. En la Parte 2 hemos mejorado la aplicacin
que habamos construido hasta entonces. En la Parte 3 hemos aadido toda la lgica
de negocio y los tests unitarios, y en la Parte 4 hemos desarrollado la interface web.
Ahora es el momento de introducir persistencia en base de datos. En las partes
anteriores hemos visto cmo cargar algunos objetos de negocio definiendo beans en
un archivo de configuracin. Es obvio que esta solucin nunca va a funcionar en el
mundo real cada vez que reiniciemos el servidor obtendremos de nuevo los precios
originales. Necesitamos aadir cdigo para persistir esos cambios en una base de
datos.
5.1. Creacin y rellenado de la base de datos
Antes de que podamos comenzar a desarrollar el cdigo de persistencia, necesitamos
una base de datos. En lugar de confiar en una base de datos integrada con la propia
aplicacin, vamos a utilizar una base de datos separada. Hemos planeado usar MySQL,
una buena base de datos de cdigo libre. Sin embargo, los pasos que se muestran a
continuacin sern similares para otras bases de datos (p. ej. PostgreSQL, HSQL...).
Desde el enlace anterior, se puede descargar MySQL para diferentes sistemas
operativos. Una vez instalada, cada distribucin proporciona sus correspondientes
scripts de inicio para arrancar la base de datos.

Primero, creamos el fichero 'springapp.sql' (en el directorio 'db') con las


sentencias SQL necesarias para la creacin de la base de datos springapp. Este
fichero crear tambin la tabla products, que alojar los productos de nuestra
aplicacin. Adems, establecer los permisos para el usuario springappuser.

'springapp/db/springapp.sql':
CREATE DATABASE springapp;

GRANT ALL ON springapp.* TO springappuser@'%' IDENTIFIED BY


'pspringappuser';
GRANT ALL ON springapp.* TO springappuser@localhost IDENTIFIED BY
'pspringappuser';

USE springapp;

CREATE TABLE products (


id INTEGER PRIMARY KEY,
description varchar(255),
price decimal(15,2)
);
CREATE INDEX products_description ON products(description);

A continuacin, necesitamos aadir nuestros datos de prueba. Para ello, creamos el


archivo 'load_data.sql' en el directorio 'db' con el siguiente contenido:
'springapp/db/load_data.sql':
INSERT INTO products (id, description, price) values(1, 'Lamp', 5.78);
INSERT INTO products (id, description, price) values(2, 'Table', 75.29);
INSERT INTO products (id, description, price) values(3, 'Chair', 22.81);

Por ltimo, ejecutamos las siguientes instrucciones sobre la lnea de comandos para
crear y rellenar la base de datos:
linux:springapp/db# mysql -u root -p
Enter password:
...
mysql> source springapp.sql
mysql> source load_data.sql

Para poder acceder desde nuestra aplicacin a la base de datos MySQL mediante JPA,
debemos incluir las siguientes dependencias en el fichero 'pom.xml':

Group Id Artifac

mysql mysql-connector-java

org.hibernate.javax.persistence hibernate-jpa-2.0-api

org.hibernate hibernate-entitymanager

org.springframework spring-orm

5.2. Crear una implementacin para JPA de un Objeto de Acceso


a Datos (DAO)
Comencemos creando un nuevo paquete
llamado 'com.companyname.springapp.repository' que contendr cualquier clase
que sea usada para el acceso a la base de datos. En este paquete vamos a crear un
nuevo interface llamado ProductDao. ste ser el interface que definir la
funcionalidad de la implementacin DAO que vamos a crear - esto nos permitir elegir
en el futuro otra implementacin que se adapte mejor a nuestras necesidades (p. ej.
JDBC, etc.).

'springapp/src/main/java/com/companyname/springapp/repository/ProductD
ao.java':
package com.companyname.springapp.repository;

import java.util.List;

import com.companyname.springapp.domain.Product;

public interface ProductDao {

public List<Product> getProductList();

public void saveProduct(Product prod);

A continuacin, creamos una clase llamada JPAProductDao que ser la


implementacin JPA de la interface anterior. Spring permite creacin automtica de
beans de acceso a datos mediante la anotacin @Repository. Asimismo, Spring
reconoce las anotaciones del API estndar JPA. Por ejemplo, la
anotacin @Persistence es utilizada en la clase JPAProductDao para inyectar
automticamente el EntityManager.

'springapp/src/main/java/com/companyname/springapp/repository/JPAProdu
ctDao.java':
package com.companyname.springapp.repository;

import java.util.List;

import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;

import org.springframework.stereotype.Repository;
import org.springframework.transaction.annotation.Transactional;

import com.companyname.springapp.domain.Product;

@Repository(value = "productDao")
public class JPAProductDao implements ProductDao {

private EntityManager em = null;

/*
* Sets the entity manager.
*/
@PersistenceContext
public void setEntityManager(EntityManager em) {
this.em = em;
}

@Transactional(readOnly = true)
@SuppressWarnings("unchecked")
public List<Product> getProductList() {
return em.createQuery("select p from Product p order by
p.id").getResultList();
}

@Transactional(readOnly = false)
public void saveProduct(Product prod) {
em.merge(prod);
}

Vamos a echarle un vistazo a los dos mtodos DAO en esta clase. El primer
mtodo, getProductList(), ejecuta una consulta usando el EntityManager. Para
ello incluimos en l una sentencia SQL que obtiene los objetos persistentes de la
clase Product. El segundo mtodo, saveProduct(), tambin usa el EntityManager.
Esta vez hacemos un merge para almacenar el producto en la base de datos. Ambos
mtodos se ejecutan de manera transaccional gracias a la
anotacin @Transactional, con la diferencia de que el
mtodo getProductList() permite la ejecucin de diversas consultas de lectura en
paralelo.

Llegados a este punto, debemos modificar la clase Product para que se persista
correctamente. Para ello modificamos el fichero 'Product.java' y aadimos las
anotaciones de JPA que realizan el mapeo entre los campos del objeto y aquellos de
la base de datos. Asimismo, hemos aadido el campo id para mapear la clave
primaria de la tabla products.

'springapp/src/main/java/com/companyname/springapp/domain/Product.java
':
package com.companyname.springapp.domain;

import java.io.Serializable;

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Table;

@Entity
@Table(name="products")
public class Product implements Serializable {

private static final long serialVersionUID = 1L;

@Id
@Column(name = "id")
@GeneratedValue(strategy = GenerationType.AUTO)
private Integer id;
private String description;
private Double price;

public Integer getId()


{
return id;
}

public void setId(Integer id)


{
this.id = id;
}

public String getDescription() {


return description;
}

public void setDescription(String description) {


this.description = description;
}

public Double getPrice() {


return price;
}

public void setPrice(Double price) {


this.price = price;
}

public String toString() {


StringBuffer buffer = new StringBuffer();
buffer.append("Description: " + description + ";");
buffer.append("Price: " + price);
return buffer.toString();
}
}

Los pasos anteriores completan la implementacin JPA en nuestra capa de


persistencia.
5.3. Implementar tests para la implementacin DAO sobre JPA
Es el momento de aadir tests a nuestra aplicacin DAO sobre JPA. Para ello,
crearemos la clase 'JPAProductDaoTests' dentro de
paquete 'com.companyname.springapp.repository' del la
carpeta 'src/test/java'.

'springapp/src/test/java/com/companyname/springapp/repository/JPAProdu
ctDaoTests.java':
package com.companyname.springapp.repository;
import java.util.List;

import static org.junit.Assert.*;

import org.junit.Before;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

import com.companyname.springapp.domain.Product;

public class JPAProductDaoTests {

private ApplicationContext context;


private ProductDao productDao;

@Before
public void setUp() throws Exception {
context = new ClassPathXmlApplicationContext("classpath:test-
context.xml");
productDao = (ProductDao) context.getBean("productDao");
}

@Test
public void testGetProductList() {
List<Product> products = productDao.getProductList();
assertEquals(products.size(), 3, 0);
}

@Test
public void testSaveProduct() {
List<Product> products = productDao.getProductList();

Product p = products.get(0);
Double price = p.getPrice();
p.setPrice(200.12);
productDao.saveProduct(p);

List<Product> updatedProducts = productDao.getProductList();


Product p2 = updatedProducts.get(0);
assertEquals(p2.getPrice(), 200.12, 0);

p2.setPrice(price);
productDao.saveProduct(p2);
}
}

An no disponemos del archivo que contiene el contexto de la aplicacin, y que es


cargado por este test, por lo que vamos a crear este archivo en una nueva carpeta
de recursos de tipo Source folder en nuestro proyecto, a la que
llamaremos 'src/test/resources':
'springapp/src/test/resources/test-context.xml':
<?xml version="1.0" encoding="UTF-8"?>
<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"
xmlns:p="http://www.springframework.org/schema/p"
xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd">

<!-- holding properties for database connectivity /-->


<context:property-placeholder location="classpath:jdbc.properties"/>

<!-- enabling annotation driven configuration /-->


<context:annotation-config/>

<bean id="dataSource"
class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<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>

<bean id="entityManagerFactory"
class="org.springframework.orm.jpa.LocalContainerEntityManagerFacto
ryBean"
p:dataSource-ref="dataSource"
p:jpaVendorAdapter-ref="jpaAdapter">
<property name="loadTimeWeaver">
<bean
class="org.springframework.instrument.classloading.InstrumentationLoadTime
Weaver"/>
</property>
<property name="persistenceUnitName"
value="springappPU"></property>
</bean>

<bean id="jpaAdapter"
class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter
"
p:database="${jpa.database}"
p:showSql="${jpa.showSql}"/>

<bean id="transactionManager"
class="org.springframework.orm.jpa.JpaTransactionManager"
p:entityManagerFactory-ref="entityManagerFactory"/>

<tx:annotation-driven transaction-manager="transactionManager"/>
<!-- Scans the classpath of this application for @Components to deploy
as beans -->
<context:component-scan base-
package="com.companyname.springapp.repository" />
<context:component-scan base-
package="com.companyname.springapp.service" />

</beans>

Hemos definido un productDao el cual es la clase que estamos testeando. Adems


hemos definido un DataSource con comodines para los valores de configuracin. Sus
valores sern tomados de un archivo de propiedades en tiempo de ejecucin. El
bean property-placeholder que hemos declarado leer este archivo de
propiedades y sustituir cada comodn con su valor actual. Esto es conveniente
puesto que separa los valores de conexin en su propio archivo, y estos valores a
menudo suelen ser cambiados durante el despliegue de la aplicacin. Vamos a poner
este nuevo archivo tanto en el directorio 'src/test/resources' como en el
directorio 'webapp/WEB-INF/classes' por lo que estar disponible cuando
ejecutemos los tests y cuando despleguemos la aplicacin web. El contenido de este
archivo de propiedades es:

'springapp/src/test/resources/jdbc.properties' y 'springapp/src/main/we
bapp/WEB-INF/classes/jdbc.properties':
jdbc.driverClassName=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/springapp
jdbc.username=springappuser
jdbc.password=pspringappuser
hibernate.dialect=org.hibernate.dialect.MySQLDialect

jpa.database = MYSQL
hibernate.generate_statistics = true
hibernate.show_sql = true
jpa.showSql = true
jpa.generateDdl = true

Por otro lado, la configuracin del bean entityManager requerir la creacin de un


fichero donde se defina la unidad de persistencia. Por defecto, este fichero se deber
llamar 'persistence.xml' y deber crearse bajo el directorio 'META-INF' dentro
del directorio de recursos de la aplicacin 'src/main/resources'

'springapp/src/main/resources/META-INF/persistence.xml':
<?xml version="1.0" encoding="UTF-8"?>
<persistence xmlns="http://java.sun.com/xml/ns/persistence"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/persistence
http://java.sun.com/xml/ns/persistence/persistence_1_0.xsd"
version="1.0">
<persistence-unit name="springappPU" transaction-
type="RESOURCE_LOCAL">
</persistence-unit>
</persistence>

Ahora disponemos del codigo suficiente para ejecutar los tests


de 'JPAProductDaoTests' y hacerlos pasar.
5.4. Resumen
Ya hemos completado la capa de persistencia y en la prxima parte vamos a
integrarla con nuestra aplicacin web. Pero primero, resumamos rpidamente todo
lo que hemos hecho en esta parte.

Primero hemos configurado nuestra base de datos y ejecutado las sentencias


SQL para crear una tabla en la base de datos y cargar algunos datos de prueba.

Hemos creado una clase DAO que manejar el trabajo de persistencia


mediante JPA usando la clase Product.

Finalmente hemos creado tests de integracin para comprobar su


funcionamiento.

A continuacin puedes ver una captura de pantalla que muestra el aspecto que
debera tener la estructura de directorios del proyecto despus de seguir todas las
instrucciones anteriores.

La estructura de directorios del proyecto al final de la parte 5


Captulo 6. Integrando la Aplicacin Web con la Capa de
Persistencia
Esta es la Parte 6 del tutorial paso a paso sobre como desarrollar una aplicacin web
desde cero usando Spring Framework. En la Parte 1 hemos configurado el entorno y
puesto en marcha una aplicacin bsica. En la Parte 2 hemos mejorado la aplicacin
que habamos construido hasta entonces. En la Parte 3 hemos aadido toda la lgica
de negocio y los tests unitarios, y en la Parte 4 hemos desarrollado la interface web.
En la Parte 5 hemos desarrollado la capa de persistencia. Ahora es el momento de
integrarlo todo junto en una aplicacin web completa.
6.1. Modificar la Capa de Servicio
Si hemos estructurado nuestra aplicacin adecuadamente, slo tenemos que cambiar
la capa de servicio para que haga uso de la persistencia en base de datos. Las clases
de la vista y el controlador no tienen que ser modificadas, puesto que no deberan
ser conscientes de ningn detalle de la implementacin de la capa de servicio. As
que vamos a aadir persistencia a la implementacin de ProductManager. Modifica la
clase SimpleProductManager y aade una referencia a la
interface ProductDao adems de un mtodo setter para esta referencia. Qu
implementacin usemos debe ser irrelevante para la clase ProductManager, a la cual
se le inyectar el DAO de manera automtica a travs del m todo
llamado setProductDao. El mtodo getProducts usar ahora este DAO para
recuperar la lista de productos. Finalmente, el mtodo increasePrices recuperar
la lista de productos y, despus de haber incrementado los precios, almacenar los
productos de nuevo en la base de datos usando el mtodo saveProduct definido en
el DAO.

'springapp/src/main/java/com/companyname/springapp/service/SimpleProdu
ctManager.java':
package com.companyname.springapp.service;

import java.util.List;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import com.companyname.springapp.domain.Product;
import com.companyname.springapp.repository.ProductDao;

@Component
public class SimpleProductManager implements ProductManager {

private static final long serialVersionUID = 1L;

@Autowired
private ProductDao productDao;

public void setProductDao(ProductDao productDao) {


this.productDao = productDao;
}

public List<Product> getProducts() {


return productDao.getProductList();
}

public void increasePrice(int percentage) {


List<Product> products = productDao.getProductList();
if (products != null) {
for (Product product : products) {
double newPrice = product.getPrice().doubleValue() *
(100 + percentage)/100;
product.setPrice(newPrice);
productDao.saveProduct(product);
}
}
}
}
6.2. Resolver los tests fallidos
Hemos modificado SimpleProductManager y ahora, evidentemente, los tests fallan.
Necesitamos proporcionar a ProductManager una implementacin en memoria
de ProductDao. Realmente no queremos usar el verdadero DAO puesto que
queremos evitar tener acceso a la base de datos en nuestros tests unitarios.
Aadiremos una clase llamada InMemoryProductDao que almacenar una lista de
productos que sern definidos en el constructor. Esta clase en memoria tiene que ser
pasada a SimpleProductManager en el momento de ejecutar los tests.

'springapp/src/test/java/com/companyname/springapp/repository/InMemory
ProductDao.java':
package com.companyname.springapp.repository;

import java.util.List;

import com.companyname.springapp.domain.Product;

public class InMemoryProductDao implements ProductDao {

private List<Product> productList;

public InMemoryProductDao(List<Product> productList) {


this.productList = productList;
}

public List<Product> getProductList() {


return productList;
}

public void saveProduct(Product prod) {


}
}

Y aqu esta la versin modificada de SimpleProductManagerTests:

'springapp/src/test/java/com/companyname/springapp/service/SimpleProdu
ctManagerTests.java':
package com.companyname.springapp.service;

import java.util.ArrayList;
import java.util.List;

import static org.junit.Assert.*;

import org.junit.Before;
import org.junit.Test;

import com.companyname.springapp.domain.Product;
import com.companyname.springapp.repository.InMemoryProductDao;
import com.companyname.springapp.repository.ProductDao;

public class SimpleProductManagerTests {

private SimpleProductManager productManager;

private List<Product> products;

private static int PRODUCT_COUNT = 2;

private static Double CHAIR_PRICE = new Double(20.50);


private static String CHAIR_DESCRIPTION = "Chair";

private static String TABLE_DESCRIPTION = "Table";


private static Double TABLE_PRICE = new Double(150.10);

private static int POSITIVE_PRICE_INCREASE = 10;

@Before
public void setUp() throws Exception {
productManager = new SimpleProductManager();
products = new ArrayList<Product>();

// stub up a list of products


Product product = new Product();
product.setDescription("Chair");
product.setPrice(CHAIR_PRICE);
products.add(product);

product = new Product();


product.setDescription("Table");
product.setPrice(TABLE_PRICE);
products.add(product);
ProductDao productDao = new InMemoryProductDao(products);
productManager.setProductDao(productDao);
//productManager.setProducts(products);
}

@Test
public void testGetProductsWithNoProducts() {
productManager = new SimpleProductManager();
productManager.setProductDao(new InMemoryProductDao(null));
assertNull(productManager.getProducts());
}

@Test
public void testGetProducts() {
List<Product> products = productManager.getProducts();
assertNotNull(products);
assertEquals(PRODUCT_COUNT, productManager.getProducts().size());

Product product = products.get(0);


assertEquals(CHAIR_DESCRIPTION, product.getDescription());
assertEquals(CHAIR_PRICE, product.getPrice());

product = products.get(1);
assertEquals(TABLE_DESCRIPTION, product.getDescription());
assertEquals(TABLE_PRICE, product.getPrice());
}

@Test
public void testIncreasePriceWithNullListOfProducts() {
try {
productManager = new SimpleProductManager();
productManager.setProductDao(new InMemoryProductDao(null));
productManager.increasePrice(POSITIVE_PRICE_INCREASE);
}
catch(NullPointerException ex) {
fail("Products list is null.");
}
}

@Test
public void testIncreasePriceWithEmptyListOfProducts() {
try {
productManager = new SimpleProductManager();
productManager.setProductDao(new InMemoryProductDao(new
ArrayList<Product>()));
//productManager.setProducts(new ArrayList<Product>());
productManager.increasePrice(POSITIVE_PRICE_INCREASE);
}
catch(Exception ex) {
fail("Products list is empty.");
}
}

@Test
public void testIncreasePriceWithPositivePercentage() {
productManager.increasePrice(POSITIVE_PRICE_INCREASE);
double expectedChairPriceWithIncrease = 22.55;
double expectedTablePriceWithIncrease = 165.11;

List<Product> products = productManager.getProducts();


Product product = products.get(0);
assertEquals(expectedChairPriceWithIncrease, product.getPrice(),
0);

product = products.get(1);
assertEquals(expectedTablePriceWithIncrease, product.getPrice(),
0);
}
}

Tambin necesitamos modificar InventoryControllerTests puesto que esta clase


tambin usa SimpleProductManager. Aqu est la version modificada
de InventoryControllerTests:

'springapp/src/test/java/com/companyname/springapp/web/InventoryContro
llerTests.java':
package com.companyname.springapp.web;

import java.util.ArrayList;
import java.util.Map;

import static org.junit.Assert.*;

import org.junit.Test;
import org.springframework.web.servlet.ModelAndView;

import com.companyname.springapp.domain.Product;
import com.companyname.springapp.repository.InMemoryProductDao;
import com.companyname.springapp.service.SimpleProductManager;

public class InventoryControllerTests {

@Test
public void testHandleRequestView() throws Exception{
InventoryController controller = new InventoryController();
SimpleProductManager spm = new SimpleProductManager();
spm.setProductDao(new InMemoryProductDao(new
ArrayList<Product>()));
controller.setProductManager(spm);
//controller.setProductManager(new SimpleProductManager());
ModelAndView modelAndView = controller.handleRequest(null,
null);
assertEquals("hello", modelAndView.getViewName());
assertNotNull(modelAndView.getModel());
@SuppressWarnings("unchecked")
Map modelMap = (Map) modelAndView.getModel().get("model");
String nowValue = (String) modelMap.get("now");
assertNotNull(nowValue);
}
}

Una vez realizados los cambios anteriores, comprobad que los


tests SimpleProductManagerTests y InventoryControllerTests se ejecutan
satisfactoriamente.
6.3. Crear un nuevo contexto de aplicacin para configurar la
capa de servicio
Hemos visto antes que es tremendamente fcil modificar la capa de servicio para
usar persistencia en base de datos. Esto es as porque est despegada de la capa
web. Ahora es el momento de despegar tambin la configuracin de la capa de
servicio de la capa web. Eliminaremos la configuracin de productManager y la lista
de productos del archivo de configuracin 'app-config.xml'. As es como este
archivo quedara ahora:

'springapp/src/main/webapp/WEB-INF/spring/app-config.xml':
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/mvc
http://www.springframework.org/schema/mvc/spring-mvc.xsd
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd">

<bean id="messageSource"
class="org.springframework.context.support.ResourceBundleMessageSource">
<property name="basename" value="messages"/>
</bean>

<!-- Scans the classpath of this application for @Components to deploy


as beans -->
<context:component-scan base-package="com.companyname.springapp.web"
/>

<!-- Configures the @Controller programming model -->


<mvc:annotation-driven/>

<bean id="viewResolver"
class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="viewClass"
value="org.springframework.web.servlet.view.JstlView"></property>
<property name="prefix" value="/WEB-INF/views/"></property>
<property name="suffix" value=".jsp"></property>
</bean>

</beans>

Todava necesitamos configurar la capa de servicio y lo haremos en nuestro propio


archivo de contexto de aplicacin. Este archivo se
llama 'applicationContext.xml' y ser cargado mediante un servlet listener que
definiremos en 'web.xml'. Todos los bean configurados en este nuevo contexto de
aplicacin estarn disponibles desde cualquier contexto del servlet.

'springapp/src/main/webapp/WEB-INF/web.xml':
<?xml version="1.0" encoding="UTF-8"?>

<web-app version="2.5" xmlns="http://java.sun.com/xml/ns/javaee"


xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee
http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd">

<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/spring/applicationContext.xml</param-value>
</context-param>

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

<display-name>Springapp</display-name>

<servlet>
<servlet-name>springapp</servlet-name>
<servlet-
class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>
/WEB-INF/spring/app-config.xml
</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>

<servlet-mapping>
<servlet-name>springapp</servlet-name>
<url-pattern>*.htm</url-pattern>
</servlet-mapping>

</web-app>
Ahora creamos un nuevo archivo 'applicationContext.xml' en el
directorio '/WEB-INF/spring'".

'springapp/src/main/webapp/WEB-INF/spring/applicationContext.xml':
<?xml version="1.0" encoding="UTF-8"?>
<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"
xmlns:p="http://www.springframework.org/schema/p"
xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx-4.2.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-4.2.xsd">

<!-- holding properties for database connectivity /-->


<context:property-placeholder location="classpath:jdbc.properties"/>

<!-- enabling annotation driven configuration /-->


<context:annotation-config/>

<bean id="dataSource"
class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<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>

<bean id="entityManagerFactory"
class="org.springframework.orm.jpa.LocalContainerEntityManagerFacto
ryBean"
p:dataSource-ref="dataSource"
p:jpaVendorAdapter-ref="jpaAdapter">
<property name="loadTimeWeaver">
<bean
class="org.springframework.instrument.classloading.InstrumentationLoadTime
Weaver"/>
</property>
<property name="persistenceUnitName"
value="springappPU"></property>
</bean>

<bean id="jpaAdapter"
class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter
"
p:database="${jpa.database}"
p:showSql="${jpa.showSql}"/>
<bean id="transactionManager"
class="org.springframework.orm.jpa.JpaTransactionManager"
p:entityManagerFactory-ref="entityManagerFactory"/>

<tx:annotation-driven transaction-manager="transactionManager"/>

<!-- Scans the classpath of this application for @Components to deploy


as beans -->
<context:component-scan base-
package="com.companyname.springapp.repository" />
<context:component-scan base-
package="com.companyname.springapp.service" />

</beans>
6.4. Test final de la aplicacin completa
Ahora es el momento de ver si todas estas piezas funcionan juntas. Construye y
despliega la aplicacin finalizada y recuerda tener la base de datos arrancada y
funcionando. Esto es lo que deberas ver cuando apuntes tu navegador web a la
aplicacin:

La aplicacin completa

La aplicacin aparece exactamente como lo haca antes. Sin embargo, hemos aadido
la persistencia en base de datos, por lo que si cierras la aplicacin tus incrementos
de precio no se perdern sino que estarn todava all cuando vuelvas a cargar la
aplicacin.
6.5. Resumen
Hemos completado las tres capas de la aplicacin -- la capa web, la capa de servicio
y la capa de persistencia. En esta ltima parte hemos reconfigurado la aplicacin.

Primero hemos modificado la capa de servicio para usar la interface


ProductDAO.

Despus hemos tenido que arreglar algunos fallos en los tests de la capa de
servicio y la capa web.

A continuacin, hemos introducido un nuevo applicationContext para separar


la configuracin de la capa de servicio y de la capa de persistencia de la
configuracin de la capa web.

Finalmente hemos desplegado la aplicacin y testeado que an funciona.

A continuacin puedes ver una captura de pantalla que muestra el aspecto que
debera tener la estructura de directorios del proyecto despus de seguir todas las
instrucciones anteriores.

También podría gustarte