viernes agosto 04, 2006
EJB3 : Cliente standalone
Introducción
En el artículo Integración de EJB3 y JSF vimos cómo podíamos consumir componentes EJB3 desde un módulo web que residía en el mismo servidor que el módulo EJB. En este artículo voy a intentar explicar cómo acceder a los componentes EJB3 desde un cliente Java standalone que se ejecuta en una JVM distinta del servidor.
El servidor de aplicaciones que he utilizado para desplegar el módulo EJB ha sido la versión 4.0.4.GA de JBoss, y el IDE utilizado para el desarrollo del proyecto ha sido la versión 5.5 Beta de NetBeans.
El primer paso que vamos a hacer es crear el módulo EJB. Vamos al menú New Project y seleccionamos EJB Module que se encuentra dentro de la categoría Enterprise. Configuramos el asistente del siguiente modo :
Creando la Entidad
A continuación tenemos que agregar una entidad al proyecto, la entidad Cliente, a la vez que configuramos el EntityManager para indicarle cómo acceder a nuestra base de datos. En JBoss tenemos configurado por defecto un DataSource al que podemos acceder a través de JNDI. Este DataSource hace referencia una base de datos HSQL, y para acceder a ella a través de JNDI se hace a través del nombre java:/DefaultDS.
El asistente para la creación de la entidad Cliente quedaría como sigue :
Y el asistente para la configuración del EntityManager nos quedaría así :
Una vez finalizados los asistentes modificaremos la entidad para agregarle los métodos que ésta necesite, tras lo cual la entidad nos quedará del siguiente modo :
package org.jlm.domain;
import java.io.Serializable;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
@Entity
public class Cliente implements Serializable {
private Integer id;
private String nombre;
private String apellidos;
/** Creates a new instance of Cliente */
public Cliente() {
}
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
@Column(name = "CUSTOMER_ID")
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
@Column(name = "CUSTOMER_NAME" )
public String getNombre() {
return nombre;
}
public void setNombre(String nombre) {
this.nombre = nombre;
}
@Column(name = "CUSTOMER_SURNAME")
public String getApellidos() {
return apellidos;
}
public void setApellidos(String apellidos) {
this.apellidos = apellidos;
}
public int hashCode() {
int hash = 0;
hash += (this.id != null ? this.id.hashCode() : 0);
return hash;
}
public boolean equals(Object object) {
// TODO: Warning - this method won't work in the case the id fields are not set
if (!(object instanceof Cliente)) {
return false;
}
Cliente other = (Cliente)object;
if (this.id != other.id && (this.id == null || !this.id.equals(other.id))) return false;
return true;
}
public String toString() {
return "org.jlm.domain.Cliente[id=" + id + "]";
}
}
Y el fichero persistence.xml nos quedará de este otro modo :
<?xml version="1.0" encoding="UTF-8"?> <persistence version="1.0" 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"> <persistence-unit name="GestionClientesPU" transaction-type="JTA"> <provider>org.hibernate.ejb.HibernatePersistence</provider> <jta-data-source>java:/DefaultDS</jta-data-source> <properties> <property name="hibernate.hbm2ddl.auto" value="create-drop"/> </properties> </persistence-unit> </persistence>Creando el Stateless Session Bean (SLSB)
El siguiente paso que tenemos que hacer es crear un SLSB que se encargue de hacer llamadas al EntityManager para comunicarse con la base de datos. Este SLSB tan sólo va a disponer de dos métodos, uno para grabar clientes, y otro para mostrarlos.
El asistente para la creación del SLSB tendremos que configurarlo como sigue :
Es importante tener en cuenta que la interfaz que tiene que implementar el SLSB tiene que ser Remota, ya que los métodos van a ser invocados desde una JVM distinta a la del servidor. Este asistente nos va a generar dos ficheros. Por una parte nos va a generar la interfaz remota, a la cual le agregaremos dos métodos :
@Remote
public interface ServicioClientesRemote {
public void grabarCliente(Cliente cliente);
public List obtenerClientes();
}
Y por otra parte, el asistente nos generará el bean propiamente dicho que lo implementaremos del siguiente modo :
@Stateless
public class ServicioClientesBean implements ServicioClientesRemote {
@PersistenceContext(unitName="GestionClientesPU")
private EntityManager em;
public void grabarCliente(Cliente cliente) {
em.persist(cliente);
}
public List obtenerClientes() {
Query query = em.createQuery( "SELECT c FROM Cliente AS c" );
return query.getResultList();
}
Hemos configurado el EntityManager para que utilice la unidad de persistencia que hemos configurado previamente. En nuestro caso no sería necesario indicarle la propiedad unitName, ya que en el proyecto tan sólo tenemos configurada una unidad de persistencia. Si tuviéramos más de una unidad de persistencia definidas en el fichero persistence.xml entonces sí sería necesario establecer la propiedad unitName.
Definiendo la aplicación cliente
Ahora tenemos que crear un nuevo proyecto que haga uso del módulo EJB que hemos definido previamente. Vamos al menú y creamos un nuevo proyecto basado en el asistente Java Application que está en la categoría General :
A continuación tendremos que decirle al cliente dónde está el módulo EJB que hemos creado anteriormente para que sea capaz de utilizar la entidad Cliente, y la interfaz remota que están dentro del módulo EJB. Para ello vamos a hacer clic con el botón derecho sobre el proyecto ProgramaCliente :
Seleccionamos Properties, Libraries y agregamos el proyecto en el que habíamos creado el EJB3 :
A continuación implementamos la clase de la siguiente forma :
public class Main {
public static void main(String[] args) {
try {
Context jndiContext = getInitialContext();
ServicioClientesRemote servicioClientes = (ServicioClientesRemote)jndiContext.lookup("ServicioClientesBean/remote");
//Grabamos el Cliente
Cliente cliente = new Cliente();
cliente.setNombre("Jose Luis");
cliente.setApellidos("Monteagudo Montañes");
servicioClientes.grabarCliente(cliente);
//Mostramos los Clientes
List clientes = servicioClientes.obtenerClientes();
for(Cliente c: clientes) {
System.out.println(c);
}
}
catch (javax.naming.NamingException ne) {
ne.printStackTrace();
}
}
private static Context getInitialContext() throws javax.naming.NamingException {
return new javax.naming.InitialContext();
}
}
El primer paso es obtener el contexto para acceder al servicio JNDI del servidor. Como vemos, no hemos asignado ninguna propiedad a la hora de obtener una conexión a JNDI. Cuando obtenemos un contexto de este modo, el JDK va a buscar el fichero jndi.properties en classpath. En este fichero vamos a definir las propiedades necesarias para poder tener acceso a JNDI, y al tenerlo definido fuera del código fuente no vamos a tener que recompilar si el módulo EJB está desplegado en un servidor u otro. Más adelante crearemos este fichero.
Una vez tenemos el contexto vamos a poder realizar búsquedas en JNDI, y así obtendremos la interfaz remota de nuestro SLSB, la cuál podremos utilizar del mismo modo que utilizamos cualquier interfaz convencional.
Escribiendo jndi.properties
Este fichero es un fichero de propiedades en el que definimos cómo el cliente puede acceder al servicio JNDI de un servidor. Lo vamos a agregar a partir un asistente al cual accedemos a través de Properties File que está dentro de la categoría Other. Guardaremos el fichero en el directorio src y definiremos las siguientes propiedades :
java.naming.factory.initial=org.jnp.interfaces.NamingContextFactory java.naming.factory.url.pkgs=org.jboss.naming:org.jnp.interfaces java.naming.provider.url=127.0.0.1:1099
Lo único que tenemos que tener en cuenta es que este fichero esté en classpath, de esta forma será encontrado cuando creamos un nuevo InitialContext.
Desplegando la aplicación
Aunque la aplicación la podemos desplegar a través del IDE, yo la voy a desplegar manualmente para ver lo sencillo que resulta esta operación en el servidor JBoss. Lo primero que tenemos que hacer es obtener el JAR correspondiente al módulo EJB. Para ello vamos a hacer un build del proyecto EJB y si todo ha funcionado correctamente en el directorio dist de nuestro proyecto encontraremos el fichero GestionClientes.jar. Para desplegarlo en un servidor JBoss lo único que tenemos que hacer es copiar dicho fichero en JBOSS_HOME\server\default\deploy y comprobamos el log del servidor para comprobar que el despliegue se ha producido sin problemas.
A continuación tenemos que hacer el build de la aplicación cliente. Antes de realizarlo y para que todo funcione correctamente y nos compile sin problemas vamos a tener que añadir unas librerías al proyecto ProgramaCliente, librerías que se encuentran en el directorio JBOSS_HOME/client. Las librerías necesarias son las siguientes :
ejb3-persistence.jar GestionClientes.jar jbossall-client.jar jboss-aop-jdk50-client.jar jboss-aspect-jdk50-client.jar jboss-ejb3-client.jar
Una vez agregadas estas librerías a nuestro proyecto ya podremos hacer el build de ProgramaCliente. Una vez hecho el build encontraremos en el directorio dist los fichero que necesitamos para ejecutar el programa. Podemos copiar este directorio a cualquier ordenador y ( configurando correctamente la propiedad java.naming.provider.url del fichero jndi.properties ) podremos ejecutar nuestra aplicación cliente de la siguiente forma :
java -jar ProgramaCliente.jar
Si queremos consultar la información en la base de datos podemos hacerlo del siguiente modo. Primero accedemos a la url http://localhost:8080/. Desde aquí enlazamos a la JMX Console. En la sección jboss accedemos a database=localDB,service=Hypersonic. Hacemos clic sobre el botón Invoke que hay bajo el comando void startDatabaseManager() y se nos abrirá el HSQL Database Manager. Desde aquí podemos ejecutar la SQL "SELECT * FROM CLIENTE" para ver los clientes que se han agregado a la base de datos.
Posted at 04:29PM ago 04, 2006 by Jose Luis Monteagudo in General | Comentarios[1]
Integracion de EJB3 y JSF
Integración de EJB3 y JSF
Hace escaso tiempo que salió la versión final de JEE 5, versión que ha traido grandes cambios a la plataforma Java con respecto a la versión anterior. Se ha intentado hacer la plataforma más accesible a los programadores facilitando el desarrollo de las aplicaciones empresariales Java, y tomando como base la configuración por defecto, que tan buenos resultados parece haber generado a la plataforma Ruby on Rails.
A pesar del esfuerzo realizado por el grupo de expertos encargado de definir las especificaciones que forman parte de JEE por simplificar el desarrollo de aplicaciones JEE, hay que tener en cuenta que JEE se trata de una plataforma compleja, alrededor de la cual se concentran usa serie de APIs y de conceptos, y que sin un conocimiento medio de éstos será difícil sacar el máximo partido a JEE, por lo que aunque a primera vista parezca sencillo el desarrollo de aplicaciones tendremos que intentar conocer al máximo todo aquello que se mueve a su alrededor si queremos sacar rendimiento a JEE.
En este artículo/tutorial voy a intentar mostrar cómo desarrollar un prototipo de aplicación en la que integramos dos tecnologías que forman parte de esta renovada plataforma, EJB3 y Java Server Faces. Para ello voy a utilizar NetBeans 5.5, cuyo equipo también ha realizado grandes esfuerzos por simplificar al máximo el desarrollo de aplicaciones JEE con este IDE. La aplicación (si es que la podemos llamar así ) que vamos a desarrollar no podría ser más trivial, y simplemente se encarga de mostrar un listado de los clientes dados de alta en una base de datos, y da la opción de creación de nuevo clientes.
Persistencia
En NetBeans 5.5 tenemos integrado el servidor de aplicaciones open source Glassfish, y éste, a su vez, integra el motor de base de datos open source Derby. Glasshfish tiene configurado vía JNDI un acceso a esta base de datos con el nombre jdbc/__default, que es el que vamos a utilizar para desarrollar este proyecto.
JPA es el nuevo framework de persistencia de Java, y se basa en gran medida en la solución open source más popular de persistencia que hasta ahora hemos tenido, Hibernate. JPA podemos utilizarlo tanto desarrollando con JEE como con JSE, lo cual supone un gran avance con respecto a cómo se controlaba la persistencia en la versión anterior de J2EE. Cualquier programador que haya trabajado con Hibernate no tendrá ningún problema en utilizar JPA debido a su gran similitud.
Iniciando un nuevo proyecto
Para comenzar un nuevo proyecto JEE con NetBeans vamos seleccionar la opción de menú File -> New Project. En la parte izquierda de la nueva ventana seleccionaremos Entreprise y a continuación Enterprise Application. Se nos mostrará un nuevo asistente que deberemos configurar del siguiente modo :
Pulsamos Finish y NetBeans nos va a crear toda la estructura básica de nuestra aplicación. Nos creará un directorio global para el proyecto, y dentro de éste, entre otros, habrá un directorio para el módulo EJB y otro para el módulo Web.
En primer lugar vamos a crear una entidad, que se llamará Cliente. Posteriormente crearemos un Stateless Session Bean para gestionar las posibles operaciones que queramos realizar sobre la entidad Cliente. Con esto ya tendremos configurado nuestro módulo EJB. Dentro de nuestro módulo web implementaremos un Managed Bean que utilizará el Stateless Session Bean tanto para listar como para crear clientes, y crearemos dos páginas web, una para listar los clientes y otra para crear nuevos clientes.
Desarrollando el módulo EJB
Entidades
El primer paso es crear una nueva entidad. Java necesita un objeto especial para trabajar con entidades, EntityManager, el cual necesita un fichero de configuración llamado persistence.xml, y en el cuál se configura la conexión a la base de datos. Por lo tanto, cuando creemos a través de NetBeans una entidad, si no se encuentra el fichero persistence.xml NetBeans nos propondrá la creación del mismo.
Para crear la entidad vamos a hacer click con el botón derecho sobre el módulo EJB GestionClientes-EJBModule, y vamos a seleccionar la opción de menú New -> File/Folder. Seleccionamos la categoría Persistence, y en la lista de la derecha hacemos click sobre Entity Class.
Tenemos un nuevo asistente en el que configuraremos el nombre y el paquete de nuestra entidad. En este asistente, una vez hayamos proporcionado el nombre tanto de la entidad como del paquete, nos aparecerá un nuevo botón ( Create Persistence Unit ) en la parte inferior del mismo a través del cual podremos crear el fichero persistence.xml.
El asistente para la creación de la entidad nos quedará del siguiente modo :
Y el asistente para la creación de nuestro fichero persistence.xml nos quedará así :
El Datasource que utilizamos para almacenar nuestras entidades es el que nos propone NetBeans por defecto, que como comentamos anteriormente es un Datasource que viene configurado de serie en Glassfish y hace referencia a una base de datos Derby.
No es necesario que creemos nosotros las tablas manualmente, ya que el nuevo framework de persistencia de Java JPA podemos configurarlo para que cree las tablas en nuestra base de datos a partir de nuestras entidades.
En posteriores artículos intentaré explicar cómo configurar persistence.xml para que almacene nuestros datos en una base de datos MySQL que ya tengamos nosotros creada.
Pulsamos aceptar a los dos asistentes que tenemos abiertos, y modificamos nuestra clase Cliente para implementarla del siguiente modo :
package org.monteagudo.entity;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
/**
*
* @author jlm
*/
@Entity
public class Cliente {
private Long id;
private String nombre;
private String apellidos;
private String direccion;
/** Creates a new instance of Cliente */
public Cliente() {
}
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getNombre() {
return nombre;
}
public void setNombre(String nombre) {
this.nombre = nombre;
}
public String getApellidos() {
return apellidos;
}
public void setApellidos(String apellidos) {
this.apellidos = apellidos;
}
public String getDireccion() {
return direccion;
}
public void setDireccion(String direccion) {
this.direccion = direccion;
}
}
Stateless Session Bean
Vamos a crear un Stateless Session Bean para poder hacer operaciones sobre la entidad Cliente. Para crear el bean vamos a hacer click con el botón derecho sobre el módulo EJB GestionClientes-EJBModule, y vamos a seleccionar la opción de menú New -> File/Folder. Seleccionamos la categoría Enterprise, y en la lista de la derecha hacemos click sobre Session Bean.
Nos aparece un nuevo asistente para configurar el bean. Lo rellenaremos para que nos quede del siguiente modo :
Como no necesitamos almacenar ningún estado tanto para listar clientes como para crear nuevos clientes, es evidente que nuestro bean va a ser sin estado, por ello en el campo Session Type elegimos Stateless.
En el campo Create Interface podemos elegir Remote, Local o ambas. Si no seleccionamos ninguna, el contenedor generará una interfaz local implícitamente en tiempo de ejecución. El cliente principal de nuestro bean va a ser un Managed Bean del módulo web, es decir, el cliente va a ser una clase que se va a ejecutar en el mismo servidor de aplicaciones, y en la misma JVM, por lo que vamos a seleccionar que nuestro bean implementa una interfaza Local.
Pulsamos Finish, y vemos que NetBeans nos ha creado dos ficheros :
ServicioClientesBean es el bean propiamente dicho que implementa la lógica de negocio, y ServicioClientesLocal es una interfaz con una anotación Local ( si no lleva anotación se asume que es Local ), y que es implementada por nuestro bean.
La interfaz va a quedar definida de este modo :
package org.monteagudo.service;
import java.util.List;
import javax.ejb.Local;
import org.monteagudo.entity.Cliente;
/**
* This is the business interface for ServicioClientes enterprise bean.
*/
@Local
public interface ServicioClientesLocal {
public void crearCliente(Cliente cliente);
public List<Cliente> listarClientes();
}
Y el bean quedará definido de este otro modo :
package org.monteagudo.service;
import java.util.List;
import javax.ejb.*;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import javax.persistence.Query;
import org.monteagudo.entity.Cliente;
/**
* This is the bean class for the ServicioClientesBean enterprise bean.
* Created 16-jul-2006 23:15:44
* @author jlm
*/
@Stateless()
public class ServicioClientesBean implements ServicioClientesLocal {
@PersistenceContext
EntityManager em;
public void crearCliente(Cliente cliente) {
em.persist(cliente);
}
public List<Cliente> listarClientes() {
Query query = em.createQuery("SELECT c FROM Cliente AS c");
return query.getResultList();
}
}
Vemos que el bean está anotado como Stateless, lo que identifica al bean como sin estado.
Otra característica importante es la anotación PersistenceContext. Cuando anteriormente creamos la entidad Cliente también configuramos el fichero persistence.xml. Este fichero es el que utiliza el servidor de aplicaciones para crear objetos de tipo EntityManager. Podríamos acceder a estos objetos vía JNDI, pero es recomendado inyectar el EntityManager a través de la anotación PersistenceContext. Una vez tenemos el EntityManager ya vamos a poder realizar operaciones sobre todas nuestras entidades definidas en nuestra aplicación.
Con todo esto ya tendríamos implementado nuestro módulo EJB. Para comprobar si ha ido todo bien podemos hacer un Build de este módulo. Para ello vamos a seleccionar con el botón derecho el módulo del EJB, y vamos a seleccionar la opción Build Project, así veremos si ha habido algún problema con alguna de las clases que hemos definido.
Desarrollando el módulo Web
Antes de comenzar a definir nuestro Managed Bean y nuestras páginas JSF, vamos a configurar un par de cosas en el módulo web.
La primera de ellas va a ser indicar a NetBeans que queremos utilizar JSF. Para ello vamos a hacer click con el botón derecho sobre el módulo web GestionClientes-WebModule, y pulsaremos la opción Properties. En la lista de categorías vamos a seleccionar Frameworks, pulsamos sobre el botón Add y seleccionamos Java Server Faces. Aceptamos, y podremos comprobar que NetBeans, por una parte ha creado el fichero de configuración de JSF, faces-config.xml, y por otra parte ha modificado el fichero web.xml para agregar opciones de configuración de JSF.
La segunda tarea que vamos a realizar a nuestro módulo web va a ser decirle que queremos utilizar las clases que hemos desarrollado en el módulo EJB. Para ello vamos a hacer de nuevo click con el botón derecho sobre el módulo web GestionClientes-WebModule, y pulsaremos la opción Properties. Seleccionamos Libaries y hacemos click al botón Add Project. Seleccionamos GestionClientes-ejb y pulsamos Add Project JAR Files, y después aceptamos.
Ahora ya podemos crear nuestro Managed Bean
Managed Bean
Hacemos click con el botón derecho sobre el módulo web GestionClientes-WebModule y seleccionamos New -> File / Folder. Nos vamos a la categoría web y pulsamos sobre JSF Managed Bean. Nos aparece un asistente para la configuración del Managed Bean, el cuál debemos configurarlo del siguiente modo :
El asistente nos crea una plantilla del bean, el cuál modificaremos para implementarlo así :
package org.monteagudo.web;
import java.util.List;
import javax.ejb.EJB;
import org.monteagudo.entity.Cliente;
import org.monteagudo.service.ServicioClientesLocal;
/**
*
* @author jlm
*/
public class ClientesManagedBean {
private Cliente cliente = new Cliente();
@EJB
ServicioClientesLocal servicioClientes;
public Cliente getCliente() {
return cliente;
}
public void setCliente(Cliente cliente) {
this.cliente = cliente;
}
public List<Cliente> getClientes() {
return servicioClientes.listarClientes();
}
public String crearCliente() {
servicioClientes.crearCliente(cliente);
return "success";
}
}
Como vemos, podemos inyectar al managed bean un EJB que hemos desarrollado anteriormente en el módulo EJB. Desde entonces podemos utilizar la interfaz como cualquier otra clase dentro del managed bean para crear nuevos clientes y para listar clientes ya existentes.
Páginas JSF
Uno de los últimos pasos que nos queda por realizar es desarrollar dos páginas JSP, así como configurar el fichero faces-config.xml.
Vamos a crear una página que nos va permitir listar todos los clientes existentes en la base de datos. Hacemos click con el botón derecho sobre el módulo web GestionClientes-WebModule y seleccionamos New -> File / Folder. Nos vamos a la categoría web y pulsamos sobre JSP. Nos aparece un asistente para la configuración del JSP en el que únicamente vamos a indicar el nombre de la página web :
Como podemos ver, la plantilla que nos ha creado NetBeans no tiene incluidas los taglib de JSF. Vamos a agregarlos manualmente. Como puede resultar un poco difícil acordarse del uri de los tags, vamos a ver dónde podemos consultarlos de forma más o menos rápida. Navegamos por la ventana de proyectos hasta los ficheros descriptores de los tag libraries (tld) :
Hacemos doble-click sobre el tld y ahí encontraremos las etiquetas short-name y uri, que son las que utilizaremos para agregar las taglibs de JSF.
Una vez tenemos definidos los taglibs que necesitamos, vamos a crear una tabla JSF para listar todos los clientes. NetBeans ofrece una paleta de controles básica que nos permite crear una tabla a partir de una de un objeto java.util.List. Si la paleta de controles no nos apareciera en el IDE podemos mostrarla seleccionando la opción de menú Window -> Palette. En la paleta seleccionamos el control JSF Data Table y lo arrastramos a la zona de código en la que queremos mostrar la tabla. Nos aparece un asistente que nos solicita por una parte el Entity Bean que queremos representar, y por otra parte el método del Managed Bean que nos devuelve la lista de clientes. El asistente lo rellenaremos con la siguiente información :
Como podemos observar, el asistente se ha encargado de generarnos de forma automática la tabla con toda la información referente a nuestra entidad cliente. Finalmente la página la modificaremos para que tenga la siguiente forma :
<%@page contentType="text/html"%>
<%@page pageEncoding="UTF-8"%>
<%@taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c"%>
<%@taglib uri="http://java.sun.com/jsf/html" prefix="h"%>
<%@taglib uri="http://java.sun.com/jsf/core" prefix="f"%>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Listado de Clientes</title>
</head>
<body>
<f:view>
<h:form>
<h1><h:outputText value="Listado de Clientes"/></h1>
<h:dataTable value="#{ClientesManagedBean.clientes}" var="item">
<h:column>
<f:facet name="header">
<h:outputText value="Id"/>
</f:facet>
<h:outputText value="#{item.id}"/>
</h:column>
<h:column>
<f:facet name="header">
<h:outputText value="Nombre"/>
</f:facet>
<h:outputText value="#{item.nombre}"/>
</h:column>
<h:column>
<f:facet name="header">
<h:outputText value="Apellidos"/>
</f:facet>
<h:outputText value="#{item.apellidos}"/>
</h:column>
<h:column>
<f:facet name="header">
<h:outputText value="Direccion"/>
</f:facet>
<h:outputText value="#{item.direccion}"/>
</h:column>
</h:dataTable>
<br/>
<h:commandButton action="create" value="Nuevo Cliente"/>
</h:form>
</f:view>
</body>
</html>
Ahora nos falta crear la página que nos va a permitir crear nuevos clientes. Agregamos la página como hicimos con la anterior, y le daremos el nombre crearCliente. Al igual que en la página listarClientes utilizamos el control JSF Data Table para que el IDE nos generara el código de la tabla, en esta página vamos a utilizar el control JSF Form para que NetBeans nos genere el código del formulario de la entidad Cliente. Para ello vamos a selecccionar el control y lo vamos a arrastrar a la zona de la página en la que queremos implementar el formulario. A continuación nos aparecerá un asistente, el cuál podemos rellenarlo como sigue :
Del formulario que nos ha creado NetBeans vamos a borrar los datos relativos al Id del Cliente, ya que este campo tiene que estar vacío, y será el framework de persistencia JPA quién nos genere el nuevo identificador de cliente.
Por otra parte, he modificado la propiedad value de los campos inputText que ha generado el diseñador, y he utilizado directamente la entidad Cliente en lugar de campos individuales en el Managed Bean. De este modo, es diseñador ha creado lo siguiente :
<h:inputText id="nombre" value="#{ClientesManagedBean.nombre}" title="Nombre" />
y lo he cambiado por esto otro :
<h:inputText id="nombre" value="#{ClientesManagedBean.cliente.nombre}" title="Nombre" />
Tampoco hay que olvidarse de agregar dentro del formulario el botón que se encargará de hacer el submit de los datos.
La página, al final, nos tiene que quedar como sigue :
<%@page contentType="text/html"%>
<%@page pageEncoding="UTF-8"%>
<%@taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c"%>
<%@taglib uri="http://java.sun.com/jsf/html" prefix="h"%>
<%@taglib uri="http://java.sun.com/jsf/core" prefix="f"%>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Nuevo Cliente</title>
</head>
<body>
<f:view>
<h2>Nuevo Cliente</h2>
<h:form>
<h:outputText>Nombre:</h:outputText>
<h:inputText id="nombre" value="#{ClientesManagedBean.cliente.nombre}" title="Nombre" /><br>
<h:outputText>Apellidos:</h:outputText>
<h:inputText id="apellidos" value="#{ClientesManagedBean.cliente.apellidos}" title="Apellidos" /><br>
<h:outputText>Direccion:</h:outputText>
<h:inputText id="direccion" value="#{ClientesManagedBean.cliente.direccion}" title="Direccion" /><br>
<br/>
<h:commandButton action="#{ClientesManagedBean.crearCliente}" value="Grabar"/>
</h:form>
</f:view>
</body>
</html>
Por último, vamos a hacer una pequeña modificación a la página index.jsp, para que al iniciar la aplicación nos redirija hacia la página listarClientes. Así es cómo debería quedar index.jsp :
<%@page contentType="text/html"%>
<%@page pageEncoding="UTF-8"%>
<%@taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c"%>
<c:redirect url="faces/listarClientes.jsp"/>
Configuración de JSF : faces-config.xml
Lo único que nos queda por hacer antes de probar la aplicación es definir el fichero de configuración faces-config.xml. Para configurar este fichero NetBeans nos provee una sencilla utilidad. Para hacer uso de ella primero tenemos que abrir el fichero faces-config.xml, que se encuentra en la ventana Projects, en el módulo web GestionClientes-WebModule, dentro de la carpeta Configuration Files. Hacemos doble-click para editarlo, y vemos que ya tenemos definido el Managed Bean que habíamos creado.
El siguiente paso es definir las reglas de navegación. Vamos a hacer click con el botón derecho sobre la ventana de código de este fichero, seleccionamos la opción de menú Java Server Faces y a continaución Add Navigation Rule. Hacemos un browse, seleccionamos la página listarClientes y pulsamos Add.
Aviso : A mí, en ciertas ocasiones, se me bloquea NetBeans cuando agrego las reglas de navegación a través del asistente, por lo tanto y por si acaso es conveniente grabar el proyecto antes de continuar. Hay que tener en cuenta que estamos ante una versión beta, por lo que este tipo de cuelgues puede pasar.
Ahora, dentro del código que nos ha generado ( entre las etiquetas navigation-rule ), volvemos a hacer click con el botón derecho, seleccionamos Add Navigation Case, y rellenamos la información que nos solicita el asistente del siguiente modo :
Ya sólo nos faltaría agregar otra regla de navegación para la página crearCliente. Al final, el fichero faces-config.xml nos quedará configurado del siguiente modo :
<?xml version='1.0' encoding='UTF-8'?>
<!-- =========== FULL CONFIGURATION FILE ================================== -->
<faces-config version="1.2"
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_1_2.xsd">
<managed-bean>
<managed-bean-name>ClientesManagedBean</managed-bean-name>
<managed-bean-class>org.monteagudo.web.ClientesManagedBean</managed-bean-class>
<managed-bean-scope>request</managed-bean-scope>
</managed-bean>
<navigation-rule>
<from-view-id>/listarClientes.jsp</from-view-id>
<navigation-case>
<from-outcome>create</from-outcome>
<to-view-id>/crearCliente.jsp</to-view-id>
<redirect/>
</navigation-case>
</navigation-rule>
<navigation-rule>
<from-view-id>/crearCliente.jsp</from-view-id>
<navigation-case>
<from-outcome>success</from-outcome>
<to-view-id>/listarClientes.jsp</to-view-id>
<redirect/>
</navigation-case>
</navigation-rule>
</faces-config>
Probando el Proyecto
Ya sólo nos queda probar que todo funcione correctamente. Para ello vamos a hacer un build del proyecto. En la solapa Projects seleccionamos GestionClientes con el botón derecho y hacemo click sobre build.
A continuación iniciamos la base de datos Derby. Vamos al menú Tools, Derby Database y seleccionamos Start Derby Server.
Y por último seleccionamos de nuevo el proyecto GestionClientes con el botón derecho y clickamos sobre Run Project, y si todo ha funcionado correctamente tendría que abrirse en el navegador la página listarClientes que hemos diseñado anteriormente.
Conclusión
Hemos visto una forma sencilla de trabajar con EJB3 y Java Server Faces con la ayuda de NetBeans. Vemos que la complejidad a la hora de programar se ha reducido bastante con respecto a la versión 2.1 de EJB, pero conviene tener en cuenta que alrededor de todo lo que hemos visto existe un conglomerado de APIs que conviene controlar para poder sacar el máximo partido a JEE.
En esta aplicación hemos trabajado con las configuraciones por defecto que nos propone la plataforma, por lo cual no resulta complejo poner a funcionar este prototipo. Pero no sólo en esto se basa JEE, tenemos servicios y APIs tales como JMS, JTA, Security, Web Services, interceptores, etcétera, que debemos conocer si queremos obtener toda la potencia que JEE nos puede ofrecer.
Posted at 09:00PM jul 17, 2006 by Jose Luis Monteagudo in General | Comentarios[10]
EJB3 - Conceptos Basicos
Introducción
Por todos es sabido que el modelo de programación propuesto por la versión 2.1 de EJB conllevaba una serie de inconvenientes que limitaron mucho el uso de esta especificación y conllevó la aparición de soluciones open source que suplían las carencias que presentaba EJB 2.1. Bajo mi punto de vista las soluciones open source que más han marcado el desarrollo empresarial dentro de la plataforma Java han sido Hibernate y Spring Framework, y en la nueva versión de Java Enterprise Edition se han incorporado muchas de las características de estos frameworks para procurar a los desarrolladores una plataforma de desarrollo bastante más sencilla que su predecesora versión 1.4. En este post voy a tratar de explicar cuáles son los distintos tipos de Enterprise Java Bean y cuáles son los propósitos de cada uno de ellos : Stateless Session Bean, Stateful Session Bean, Entity Bean y Message Driven Bean. Debido a que éste pretende ser una breve explicación a los tipos de Entreprise Bean existentes, no voy a entrar en detalles tales como ciclo de vida de los mismos y otros tipos de características.Stateless Session Bean
Un bean de sesión sin estado es aquél que no dispone de variables de instancia en las cuales se guarden datos que puedan ser compartidos entre los distintos métodos del bean. Es decir, se trata de un bean que por lo general contará con una serie de métodos que realizarán un trabajo determinado e independiente y que el resultado de las operaciones realizadas dentro de cada uno de los métodos no dependerá de ningún ?estado? relativo a la conversación que mantiene el cliente con el bean. Como puede ser que a través de mis palabras no quede suficientemente claro a qué me refiero creo que va a ser mejor que veamos un ejemplo. Tomemos como ejemplo un bean que se encarga de gestionar las operaciones básicas que podemos realizar con un cliente, como son consultar un cliente, guardar un cliente y borrarlo. En primer lugar tendríamos que crear una interface :
public interface CustomerServiceLocal {
public void saveCustomer( Customer customer );
public Customer getCustomer( Long id );
public Collection getAllCustomers();
public void deleteCustomer( Long id );
}
Podemos ver que se trata de una simple interface que no tiene ninguna característica particular. Todo bean de sesión sin estado es necesario que disponga de una interface y la que hemos definido arriba es perfectamente válida para nuestro servicio de clientes. Si no la hubiéramos creado y hubiéramos definido directamente el bean sin la creación de la interface, hubiera sido el propio contenedor quién la hubiera creado por nosotros. La intefaz debiera de tener una anotación, que le indicara si se trata de una interface Local o Remote. Si el bean va a ser utilizado por una clase que se ejecute dentro de la misma JVM en la que se ejecuta el bean de sesión entonces la interface podría ser Local, y si el bean va a ser llamado por una clase que se ejecuta en una JVM distinta entonces la interface tendría que ser Remote. Por defecto, si no se indica nada, será tratada como una interface Local.
Ejemplo de interface local :
@Local
public interface CustomerServiceLocal {
public void saveCustomer( Customer customer );
public Customer getCustomer( Long id );
public Collection getAllCustomers();
public void deleteCustomer( Customer customer );
}
Ejemplo de interface remota :
@Remote
public interface CustomerServiceRemote{
public void saveCustomer( Customer customer );
public Customer getCustomer( Long id );
public Collection getAllCustomers();
public void deleteCustomer( Customer customer );
}
Ahora nos faltaría crear el bean propiamente dicho :
@Stateless
public class CustomerService implements CustomerServiceLocal {
@PersistenceContext()
private EntityManager em;
public void saveCustomer( Customer customer ) {
em.persist( customer );
}
public Customer getCustomer( Long id ) {
Query q = em.createQuery( "SELECT c FROM Quiz c WHERE c.id = :id" ).
setParameter( "id", id );
return (Customer)q.getSingleResult();
}
public Collection getAllCustomers() {
return em.createQuery( "SELECT c from Customer c" ).getResultList();
}
public void deleteCustomer( Customer customer ) {
em.remove( customer );
}
}
Stateful Session Bean
Al contrario que en los beans de sesión sin estado, los stateful session bean, como su nombre indica, sí tienen estado. Lo que esto significa es que dentro de la sesión del usuario estos beans van a almacenar datos en variables de instancia, y esos datos van a tener un significado concreto durante toda la convesación mantenida entre el cliente y el bean. Un ejemplo típico de bean de sesión con estado es un carro de la compra, en el cual, durante la sesión de usuario, éste va agregando productos en el carro a través de una lista. Tras ir agregando productos llegará un momento en el que el usuario quiera efectuar la compra, para lo cuál tendrá que realizar previamente un registro de sus datos, especificar la dirección de entrega, indicar el número de su tarjeta de crédito, etcétera, y finalmente confirmará la compra de los productos seleccionados. Como vemos, todos estos datos hay que almacenarlos en unas variables, que son las que conforman el estado del bean. Al igual que los Stateless Session Bean, los Stateful también deben de implementar una interface que puede ser Local o Remote :
@Local
public interface CartServiceLocal {
public void addProduct( Product product );
public void removeProduct( Product product );
public Collection getProducts();
}
Y la implementación de esta interface sería el Stateful Session Bean :
@Stateful
public class CartService implements CartServiceLocal {
private List products;
public void addProduct( Product product ) {
products.add( product );
}
public void removeProduct( Product product ) {
products.remove( product );
}
public Collection getProducts() {
return products;
}
}
Entity Bean
Un Entity Bean es una clase ( POJO ) que representa una tabla de una base de datos, y cada instancia de esta clase representa un registro de la tabla, es decir, con los entity beans lo que conseguimos es crear un mapeo entre las propiedades de una clase y los campos de una tabla. Además de este mapeo también vamos a poder especificar las relaciones que tienen las clases entre sí ( uno a uno, uno a muchos, muchos a uno y muchos a muchos ). Todo Entity Bean debe de tener una clave primaria que identifica a ese registro de forma única dentro de la tabla. Todas estas configuraciones las vamos a realizar a través de anotaciones, y el API que se encarga de gestionar todos los aspectos relativos a la persistencia es JPA ( Java Persistent API ). Vamos a ver un ejemplo. Vamos a crear una entidad llamada Team, para lo cual veremos que lo único que vamos a tener en cuenta es establecer una anotación @Entity al principio de la clase, una anotación @Id para indicar cuál es la propiedad que representa la clave primaria, y por último una anotación @OneToMany para indicar que un equipo puede estar compuesto por muchos jugadores y que éstos sólo pueden pertenecer a un equipo :
@Entity
public class Team {
private int id;
private String name;
private Date foundationDate;
private Collection players;
@Id
public int getId() {
return id;
}
public void setId( int value ) {
id = value;
}
public String getName() {
return name;
}
public void setName( String value ) {
name = value;
}
public Date getFoundationDate() {
return foundationDate;
}
public void setFoundationDate( Date value ) {
foundationDate = value;
}
@OneToMany
public Collection getPlayers() {
return players;
}
public void setPlayers( Collection value ) {
players = value;
}
}
A continuación vamos a crear la Entidad Player, que al igual que la entidad anterior va a tener una anotación @Entity, otra anotación @Id, y por último una anotación @ManyToOne para establecer una relación bidireccional entre las dos entidades :
public class Player {
private int id;
private String name;
private Team team;
@Id
public int getId() {
return id;
}
public void setId( int value ) {
id = value;
}
public String getName() {
return name;
}
public void setName( String value ) {
name = value;
}
@ManyToOne
public Team getTeam() {
return team;
}
public void setTeam( Team value ) {
team = value;
}
}
Message Driven Bean
Este tipo de beans ( MDB ) permite a las aplicaciones procesar mensajes de forma asíncrona a través del servicio JMS ( Java Messaging Service ). Este servicio funciona a través de colas de mensajes, que es donde los clientes envían sus peticiones, y estas colas son controladas por los Message Driven Beans, los cuales procesan los mensajes que hay en ellas y ejecutan ciertos servicios dependiendo del mensaje procesado. Este tipo de beans se aproximan más a la forma conceptual de los Stateless Session Bean en el sentido que son beans que no deben almacenar estado alguno. Cuando un mensaje llega a la cola el contenedor hace una llamada al método onMessage del Message Driven Bean y en este método el bean debería invocar a métodos de otros sesión bean o realizar la lógica de negocio de debiera ejecutar ( es más conveniente no tener aquí lógica de negocio, sino hacer invocaciones a métodos de otras clases que sí se encarguen de realizar lógica de negocio ). Para declarar un MDB sólo hemos de establecer la anotación @MessageDriven a una clase que implemente la interface MessageListener, e indicar el nombre de la cola del contenedor ( la cuál es accesible vía JNDI ) que va a controlar el MDB. Veamos un ejemplo :
@MessageDriven( mappedName = "jms/Queue" )
public class AsynchronousService implements MessageListener {
public void onMessage( Message message ) {
TextMessage textMessage = (TextMessage)message;
System.out.println( textMessage.getText() );
}
}
Conclusión
Estos son los cuatro tipos de beans existentes en la plataforma JEE, y cada uno de ellos cuenta con unas características determinadas que hacen que cada bean sea más apropiado para unas situaciones determinadas. En el próximo artículo intentaré explicar cómo desarrollar una aplicación JEE que haga uso de Stateless Session Beans, Entity Beans y que éstos sean utilizados a través de una aplicación web.Posted at 08:59PM jul 17, 2006 by Jose Luis Monteagudo in General | Comentarios[8]
Spring AOP
Spring AOP
El propósito de este artículo es ver de qué forma podemos trabajar con AOP con la implementación que Spring nos proporciona. Hay varios frameworks AOP, muchos de ellos open source ( AspectWerkz, AspectJ, Jboss AOP, dynaop ), pero en este artículo nos vamos a centrar en Spring AOP.
La primera vez que nos sumergimos en el mundo de AOP nos encontramos con una serie de nuevos conceptos que pueden ser no muy sencillos de digerir vistos desde un punto de vista teórico, pero si ponemos a trabajar a estos conceptos dentro del campo de batalla vemos que no resulta tan difícil como a priori parece. Por ello, éste va a ser un artículo meramente práctico, por lo que si el lector quiere disponer de un conocimiento teórico del mundo AOP tendrá que remitirse a otros artículos que explican la teoría en profundidad.
La implementación AOP de Spring se basa en la regla 80-20, lo cual significa que con Spring podemos realizar el 80% de las funciones que con AOP se pueden realizar, pero si queremos implementar el otro 20% tendremos que recurrir a otros frameworks más especializados. No hay que asustarse por este dato ni descartar la implementación AOP de Spring por ello, ya que prácticamente cualquier cosa que queramos hacer con AOP entrará dentro de este 80%.
AOP es útil para realizar tareas puntuales que no encajan de forma natural en nuestras entidades ni en nuestra lógica de negocio; estas tareas podríamos implementarlas en la lógica de negocio por ejemplo, pero estaríamos ensuciando nuestras clases con código que no les corresponde controlar. Imagínemos por ejemplo el envío de un mail al administrador cuando un usuario nuevo ha sido dado de alta. La función de enviar un mail no está relacionada en absoluto con las funciones que debería de hacer una clase encargada que administrar usuarios. Para no mezclar estas funciones podríamos utilizar perfectamente AOP y definir fuera de nuestro código que cuando se dé de alta un usario nuevo se envíe un mail a una dirección determinada. Hay varios campos para los que AOP se puede emplear de forma satisfactoria, como puede ser para realizar Transacciones, para Depurar un programa registrando lo que está sucediendo en los métodos, o para implementar unas políticas de seguridad. Spring tiene implementadas clases para controlar el tema de las Transacciones y el de registro de acciones. También existe un framework de seguridad ( Acegi ) que va a formar parte de Spring. Pero si queremos implementar otro tipo de aspectos personalizados debemos definirlos nosotros mismos implementando unas interfaces creadas para tal efecto, y precisamente éste va a ser el objetivo de este artículo, la definición de aspectos personalizados en Spring.
Demostración de Spring AOP con un sencillo ejemplo
Vamos a empezar a ponernos manos a la obra con un pequeño ejemplo. Este ejemplo sólo pretende mostrar cómo trabaja Spring AOP.
El árbol de directorio con el que vamos a trabajar va a ser el siguiente :

Vamos a tener una entidad Usuario y una intefaz UsuarioService que nos va a permitir realizar las operaciones típicas ( CRUD ) que con una entidad se pueden realizar :
- Create : Creación de un usuario nuevo
- Retrieve : Consulta de un usuario existente
- Update : Actualización de usuario
- Delete : Borrado de usuario
Por otra parte tenemos una serie de Advices que no son más que clases Java que implementan una interfaz determinada y que es el código que se ejecuta antes, después o durante la llamada a uno de los métodos de la interface UsuarioService. También se podrían ejecutar en el momento en el que ocurre una excepción.
Tipos de Advices en Spring AOP
Antes de continuar con el ejemplo hay que dejar claro que con Spring hay varios tipos de Advices, y según cuál de ellos queramos implementar hay que implementar una u otra interface :
- Before : para implementar este tipo de advice tenemos que
implementar la interface
org.springframework.aop.MethodBeforeAdvice.
Es fácil de intuir que este advice se ejecutará
siempre antes de la llamada a un método.
- After Returning : la interfaz a implementar para realizar un
advice de este tipo es
org.springframework.aop.AfterReturningAdvice. Este advice
se ejecuta después de la llamada a un método.
- Around : hay que implementar la interfaz
org.aopalliance.intercept.MethodInterceptor.Cuando
implementamos un advice de este tipo podremos realizar acciones
antes y después de invocar el método interceptado.
- Throws : hay que implementar
org.springframework.aop.ThrowsAdvice. Se ejecuta cuando
ocurre una excepción.
Para llevar a cabo el ejemplo vamos a tener de definir en primer lugar dos clases más una interfaz y podremos ver rápidamente cómo estas clases son completamente independientes de los aspectos que definamos posteriormente. Esto significa que a cualquier aplicación que tengamos ya en producción podremos añadirle cierta funcionalidad sin tener que retocar nada del código escrito hasta ahora.
Definimos en primer lugar nuestra entidad Usuario
package sample.model;
public class Usuario extends BaseObject {
private String nombre;
private String apellidos;
private String direccion;
public String getNombre() {
return nombre;
}
public void setNombre(String nombre) {
this.nombre = nombre;
}
public String getApellidos() {
return apellidos;
}
public void setApellidos(String apellidos) {
this.apellidos = apellidos;
}
public String getDireccion() {
return direccion;
}
public void setDireccion(String direccion) {
this.direccion = direccion;
}
}
A continuación definimos una interfaz con los métodos necesarios para administrar nuestra entidad Usuario
package sample.service;
import sample.model.Usuario;
public interface UsuarioService {
public Usuario consultaUsuario( Usuario usuario );
public boolean agregarUsuario( Usuario usuario );
public int actualizarUsuario( Usuario usuario );
public boolean borrarUsuario( Usuario usuario );
public void procesarInformacion();
}
Y por último creamos una clase que implementa la interfaz anterior; la implementación de la interfaz en este ejemplo es completamente trivial para que el lector pueda probar fácilmente la aplicación; además la única función en este artículo es ver cómo trabajar con Spring AOP, aunque no resulta nada complicado cambiar la implementación de esta clase para que interactue con una base de datos.
package sample.service.impl;
import sample.model.Usuario;
import sample.service.UsuarioService;
public class UsuarioServiceImpl implements UsuarioService {
public Usuario consultaUsuario( Usuario usuario ) {
System.out.println( "Consultando el Usuario : " + usuario );
return usuario;
}
public boolean agregarUsuario( Usuario usuario ) {
System.out.println( "Insertando Usuario : " + usuario );
return true;
}
public int actualizarUsuario( Usuario usuario ) {
System.out.println( "Actualizando Usuario : " + usuario );
return 1;
}
public boolean borrarUsuario( Usuario usuario ) {
System.out.println( "Borrando Usuario : " + usuario );
return true;
}
public void procesarInformacion() {
try {
Thread tarea = new Thread( new Runnable() {
public void run() {}
}
);
tarea.start();
tarea.sleep( 5000 );
}
catch( Exception e ) {
e.printStackTrace();
}
}
}
Como podemos ver, estas clases pueden trabajar perfectamente de forma independiente. Es decir, no necesitan extender ninguna clase ni implementar ninguna interfaz para que puedan trabajar. Podríamos hacerlas funcionar en cualquier contenedor. De hecho, ahora vamos a hacerlas funcionar dentro del contenedor de Spring.
Para conseguir que este conjunto de clases trabaje dentro del contenedor de Spring no tenemos que realizar mucho trabajo. Tan solo tenemos que declarar de la siguiente forma un bean en el fichero de configuración applicationContext.xml :
<beans>
<bean id = "usuarioService" class = "sample.service.impl.UsuarioServiceImpl"/>
</beans>
A continuación vamos a crear una clase con un método main() para poner a trabajar el bean que hemos definido anteriormente :
package sample;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.FileSystemXmlApplicationContext;
import sample.model.Usuario;
import sample.service.UsuarioService;
public class Principal {
private static ApplicationContext ctx;
private Usuario usuario = new Usuario();
public Principal() {
String[] paths = { "src/sample/applicationContext.xml" };
ctx = new FileSystemXmlApplicationContext(paths);
usuario.setNombre( "USUARIO_NOMBRE" );
usuario.setApellidos( "USUARIO_APELLIDOS" );
usuario.setDireccion( "USUARIO_DIRECCION" );
}
public static void main(String[] args) {
Principal programa = new Principal();
programa.test ();
}
public void test () {
UsuarioService service = (UsuarioService)ctx.getBean( "usuarioService" );
service.consultaUsuario( usuario );
}
}
Como vemos, es muy sencillo hacer trabajar estas clases; volvemos a hacer hincapié en la trivialidad de la implementación de los métodos de la clase UsuarioServiceImpl; en un entorno real el método consultaUsuario lo que tendría que realizar sería un acceso a una base datos para retornar un usuario, pero como lo que aquí nos interesa es AOP, no vamos a emplear tiempo en la implementación de la clase.
Ahora, lo que nos interesa es ver cómo vamos a incrementar la funcionalidad de estas clases ( la funcionalidad de UsuarioServiceImpl en particular ) sin tener que modificar ni una línea su código. Aquí es donde entra en juego AOP.
Advice de tipo Before
Como se especificó unas líneas más arriba, Spring nos brinda la posibilidad de trabajar con distintos tipos de Advices, y el primero que vamos a ver es el Advice de tipo Before.
Cuando interceptamos un método de una clase con un Advice de tipo Before lo que sucede es que antes de ejecutarse el método se ejecuta el código del Advice. Dentro del Advice vamos a tener acceso tanto al método de la clase a la que vamos a llamar, como a los argumentos que se le pasan al método, como al objeto al que le estamos aplicando el advice.
El único proceso que tenemos que realizar para implementar un Advice de tipo Before es crear una clase que implemente la interfaz MethodBeforeAdvice. La interfaz está definida de la siguiente forma :
public interface MethodBeforeAdvice extends BeforeAdvice {
/**
* Callback before a given method is invoked.
* @param method method being invoked
* @param args arguments to the method
* @param target target of the method invocation. May be null.
* @throws Throwable if this object wishes to abort the call.
* Any exception thrown will be returned to the caller if it's
* allowed by the method signature. Otherwise the exception
* will be wrapped as a runtime exception.
*/
void before(Method method, Object[] args, Object target) throws Throwable;
}
Vamos a crear un Advice que no tiene mucha funcionalidad, pero que nos sirve para ver cómo funciona este tipo de Advice. Lo único que hace esta clase es modificar el parámetro que se le pasa al método, y una vez modificado lo imprime por la pantalla. Como vemos, lo que hace esta clase no es nada interesante, pero le podríamos dar otro tipo de funcionalidad como podría ser implementar un control de accesos al método.
La interfaz la podemos implementar como sigue :
package sample.aop.advice;
import org.springframework.aop.MethodBeforeAdvice;
import java.lang.reflect.Method;
import sample.model.Usuario;
public class EventoBeforeAdvice implements MethodBeforeAdvice {
public void before( Method method, Object[] parameters, Object target ) throws Throwable {
System.out.println( "***************** AOP BEFORE ADVICE *********************" );
Usuario usuario = (Usuario)parameters[0];
usuario.setNombre( usuario.getNombre() + " MODIFICADO POR AOP" );
System.out.println( "AOP BEFORE ADVICE : Accediendo al Usuario " + usuario );
}
}
Ahora lo único que faltaría hacer sería especificar que cuando se realice la llamada a un método de una clase que sea interceptado por este advice. Para conseguir esto lo único que tenemos que hacer es declararlo en el fichero applicationContext :
<beans>
<bean id = "usuarioServiceTarget" class = "sample.service.impl.UsuarioServiceImpl"/>
<bean id = "eventoBeforeAdvice" class = "sample.aop.advice.EventoBeforeAdvice"/>
<bean id = "usuarioService" class = "org.springframework.aop.framework.ProxyFactoryBean">
<property name = "proxyInterfaces">
<value>sample.service.UsuarioService</value>
</property>
<property name = "interceptorNames">
<list>
<value>eventoBeforeAdvice</value>
</list>
</property>
<property name = "target">
<ref bean = "usuarioServiceTarget"/>
</property>
</bean>
</beans>
Como vemos, en primer lugar tenemos que cambiar la definición que teníamos del bean usuarioService. Antes estaba definido así :
<bean id = "usuarioService" class = "sample.service.impl.UsuarioServiceImpl"/>
y ahora lo definimos así :
<bean id = "usuarioServiceTarget" class = "sample.service.impl.UsuarioServiceImpl"/>
El siguiente paso es declarar un bean del advice que habíamos creado :
<bean id = "eventoBeforeAdvice" class = "sample.aop.advice.EventoBeforeAdvice"/>
Y por último, lo que nos queda es definir un proxy para el bean usuarioServiceTarget. A este proxy le tenemos que decir :
- qué interfaz tiene que implementar : UsuarioService
- qué interceptores queremos aplicar : eventoBeforeAdvice
- a qué bean queremos aplicar los interceptores : usuarioServiceTarget
Con estos cambios realizados en el fichero applicationContext ya podríamos probar de nuevo la clase Principal que habíamos creado anteriormente. No es necesario que modifiquemos ninguna línea de código de las clases anteriores; vemos que lo único que hemos hecho ha sido crear una nueva clase, y modificar el fichero de configuración de Spring. Si ejecutamos de nuevo la clase Principal obtendríamos el siguiente resultado :
******************** AOP BEFORE ADVICE ************************ AOP BEFORE ADVICE : Accediendo al Usuario sample.model.Usuario@a4488[ nombre=USUARIO_NOMBRE MODIFICADO POR AOP apellidos=USUARIO_APELLIDOS direccion=USUARIO_DIRECCION ] Consultando el Usuario : sample.model.Usuario@a4488[ nombre=USUARIO_NOMBRE MODIFICADO POR AOP apellidos=USUARIO_APELLIDOS direccion=USUARIO_DIRECCION ]
En este ejemplo podemos comprobar cómo se pueden manipular parámetros que se le envian al método interceptado. No estoy seguro que sea una buena práctica realizar este tipo de cosas dentro de un advice, pero lo hacemos simplemente para demostrar que podemos manipular parámetros dentro del advice.
Pues con esto ya estaríamos aplicando AOP a nuestras clases. Pero si modificamos la clase Principal para que haga llamadas a otros métodos de la clase UsuarioServiceImpl además de al método consultaUsuario vemos que todos los métodos son interceptados. Necesitamos algo más de flexibilidad, necesitamos poder especificar a qué métodos se les aplica qué advices, cosa que vamos a conseguir con los PointCuts.
Definiendo PointCuts
Pues bien, un PointCut no es más que definir qué métodos van a ser interceptados con qué advices. No tenemos por qué especificar el nombre del método completo, sino que podemos utilizar comodines para decir que se intercepten todos aquellos métodos que empiecen por unas palabras determinadas, o que acaben o contegan otras palabras, es decir, podemos establecer que se intercepten aquellos métodos que cumplan un determinado patrón.
La forma más sencilla de definir PointCuts es a través de la clase RegexpMethodPointcut que ya viene implementada en Spring. Como vamos a ver a continuación no necesitamos escribir nuevo código, simplemente se trata de configurar correctamente el fichero applicationContext :
<bean id = "consultaPointCut" class="org.springframework.aop.support.RegexpMethodPointcutAdvisor">
<property name = "pattern">
<value>.*consulta.*</value>
</property>
<property name = "advice">
<ref bean = "eventoBeforeAdvice"/>
</property>
</bean>
Por último nos faltaría una cosa más. Tenemos que cambiar lo siguiente en la declaración del bean usuarioService :
<bean id = "usuarioService" class = "org.springframework.aop.framework.ProxyFactoryBean">
<property name = "proxyInterfaces">
<value>sample.service.UsuarioService</value>
</property>
<property name = "interceptorNames">
<list>
<value>eventoBeforeAdvice</value>
</list>
</property>
<property name = "target">
<ref bean = "usuarioServiceTarget"/>
</property>
</bean>
Por esto otro :
<bean id = "usuarioService" class = "org.springframework.aop.framework.ProxyFactoryBean">
<property name = "proxyInterfaces">
<value>sample.service.UsuarioService</value>
</property>
<property name = "interceptorNames">
<list>
<value>consultaPointCut</value>
</list>
</property>
<property name = "target">
<ref bean = "usuarioServiceTarget"/>
</property>
</bean>
Con esta configuración, como se puede intuir, lo que conseguimos es que cuando hagamos una llamada al método consultaUsuario del bean usuarioService, antes de ejecutarse el código del método se ejecute el código del advice que hemos configurado.
Advice de tipo After Returning
Anteriormente hemos estado viendo lo sencillo que resulta aplicar advices de tipo before a clases que ya tenemos creadas. Ahora vamos a tratar los advices de tipo after returning pero sin detenernos en dar muchos detalles de cómo se implementa, ya que la forma de declararlos es idéntica a los anteriores. La única diferencia estriba en la clase que tenemos que implementar a la hora de crear el advice, que en este caso nuestra clase va a tener que implementar la interface AfterReturningAdvice :
public interface AfterReturningAdvice extends Advice {
/**
* Callback after a given method successfully returned.
* @param returnValue the value returned by the method, if any
* @param method method being invoked
* @param args arguments to the method
* @param target target of the method invocation. May be null.
* @throws Throwable if this object wishes to abort the call.
* Any exception thrown will be returned to the caller if it's
* allowed by the method signature. Otherwise the exception
* will be wrapped as a runtime exception.
*/
void afterReturning(Object returnValue, Method method, Object[] args, Object target) throws Throwable;
}
Como vemos en la definición de la interfaz, cuando la implementemos podremos trabajar con el método del objeto que fue llamado, con los parámetros que se le pasaron al método, con el objeto al que se le ha interceptado la llamada al método y además con el valor que el método ha devuelto.
A continuación vamos a crear una clase que implemente esta interfaz. De nuevo, la implementación de esta clase va a ser trivial, aunque le podríamos dar funcionalidad un poco más interesante haciendo por ejemplo que cuando se dé de alta un usuario nuevo envíe automáticamente un mail al administrador del sistema informándole de que tal usuario ha sido dado de alta ( no lo vamos a implementar en este ejemplo ).
Creamos la clase de la siguiente forma :
package sample.aop.advice;
import org.springframework.aop.AfterReturningAdvice;
import java.lang.reflect.Method;
import sample.model.Usuario;
public class EventoAfterAdvice implements AfterReturningAdvice {
public void afterReturning( Object valueReturned, Method method, Object[] parameters, Object target ) throws Throwable {
System.out.println( "***************** AOP AFTER ADVICE *********************" );
int records = ((Integer)valueReturned).intValue();
if ( records == 1 ) {
System.out.println( "AOP AFTER ADVICE : Nº Registros actualizados : " + records );
System.out.println( "MAIL ENVIADO AL ADMINISTRADOR" );
}
else {
System.out.println( "AOP AFTER ADVICE : NO se ha actualizado ningún registro" );
}
}
}
Registramos el advice en el fichero de configuración de Spring :
<bean id = "eventoAfterAdvice" class = "sample.aop.advice.EventoAfterAdvice"/>
Creamos un nuevo PointCut para que se ejecute este advice siempre que se invoque a algún método que contenga la palabra actualizar :
<bean id = "actualizaPointCut" class="org.springframework.aop.support.RegexpMethodPointcutAdvisor">
<property name = "pattern">
<value>.*actualizar.*</value>
</property>
<property name = "advice">
<ref bean = "eventoAfterAdvice"/>
</property>
</bean>
Agregamos el PointCut al bean usuarioService :
<bean id = "usuarioService" class = "org.springframework.aop.framework.ProxyFactoryBean">
<property name = "proxyInterfaces">
<value>sample.service.UsuarioService</value>
</property>
<property name = "interceptorNames">
<list>
<value>consultaPointCut</value>
<value>actualizaPointCut</value>
</list>
</property>
<property name = "target">
<ref bean = "usuarioServiceTarget"/>
</property>
</bean>
Así de simple hemos configurado nuestro bean para que sea interceptado.
Advice de tipo Around
Finalmente, el último tipo de advice que de forma rápida vamos a ver en este artículo es el advice de tipo Around. Nos faltaría también ver el de tipo Throws, pero no lo vamos a ver en este artículo, ya que visto cómo se configuran los advices que hemos visto hasta ahora no resulta complicado configurar el de tipo Throws.
Según nos dicen en el manual de referencia de Spring, el advice de tipo Around es el tipo de advice más general, de hecho, muchos frameworks AOP tan sólo ofrecen este tipo de advice.
Este tipo de advice es una especie de fusión de los advices Before y After Returning. Es decir, cuando un método es interceptado por este tipo de advice podemos controlar qué hacer antes de que el método sea invocado, podemos decidir si invocamos o no el método y finalmente podemos realizar más operaciones una vez el método haya finalizado.
De forma breve vamos a ver cómo se implementa este tipo de advice. En primer lugar tenemos que implementar la interfaz org.aopalliance.intercept.MethodInterceptor. El ejemplo que vamos a realizar consiste en un sencillo trabajo que se basa en calcular el tiempo que tarda en ejecutarse el método que interceptamos. La clase es la siguiente :
package sample.aop.advice;
import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
import sample.model.Usuario;
public class EventoMethodInterceptorAdvice implements MethodInterceptor {
public Object invoke( MethodInvocation methodInvocation ) throws Throwable {
System.out.println( "****** INICIO AOP METHOD INTERCEPTOR ADVICE **********" );
long tiempoInicial = new java.util.Date().getTime();
Object objeto = methodInvocation.proceed();
long tiempoEjecucion = ( new java.util.Date().getTime() - tiempoInicial ) / 1000;
System.out.println( "Nombre del Método : " + methodInvocation.getMethod() );
System.out.println( "Tiempo de proceso del método ( segundos ) : " + tiempoEjecucion );
System.out.println( "******* FIN AOP METHOD INTERCEPTOR ADVICE **********" );
return objeto;
}
}
Y a continuación configuramos el advice y el bean que tiene que ser interceptado por este advice.
Declaramos el advice :
<bean id = "eventoMethodInterceptorAdvice" class = "sample.aop.advice.EventoMethodInterceptorAdvice"/>
Declaramos el PointCut :
<bean id = "procesaPointCut" class="org.springframework.aop.support.RegexpMethodPointcutAdvisor">
<property name = "pattern">
<value>.*procesar.*</value>
</property>
<property name = "advice">
<ref bean = "eventoMethodInterceptorAdvice"/>
</property>
</bean>
Y por último agregamos el interceptor al bean :
<bean id = "usuarioService" class = "org.springframework.aop.framework.ProxyFactoryBean">
<property name = "proxyInterfaces">
<value>sample.service.UsuarioService</value>
</property>
<property name = "interceptorNames">
<list>
<value>consultaPointCut</value>
<value>actualizaPointCut</value>
<value>procesaPointCut</value>
</list>
</property>
<property name = "target">
<ref bean = "usuarioServiceTarget"/>
</property>
</bean>
Y con esto ya habríamos finalizado la configuración de nuestras clases para que sean interceptadas.
Conclusión
Con este artículo se ha pretendido demostrar que tabajar con Spring AOP resulta bastante sencillo. No se ha intentado explicar la teoría de AOP ni cuáles son las áreas más idóneas en las que AOP puede ponerse a trabajar, por lo contrario ha sido un artículo meramente práctico en el que se ha trabajado la mayoría de los tipos de advices con que cuenta Spring.
En próximos artículos sobre AOP, si se considera oportuno, se podría tratar cómo declarar Transacciones con Spring ( cosa muy sencilla una vez hemos comprendido cómo trabaja Spring AOP ), cómo hacer Logging ( más sencillo todavía ) y/o cómo implementar seguridad con Acegi.
El código fuente puede ser bajado desde aquí
Posted at 08:58PM jul 17, 2006 by Jose Luis Monteagudo in General | Comentarios[4]