Por Qué Spring
Uno de sus propósitos principales es permitir a los programadores centrarse
en la parte más importante de todas: modelar e implementar requerimientos de
negocio. Otro de sus propósitos es hacer seguir las mejores prácticas de
programación de una manera sencilla.
Spring se acopla a
tu aplicación sin obligarte a modificar tu código para utilizar las
funcionalidades y beneficios que ofrece. Para usar Spring no es imprescindible
implementar un interfaz propia de Spring o heredar de una clase propia, lo que
significa que Spring estará ahí pero tus clases serán Java puras. Como mucho,
tus clases tendrán anotaciones propias de Spring, pero no dejan de ser
anotaciones por lo que tu clase será reutilizable en cualquier momento, sin
cambios.
Gracias a Spring, nuestro código Java puede ser más limpio, elegante y
reutilizable. Elegante y reutilizable ya que la filosofía de Spring nos guía a
programar orientado a interfaces, de modo que toda nuestra aplicación sea
altamente modular y posea bajo acoplamiento.
Entorno de desarrollo
Utilizaremos Spring Tool Suite. Es un entorno de desarrollo basado en
Eclipse, listo para usar. Nos permite implementar, depurar, ejecutar y
desplegar las aplicaciones de Spring.
Podemos descargarnos el entorno STS de http://spring.io/tools.
En este momento tenemos la versión es la 3.6 que está basada en Eclipse Luna 4.4
Maven
Maven es una herramienta Open Source para gestionar el ciclo de vida de
proyectos software. Cuando hablamos de gestionar el ciclo de vida, nos
referimos desde la creación de un proyecto en un lenguaje dado, hasta la
generación de un binario que pueda distribuirse con el proyecto.
Maven nació dentro de la fundación Apache para complementar a Apache Ant,
la herramienta de compilación más usada en el mundo Java. Ant es una
herramienta que permite crear scripts que indican cómo compilar un proyecto
Java y generar un binario. Maven nos brinda una mejora respecto a Apache Ant,
ya que nos proporciona una estructura homogénea para cualquier proyecto y nos
permite, además, una gestión avanzada de dependencias, informes sobre testing
automático y extensibilidad vía plugins que Ant no proporcionaba.
Sin embargo, hay que destacar que, en segundo plano, Maven sigue utilizando
internamente Ant para manejar la compilación (e incluso se pueden lanzar tareas
de Ant desde Maven). Si somos correctos a la hora de presentar Maven no diremos
que es un sustituto de Ant ni una mejora de éste, sino un complemento de este
último que nos brinda importantes mejoras y estándares de modelado de proyecto.
Un Proyecto en Maven se define mediante un archivo POM llamado pom.xml.
Maven proporciona arquetipos para ayudar a la creación de proyectos de
cualquier tipo.
Para este manual utilizaremos el arquetipo maven-archetipe-webapp.
El elemento groupId
Representa un grupo, empresa, equipo u organización. Generalmente se nombra
empezando con un dominio y la organización que crea el proyecto ej: org.miempresa.
Con groupID establecemos el conjunto de proyectos al que pertenece nuestro
proyecto. Este nombre va a servir de paquete inicial para todas las clases del
proyecto. Todos los proyectos maven deben
pertenecer a un grupo, aunque sea único para él.
El elemento artifactId
Representa de manera única el nombre ya sea del proyecto completo o de un
módulo de este.
Con artifacId establecemos el nombre que queremos dar al proyecto. Maven creará un directorio con
este nombre.
El elemento version
Representa la versión de este proyecto o de este módulo. Bajo desarrollo
suele usarse un número más la palabra SNAPSHOT.
Creación de un nuevo proyecto
Para estos desarrollos vamos a utilizar maven, aunque no sería necesario,
lo usamos para resolver problemas de dependencias y para que nos ayude en todas
las fases del ciclo de vida de los proyectos, incluyendo validación, generación
de código, compilación, pruebas, empaquetamiento, pruebas de integración,
verificación, instalación, despliegue y creación e implementación de sitios.
Creamos un nuevo
proyecto maven
Lo almacenamos en
el espacio de trabajo por defecto
Seleccionamos el
arquetipo WebApp
Introducimos el
Group Id y el Artifact Id
Y finalizamos
Si nos diera un problema de que no encuentra el arquetipo, realizamos estos
pasos previos:
- Open Window > Preferences
- Open Maven > Archetypes
- Click 'Add Remote Catalog' and add the following:
- Catalog File: http://repo1.maven.org/maven2/archetype-catalog.xml
- Description: maven catalog
Y seleccionamos el arquetipo del catálogo que hemos creado
Configuración dependencias pom.xml
Añadimos estas dependencias al pom.xml
<!-- Faces Implementation
-->
<dependency>
<groupId>com.sun.faces</groupId>
<artifactId>jsf-impl</artifactId>
<version>2.2.4</version>
</dependency>
<!-- Faces Library -->
<dependency>
<groupId>com.sun.faces</groupId>
<artifactId>jsf-api</artifactId>
<version>2.2.4</version>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>jstl</artifactId>
<version>1.1.2</version>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>3.1.0</version>
<scope>provided</scope>
</dependency>
<!-- Primefaces
dependency -->
<dependency>
<groupId>org.primefaces</groupId>
<artifactId>primefaces</artifactId>
<version>4.0</version>
</dependency>
<!-- Spring dependencies
-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-orm</artifactId>
<version>3.0.5.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
<version>3.0.5.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>3.0.5.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.webflow</groupId>
<artifactId>spring-faces</artifactId>
<version>2.3.0.RELEASE</version>
</dependency>
<!-- MySQL Java Connector
dependency -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.9</version>
</dependency>
<!-- JUnit -->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.11</version>
<scope>test</scope>
</dependency>
Añadir el repositorio para PrimeFaces
En el caso de que
no pueda bajarse la librería de PrimeFaces añadimos este repositorio
<repositories>
<repository>
<id>prime-repo</id>
<name>PrimeFaces Maven Repository</name>
<url>http://repository.primefaces.org</url>
<layout>default</layout>
</repository>
</repositories>
Configuración WEB-INF/web.xml
El fichero web.xml es el descriptor de despliegue para una aplicación Web
Java. Este fichero ya está creado, lo modificamos con estos parámetros dentro
de los tags <web-app></web-app>:
<display-name>Biblio</display-name>
<!-- Change to
"Production" when you are ready to deploy -->
<context-param>
<param-name>javax.faces.PROJECT_STAGE</param-name>
<param-value>Development</param-value>
</context-param>
<!-- Spring Context
Configuration' s Path definition -->
<context-param>
<param-name>
contextConfigLocation
</param-name>
<param-value>
classpath:applicationContext.xml
</param-value>
</context-param>
<!-- Listeners -->
<listener>
<listener-class>
org.springframework.web.context.ContextLoaderListener
</listener-class>
</listener>
<listener>
<listener-class>
org.springframework.web.context.request.RequestContextListener
</listener-class>
</listener>
<!-- JSF mapping -->
<servlet>
<servlet-name>Faces Servlet</servlet-name>
<servlet-class>javax.faces.webapp.FacesServlet</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>
<welcome-file-list>
<welcome-file>index.xhtml</welcome-file>
</welcome-file-list>
<!-- Mapping with servlet
and url for the http requests. -->
<!-- Servlet Mappings
-->
<servlet-mapping>
<servlet-name>Faces Servlet</servlet-name>
<url-pattern>/faces/*</url-pattern>
</servlet-mapping>
<servlet-mapping>
<servlet-name>Faces Servlet</servlet-name>
<url-pattern>*.jsf</url-pattern>
</servlet-mapping>
<servlet-mapping>
<servlet-name>Faces Servlet</servlet-name>
<url-pattern>*.faces</url-pattern>
</servlet-mapping>
<servlet-mapping>
<servlet-name>Faces Servlet</servlet-name>
<url-pattern>*.xhtml</url-pattern>
</servlet-mapping>
Configuración WEB-INF/faces-config.xml
Creamos el fichero de configuración de JSF faces-config.xml
<?xml version="1.0"
encoding="UTF-8"?>
<faces-config version="2.0"
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-facesconfig_2_0.xsd">
<!-- JSF and Spring are integrated
-->
<application>
<el-resolver>
org.springframework.web.jsf.el.SpringBeanFacesELResolver
</el-resolver>
</application>
</faces-config>
De esta
manera estamos activando el soporte de Spring para la inyección de dependencias
de tal forma que JSF sabrá que si no encuentra un bean bajo su contexto debe ir
a buscarlo al contexto de Spring.
Configuración /src/main/resources/applicationContext.xml
Este es el fichero de configuración de Spring. Puede estar
ubicado en muchos sitios pero se debe mapear en el web.xml. Nuestra
configuración de ubicación ha sido classpath:applicationContext.xml,
con lo que lo metemos dentro de los recursos en el classpath, concretamente en src/main/resources/applicationContext.xml
<?xml version="1.0"
encoding="UTF-8"?>
<beans
xmlns="http://www.springframework.org/schema/beans"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:aop="http://www.springframework.org/schema/aop" xmlns:tx="http://www.springframework.org/schema/tx"
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/tx
http://www.springframework.org/schema/tx/spring-tx-3.0.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-3.0.xsd">
<!--
Scans the classpath of this application for @Components -->
<context:component-scan base-package="es.ocascal.biblio" />
</beans>
Con esta anotación realiza un escaneo dentro de es.ocascal.biblio e inyectará todas las
clases anotadas, veremos esto un poco mas adelante.
Aplicación Biblio
Estructura del proyecto
Creamos la estructura de paquetes, quedando nuestro proyecto
de la siguiente forma:
BBDD Mysql biblio
Creamos las tablas de Mysql que vamos a utilizar y las
rellenamos con algunos datos.
CREATE TABLE IF NOT EXISTS `categoria` (
`id`
varchar(10) NOT NULL,
`descripcion`
varchar(20) NOT NULL,
PRIMARY
KEY (`id`)
) ENGINE=MyISAM DEFAULT CHARSET=latin1;
INSERT INTO `categoria` (`id`, `descripcion`) VALUES
('1', 'Java'),
('2', 'Net'),
('3', 'Web');
CREATE TABLE IF NOT EXISTS `libros` (
`Isbn`
varchar(10) NOT NULL,
`Titulo`
varchar(30) NOT NULL,
`Categoria`
varchar(30) NOT NULL,
PRIMARY KEY
(`Isbn`)
) ENGINE=MyISAM DEFAULT CHARSET=latin1;
INSERT INTO `libros` (`Isbn`,
`Titulo`, `Categoria`) VALUES
('1', 'Programacion', '1'),
('2', 'Programacion', '2'),
('3', 'CSS', '3');
Clases de Modelo de Negocio
Vamos a crear una clase por cada
tabla de nuestra base de datos, dentro del paquete bo que será donde se alojen nuestras clases que modelan el negocio.
Estas clases implementan la interfaz serializable.
Clase Categoria
package es.ocascal.biblio.bo;
import java.io.Serializable;
public class Categoria implements Serializable{
private static final long serialVersionUID = 1L;
private String id;
private String descripcion;
public void setId(String id){
this.id = id;
}
public void setDescripcion(String descripcion){
this.descripcion = descripcion;
}
public String getId(){
return this.id;
}
public String getDescripcion(){
return this.descripcion;
}
}
Clase Libro
package es.ocascal.biblio.bo;
import java.io.Serializable;
public class Libro implements Serializable{
private static final long serialVersionUID = 1L;
private String isbn;
private String titulo;
private String categoria;
public void setIsbn(String isbn){
this.isbn = isbn;
}
public void setTitulo(String titulo){
this.titulo = titulo;
}
public void setCategoria(String categoria){
this.categoria = categoria;
}
public String getIsbn(){
return this.isbn;
}
public String getTitulo(){
return this.titulo;
}
public String getCategoria(){
return this.categoria;
}
}
DAO
Creamos un DAO genérico con las
operaciones básicas:
package es.ocascal.biblio.dao;
import java.io.Serializable;
import java.util.List;
public interface GenericDAO<T,Id extends Serializable> {
T buscarPorClave (Id id);
List<T>buscarTodos();
void insertar(T objeto);
void actualizar(T objeto);
void borrar(T objeto);
}
Deberíamos crear un DAO por cada
entidad de negocio que tengamos, en este caso uno para libro y otro para
categoría, haciendo que extiendan a nuestro DAO genérico, pero de categoría
solo nos interesa el listado a la hora de dar de insertar/actualizar libros,
por lo que solo vamos a crear una DAO para la biblioteca donde los métodos
heredados serán para operaciones sobre libros y añadiremos un método a su
definición para obtener el listado de
categorías.
package es.ocascal.biblio.dao;
import java.util.List;
import es.ocascal.biblio.bo.Categoria;
import es.ocascal.biblio.bo.Libro;
public interface BiblioDAO extends GenericDAO<Libro,String> {
List<Categoria>buscarCategoriasTodos();
}
Ahora vamos a realizar la implementación del DAO mediante la
plantilla JDBC.
Creamos el paquete es.ocascal.biblio.dao.jdbc
y añadimos la clase que implementa las operaciones de nuestro DAO mediante JDBC.
El proyecto debe quedar así:
Esta clase, JDBCBiblioDAO,
la marcaremos con la anotación @Repository y la
implementamos de esta forma:
package
es.ocascal.biblio.dao.jdbc;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import
org.springframework.dao.DataAccessException;
import
org.springframework.jdbc.core.BeanPropertyRowMapper;
import
org.springframework.jdbc.core.simple.SimpleJdbcTemplate;
import
org.springframework.stereotype.Repository;
import es.ocascal.biblio.bo.Categoria;
import es.ocascal.biblio.bo.Libro;
import es.ocascal.biblio.dao.BiblioDAO;
@Repository(value = "biblioDAO")
public class JDBCBiblioDAO implements BiblioDAO {
private SimpleJdbcTemplate jdbcTemplate;
private static final String SQL_INSERT_LIBRO = "INSERT INTO libros (ISBN, TITULO,
CATEGORIA) VALUES (:isbn, :titulo, :categoria)";
private static final String SQL_UPDATE_LIBRO = "UPDATE libros SET TITULO =
:titulo, CATEGORIA= :categoria WHERE ISBN = :isbn";
private static final String SQL_DELETE_LIBRO = "DELETE FROM libros WHERE ISBN =
:isbn";
private static final String SQL_ALL_LIBRO = "SELECT LIBROS.ISBN AS ISBN,
LIBROS.TITULO AS TITULO, CATEGORIA.DESCRIPCION AS CATEGORIA FROM LIBROS,
CATEGORIA WHERE LIBROS.CATEGORIA=CATEGORIA.ID";
private static final String SQL_ONE_LIBRO ="SELECT ISBN, TITULO, CATEGORIA
FROM LIBROS WHERE ISBN = :isbn";
private static final String SQL_ALL_CATEGORIA = "SELECT ID, DESCRIPCION FROM
CATEGORIA";
public void setJdbcTemplate(SimpleJdbcTemplate jdbcTemplate) {
this.jdbcTemplate = jdbcTemplate;
}
@SuppressWarnings("unchecked")
public Libro buscarPorClave(String id) {
try {
Map<String,
Object> params =
new
HashMap<String, Object>();
params.put("isbn", id);
Libro libro = (Libro) jdbcTemplate.queryForObject(SQL_ONE_LIBRO, new BeanPropertyRowMapper(Libro.class), params);
return libro;
} catch (DataAccessException ex) {
return null;
}
}
@SuppressWarnings("unchecked")
public List<Libro> buscarTodos() {
List<Libro>
libros = jdbcTemplate.query(SQL_ALL_LIBRO, new BeanPropertyRowMapper(Libro.class));
return libros;
}
public void actualizar(Libro libro) {
Map<String, Object> params = new HashMap<String, Object>();
params.put("isbn", libro.getIsbn());
params.put("titulo", libro.getTitulo());
params.put("categoria", libro.getCategoria());
jdbcTemplate.update(SQL_UPDATE_LIBRO, params);
}
public void borrar(Libro libro) {
Map<String, Object> params = new HashMap<String, Object>();
params.put("isbn", libro.getIsbn());
jdbcTemplate.update(SQL_DELETE_LIBRO, params);
}
public void insertar(Libro libro) {
Map<String, Object> params = new HashMap<String, Object>();
params.put("isbn", libro.getIsbn());
params.put("titulo", libro.getTitulo());
params.put("categoria", libro.getCategoria());
jdbcTemplate.update(SQL_INSERT_LIBRO, params);
}
@SuppressWarnings("unchecked")
public List<Categoria>
buscarCategoriasTodos() {
List<Categoria>
categorias = jdbcTemplate.query(SQL_ALL_CATEGORIA, new BeanPropertyRowMapper(Categoria.class));
return categorias;
}
}
Con la anotación @Repository(value = "biblioDAO") le indicamos a Spring que es del tipo
persistencia, automáticamente Spring lo pondrá a disposición como Singleton.
Nuestro SimpleJdbcTemplate jdbcTemplate es
la plantilla que nos proporciona Spring y que vamos a usar con inyección de
dependencias. Lo haremos por setter, así que debemos crear un setJdbcTemplate(SimpleJdbcTemplate jdbcTemplate).
Definiremos las órdenes que vamos a ejecutar en variables
estáticas y las utilizáramos en los distintos métodos de nuestro DAO.
Modificación para DAO del fichero applicationContext.xml
Añadimos al fichero de
configuración el datasouce que
usaremos para conectarnos a la base de datos y la plantilla JDBC, a la que le
suministramos el datasource definido anteriormente.
<!-- DataSource -->
<bean id="dataSource"
class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="driverClassName"
value="com.mysql.jdbc.Driver" />
<property name="url"
value="jdbc:mysql://localhost:3306/biblio"
/>
<property name="username"
value="java" />
<property name="password"
value="java" />
</bean>
<!-- Plantilla JDBC -->
<bean id="jdbcTemplate"
class="org.springframework.jdbc.core.simple.SimpleJdbcTemplate">
<constructor-arg ref="dataSource" />
</bean>
<!-- DAO Biblioteca
-->
<bean id="biblioDAO"
class=" es.ocascal.biblio.dao.jdbc.JDBCBiblioDAO">
<property name="jdbcTemplate"
ref="jdbcTemplate" />
</bean>
A la plantilla JDBC le pasamos el datasource con constructor-arg
Realizar un Test de JDBCBiblioDAO
Vamos a realizar un test para ver como funciona nuestro DAO.
Creamos la clase
JDBCBiblioDAOTest dentro de la carpeta de test que nos proporciona maven. Vamos
a crear un variable context donde cargaremos nuestro fichero
applicationContext.xml de configuración de Spring. Una vez cargado, obtenemos nuestro bean biblioDAO y realizamos las
pruebas. A simple vista se ve que el testCrudLibro() crea
un libro, lo modifica y lo elimina, realizando las operaciones crud y
asegurándonos que funciona correctamente.
package es.ocascal.biblio.dao;
import static org.junit.Assert.*;
import java.util.List;
import org.junit.Before;
import org.junit.Test;
import
org.springframework.context.ApplicationContext;
import
org.springframework.context.support.ClassPathXmlApplicationContext;
import es.ocascal.biblio.bo.Libro;
import es.ocascal.biblio.dao.jdbc.JDBCBiblioDAO;
public class JDBCBiblioDAOTest {
private ApplicationContext context;
private BiblioDAO biblioDAO;
@Before
public void setUp() throws Exception {
context = new ClassPathXmlApplicationContext("classpath:applicationContext.xml");
biblioDAO = (JDBCBiblioDAO) context.getBean("biblioDAO");
}
@Test
public void testGetLibroList() {
List<Libro> libros = biblioDAO.buscarTodos();
assertEquals(libros.size(), 3, 0);
}
@Test
public void testCrudLibro() {
Libro
libroTest = new Libro();
libroTest.setIsbn("ZZZZ");
libroTest.setTitulo("Test Libro");
libroTest.setCategoria("Test Categoria");
biblioDAO.insertar(libroTest);
Libro
libroTestAux = biblioDAO.buscarPorClave("ZZZZ");
assertEquals(libroTest.getTitulo(), libroTestAux.getTitulo());
assertEquals(libroTest.getCategoria(), libroTestAux.getCategoria());
libroTest.setTitulo("Test Libro Update");
biblioDAO.actualizar(libroTest);
libroTestAux = biblioDAO.buscarPorClave("ZZZZ");
assertEquals(libroTest.getTitulo(), libroTestAux.getTitulo());
biblioDAO.borrar(libroTest);
}
}
Capa de Servicio
La capa de servicio es una capa
que se encuentra por encima de las capas DAOs y será utilizada por los beans
que usaremos para Primefaces.
¿Por qué se usa esta capa en vez
de acceder directamente al DAO? Pues, a esta pregunta se contesta con otra
pregunta, ¿Qué pasa si necesitamos consumir un servicio Web donde se encuentre
el manejo de toda nuestra persistencia? Esto simplifica mucho el problema y nos
permite más abstracción. También facilita sacar nuestros servicio locales a
servicios Web para futuras necesidades que pueda tener nuestro sistema.
Creamos la interfaz GestorBiblio
y su implementación, dentro del paquete service.
package es.ocascal.biblio.service;
import java.io.Serializable;
import java.util.List;
import es.ocascal.biblio.bo.Categoria;
import es.ocascal.biblio.bo.Libro;
public interface GestorBiblio extends Serializable{
public List<Libro> getLibros();
public List<Categoria>
getCategorias();
public Libro buscarLibro(String isbn);
public void insertLibro(Libro libro);
public void updateLibro(Libro libro);
public void deleteLibro(Libro libro);
}
package es.ocascal.biblio.service;
import java.util.List;
import
org.springframework.beans.factory.annotation.Autowired;
import
org.springframework.stereotype.Component;
import es.ocascal.biblio.bo.Categoria;
import es.ocascal.biblio.bo.Libro;
import es.ocascal.biblio.dao.BiblioDAO;
@Service
public class GestorBiblioImpl implements GestorBiblio{
private static final long serialVersionUID = 1L;
@Autowired
private BiblioDAO biblioDAO;
public void setBiblioDAO(BiblioDAO biblioDAO) {
this.biblioDAO = biblioDAO;
}
public List<Libro> getLibros() {
return this.biblioDAO.buscarTodos();
}
public Libro buscarLibro(String isbn) {
return this.biblioDAO.buscarPorClave(isbn);
}
public List<Categoria>
getCategorias() {
return this.biblioDAO.buscarCategoriasTodos();
}
public void insertLibro(Libro libro) {
this.biblioDAO.insertar(libro);
}
public void updateLibro(Libro libro) {
this.biblioDAO.actualizar(libro);
}
public void deleteLibro(Libro libro) {
this.biblioDAO.borrar(libro);
}
}
Con
la anotación @Service indicamos que es un componente de
servicios y Spring al detectarlo lo tratará automáticamente.
Test sobre el servicio
Para hacer el test sobre el servicio debemos definir el
applicationContext el servicio para así poder leerlo desde el test.
<!-- Servicio declarado explicitamente
-->
<bean id="gestorBiblio"
class="es.ocascal.biblio.service.GestorBiblioImpl">
<property name="biblioDAO"
ref="biblioDAO" />
</bean>
Y creamos
el test
package es.ocascal.biblio.service;
import static org.junit.Assert.*;
import java.util.List;
import org.junit.Before;
import org.junit.Test;
import
org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import es.ocascal.biblio.bo.Libro;
public class GestorBiblioTest {
private ApplicationContext context;
private GestorBiblio gestorBiblio;
@Before
public void setUp() throws Exception {
context = new ClassPathXmlApplicationContext("classpath:applicationContext.xml");
gestorBiblio = (GestorBiblio) context.getBean("gestorBiblio");
}
@Test
public void testGetLibroList()
{
List<Libro> libros = gestorBiblio.getLibros();
assertEquals(libros.size(), 3, 0);
}
@Test
public void testCrudLibro() {
Libro libroTest = new Libro();
libroTest.setIsbn("ZZZZ");
libroTest.setTitulo("Test Libro");
libroTest.setCategoria("Test
Categoria");
gestorBiblio.insertLibro(libroTest);
Libro libroTestAux = gestorBiblio.buscarLibro("ZZZZ");
assertEquals(libroTest.getTitulo(), libroTestAux.getTitulo());
assertEquals(libroTest.getCategoria(), libroTestAux.getCategoria());
libroTest.setTitulo("Test Libro Update");
gestorBiblio.updateLibro(libroTest);
libroTestAux = gestorBiblio.buscarLibro("ZZZZ");
assertEquals(libroTest.getTitulo(), libroTestAux.getTitulo());
gestorBiblio.deleteLibro(libroTest);
}
}
Los Beans y PrimeFaces
Vamos a crear el bean que será
utilizado por las vistas. Es una aplicación simple y vamos a utilizar un solo
bean en el que vamos a inyectar el servicio GestorBiblio.
package es.ocascal.biblio.bean;
import java.util.List;
import javax.faces.bean.ManagedBean;
import javax.faces.bean.RequestScoped;
import
org.springframework.beans.factory.annotation.Autowired;
import
org.springframework.stereotype.Component;
import es.ocascal.biblio.bo.Libro;
import
es.ocascal.biblio.service.GestorBiblio;
@Component
@ManagedBean
@RequestScoped
public class LibroBean {
@Autowired
private GestorBiblio gestorBiblio;
public void setGestorBiblio(GestorBiblio gestorBiblio) {
this.gestorBiblio = gestorBiblio;
}
public List<Libro>
getListaLibros(){
return this.gestorBiblio.getLibros();
}
}
Creamos la vista para el listado y probamos
<!DOCTYPE html PUBLIC "-//W3C//DTD
XHTML 1.0 Strict//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:h="http://java.sun.com/jsf/html"
xmlns:f="http://java.sun.com/jsf/core"
xmlns:p="http://primefaces.org/ui"
xmlns:ui="http://java.sun.com/jsf/facelets">
<h:head>
<title>Biblio</title>
</h:head>
<h:body>
<h1>Listado Libros</h1>
<hr />
<p:dataTable var="libro" value="#{libroBean.listaLibros}">
<p:column headerText="ISBN">
<h:outputText value="#{libro.isbn}"
/>
</p:column>
<p:column headerText="Titulo">
<h:outputText value="#{libro.titulo}"
/>
</p:column>
<p:column headerText="Categoria">
<h:outputText value="#{libro.categoria}"
/>
</p:column>
</p:dataTable>
</h:body>
</html>
Y probamos:
Antes de continuar, vamos a cambiar el aspecto de nuestra
aplicación.
En el pom.xml añadimos la referencia al theme bluesky, ahora
si deberemos añadir el repositorio para Primefaces.
<!-- Tema PF -->
<dependency>
<groupId>org.primefaces.themes</groupId>
<artifactId>bluesky</artifactId>
<version>1.0.10</version>
</dependency>
En el web.xml añadimos el theme bluesky
<!-- Tema PF -->
<context-param>
<param-name>primefaces.THEME</param-name>
<param-value>bluesky</param-value>
</context-param>
Y nos queda una vista más elegante:
Versión final de LibroBean
package es.ocascal.biblio.bean;
import java.util.List;
import
javax.faces.application.FacesMessage;
import javax.faces.bean.ManagedBean;
import javax.faces.bean.ViewScoped;
import
javax.faces.context.FacesContext;
import org.springframework.beans.factory.annotation.Autowired;
import
org.springframework.stereotype.Component;
import es.ocascal.biblio.bo.Categoria;
import es.ocascal.biblio.bo.Libro;
import
es.ocascal.biblio.service.GestorBiblio;
@Component
@ManagedBean
@ViewScoped
public class LibroBean{
private String isbn;
private String titulo;
private String categoria;
private Libro selectedLibro; // Libro seleccionado para
modificación
@Autowired
private GestorBiblio gestorBiblio;
public void setGestorBiblio(GestorBiblio gestorBiblio) {
this.gestorBiblio = gestorBiblio;
}
public LibroBean(){ }
public Libro getSelectedLibro() {
return selectedLibro;
}
public void setSelectedLibro(Libro selectedLibro) {
this.selectedLibro = selectedLibro;
}
public List<Categoria>
getListaCategorias(){
return this.gestorBiblio.getCategorias();
}
public List<Libro>
getListaLibros(){
return this.gestorBiblio.getLibros();
}
public String getIsbn() {
return this.isbn;
}
public void setIsbn(String isbn) {
this.isbn = isbn;
}
public String getTitulo() {
return this.titulo;
}
public void setTitulo(String titulo) {
this.titulo = titulo;
}
public String getCategoria() {
return this.categoria;
}
public void setCategoria(String categoria) {
this.categoria = categoria;
}
public void saveLibro() {
if(gestorBiblio.buscarLibro(isbn)==null){
Libro l = new Libro();
l.setIsbn(this.isbn);
l.setTitulo(this.titulo);
l.setCategoria(this.categoria);
gestorBiblio.insertLibro(l);
mostrarMensaje("Tenemos un nuevo libro en la biblioteca (" + this.titulo + ")");
}else{
mostrarMensaje("Ya existe un Libro con ISBN=" + this.isbn);
}
}
public void updateLibro() {
this.gestorBiblio.updateLibro(this.selectedLibro);
mostrarMensaje("Libro con ISBN=" + selectedLibro.getIsbn() + " actualizado");
}
public void deleteLibro() {
this.gestorBiblio.deleteLibro(this.selectedLibro);
mostrarMensaje("Libro con ISBN=" + selectedLibro.getIsbn() + " dado de baja");
}
public void mostrarMensaje(String mensaje) {
FacesContext
context = FacesContext.getCurrentInstance();
context.addMessage(null, new FacesMessage("BIBLIO", mensaje) );
}
}
Anotaciones:
- @Component: Con esta anotación le indicamos a Spring que es un componente y que trate al objeto automáticamente.
- @ManagedBean: Con esta anotación le indicamos a Primefaces que es un componente Bean.
- @ViewScoped: Esta anotación sirve para especificar a Primefaces que el bean tendrá alcance de vista.
- @Autowired: inyección automática.
Vistas
Para implementar las vistas
usamos Primefaces como siempre salvo por un problema que he detectado con los
formularios. Debemos usar <form></form> en lugar de
<h:form></h:form>. Este problema es por alguna de las librerías
pero por más que he cambiado las versiones de jsf-impl y jsf-api no lo
he podido resolver.
Esto provoca que salgan estos mensajes
Pero si cambiamos a “Producction” en el web.xml, se dejan de mostrar
<!-- Change to
"Production" when you are ready to deploy -->
<context-param>
<param-name>javax.faces.PROJECT_STAGE</param-name>
<param-value>Development</param-value>
</context-param>
En cualquier caso, voy a intentar encontrar una solución.
Respecto al código de las vistas,
es Primefaces 4 y existe mucha documentación sobre ello, no voy a profundiza,
ya que este manual trata sobre la integración de Spring+Primefaces
index.xhtml
La versión final de esta vista es esta:
Y el código:
<?xml version='1.0' encoding='UTF-8' ?>
<!DOCTYPE html PUBLIC "-//W3C//DTD
XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:h="http://java.sun.com/jsf/html"
xmlns:f="http://java.sun.com/jsf/core"
xmlns:ui="http://java.sun.com/jsf/facelets"
xmlns:p="http://primefaces.org/ui">
<h:head>
<title>Biblio</title>
</h:head>
<h:body>
<h1>Listado Libros</h1>
<hr />
<form id="form">
<p:growl id="growl"
showDetail="true" sticky="true"
/>
<p:dataTable id="tablaLibros" var="libro"
value="#{libroBean.listaLibros}">
<p:column headerText="ISBN"
style="width:64px;text-align: center">
<h:outputText value="#{libro.isbn}"
/>
</p:column>
<p:column headerText="Titulo">
<h:outputText value="#{libro.titulo}"
/>
</p:column>
<p:column headerText="Categoria">
<h:outputText value="#{libro.categoria}"
/>
</p:column>
<p:column style="width:32px;text-align: center">
<p:commandButton update=":libroDetail"
oncomplete="PF('libroDialog').show()" icon="ui-icon-disk"
title="Modificar">
<f:setPropertyActionListener value="#{libro}"
target="#{libroBean.selectedLibro}" />
</p:commandButton>
</p:column>
<p:column style="width:32px;text-align: center">
<p:commandButton icon="ui-icon-trash"
title="Eliminar"
actionListener="#{libroBean.deleteLibro}" update=":tablaLibros :growl">
<f:setPropertyActionListener value="#{libro}"
target="#{libroBean.selectedLibro}" />
</p:commandButton>
</p:column>
</p:dataTable>
<p:dialog header="Actualizar Libro " widgetVar="libroDialog"
modal="true" showEffect="fade"
hideEffect="fade" resizable="false">
<p:outputPanel id="libroDetail"
style="text-align:center;">
<p:panelGrid columns="2"
rendered="#{not empty libroBean.selectedLibro}"
columnClasses="label,value">
<h:outputText value="ISBN:"
/>
<h:outputText id="isbn"
value="#{libroBean.selectedLibro.isbn}" />
<p:outputLabel for="titulo" value="Titulo:"
/>
<p:inputText id="titulo"
value="#{libroBean.selectedLibro.titulo}"
required="true" />
<h:outputText value="Categoria:"
/>
<h:selectOneMenu id="categoria"
value="#{libroBean.selectedLibro.categoria}">
<f:selectItem itemLabel="#{libroBean.selectedLibro.categoria}"
/>
<f:selectItems value="#{libroBean.listaCategorias}"
var="cat"
itemValue="#{cat.id}" itemLabel="#{cat.descripcion}"
/>
</h:selectOneMenu>
</p:panelGrid>
<p:commandButton value="Guardar"
icon="ui-icon-disk"
actionListener="#{libroBean.updateLibro}" update=":tablaLibros :growl" />
</p:outputPanel>
</p:dialog>
</form>
<p:separator />
<form>
<p:commandButton value="Nuevo"
icon="ui-icon-document" process="@this"
action="add.xhtml?faces-redirect=true" />
</form>
</h:body>
</html>
add.xhtml
Y el código:
<?xml version='1.0' encoding='UTF-8' ?>
<!DOCTYPE html PUBLIC "-//W3C//DTD
XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:h="http://java.sun.com/jsf/html"
xmlns:f="http://java.sun.com/jsf/core"
xmlns:ui="http://java.sun.com/jsf/facelets"
xmlns:p="http://primefaces.org/ui">
<h:head>
<title>Biblio</title>
</h:head>
<h:body>
<h1>Alta</h1>
<form>
<p:growl id="growl"
showDetail="true" sticky="true"
/>
<p:panel header="Datos del Libro">
<h:panelGrid columns="2"
cellpadding="5">
<p:outputLabel for="isbn"
value="ISBN:" />
<p:inputText id="isbn"
value="#{libroBean.isbn}" required="true" />
<p:outputLabel for="titulo"
value="Titulo:" />
<p:inputText id="titulo"
value="#{libroBean.titulo}" required="true" />
<p:outputLabel for="categoria"
value="Categoria:" />
<h:selectOneMenu id="categoria"
value="#{libroBean.categoria}">
<f:selectItems value="#{libroBean.listaCategorias}"
var="cat"
itemValue="#{cat.id}" itemLabel="#{cat.descripcion}"
/>
</h:selectOneMenu>
</h:panelGrid>
<p:commandButton value="Volver"
icon="ui-icon-refresh"
process="@this" action="index.xhtml?faces-redirect=true"
/>
<p:commandButton value="Guardar"
icon="ui-icon-disk"
actionListener="#{libroBean.saveLibro}" update="growl" />
</p:panel>
</form>
</h:body>
</html>
EDITADO - Solución librerías pom.xml
Modificamos el fichero para que use las versiones adecuadas y solucionamos el problema de los mensajes...
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<java.version>1.6</java.version>
<junit.version>4.9</junit.version>
<slf4j.version>1.6.4</slf4j.version>
<logback.version>1.0.1</logback.version>
<log4j.version>1.2.14</log4j.version>
<spring.version>3.1.0.RELEASE</spring.version>
<spring.data.jpa.version>1.1.0.RELEASE</spring.data.jpa.version>
<validation.version>1.0.0.GA</validation.version>
<hibernate.version>4.1.7.Final</hibernate.version>
<hibernate-validator.version>4.2.0.Final</hibernate-validator.version>
<aspectj.version>1.6.8</aspectj.version>
<cglib.version>2.2</cglib.version>
<mysql.version>5.1.19</mysql.version>
<commons.dbcp.version>1.4</commons.dbcp.version>
<commons.pool.version>1.6</commons.pool.version>
<commons.lang.version>2.5</commons.lang.version>
<jsf.version>2.1.10</jsf.version>
<primefaces.version>4.0</primefaces.version>
<primefaces-theme.version>1.0.10</primefaces-theme.version>
<servlet.version>2.5</servlet.version>
<jsp.version>2.1</jsp.version>
<jstl.version>1.2</jstl.version>
<taglibs-standard.version>1.1.2</taglibs-standard.version>
<maven.compiler.plugin>2.3.2</maven.compiler.plugin>
<maven.failsafe.plugin>2.4.3-alpha-1</maven.failsafe.plugin>
</properties>
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>${junit.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>${slf4j.version}</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>jcl-over-slf4j</artifactId>
<version>${slf4j.version}</version>
<scope>runtime</scope>
</dependency>
<!-- Spring -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
<version>${spring.version}</version>
<exclusions>
<exclusion>
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-beans</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aop</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-expression</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-tx</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-asm</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context-support</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-orm</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>${spring.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-jpa</artifactId>
<version>${spring.data.jpa.version}</version>
<exclusions>
<exclusion>
<artifactId>junit-dep</artifactId>
<groupId>junit</groupId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjrt</artifactId>
<version>${aspectj.version}</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>${aspectj.version}</version>
</dependency>
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib-nodep</artifactId>
<version>${cglib.version}</version>
</dependency>
<dependency>
<groupId>javax.validation</groupId>
<artifactId>validation-api</artifactId>
<version>${validation.version}</version>
</dependency>
<dependency>
<groupId>commons-dbcp</groupId>
<artifactId>commons-dbcp</artifactId>
<version>${commons.dbcp.version}</version>
</dependency>
<dependency>
<groupId>commons-lang</groupId>
<artifactId>commons-lang</artifactId>
<version>${commons.lang.version}</version>
</dependency>
<dependency>
<groupId>commons-pool</groupId>
<artifactId>commons-pool</artifactId>
<version>${commons.pool.version}</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>${mysql.version}</version>
<scope>runtime</scope>
</dependency>
<!-- JSF 2 -->
<dependency>
<groupId>com.sun.faces</groupId>
<artifactId>jsf-api</artifactId>
<version>${jsf.version}</version>
</dependency>
<dependency>
<groupId>com.sun.faces</groupId>
<artifactId>jsf-impl</artifactId>
<version>${jsf.version}</version>
</dependency>
<dependency>
<groupId>org.primefaces</groupId>
<artifactId>primefaces</artifactId>
<version>${primefaces.version}</version>
</dependency>
<dependency>
<groupId>org.primefaces.themes</groupId>
<artifactId>bluesky</artifactId>
<version>${primefaces-theme.version}</version>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>servlet-api</artifactId>
<version>${servlet.version}</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>javax.servlet.jsp</groupId>
<artifactId>jsp-api</artifactId>
<version>${jsp.version}</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>taglibs</groupId>
<artifactId>standard</artifactId>
<version>${taglibs-standard.version}</version>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>jstl</artifactId>
<version>${jstl.version}</version>
</dependency>
</dependencies>
@ocascal
Me parece muy bien detallada y explicación en la entrada, te felicito!
ResponderEliminarBuen ejemplo, felicitaciones :)
ResponderEliminar