Aspect Oriented Programming – AspectJ

En el desarrollo de sistemas es frecuente encontrarnos con métodos similares al siguiente, donde se puede apreciar que la lógica del código se pierde junto con otras funcionalidades como auditoría, logging, seguridad, etc.:

public String doSomething(Request request) {
    // log method start
    // ...
    // perform security check
    // ...

    // Do the actual work
    Result result = getResults(request);

    // log method finished
    // ...
    // save user access
    // ...
    
    return result;
}

Cuando aumenta la complejidad del código, la modularidad es de gran ayuda para simplificarlo, y así aumentar la facilidad de mantenimiento. La programación orientada a aspectos busca pasar desde módulos que utilizan explícitamente una funcionalidad global:

AOP-from

A una funcionalidad global que es aplicada en aquellos lugares donde es necesario:

AOP-to

 Conceptos Principales

Fundamentalmente, la programación orientada a aspectos ofrece dos partes:

  1. Una forma de codificar los distintos aspectos necesarios.
  2. La exposición de ciertos puntos del código que se utilizan para vincularlo con los aspectos.

Los siguientes son los conceptos más importantes:

  • Join point: son puntos identificables dentro de la ejecución del código. Por ejemplo, la invocación de un método, la ejecución completa del mismo, la inicialización estática de una clase, etc. Estos puntos son expuestos por el sistema.
  • Pointcut: es un constructor que permite seleccionar join points. Permite adherir el código de los aspectos a diferentes join points. El concepto es similar a los selectores de css o JQuery.
  • Advice: es la porción de código que va a alterar el comportamiento en el join point seleccionado mediante el pointcut. Existen distintos tipos, es decir, se pueden ejecutar antes de proceder con el join point, después, o alrededor.
  • Aspect: es el modelo que permite contener todo el código correspondiente a un aspecto en particular. Es el equivalente a una clase.

AspectJ – Un ejemplo

Para mostrar una implementación sencillla de AOP, podemos considerar una tarea bastante común en el desarrollo de sistemas: el logging de información. La idea es habilitar el logging donde se requiera, utilizando la anotación @Log.

A continuación vemos los 3 componentes principales para resolver esta tarea:

  • La anotación @Log creada (declarada en este caso como para que solamente pueda ser usada en métodos).
  • un servicio de ejemplo (SomeService.java) que utilice la anotación.
  • El aspecto que será el encargado de escribir los logs para ese servicio (LoggerAspect.aj).

Log.java:

@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface Log {
    LogLevel level() default LogLevel.INFO;
}

SomeService.java:

public class SomeService {

    @Log(level = LogLevel.INFO)
    public void addEntity(int entity) {
        System.out.println("Adding entity " + entity);
    }

    @Log(level = LogLevel.WARNING)
    public void removeEntity(int entity) {
        System.out.println("Removing entity " + entity);
    }
}

LoggerAspect.aj:

public aspect LoggerAspect {

    private static final Logger LOGGER = Logger.getLogger("LoggerAspect");

    pointcut annotatedMethods(): execution(@Log * *.*(..));

    before() : annotatedMethods() {
        MethodSignature ms = (MethodSignature) thisJoinPoint.getSignature();
        Log log = ms.getMethod().getAnnotation(Log.class);

        LOGGER.log(getLoggingLevel(log.level()), "Starting execution of "        
            + thisJoinPoint.getSignature());
    }

    after() : annotatedMethods() {
        MethodSignature ms = (MethodSignature) thisJoinPoint.getSignature();
        Log log = ms.getMethod().getAnnotation(Log.class);

        LOGGER.log(getLoggingLevel(log.level()), "Finishing execution of "
            + thisJoinPoint.getSignature().getName());
    }
    // ...
}

La declaración del pointcut selecciona aquellos lugares que están anotados con @Log. El pointcut, al igual que las clases, variables o métodos, tiene un nombre, el cual debe ser intuitivo, y facilitar su entendimiento. Para este ejemplo, se llamó anotatedMethods() para referenciar justamente, los métodos que están marcados con la anotación.

En este caso generamos dos advises, uno para agregar el log antes de la ejecución de los métodos (before()) y el otro para después (after()).

Vale la pena destacar que el operador thisJoinPoint nos proporciona información sobre el join point en el que nos encontramos actualmente. En el ejemplo, lo utilizamos para obtener el nombre del método cuya ejecución está comenzando / terminando, así como también el nivel de log que se especificó en la anotación.

Conclusiones

La programación orientada a aspectos nos beneficia en ciertos aspectos como poder separar funcionalidades que son globales, e independientes de la lógica propia del sistema. Si es posible diferenciar las funcionalidades globales, de una forma clara, entonces probablemente sea conveniente que esas funcionalidades globales sean implementadas utilizando AOP.

Otra ventaja es que al separar la lógica del dominio de los aspectos globales, podemos aumentar el paralelismo en el desarrollo, lo que puede acelerar tiempos, y además permite escribir un código más limpio y concreto, enfocado sólo en llevar a cabo los requerimientos funcionales.

Debe quedar claro que AOP es una herramienta más, que no va a ser útil en todos los casos, y se debe realizar un análisis medianamente profundo antes de embarcarse en su implementación.

Tags

Access top talent now!

Related

Get in Touch