Documenti di Didattica
Documenti di Professioni
Documenti di Cultura
Autor
Francisco Grimaldo Moreno (francisco.grimaldo@uv.es - Website)
Fuentes consultadas
Este tutorial es una adaptacin del tutorial 'Developing a Spring Framework MVC application step-by-step'
para la versin 3 de Spring Framework donde, adems, la persistencia de datos se realiza mediante JPA.
Una parte del texto, a su vez, proviene de la traduccin al castellano realizada por David Marco Palao. Los
tutoriales anteriores se pueden consultar en:
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
A. Descargar Proyecto Completo para Spring Tool Suite
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), as como las
diversas libreras de servicios (como la librera JDBC).
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 3.2.1
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.
2. Software Requerido
Se requiere el siguiente software y su adecuada configuracin en el entorno. Deberas sentirte razonablemente
confortable usando las siguientes tecnologas.
'maven-archetype-webapp'.
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.
Si aparecera un warning en el proyecto causado por el uso de diferentes versiones de la JDK, ajustarlas conveniente
configurando el Build Path del proyecto.
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).
'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/WEBINF' 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/webapp_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: SpringSource 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).
'pom.xml'. Las libreras seran descargadas y aadidas automticamente al proyecto en el momento en el que
guardemos el fichero 'pom.xml'
Group Id
junit
Artifact Id
junit
org.springframework spring-core
Version
4.11
Scope
Test
${org.springframework.version}
servlet-api
2.5
Provided
Ntese como la dependencia 'junit' (incluida por defecto en el fichero 'pom.xml') ha sido actualizada a la versin
'4.11'. 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.
10
( DispatcherServlet).
11
'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/webapp_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).
12
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 '3.2' de Spring
Framework.
13
'springapp/src/main/java/com/companyname/springapp/web/HelloController.java':
package com.companyname.springapp.web;
import
import
import
import
java.io.IOException;
javax.servlet.ServletException;
javax.servlet.http.HttpServletRequest;
javax.servlet.http.HttpServletResponse;
import
import
import
import
import
org.apache.commons.logging.Log;
org.apache.commons.logging.LogFactory;
org.springframework.stereotype.Controller;
org.springframework.web.bind.annotation.RequestMapping;
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 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 creaci
de ficheros.
14
'springapp/src/test/java/com/companyname/springapp/web/HelloControllerTests.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.
15
La aplicacin actualizada
1.11. Resumen
Echemos un vistazo rpido a las partes de nuestra aplicacion que hemos creado hasta ahora.
1. 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.
2. Un controlador frontal, DispatcherServlet, con el correspondiente archivo de configuracin 'app-
config.xml'.
3. Un controlador de pgina, HelloController, con funcionalidad limitada simplemente devuelve un objeto
16
17
config.xml'.
3. Un controlador de pgina, HelloController, con funcionalidad limitada simplemente devuelve un objeto
jstl
1.2
taglibs
standard
1.1.2
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:
18
'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/HelloControllerTests.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, 'WEBINF/views/hello.jsp', as como la pareja clave/valor con la fecha y hora actual con la clave "now" y el valor:
'now'.
19
'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
import
import
import
import
org.apache.commons.logging.Log;
org.apache.commons.logging.LogFactory;
org.springframework.stereotype.Controller;
org.springframework.web.bind.annotation.RequestMapping;
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
20
'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/HelloControllerTests.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);
}
}
21
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
import
import
import
import
org.apache.commons.logging.Log;
org.apache.commons.logging.LogFactory;
org.springframework.stereotype.Controller;
org.springframework.web.bind.annotation.RequestMapping;
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.
1. 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.
1. 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.
22
23
24
'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.).
25
'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/ProductManager.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();
}
26
'springapp/src/main/java/com/companyname/springapp/service/SimpleProductManager.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/SimpleProductManagerTests.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());
}
}
27
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/SimpleProductManagerTests.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);
28
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
'springapp/src/test/java/com/companyname/springapp/service/SimpleProductManagerTests.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;
29
30
@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/SimpleProductManager.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).
31
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 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.
32
java.io.IOException;
java.util.Date;
java.util.HashMap;
java.util.Map;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import
import
import
import
import
import
org.apache.commons.logging.Log;
org.apache.commons.logging.LogFactory;
org.springframework.beans.factory.annotation.Autowired;
org.springframework.stereotype.Controller;
org.springframework.web.bind.annotation.RequestMapping;
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());
33
34
35
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 width="95%" bgcolor="f8f8ff" border="0" cellspacing="0" cellpadding="5">
<tr>
<td align="right" width="20%">Increase (%):</td>
36
<td width="20%">
<form:input path="percentage"/>
</td>
<td width="60%">
<form:errors path="percentage" cssClass="error"/>
</td>
</tr>
</table>
<br>
<input type="submit" align="center" 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
Artifact Id
Version
javax.validation
validation-api
1.1.0.Final
org.hibernate
hibernate-validator
4.3.1
org.slf4j
slf4j-api
1.7.5
org.slf4j
slf4j-log4j12
1.7.5
Para configurar adecuadamente la librera log4j y evitar as algunos Warnings, recomendamos aadir 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'
'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.
-->
-->
<!-<!-<!-<!-<!-<!--
-->
-->
-->
-->
-->
-->
<!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"
>
37
-->
-->
appender-ref?)>
38
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
>
39
'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/PriceIncrease.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 {
40
org.springframework.beans.factory.annotation.Autowired;
org.springframework.stereotype.Controller;
org.springframework.validation.BindingResult;
org.springframework.web.bind.annotation.RequestMapping;
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();
41
'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.
42
La aplicacin actualizada
4.7. Resumen
Vamos a ver lo que hemos hecho en la Parte 4.
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.
43
44
45
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
Artifact Id
Version
mysql
mysql-connector-java
5.1.24
org.hibernate.java-persistence
jpa-api
2.0-cr-1
org.hibernate
hibernate-entitymanager
4.2.0.Final
org.springframework
spring-orm
${org.springframework.version}
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/ProductDao.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
46
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
import
import
import
import
import
javax.persistence.Column;
javax.persistence.Entity;
javax.persistence.GeneratedValue;
javax.persistence.GenerationType;
javax.persistence.Id;
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;
}
47
el
momento
de
aadir
tests
nuestra
aplicacin
DAO
sobre
JPA.
Para
ello,
crearemos
la
clase
org.junit.Before;
org.junit.Test;
org.springframework.context.ApplicationContext;
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() {
48
49
<!-- 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/webapp/WEBINF/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.
50
51
52
}
}
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/InMemoryProductDao.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/SimpleProductManagerTests.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";
53
54
necesitamos
modificar
clase
tambin
usa
'springapp/src/test/java/com/companyname/springapp/web/InventoryControllerTests.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());
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
55
'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/webapp_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>
56
<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.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.LocalContainerEntityManagerFactoryBean"
p:dataSource-ref="dataSource"
p:jpaVendorAdapter-ref="jpaAdapter">
<property name="loadTimeWeaver">
<bean class="org.springframework.instrument.classloading.InstrumentationLoadTimeWeaver"/>
</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"/>
57
<!-- 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.
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.
58
59