lunes, 30 de septiembre de 2013

Spring: AOP con Aspect mediante archivos xml y Anotation

Recorriendo Spring
Existen diferentes formas de trabajar con aspectos en Spring AOP  (mediante XML, mediante anotaciones AspectJ, utilizando configuración por Java, utilizando configuración por XML, con CGLib, etc…)  Nosotros nos centraremos en Spring AOP con anotaciones AspectJ, utilizando proxies que envuelven a las clases y ejecutan los aspectos. Hay que tener en cuenta que, por defecto, Spring AOP sólo permite definir aspectos sobre clases que implementen algún interfaz ya que para ello utiliza proxies dinámicos de JDK.


Lo primero que debemos aclarar como lo mencione en anteriores entregas, el manejo de aspectos por notaciones es mucho mas comodo y mucho mas simple, por que la responsabilidad de definir los interceptores y los patrones bajo los cuales deben ser ejecutados no estará en nuestro archivo spring bean configuration, sino que lo trasladaremos a la clase que tiene la implementacion de los advise, el proyecto y las clases sobre las que trabajaremos son las del proyecto que utilizamos en el tutorial anterior a fin de poder abordar en mayor detalle la mecanica Aop con notaciones y no demorar en entender las clases del ejemplo.

Nuestra estructura de proyecto no presenta grandes complicaciones.








































Nuestro archivo POM no presentara grandes diferencias en cuanto al que armamos en anteriores entregas, por lo que el  siguiente paso es definir nuestro archivo POM


<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>
  <groupId>com.company</groupId>
  <artifactId>Aop_Nativo</artifactId>
  <version>0.0.1-SNAPSHOT</version>
  <properties>
   <spring.version>3.0.5.RELEASE</spring.version>
  </properties>
  <repositories>
 <repository>
     <id>java.net</id>
     <url>https://repo.maven.apache.org/maven2/</url>
 </repository>
    </repositories>
  <dependencies>
   <dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-core</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.aspectj</groupId>
    <artifactId>aspectjweaver</artifactId>
    <version>1.6.6</version>
   </dependency>
   <dependency>
    <groupId>org.aspectj</groupId>
    <artifactId>aspectjrt</artifactId>
    <version>1.6.11</version>
   </dependency>
  </dependencies>
</project>

Donde declaro las bibliotecas que utilizaremos, para esta ocasión trabajaremos con la version 3.0.5.RELEASE para lo que definimos una propiedad con ese fin. 

Incluimos las librerías comunes para trabajar con SPRING 
  • spring-core
  • spring-context
Para trabajar con AOP utilizaremos 
  • spring-aop
  • aspectjweaver
  • aspectjrt

De manera inicial es una estructura simple inicial de todo proyecto spring, donde todo nace lógicamente de la definición del pojo.

com.company.modelo.Usuario.java:   como podemos ver es solo una entidad con sus getter y setter como lo indica spring.


package com.mycompany.modelo;

public class Usuario {

 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;
 }
 
    
}

el siguiente paso es definir los contratos o interfaces sobre las que se desarrollaran implementaciones.

com.company.services.UsuarioService.java: es solo nuestro contrato donde definimos los métodos que serán implementado,  mucho de los metodos estan definidos claramente para poder mostrar las virtudes de AOP.


package com.company.services;

import com.mycompany.modelo.Usuario;

public interface UsuarioService {
 public Usuario consultaUsuario( Usuario usuario );
 public Usuario consultaUsuario_alternativo( Usuario usuario );
    public boolean agregarUsuario( Usuario usuario );
    public int actualizarUsuario( Usuario usuario );
    public boolean borrarUsuario( Usuario usuario );
    public void procesarInformacion();
    public void ProbarThrowException() throws Exception;
}


El siguiente paso sera definir la implementacion  de la interfaz.

com.company.services.UsuarioServiceImpl.java:  como podemos observar no tiene mucho código y funcionalidad dado que no es el objetivo de este tutorial. Contiene métodos que reciben objetos y retornan resultados y que lo unico que hacen es imprimir por consola el paso sobre el metodo.


package com.company.services;

import com.mycompany.modelo.Usuario;

public class UsuarioServiceImpl implements UsuarioService {

 public Usuario consultaUsuario( Usuario usuario ) {
        System.out.println( "Consultando el Usuario : " + usuario.getNombre() + " " + usuario.getApellidos());
        return usuario;
    }
 
 public Usuario consultaUsuario_alternativo( Usuario usuario ) {
        System.out.println( "Consultando el Usuario alternativo : " + usuario.getNombre() + " " + usuario.getApellidos());
        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.getNombre() );
        return 1;
    }

    public boolean borrarUsuario( Usuario usuario ) {
        System.out.println( "Borrando Usuario : " + usuario.getNombre() );
        return true;
    }

    public void ProbarThrowException() throws Exception {
  System.out.println("ProbarThrowException() esta corriendo ");
  throw new Exception("Generic Error");
 }
    
    public void procesarInformacion() {

        try {

            Thread tarea = new Thread( new Runnable() {
                public void run() {}
            }
            );
            tarea.start();
            tarea.sleep( 5000 );
        }
        catch( Exception e ) {
            e.printStackTrace();
        }

    }

}


Lo siguiente es lo normal trabajar en nuestro spring bean configuration o nuestro archivo contenedor de beans,  Lo primero que haremos será permitir el uso de aspectos mediante la etiqueta:  <aop:aspectj-autoproxy/>

src/main/resources/usuarioe.xml:  


<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
 xmlns:aop="http://www.springframework.org/schema/aop"
 xsi:schemaLocation="http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.0.xsd
  http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">

<aop:aspectj-autoproxy />

 <!-- Definicion la implementacion de la interfaz con el metodo a interceptar--> 
   <bean id = "usuarioServiceTarget" class = "com.company.services.UsuarioServiceImpl"/>
  <!-- Definicion del consejo--> 
   <bean id = "logAspect" class = "com.company.aop.LoggingAspect"/>  
</beans>

Nota: aquí es donde viene lo loco, en mi archivo de configuración solo voy a definir 2 bean 

el primero que hace referencia al bean de la clase que implementaremos 

<!-- Definicion la implementacion de la interfaz con el metodo a interceptar--> 
   <bean id = "usuarioServiceTarget" class = "com.company.services.UsuarioServiceImpl"/>

el segundo hace referencia a la clase que va implementar los advise.

<!-- Definicion del consejo--> 
   <bean id = "logAspect" class = "com.company.aop.LoggingAspect"/>  

cabe destacar que la lógica de ejecución de los advise en el ejemplo anterior estaba en el archivo de configuración y ahora va estar en la clase de implementacion.

nuestro archivo de test que consume por supuesto los beans y que ejecuta los métodos de la interfaz.

com.company.test.UsuarioServiceTest.java:  es una clase muy simple que solo genera el aplicationcontext, busca el archivo de configuración que utilizaremos y solicita el retorno del bean que utilizaremos y por ultimo ejecuta una serie de metodos.


package com.company.test;

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.FileSystemXmlApplicationContext;

import com.company.services.UsuarioService;
import com.mycompany.modelo.Usuario;

public class UsuarioServiceTest {
 

    public static void main(String[] args) throws Exception {
     ApplicationContext ctx;        
     ctx = new FileSystemXmlApplicationContext("src/main/resources/usuarioe.xml");
        UsuarioService service = (UsuarioService)ctx.getBean( "usuarioService" );
        
        Usuario usuario = new Usuario();
        usuario.setNombre( "diego" );
        usuario.setApellidos( "herrera" );
        usuario.setDireccion( "loria 134" );
        
        service.consultaUsuario( usuario );
        service.borrarUsuario(usuario);
        service.consultaUsuario_alternativo(usuario);
        service.actualizarUsuario(usuario);
        service.procesarInformacion();
        service.ProbarThrowException();
    }

    
}

Nota: podemos notar que crean un objeto, cargado desde el código solo con fines de contar con datos para poder trabajar. Es decir hacemos esto para simular datos.

ahora llega la parte importante por que toda la logica y notaciones de ejecución de los advice esta en la clase que implementa los advice lo que es mucho mas practico en proyecto de gran alcance.

Ahora pero la diferencia solo esta en que antes trabajaba con el archivo bean de configuracion y ahora meto todo en la clase que implementa los interceptores ??


Spring AOP utiliza proxys dinámicos de manera que basta con la JDK para usar Spring AOP mientras que AspectJ cambia el .class de la clase para añadirle los aspectos que programemos así es necesario el uso de la librería de AspectJ. 



que !! si funciona de manera diferente pero amparado en los mismos conceptos, toma nota.

Un sistema final se va descomponer en clases y aspectos,  la relación entrega clases y aspectos  va estar asociado a los conceptos de joinpoint y weaving.

Un Join Poin indica un posible punto del sistema donde la implementación del aspecto puede insertarse.

Un aspecto weaver tiene la responsabilidad de procesar el lenguaje base y el lenguaje de aspecto y componerlos con el objetivo de obtener la implementación final del  sistema.

“En pocas palabras weaver utiliza el joinpoint para armar un código que tendrá la implementación del aspecto y el código fuente original”.

Pero te lo pongo mas fácil en una formula:

Join point  +  pointcuts + advise  = aspect

Mas simple imposible imposible:
  • Join point: punto de ejecución.
  • Pointcut: predicados que atrapan los join point  es decir la condición para la que se dispara.
  • Advise: es la acción a realizar dado un PointCut.


En Spring AOP sólo se pueden aplicar aspectos cuando un método es llamado mientras que en AspectJ se pueden aplicar a un método cuando es llamado, cuando se está ejecutando, a todo un paquete, y también se pueden definir precondiones y postcondiciones a los apectos de tal manera que no se puedan programar métodos que no cumplan con las condiciones de los aspectos que se les asocian.

Si recuerdan en el ejemplo spring Aop,  para la implementacion de los advise definimos una clase para cada implementacion de advise, pero lo cierto es que lo común es que se use una sola clase implementando las diferentes interfaces que necesitamos para cada advise un ejemplo podría ser algo como:

Ejemplo mas real de implementacion de advise utilizando spring Aop 


package info.hcosta.ejercicioaop;
import java.lang.reflect.Method;
import org.springframework.aop.AfterReturningAdvice;
import org.springframework.aop.MethodBeforeAdvice;
import org.springframework.aop.ThrowsAdvice;
 
public class LoggerAdvice 
implements MethodBeforeAdvice
, AfterReturningAdvice
, MethodInterceptor
, ThrowsAdvice {
 
 
    public void before(Method method, Object[] args, Object target) throws Throwable {
      ............
    }
 
    public void afterReturning(Object o, Method method, Object[] args, Object object) throws Throwable {
       ..........
    }
 
    public void afterThrowing(Exception e) throws Throwable {
     ............
    }
    public Object invoke(MethodInvocation arg0) throws Throwable {
    .............
    }
    ..........
}

Nota: Quería mostrar esta alternativa para que la forma en que implementamos nuestros advise no parezca tan estraña y pueda ser familiar.

Pero regresemos a nuestro ejemplo puntual de la implementacion de aspectos de este ejemplo que es donde nos vamos a demorar un poco explicando su contruccion y por supuesto podremos tener una mirada a las notaciones que nos proporciona aspectj para trabajar con aspectos.

com.company.aop.LoggingAspect : es nuestra clase definida para implementar el aspecto.


package com.company.aop;

import java.util.Arrays;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;

@Aspect
public class LoggingAspect {

 @Before("execution(* com.company.services.UsuarioService.consultaUsuario(..))")
 public void logBefore(JoinPoint joinPoint) {
  System.out.println("*********************ADVISE BEFORE*******************");
  System.out.println("metodo logBefore() esta corriendo!");
  System.out.println("metodo interceptado : " + joinPoint.getSignature().getName());
  System.out.println("*****************************************************");
 }
 
 @After("execution(* com.company.services.UsuarioService.consultaUsuario(..))")
 public void logAfter(JoinPoint joinPoint) {
  System.out.println("****************ADVISE AFTER*************************");
  System.out.println("metodo logAfter() esta corriendo!");
  System.out.println("metodo interceptado : " + joinPoint.getSignature().getName());
  System.out.println("******************************************************");
 
 }
 
 @AfterReturning(
        pointcut = "execution(* com.company.services.UsuarioService.agregarUsuario(..))",
        returning= "result")
     public void logAfterReturning(JoinPoint joinPoint, Object result) {
      System.out.println("****************ADVISE AFTERRETURN********************");
   System.out.println("metodo logAfterReturning() esta corriendo!");
   System.out.println("metodo interceptado : " + joinPoint.getSignature().getName());
   System.out.println("El metodo retorna el valor : " + result);
   System.out.println("******************************************************");
   
     }
 
 @AfterThrowing(
        pointcut = "execution(* com.company.services.UsuarioService.ProbarThrowException(..))",
        throwing= "error")
      public void logAfterThrowing(JoinPoint joinPoint, Throwable error) {
      System.out.println("****************ADVISE AfterThrowing********************");
   System.out.println("metodo logAfterThrowing() esta corriendo!");
   System.out.println("metodo interceptado : " + joinPoint.getSignature().getName());
   System.out.println("Exception : " + error);
   System.out.println("********************************************************");
   
      }
 
 @Around("execution(* com.company.services.UsuarioService.procesarInformacion(..))")
    public void logAround(ProceedingJoinPoint joinPoint) throws Throwable {
  System.out.println("****************ADVISE Around ****************************");
  System.out.println("metodo logAround() is running!");
  System.out.println("metodo interceptado : " + joinPoint.getSignature().getName());
  //System.out.println("parametros de entrada : " + Arrays.toString(joinPoint.getArgs()));
  
  System.out.println("Ejecucion antes del procesamiento del metodo!");
  joinPoint.proceed(); //continue on the intercepted method
  System.out.println("Ejecucion luego del procesamiento del metodo!");
  
  System.out.println("***********************************************************");
  
    }
}


la primera observación que podemos realizar es la forma en que definimos un aspecto, o sea en primera instancia generamos una clase que contendrá las implementaciones y agregamos la notación @Aspect.

Lo siguiente en lo que quiero detenerme es como se define la condición para la que se ejecuta un advise que lo llamamos pointcut.

Para definir pointcuts en Spring AOP utilizaremos la notación de AspectJ y seguirán el siguiente patrón: execution() Para que un método sea “interceptado” por un aspecto, deberá cumplir el patrón que indiquemos. Además, podemos componer pointcuts utilizando && (and), || (or) y ! (not).

[Modificadores] TipoRetorno [Clase] NombreMétodo ([Parámetros]) [throws TipoExcepción]

pero miremos un ejemplo de patron y lo analicemos

execution(* com.company.services.UsuarioService.consultaUsuario(..))


  1. * cualquier tipo de retorno
  2. com.company.services.UsuarioService  paquete e interfaz que contiene el metodo a interceptar.
  3. consultaUsuario metodo que buscaremos interceptar 
  4.  .. aceptando 0 a varios parametros 


Hay que recordar que los métodos que vayan a ser seleccionados por un pointcunt deben ser visibles (públicos).


  • execution(void send*(String)): cualquier método visible que comience por send, tome un String como único parámetro y cuyo tipo de retorno sea void.
  • execution(* send(*)): cualquier método visible llamado send que tome como parámetro un parámetro de cualquier tipo.
  • execution(* send(int, ..)): cualquier método visible llamado send que tome al menos un parámetro de tipo int. En este caso “..” indica 0 o más.
  • execution(void org.ejemplo.MessageServiceImpl.*(..)): cualquier método visible de la clase org.ejemplo.MessageServiceImpl que tenga como tipo de retorno void. 
  • execution(void org.ejemplo.MessageService+.send(*)): cualquier método visible con nombre send de las clases del tipo org.ejemplo.MesssageService, incluyendo hijos e implementaciones, que reciban un único parámetro de cualquier tipo y tengan void como tipo de retorno.
  • execution(@javax.annotation.security.PermitAll void send*(..)): cualquier método visible que comience por send y que esté anotado con la anotación @PermitAll.
  • execution(* org.ejemplo.*.impl.*.*(..)): cualquier método visible de cualquier clase de cualquier paquete impl situada dos escalones por debajo de org.ejemplo en la jerarquía de paquetes.
  • execution(* org.ejemplo..impl.*.*(..)): cualquier método visible de cualquier clase de cualquier paquete impl situada cualquier nivel por debajo por debajo de org.ejemplo en la jerarquía de paquetes. En este caso “..” indica que puede haber 0 o más directorios en la jerarquía de paquetes.
En fin podemos observar que podemos jugar mucho con la condición en la que se ejecutara un aspecto por lo que son infinitas las posibilidades para la utilización de interceptores.

Ahora si aplico la regla antes descrita vamos a entender como se implementan los advise de mi clase.

Join point  +  pointcuts + advise  = aspect


implementando advise @before

@Before("execution(* com.company.services.UsuarioService.consultaUsuario(..))")
 public void logBefore(JoinPoint joinPoint) {
  System.out.println("*********************ADVISE BEFORE*******************");
  System.out.println("metodo logBefore() esta corriendo!");
  System.out.println("metodo interceptado : " + joinPoint.getSignature().getName());
  System.out.println("*****************************************************");
 }

Nota: primero que utilizamos la notación @before, segundo que definimos nuestro pointcut que es la condición en la que ejecutaremos y por ultimo la implementacion del advise  que debería contar con la información del punto de ejecución de la aplicación para lo que pasamos un parametro de tipo joinpoint a la implementacion.

Si recordamos este advise se ejecuta antes del procesamiento del método interceptado, segun mi pointcut sera sobre el metodo consultarusuario. A nivel código no presenta grandes problemáticas dado que no es el objeto del tutorial, solo imprime por consola su paso por el metodo y un dato nos demuestra como acceder a estructuras de datos desde el parametro de tipo joinpoint que en este caso solo la utilizamos para obtener la informacion del metodo que interceptamos.

Implementando advise @After

@After("execution(* com.company.services.UsuarioService.consultaUsuario(..))")
 public void logAfter(JoinPoint joinPoint) {
  System.out.println("****************ADVISE AFTER*************************");
  System.out.println("metodo logAfter() esta corriendo!");
  System.out.println("metodo interceptado : " + joinPoint.getSignature().getName());
  System.out.println("******************************************************");
 
 }

Nota: Primero que utilizamos la notación @after, segundo definimos nuestro pointcut que nos indica que trabajara sobre el método consultaUsuario() y para finalizar la implementacion del advise. Recordemos que este advise es el que se ejecuta finalizado el procesamiento del metodo interceptado. Recibe un parámetro joinpoint  para obtener la información del punto de ejecución.

Implementando advise @AfterReturning


@AfterReturning(
        pointcut = "execution(* com.company.services.UsuarioService.agregarUsuario(..))",
        returning= "result")
     public void logAfterReturning(JoinPoint joinPoint, Object result) {
      System.out.println("****************ADVISE AFTERRETURN********************");
   System.out.println("metodo logAfterReturning() esta corriendo!");
   System.out.println("metodo interceptado : " + joinPoint.getSignature().getName());
   System.out.println("El metodo retorna el valor : " + result);
   System.out.println("******************************************************");
   
     }

Nota: Primero utilizamos la notacion @AfterReturning para definir el tipo del advise, pero una particularidad este advise se ejecuta luego del procesamiento del metodo interceptado y tiene la particularidad que puede trabajar con lo que retorna el metodo luego de su procesamiento por lo que este advise requiere aparte del pointcut indicar el retorno, por ello podremos notar que los parametros de entrada a la implementacion son el clasico joinpoint y el objeto de retorno con el cual poder trabajar.  Para este caso el metodo que vamos a interceptar es agregarusuario.

Implementando advise @AfterThrowing


@AfterThrowing(
        pointcut = "execution(* com.company.services.UsuarioService.ProbarThrowException(..))",
        throwing= "error")
      public void logAfterThrowing(JoinPoint joinPoint, Throwable error) {
      System.out.println("****************ADVISE AfterThrowing********************");
   System.out.println("metodo logAfterThrowing() esta corriendo!");
   System.out.println("metodo interceptado : " + joinPoint.getSignature().getName());
   System.out.println("Exception : " + error);
   System.out.println("********************************************************");
   
      }

Nota: Primero utilizamos la notación @AfterThrowing para definir el tipo de advise, recordemos que este permite capturar las excepciones que ocurran en un metodo y nosotros a nivel codigo en el metodo a interceptar estamos generando una excepcion por codigo para verificar el funcionamiento, la diferencia puntual que existe con el tradicion spring aop es que si recuerdan tenia que implementar para cada excepcion que deseara capturar el metodo con el parametro de entrada, en nuestro caso con aspectj ya no es necesario , sea cual sea la excepcion que se dispare entrare a este metodo interceptor, ahora dentro del mismo podre identificar la tipologia trabajando con el parametro de entrada error que me incluye la implementacion.

Implementando advise @Around


@Around("execution(* com.company.services.UsuarioService.procesarInformacion(..))")
    public void logAround(ProceedingJoinPoint joinPoint) throws Throwable {
  System.out.println("****************ADVISE Around ****************************");
  System.out.println("metodo logAround() is running!");
  System.out.println("metodo interceptado : " + joinPoint.getSignature().getName());
  //System.out.println("parametros de entrada : " + Arrays.toString(joinPoint.getArgs()));
  
  System.out.println("Ejecucion antes del procesamiento del metodo!");
  joinPoint.proceed(); //continue on the intercepted method
  System.out.println("Ejecucion luego del procesamiento del metodo!");
  
  System.out.println("***********************************************************");
  
    }

Nota: sobre este no vamos a hablar mucho solo que se ejecuta sobre el metodo procesar información donde simulamos tiempo de procesamiento con hilos buscando mostrar que trabajamos antes del procesamiento y después del procesamiento del metodo interceptado y nos posibilita decidir en base al escenario si ejecutamos el metodo o no.

el resultado de la ejecución posibilita de una manera mas visual el orden de ejecución de métodos e interceptores.




Para finalizar quiero recalcar que existe una manera muy comoda de trabajar con el archivo spring bean configuration con aspectj que pueden buscarla en internet como otra alternativa de trabajo. Recomiendo este articulo que muestra un paralelismo sobre como hacerlo con notaciones y mediante archivo.

El codigo fuente del proyecto lo puedes descargar aqui codigofuente.zip

No hay comentarios:

Publicar un comentario