Introducción a la AOP (Programación Orientada al Aspecto)

Puedes encontrar la versión original de este tutorial en Inglés en:

http://www.onjava.com/

Introducción

Cuando apareció la programación Orientada a Objetos (OO) en el desarrollo de software, tuvo un efecto dramático en cómo se desarrollaba éste. Los desarrolladores podían visualizar sistemas como grupos de entidades y la interacción entre esas entidades, lo que les permitía realizar sistemas más grandes y más complicados y desarrollarlos en mucho menos tiempo. El único problema con la programación OO es que es esencialmente estática, y un cambio en los requerimientos puede tener un profundo impacto en el tiempo de desarrollo.

Consideremos un ejemplo: muchos de vosotros habreís desarrollado una sencilla aplicación web que utilice servlets como punto de entrada, donde un servlet acepta los valores de un formulario HTML, los une a un objeto, lo pasa a la aplicación para procesarlo y luego devuelve una respuesta al usuario. La primera versión del servlet podría ser muy simple, con la cantidad de código mínima necesaria para cumplir el papel que va a desempeñar. Sin embargo, el código se infla tres o cuatro veces su tamaño original debido a los requerimientos secundarios como el manejo de excepciones, la seguridad, y el logging que se han implementado. Utilizo el término requerimientos secundarios porque un servlet no debería saber nada sobre el loggin o los mecanismos de seguridad que se van a utilizar; su función principal es aceptar una entrada y procesarla.

AOP nos ayuda a modificar dinámicamente nuestro modelo estático para incluir el código requerido para cumplir los requerimientos secundarios sin tener que modificar el modelo estático original (de echo, ni siquiera necesitaremos tener el código original). Mejor aún, normalmente podremos tener este código adicional en una única localización en vez de tenerlo repartido por el modelo existente, como haríamos si estuvieramos usando sólo OO.

En este artículo, veremos una sencilla aplicación que nos permitirá utilizar los beneficios de la AOP de primera mano, y luego discutiremos brevemente cómo se podría utilizar AOP en tus proyectos existentes.

Terminología

Antes de entrar en profundidad dentro de AOP, presentemos alguna termonología estándar para ayudarnos a entender los conceptos.

  1. Cross-cutting concerns (problema cruzado):
    Incluso aunque la mayoría de las clases de un modelo OO realizarán una función sencilla y específica, normalmente comparten requerimientos secundarios comunes con otras clases. Por ejemplo, podríamos querer añadir logging a las clases dentro de la capa de acceso a los datos y también a las clases de la capa de presentación siempre que un thread entre o salga de un método. Incluso aunque la funcionalidad primaria de cada clase sea muy diferente, el código necesario para realizar la segunda funcionalidad es casi siempre idéntico.
  2. Advice (Consejo):
    Este es el código adicional que queremos aplicar a nuestro modelo existente. En nuestro ejemplo, es el código de logging que queremos aplicar siempre que el thread entre o salga de un método.
  3. Point-cut (punto de corte):
    Este es el término dado al punto de ejecución en flujo de la aplicación donde necesitamos aplicar el problema cruzado. En nuestro ejemplo, tendremos un punto de corte cuando el thread entra a un método, y otro punto de corte cuando el thread sale de ese método.
  4. Aspect (Aspecto):
    La combinación del punto de corte y el consejo es conocida como un aspecto. En el siguiente ejemplo, añadimos un aspecto de login a nuestra aplicación definiendo un punto de corte y dándole el consejo correcto.

Hay muchas otras facetas en AOP, como las introducciones (donde se pueden añadir interfaces/métodos/campos a clases existentes), que contiene un potencial tremendo para los desarrolladores, pero prefiero mantener sencillo este artículo. Cuando estés familiarizado con los conceptos presentados aquí, te recomendaría que continuaras investigando AOP para ver cómo se podrían utilizar las otras facetas en tu entorno de desarrollo.

Marcos de Trabajo Existentes

Probablemente el marco de trabajo más maduro y con más características disponible hoy en día es AspectJ. Aunque AspectJ ha configurado el estándar que han seguido la mayoría de los marcos de trabajo, los arquitectos tomaron el paso inusual de añadir en su implementación nuevas palabras clave al lenguaje Java. Aunque la nueva sintaxis no es muy díficil de aprender, significa que tienes que cambiar tu compilador, y potencialmente reconfigurar tu editor, para poder utilizar la nueva sintaxis. En un gran equipo esto no sería factible, ya que se vería afectado todo el equipo. La modificación del lenguaje también incrementa la curva de aprendizaje para que los equipos introduzcan AOP en proyectos ya existentes.

Lo que necesitamos es un marco de trabajo que se pueda introducir fácilmente sin impactar seriamente en los procesos de desarrollo y construcción existentes. Hay varios marcos que cumplen con esto, como JBoss AOP, Nanning, y Aspectwerkz (AW). Para este artículo hemos elegido Aspectwerkz porque probablemente es el más sencillo de aprender y de integrar en proyectos existentes.

Aspectwerkz, creado por Jonas Boner y Alexandre Vasseur, sigue siendo uno los marcos de trabajo AOP más rápidos y con más caracteristicas. Aunque no tiene todas las características de AspectJ, es suficiente para ser de gran utilización por la mayoría de los desarrolladores en muchas situaciones.

Una de las caracteríticas más interesante de Aspectwerkz es su habilidad para ejecutarse en dos modos diferentes: online y offline. En el modo online, AW se engancha al mecanismo de carga de clases a bajo nivel como parte de la JVM, permitiéndole interceptar todas las llamadas de carga de clases y transformando los bytecodes al vuelo. AW proporciona muchas opciones para engancharse, y se puede utilizar un script envoltura como reemplazo para el comando bin/java para detectarlo automáticamente y seleccionar una configuración de trabajo dependiendo de la versión de Java y las capacidades de la JVM. Este modo ofrece muchos beneficios para los desarrolladores, ya que se puede enganchar en cualquier cargador de clases y armar las clases en tiempo de carga, lo que significa que tus ficheros de clases no se modifican manualmente, sino que se despliegan de la forma usual. Sin embargo, requiere una configuración adicional de tu servidor de aplicación, lo que podría no ser posible en algunas situaciones.

En modo offline, se requieren dos fases para generar nuestras clases. La primera fases es la compilación estándar utilizando la herramienta javac que todos conocemos. (De echo, la mayoría de nosotros la ama tanto que la hemos reemplazado con tareas de Ant desde hace años). La segunda fase es donde las cosas se ponen interesantes, ejecutamos el AWcompiler en modo offline y lo apuntamos hacia las clases recien creadas. El compilador modificará los bytecodes de las clases para incluir nuestro 'consejo' en los 'punto de corte' correctos, según se define en un fichero XML. El beneficio de utilizar el modo offline es que las clases se podrán ejecutar en cualquier versión de JVM superior a la 1.3. Este es el modo en el que lo utilizaremos en este artículo, ya que no requiere modificar Tomcat y se podría aplicar a la mayoria de los proyectos existentes con unas ligeras modificaciones en el proceso de construcción.

Instalación

En este artículo vamos a ver una sencilla aplicación, la vamos a compilar utilizando Ant, y la vamos a desplegar en el contenedor Tomcat versión 4 o superior. Vamos a asumir que ya tienes instalado todo lo de arriba, junto con una JVM 1.3+, y que tu instalación de Tomcat está configurada para desplegar aplicaciones automáticamente desde la carpeta webapps y que expande los ficheros WAR en directorios (este es el comportamiento estándar de Tomcat, si no lo has modificado está aplicación debería funcionar si hacer nada más). Nos referiremos a la localización donde está instalado Tomcat como %TOMCAT_HOME%.

  1. Descarga Aspectwerkz de su sitio web y extrae el fichero comprimido en tu localización preferida. Nos referirémos a esta localización como %ASPECTWERKZ_HOME%.
  2. Configura la variable de entorno %ASPECTWERKZ_HOME%.
  3. Añade el compilador de Aspectwerkz a tu variable de entorno PATH, por ejemplo:
    set PATH=%PATH%;%ASPECTWERKZ_HOME%inaspectwerkz
    
  4. Descarga la aplicación de ejemplo que acompaña a este artículo y copiala en tu carpeta %TOMCAT_HOME%webapps.
  5. Añade las clases de ejecución de Aspectwerkz al classpath de Tomcat. Puedes copiar los ficheros JAR desde %ASPECTWERKZ_HOME%lib en la carpeta WEB-INFlib de la aplicación de ejemplo, o dentro de %TOMCAT_HOME%commonlib.

Compilar la Aplicación de Ejemplo

Si quieres jugar con esta aplicación demo, extrae los contenidos del fichero WAR. Encontrarás el fichero aspectwerkz.xml en la raíz de directorios, y que se copia al directorio WEB-INF/classes cuando se construye la aplicación. Los ficheros fuente del servlet y de las clases advice están en WEB-INF/src; he incluido un script Ant que construye estas clases por tí.

Antes de poder ver la demo en acción, también tendrás que completar la fase de post-compilación, y aquí está como:

  1. Abre una ventana de consola en el directorio donde extraíste el fichero WAR.
  2. Teclea el siguiente comando para invocar el compilador AW (todo en una línea):
    aspectwerkz -offline aspectwerkz.xml WEB-INF/classes
    -cp %TOMCAT_HOME%commonlibservlet.jar
    

Deberías ver la siguiente salida si se ha completado con éxito la post-compilación:

( 1 s )
SUCCESS: WEB-INFclasses

Hay una tarea de Ant en el fichero de construcción llamada war que nos ayudará a crear el fichero WAR.

Ejecutar la Aplicación de Ejemplo

  1. Copia de nuevo el fichero WAR al directorio %TOMCAT_HOME%webapps.
  2. (Re)inicia Tomcat.
  3. Abre la dirección http://localhost:8080/demo/ en tu navegador.

Cuando abras tu navegador, verás un formulario estándar HTML con dos campos: uno para el nombre y otro para la dirección de email del contacto. Introduce algunos detalles y pulsa Submit, y verás que se muestran los detalles del contacto y un enlace a un fichero que contiene una sencilla lista de contactos. OK, esta demo no va a cambiar el mundo, de acuerdo; pero echemos un vistado bajo la manta para ver que ha sucedido realmente.

Paseo por el Código

Ignoremos los ficheros JSP, ya que tienen muy poco interés para nosotros ahora. En su lugar, veamos el código del AOPServlet.

package example;

import java.io.*;
import javax.servlet.*;
import javax.servlet.http.*;
public class AOPServlet extends HttpServlet {
    public void doGet(HttpServletRequest request,
                      HttpServletResponse response)
        throws ServletException, IOException {
        Person person = new Person();
        if (request.getParameter("name") != null) {
            person.setName(
                request.getParameter("name"));
        }
        if (request.getParameter("email") != null) {
            person.setEmail(
                request.getParameter("email"));
        }
        request.setAttribute("person", person);
        RequestDispatcher rd =
            request.getRequestDispatcher("/view.jsp");
        rd.forward(request, response);
    }
}

En este ejemplo, observarás que el código del servlet es mínimo; sólo contiene suficiente código para crear un objeto, unirlo a los parámetros de la solicitud, y pasarlo junto con la solicitud. No hay código de persistencia, ni sentencicas import adicionales; simplemente hace lo que se supone que hace.

Pero nuestro documento de diseño estipula que debemos persistir todos los objetos del tipo Person de nuestra aplicación, por eso necesitamos añadir un aspecto a la aplicación. Primero creamos un fichero llamado aspectwerkz.xml y lo situamos en el classpath. He incluido un sencillo ejemplo en la aplicación de demostración que puedes abrir en tu editor favorito; explicaremos lo que está haciendo.

La primera sección define los diferentes advices que tenemos disponibles. Podemos añadir tantas definiciones de consejos diferentes como queramos:

<advice-def name="persist" class="example.PersistenceAdvice" 
    deployment-model="perJVM"/>

En este fragmento, hemos definido un advice llamado persist, que es del tipo example.PersistenceAdvice. El último atributo define la exclusividad de este advice; en este caso, se selecciona a perJVM, lo que significa que sólo se creará un consejo de este tipo por cada JVM. (Puedes ver la documentación de Aspectwerkz para obtener más información sobre los modelos de despliegue).

La siguiente sección define nuestro aspecto. Aquí es donde mapeamos nuestro advice a un point-cut para crear un aspect.

<aspect name="servlet">
    <pointcut-def name="all" type="method"
        pattern="* example.*Servlet.doGet(..)"/>
    <bind-advice pointcut="all">
        <advice-ref name="persist"/>
    </bind-advice>
</aspect>

Veamos esta sección línea a línea:

  1. Estamos creando un aspecto llamado servlet; podemos crear tantos aspectos como necesitemos.
  2. En la segunda línea, estamos creando un punto de corte llamado all que sólo se aplicará a los métodos (type="method").
  3. La tercera línea es donde definimos, usando una expresión regular, donde queremos que se de el consejo. En este ejemplo, decimos que queremos aplicar el consejo, sin importar el tipo de retorno (el primer *), a cualquier clase del paquete example cuyo nombre termine en "Servlet" (*Servlet) que contenga un método llamado doGet con cualquier lista de parámetros (doGet(..)).
  4. En la cuarta línea, le decimos al compilador Aspectwerkz que queremos aplicar el siguiente consejo al punto de corte llamado all.
  5. Aquí decimos que queremos usar el consejo persist.

Ahora que ya sabemos como mapear puntos de corte y consejos para crear aspectos, veamos un ejemplo de una clase que proporciona el advice. En nuestro fichero de mapeo, hemos registrado un consejo del tipo example.PersistenceAdvice. Aquí está su código fuente:

package example;

import javax.servlet.http.*;
import org.codehaus.aspectwerkz.advice.*;
import org.codehaus.aspectwerkz.joinpoint.*;

public class PersistenceAdvice extends AroundAdvice {

    public PersistenceAdvice() {
        super();
    }

    public Object execute(final JoinPoint joinPoint)
        throws Throwable {

        MethodJoinPoint jp = (MethodJoinPoint) joinPoint;
        final Object result = joinPoint.proceed();
        Object[] parameters = jp.getParameters();

        if (parameters[0] instanceof HttpServletRequest) {
            HttpServletRequest request = (HttpServletRequest) parameters[0];
            if (request.getAttribute("person") != null) {
                Person contact = (Person) request.getAttribute("person");
                ContactManager persistent = new ContactManager();
                String fileName = (request.getRealPath("/")+
                     "contacts.txt");
                persistent.save(contact, fileName);
            }
        }
        return result;
    }
}

La primera línea de este método es auto-explicativa: simplemente forzamos al tipo más específico que podemos. La segunda línea probablemente sea la más importante: ya que queremos disparar el método y luego ver el resultado, debemos llamar a proceed(), que permite que se complete el método. En la siguiente sección estamos capturando el HttpServletRequest y recuperando el objeto que el servlet ha situado en el ámbito de la solicitud (recuerda que le método doGet() se terminó en este punto).

Finalmente, creamos una clase llamada ContactManager que persiste los detalles de la persona en un fichero de texto. Esta clase sólo debería grabar fácilmente los detalles en un fichero XML, una base de datos o cualquier otro almacenamiento persistente. El punto importante es que el servlet no tiene ni idea de lo que le iva a sucede al bean cuando estabamos diseñando/prototipando la aplicacón; la segunda funcionalidad se puede añadir en cualquier momento en el futuro. Es lo que quise decir con que tu aplicación básica puede aprender nuevos comportamientos según crezca, y añadir funcionalidades adicionales posteriormente es algo trivial.

¿Y ahora Qué?

En el ejemplo de la sección anterior hemos tomado una aplicación muy básica, desplegada en Tomcat, y ejecutada en un navegdor para probar la funcionalidad. Aunque la aplicación no parece muy útil, los principios que demuestra son realmente muy poderosos. Imagina que sea aposible prototipar rápidamente una funcionalidad y luego aplicar los problemas cruzados como la seguridad, el logging, la persistencia y el caché cuando has terminado. Podrías facilmente añadir un logging a toda la aplicación, sin importar el número de líneas de código, en menos de ciez minutos!!!

Espero que puedas ver más allá de la simplicidad de esta aplicación y puedas ver donde poder aplicar AOP en tus proyectos. Aunque podría haber una importante curva de aprendizaje de todos los conceptos unidos a AOP, definitivamente merece la pena, te ahorrarás semanas y probablemente miles de líneas de código repetitivo en un proyecto medio.

COMPARTE ESTE ARTÍCULO

ENVIAR A UN AMIGO
COMPARTIR EN FACEBOOK
COMPARTIR EN TWITTER
COMPARTIR EN GOOGLE +
¡SÉ EL PRIMERO EN COMENTAR!
Conéctate o Regístrate para dejar tu comentario.