sábado, 28 de septiembre de 2013

Spring: AOP Programación Orientada a Aspectos

Recorriendo Spring
Es un paradigma de programación relativamente reciente cuya intención es permitir una adecuada modularización de las aplicaciones y posibilitar una mejor separación de incumbencias cuyo  principal objetivo es la separación de las funcionalidades dentro del sistema:
  • Por un lado funcionalidades comunes utilizadas a lo largo de la aplicación.
  • Por otro lado, las funcionalidades propias de cada módulo.
Nota: AOP busca poder tratar de extraer como módulos aquel código que resuelve problemas que son transversales a los componentes de una aplicación.

Whats ¿?

Son funcionalidades genéricas que se utilizan en muchos puntos diferentes de nuestra aplicación. Algunas de las más comunes son: logs, transacciones, seguridad, cachés, manejo de errores, monitorización
Si amigo se trata de las responsabilidades de un componente, un objeto de una clase servicio debería preocuparse de sus responsabilidades principales, pero sin embargo se encuentra lleno de código que excede su responsabilidad, generando código repetido y ensuciando los métodos por la mezcla.


Caso de Análisis 1: Imaginemos una implementación de servicio donde se requiere que usuario tenga un determinado rol para realizar todas las operaciones que puede exponer.  incluiría una verificación de permisos antes de ejecutar los métodos expuesto en la service layer.

public class PatientServiceImpl implements PatientService{
...

 public Patient findById(Integer patientId) {
   if(!hasPermission(SecurityContext.getPrincipal())) {
     throw new AccessDeniedException();
   }
   return patientDao.findById(patientId);
 }
 public List findByRoom(Integer roomId) {
   if(!hasPermission(SecurityContext.getPrincipal())) {
     throw new AccessDeniedException();
   }
   return patientDao.findByRoom(roomId);
 }

}

Este código tiene dos problemas:
  • ·         Mezcla y acopla conceptos que son diferentes: los métodos findById y findByRoom deben preocuparse de encontrar los datos, no de gestionar la seguridad. Esto es lo que conocemos como “code tangling” (enredo de código).
  • ·         La solución a un mismo problema aparece repetida varias veces en diferentes partes de la aplicación: el código que comprueba el rol de usuario está repetido en diferentes puntos. Esto es lo que conocemos como “code scattering” (dispersión de código).

Nota: En una aplicación pequeña puede que esto no suponga un gran problema pero a medida que nuestra aplicación crece, es muy costoso mantener código disperso cuya funcionalidad, además, está entremezclada con otras.


Caso de Análisis 2:  Uno de los ejemplos más famosos para ver el funcionamiento de los aspectos es el de un Logger. El Logger lo implementamos como una clase de nuestra aplicación cuya función suele ser narrar y guardar los eventos, excepciones que suceden en las demás clases.

Clase Persona.java

package info.hcosta.poa.ejemplo;

/**
 *
 * @author Administrador
 */
public class Persona implements IPersona {
     
    private Logger logger = new Logger();
    private String nombre;
     
    public void setNombre(String nombre){
        this.nombre = nombre;
    }
     
    public String getNombre(){
        return this.nombre;
    }
     
    public void saludar() {
        logger.antesSaludo(this);
        System.out.println( this.nombre +" dice: - Hola que tal?");
        logger.despuesSaludo(this);
    }

}

Nota: como podemos ver la clase persona utiliza una clase logger para registrar eventos antes y después de la ejecución del método de responsabilidad en si del componente.

Clase logger.java

package info.hcosta.poa.ejemplo;

import java.util.Calendar;
import java.util.GregorianCalendar;

/**
 *
 * @author Administrador
 */
public class Logger {
     
    private Calendar cal = new GregorianCalendar();
     
    public void antesSaludo(Persona persona) {
        System.out.println(cal.getTime() + " " + persona.getNombre() + " va a saludar.");
    }
     
    public void despuesSaludo(Persona persona) {
        System.out.println(cal.getTime() + " " + persona.getNombre() + " ha saludado.");
    }
     
}

En definitiva lo que estamos haciendo es hacer la clase Logger dependiente de Persona. Imagina que tenemos una aplicación con 50 clases y todas necesitan un Logger para apuntar un evento. En el peor de los casos instanciaríamos el Logger 50 veces y llamaríamos las funciones del log desde el propio flujo de los métodos de las clases. Sería un gran trabajo y además estaríamos repitiendo mucho código.

AOP es una solución muy elegante para eliminar estos problemas en  tres pasos:
  • ·         Implementa la lógica de negocio de tu aplicación.
  • ·         Implementa aspectos que resuelvan los problemas transversales a tu aplicación.
  • ·         Enlaza estos aspectos en los puntos en los que sean necesarios.
Ahora para entender esta solución mágica es necesario conocer algunos conceptos utilizados por spring.

Aspect (Aspecto) es una funcionalidad transversal (cross-cutting) que se va a implementar de forma modular y separada del resto del sistema. El ejemplo más común y simple de un aspecto es el logging (registro de sucesos) dentro del sistema, ya que necesariamente afecta a todas las partes del sistema que generan un suceso.

Join point (Punto de Cruce o de Unión) es un punto de ejecución dentro del sistema donde un aspecto puede ser conectado, como una llamada a un método, el lanzamiento de una excepción o la modificación de un campo. El código del aspecto será insertado en el flujo de ejecución de la aplicación para añadir su funcionalidad.

Advice (Consejo) es la implementación del aspecto, es decir, contiene el código que implementa la nueva funcionalidad. Se insertan en la aplicación en los Puntos de Cruce.

Quiero dejar claro que en  Spring hay varios tipos de Advices, y según cuál de ellos queramos implementar hay que implementar una u otra interface :
  • Before : Es fácil de intuir que este advice se ejecutará   siempre antes de la llamada a un método.
  • After Returning :  Este advice se ejecuta después de la llamada a un método.
  • Around : Cuando  implementamos un advice de este tipo podremos realizar acciones antes y después de invocar el método interceptado.
  • Throws : Se ejecuta cuando  ocurre una excepción.

Pointcut (Puntos de Corte) define los Consejos que se aplicarán a cada Punto de Cruce. Se especifica mediante Expresiones Regulares o mediante patrones de nombres (de clases, métodos o campos), e incluso dinámicamente en tiempo de ejecución según el valor de ciertos parámetros.
El objetivo sera que mediante ejemplos prácticos pueda abordar las diferentes opciones que brinda spring para trabajar en AOP.
  • Mediante el Spring Bean configuration
  • Mediante el Spring Bean Configuration con Aspctj.
  • Mediante notaciones con Aspectj.

Trabajando los advise

En primera instancia la idea es comenzar con los Advise, por lo que trabajaremos en un ejemplo practico basado en la simulación de un crud (create read update delete) de una clase usuario, no vamos a desarrollar grandes código dado que el objeto de estudio es AOP.


Nota: En lo personal me parece mas aceptable la utilización de anotaciones, pero es cierto que utilizando aop nativo por archivo de configuración te permite entender los conceptos de una manera mas clara lo que posibilitara que la curba de aprendizaje de anotaciones se corta.

Como en todo nuestros ejemplos vamos a trabajar con  proyectos simples en Maven. 


Generamos nuestro proyecto de tipo Maven



Elegimos la tipologia mas simple que nos generara una estructura de proyecto general.



Proporcionamos información del proyecto.



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

Nota: para este ejemplo con solo incluir la primera spring-aop alcanza, pero incluyo las otras dos por que la utilizaremos en la utilización  de aspectj en futuros ejemplos.

la estructura inicial con la que vamos a trabajar es la siguiente:





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,

src/main/resources/usuarioe.xml:  de manera inicial el unico bean sera el asociado a la clase que implementa las funcionalidades UsuarioServiceImpl.java


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

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

por ultimo nuestro archivo de test que consume por supuesto los beans y que ejecuta los metodos 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.

el resultado final sera por supuesto los mensajes que arroja actualmente cada uno de estos métodos y un error generado desde uno de los métodos a propósito.



















El código del proyecto hasta aquí lo podemos descargar sin problema codigofuente.zip

Ahora lo que les pido es que imaginemos que tenemos grandes implementaciones y una capa data acces layer que consume datos de una base de datos como Oracle o MYSQL que definen un proyecto de gran envergadura. 

Problemática 1: Imaginemos que surge la necesidad de realizar acciones antes de la llamada a un método de una clase, y de acuerdo a los datos de ingresos necesitamos realizar variaciones sobre los mismos o quizás registrar los mismos,  no imagínate que lo necesitas hacer sobre muchos métodos de una clase. De seguro viene un programador piola y te dice no hay problema agregamos código en la capa de servicio del componente y solucionado.

Overtime bienvenido…. En ese momento se le subió el colesterol al equipo, los programadores comienzan a llamar por teléfono avisando que llegan tarde o que no llegan.

Ahora el tema es que vamos a tener que tocar muchos métodos, muchas horas de desarrollo, horas de test y estamos dejando de lado que la responsabilidad del componente no es esa.

Para esto AOP de proporciona una solución con la utilización de un advise before, que posibilita interceptar a un método antes de su procesamiento, posibilitando manipular los datos de ingresos.

Que Gano ¿?  Y primero el poder realizar las acciones que crea conveniente antes del procesamiento como lo necesita mi necesidad, segundo no tener que modificar mi capa de servicio que ya fue recontra testeada, solo me centrare en testear el código de mi intercepsión o acciones que quiera realizar, por ultimo no le daré responsabilidades que no corresponden al método.

Mama o señora esposa gracias a spring AOP voy a llegar para la comida.

Primero vamos a definir el codigo que aplicaremos realizada la intercepción y esto lo haremos creando una clase que implemente MethodBeforeAdvice y trabajando sobre el método before.

Com.company.aop. EventoBeforeAdvice.java

package com.company.aop;

import java.lang.reflect.Method;

import org.springframework.aop.MethodBeforeAdvice;

import com.mycompany.modelo.Usuario;

public class EventoBeforeAdvice implements MethodBeforeAdvice {

 public void before(Method arg0, Object[] arg1, Object arg2)
   throws Throwable {
  System.out.println( "***************** AOP BEFORE ADVICE *********************" );
        Usuario usuario = (Usuario)arg1[0];
        usuario.setNombre( usuario.getNombre() + " MODIFICADO POR AOP" );
        System.out.println( "AOP BEFORE ADVICE : Accediendo al Usuario " + usuario.getNombre() );
        System.out.println( "**********************************************************" );
 }

}


Nota: podemos observar que trabajamos sobre el método before que recibe el método y los parámetros con los cuales podemos trabajar, en el ejemplo realizo una modificación al input que por supuesto es lo que llegara luego al método interceptado antes de su procesamiento.

Ahora todo bien, pero si no le indico a mi spring bean configuration usuarioe.xml de nuestro proyecto como aplicar mi interceptor no vamos a llegar a ningún lado, por eso realizamos cambios sobre el archivo mencionado donde queda asi.

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"
 xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
   <!-- Definicion la implementacion de la interfaz con el metodo a interceptar--> 
   <bean id = "usuarioServiceTarget" class = "com.company.services.UsuarioServiceImpl"/>
   <!-- Definiciono del bean de la implementacion del advise a aplicar--> 
   <bean id = "eventoBeforeAdvice" class = "com.company.aop.EventoBeforeAdvice"/>
   <!-- definimos mi ProxyFactoryBean: Es la clase de Spring Framework para crear proxys dinámicos, al estilo 
   Spring. Deja un bean configurado dentro del contexto, listo para ser usado.  -->
   <bean id = "usuarioService" class = "org.springframework.aop.framework.ProxyFactoryBean">    
        <!-- Definicion la interfaz que tiene el metodo a interceptar-->   
        <property name = "proxyInterfaces">
            <value>com.company.services.UsuarioService</value>
        </property>
        <!--  defino el bean interceptor -->
        <property name = "interceptorNames">
            <list>
                <value>eventoBeforeAdvice</value>
            </list>
        </property>
        <!-- Referencia a un Bean que implementa los metodos a los que vamos aplicar el advice-->
        <property name = "target">
            <ref bean = "usuarioServiceTarget"/>
        </property>
    </bean>
</beans>

Lo primero que hacemos es definir los bean con los que vamos a trabajar para tal propósito.

Primero defino por supuesto el bean que hace referencia a la clase con la implementación del o los métodos a interceptar.

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

Lo siguiente sera definir el bean para  codigo que ejecutaremos cuando realicemos la intercepsion.


<!-- Definiciono del bean de la implementacion del advise a aplicar--> 
   <bean id = "eventoBeforeAdvice" class = "com.company.aop.EventoBeforeAdvice"/>

Por ultima defino el bean que la tiene clara y sabe lo que tiene que hacer.

<!-- definimos mi ProxyFactoryBean: Es la clase de Spring Framework para crear proxys dinámicos, al estilo 
   Spring. Deja un bean configurado dentro del contexto, listo para ser usado.  -->
   <bean id = "usuarioService" class = "org.springframework.aop.framework.ProxyFactoryBean">    
        <!-- Definicion la interfaz que tiene el metodo a interceptar-->   
        <property name = "proxyInterfaces">
            <value>com.company.services.UsuarioService</value>
        </property>
        <!--  defino el bean interceptor -->
        <property name = "interceptorNames">
            <list>
                <value>eventoBeforeAdvice</value>
            </list>
        </property>
        <!-- Referencia a un Bean que implementa los metodos a los que vamos aplicar el advice-->
        <property name = "target">
            <ref bean = "usuarioServiceTarget"/>
        </property>
    </bean>

Nota: tiene que quedar claro que este bean hace referencia a  proxyfactorybean que deja un bean configurado en el contexto listo para ser usado, donde definimos la interfaz de la clase con los métodos a intercetpar , segundo la lista de interceptores  que aplicaremos y por ultimo el bean que contiene la implementación de los métodos a interceptar. Note que cambie el nombre del bean de la clase que implementa los metodos, solo lo realice para que no toquemos nada en nuestra clase test.

Pero analicemos la corrida de estos cambios, que groso es verdad se esta ejecutando el código que puse en mi clase interceptora antes de la llamada de cada uno de los metodos de la clase implementada que mencione y por supuesto puedo ver como va cambiando el input ante de la llamada a los métodos..


Nota: lo mas loco es que intercepte con código los métodos de la clase y pude realizarlo de manera externa. 

Puede descargar el proyecto hasta donde estamos codigofuente.zip

Cambio de alcance: Pero la realidad es que esto no estaría bueno si no me proporcionara cierta flexibilidad en poder determinar que métodos quiero interceptar es decir un filtro para los advice y aquí es donde entra el concepto de point cut. Se necesita interceptar solo los métodos que tengan en su nombre consulta.

Para ello nuestro usuarioe.xml quedaría asi.

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"
 xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">

  <!-- Definicion la implementacion de la interfaz con el metodo a interceptar--> 
   <bean id = "usuarioServiceTarget" class = "com.company.services.UsuarioServiceImpl"/>
   <!-- Definiciono del bean de la implementacion del advise a aplicar--> 
   <bean id = "eventoBeforeAdvice" class = "com.company.aop.EventoBeforeAdvice"/>
   
   
   <!-- definir el patron para los metodos que seran interceptados por los advice -->
   <bean id = "consultaPointCut" class="org.springframework.aop.support.RegexpMethodPointcutAdvisor">
       <!-- dentro de los patrones puedo definir  -->
        <property name = "pattern">
            <value>.*consulta.*</value>
        </property>        
        <property name = "advice">
            <ref bean = "eventoBeforeAdvice"/>
        </property>
  </bean>
  
   <!-- definimos mi ProxyFactoryBean: Es la clase de Spring Framework para crear proxys dinámicos, al estilo 
   Spring. Deja un bean configurado dentro del contexto, listo para ser usado.  -->
   <bean id = "usuarioService" class = "org.springframework.aop.framework.ProxyFactoryBean">    
        <!-- Definicion la interfaz que tiene el metodo a interceptar-->   
        <property name = "proxyInterfaces">
            <value>com.company.services.UsuarioService</value>
        </property>
        <!--     defino el bean interceptor -->
        <property name = "interceptorNames">
            <list>
                <value>consultaPointCut</value>
            </list>
        </property>
        <!-- Referencia a un Bean que implementa los metodos a los que vamos aplicar el advice-->
        <property name = "target">
            <ref bean = "usuarioServiceTarget"/>
        </property>
    </bean>
    
</beans>

Ahora en nuestro archivo, lo primero que hacemos es definir nuestro bean para la clase UsuarioServiceImpl.java y por supuesto para nuestro interceptor.

<!-- Definicion la implementacion de la interfaz con el metodo a interceptar--> 
   <bean id = "usuarioServiceTarget" class = "com.company.services.UsuarioServiceImpl"/>
   <!-- Definiciono del bean de la implementacion del advise a aplicar--> 
   <bean id = "eventoBeforeAdvice" class = "com.company.aop.EventoBeforeAdvice"/>


El siguiente paso sera definir el filtro o pointcut que define el patron para metodos que se aplicara el advice, y por supuesto define el advise.

<!-- definir el patron para los metodos que seran interceptados por los advice -->
   <bean id = "consultaPointCut" class="org.springframework.aop.support.RegexpMethodPointcutAdvisor">
       <!-- dentro de los patrones puedo definir  -->
        <property name = "pattern">
            <value>.*consulta.*</value>
        </property>        
        <property name = "advice">
            <ref bean = "eventoBeforeAdvice"/>
        </property>
  </bean>


Por ultimo la definición del aspecto por medio de ProxyFactoryBean deja listo el bean en el contexto para ser utilizado por la aplicación.

<!-- definimos mi ProxyFactoryBean: Es la clase de Spring Framework para crear proxys dinámicos, al estilo 
   Spring. Deja un bean configurado dentro del contexto, listo para ser usado.  -->
   <bean id = "usuarioService" class = "org.springframework.aop.framework.ProxyFactoryBean">    
        <!-- Definicion la interfaz que tiene el metodo a interceptar-->   
        <property name = "proxyInterfaces">
            <value>com.company.services.UsuarioService</value>
        </property>
        <!--     defino el bean interceptor -->
        <property name = "interceptorNames">
            <list>
                <value>consultaPointCut</value>
            </list>
        </property>
        <!-- Referencia a un Bean que implementa los metodos a los que vamos aplicar el advice-->
        <property name = "target">
            <ref bean = "usuarioServiceTarget"/>
        </property>
    </bean>

El resultado final será que interceptamos con el advice solo las llamadas de los métodos con nombre que contenga el string “consulta”.


Nota final: alcanzamos el objetivo de interceptar los métodos sin tocar el código de las clases del negocio sin ensuciar nuestro código,

El código del proyecto al estado actual se puede realizar desde aqui codigofuente.zip


Problemática 2: utilizaremos el ejemplo anterior para mostrar el funcionamiento de 2 advise disponibles con spring.

El siguiente paso sera abordar los siguientes advice:
  • After Returning :  Es decir interceptar a un método luego de su ejecución y trabajar por supuesto con su output. Para nuestro ejemplo la condición para el advice estará sobre los métodos que contengan en su nombre el string "Actualizar", lo que en nuestro ejemplo significara interceptar la llamada al método Actualizar.
  • Around : Con el buscaremos interceptar un metodo, realizando acciones antes y después de su procesamiento.  Para nuestro ejemplo trabajaremos sobre la llamada al metodo ProcesarInformacion, donde mediante hilos buscamos simular procesamiento para poder observar el comportamiento.
ahora la primera instancia por supuesto sera trabajar sobre la implementacion de los interceptores:

com.company.aop.EventoAfterAdvice.java:  sera la clase que contiene el codigo de la implementacion par advise After Returning que implementa la interfaz AfterReturningAdvice y que nos proporciona el metodo afterReturning el cual claramente nos brindara como parametros de entrada el retorno que realiza el metodo interceptado.

package com.company.aop;

import java.lang.reflect.Method;

import org.springframework.aop.AfterReturningAdvice;

public class EventoAfterAdvice implements AfterReturningAdvice {

 public void afterReturning(Object arg0, Method arg1, Object[] arg2,
   Object arg3) throws Throwable {
  System.out.println( "***************** AOP AFTER ADVICE *********************" );
        int records = ((Integer)arg0).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" );
        }

 }

}

Nota: en nuestro ejemplo el método ActualizarUsuario retorna un valor escalar siempre, por lo que mi implementacion tendra como parametro de salida un valor entero. Podemos observar que a nivel codigo no realiza grandes cosas dado que solo intentamos demostrar la flexibilidad para trabajar en base a los datos de salida del metodo interceptado, es decir imprimimos por consola uno o otro mensaje dependiendo del valor de salida. 

com.company.aop.EventoMethodInterceptorAdvice.java:  sera la clase que contiene el codigo de la implementacion para el advise Around. Imagina la posibilidad de agregar una condicion de validacion para la ejecucion de un metodo en base a los valores de ingreso al metodo totalmente desacoplado de responsabilidades para el componente del negocio, este es el caso dado que esta clase implementa la interfaz MethodInterceptor que nos proporciona el metodo invoke donde podemos trabar antes de la ejecucion del metodo, decidir si vamos a ejecutar el metodo , y trabajar posterior a la ejecucion del mismo, pero me diran sos un piola "solo veo como parámetro de entrada el metodo a interceptar", pero esto por que están perdiendo visibilidad, por que los advice se utilizan en conjunto, podriamos con el advice before tener los datos de ingreso y con el after a los datos de salida.

package com.company.aop;

import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;

public class EventoMethodInterceptorAdvice implements MethodInterceptor {

 public Object invoke(MethodInvocation arg0) throws Throwable {
  // TODO Auto-generated method stub
  System.out.println( "****** INICIO AOP METHOD INTERCEPTOR ADVICE **********" );

        long tiempoInicial = new java.util.Date().getTime();
        Object objeto = arg0.proceed();
        long tiempoEjecucion = ( new java.util.Date().getTime() - tiempoInicial ) / 1000;

        System.out.println( "Nombre del Método : " + arg0.getMethod() );
        System.out.println( "Tiempo de proceso del método ( segundos ) : " + tiempoEjecucion );
        System.out.println( "******* FIN AOP METHOD INTERCEPTOR ADVICE **********" );

        return objeto;
 }

}


Nota: en nuestro ejemplo no hacemos nada loco, solo una demostración de que trabajamos antes y después de la ejecución del método, imprimimos mensajes en consola y calculamos tiempos para demostrar que trabajamos ejecutado el metodo.

ahora nuestro archivo de usuarioe.xml quedaría de la siguiente forma luego de las modificaciones necesaria.  
  
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"
 xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">

  <!-- Definicion la implementacion de la interfaz con el metodo a interceptar--> 
   <bean id = "usuarioServiceTarget" class = "com.company.services.UsuarioServiceImpl"/>
   <!-- Definiciono del bean de la implementacion del advise a aplicar--> 
   <bean id = "eventoBeforeAdvice" class = "com.company.aop.EventoBeforeAdvice"/>
   <!-- definimos el bean de la implementacion del advice after -->
   <bean id = "eventoAfterAdvice" class = "com.company.aop.EventoAfterAdvice"/>
   <!-- definimos el bean de la implementacion del advice around -->
   <bean id = "eventoMethodInterceptorAdvice" class = "com.company.aop.EventoMethodInterceptorAdvice"/>
   
   
   <!-- definir el patron para los metodos que seran interceptados por los advice -->
   <bean id = "consultaPointCut" class="org.springframework.aop.support.RegexpMethodPointcutAdvisor">
       <!-- dentro de los patrones puedo definir  -->
        <property name = "pattern">
            <value>.*consulta.*</value>
        </property>        
        <property name = "advice">
            <ref bean = "eventoBeforeAdvice"/>
        </property>
  </bean>
  
  <bean id = "actualizaPointCut" class="org.springframework.aop.support.RegexpMethodPointcutAdvisor">
        <property name = "pattern">
            <value>.*actualizar.*</value>
        </property>
        <property name = "advice">
            <ref bean = "eventoAfterAdvice"/>
        </property>
 </bean>
 
     <bean id = "procesaPointCut" class="org.springframework.aop.support.RegexpMethodPointcutAdvisor">
        <property name = "pattern">
            <value>.*procesar.*</value>
        </property>
        <property name = "advice">
            <ref bean = "eventoMethodInterceptorAdvice"/>
        </property>
    </bean>
    
   
   
 
  
   <!-- definimos mi ProxyFactoryBean: Es la clase de Spring Framework para crear proxys dinámicos, al estilo 
   Spring. Deja un bean configurado dentro del contexto, listo para ser usado.  -->
   <bean id = "usuarioService" class = "org.springframework.aop.framework.ProxyFactoryBean">    
        <!-- Definicion la interfaz que tiene el metodo a interceptar-->   
        <property name = "proxyInterfaces">
            <value>com.company.services.UsuarioService</value>
        </property>
        <!--     defino el bean interceptor -->
        <property name = "interceptorNames">
            <list>
                <value>consultaPointCut</value>
                <value>actualizaPointCut</value>
                <value>procesaPointCut</value>
            </list>
        </property>
        <!-- Referencia a un Bean que implementa los metodos a los que vamos aplicar el advice-->
        <property name = "target">
            <ref bean = "usuarioServiceTarget"/>
        </property>
    </bean>
    
</beans>



En primera instancia es necesario declarar los beans correspondiente a la implementacion de los advice.


<!-- definimos el bean de la implementacion del advice after -->
   <bean id = "eventoAfterAdvice" class = "com.company.aop.EventoAfterAdvice"/>
   <!-- definimos el bean de la implementacion del advice around -->
   <bean id = "eventoMethodInterceptorAdvice" class = "com.company.aop.EventoMethodInterceptorAdvice"/>

La segunda parte sera la definición de la condicion para que se disparen los interceptores y en nuestro caso tendremos 2.

actualizaPointCut es el pointcut que hace referencia al patron que indica una condicion sobre el metodo actualizar y que por supuesto hace referencia al advice after.


<bean id = "actualizaPointCut" class="org.springframework.aop.support.RegexpMethodPointcutAdvisor">
        <property name = "pattern">
            <value>.*actualizar.*</value>
        </property>
        <property name = "advice">
            <ref bean = "eventoAfterAdvice"/>
        </property>
 </bean>

procesaPointCut es el pointcut que hace referencia al patron que indica una condicion sobre el metodo procesar y que por supuesto hace referencia al advice around.


     <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 supuesto deberemos modificar la definicion del aspecto generado por ProxyFactoryBean que tiene el bean listo y cargado en el contexto, donde podemos notar claramente que agregamos a la lista de interceptores los 2 nuevos pointcut.


   <!-- definimos mi ProxyFactoryBean: Es la clase de Spring Framework para crear proxys dinámicos, al estilo 
   Spring. Deja un bean configurado dentro del contexto, listo para ser usado.  -->
   <bean id = "usuarioService" class = "org.springframework.aop.framework.ProxyFactoryBean">    
        <!-- Definicion la interfaz que tiene el metodo a interceptar-->   
        <property name = "proxyInterfaces">
            <value>com.company.services.UsuarioService</value>
        </property>
        <!--     defino el bean interceptor -->
        <property name = "interceptorNames">
            <list>
                <value>consultaPointCut</value>
                <value>actualizaPointCut</value>
                <value>procesaPointCut</value>
            </list>
        </property>
        <!-- Referencia a un Bean que implementa los metodos a los que vamos aplicar el advice-->
        <property name = "target">
            <ref bean = "usuarioServiceTarget"/>
        </property>
    </bean>

El resultado se reduce a la demostración de como interceptamos con estos advice la ejecución de los métodos que indicamos con anterioridad.




Nota: como siempre para el que se cuelga de la programación en este momento, jajaja. la excepcion que se dispara esta generada de manera intencional desde el principio dado que la utilizaremos en el siguiente paso.

Pero en cosas a notar debemos observar que interceptamos el método actualizar luego de su ejecución y que interceptamos el método procesar antes de su ejecución y trabajamos luego de su procesamiento, con lo que el objetivo de la demostración llego a su fin.

El código del proyecto hasta el estado actual se puede descargar codigofuente.zip.

Nota Final: dado que no estaría bueno no hablar del advice Throws teniendo en cuenta que trabajamos con código que al final siempre me dio una exeption generada por nuestro a codigo  con este fin es que vamos a tocar un poco el tema.

En primera instancia como los otros debemos generar una implementacion para este interceptor que implementa la interfaz ThrowsAdvice, que nos proporciona un método con el que podemos trabajar afterThrowing, que no brinda la posibilidad de poder trabajar generada una exception desde un metodo que le indicaremos por supuesto desde usuarioe.xml.

com.company.oap.EventoThrowsAdvice.java: la clase a nivel código no tiene gran complicación solo la impresión de un mensaje en consola para demostrar que pasamos por el código realizada la intercepcion. Pero prestar atención en el input del método recibe una excepción, por que dependiendo de la excepción que genere desde mi codigo entrara por aqui, por lo que necesito sobrecargar este metodo para todas las excepciones en las que quiera trabajar en acción.


package com.company.aop;

import org.springframework.aop.ThrowsAdvice;

public class EventoThrowsAdvice implements ThrowsAdvice {
 
 public void afterThrowing(Exception e) throws Throwable {
  System.out.println("Exception capturada : Throw exception capturada y lista para procesamiento!");
 }
 
}


En el unico metodo  ProbarThrowException()  generamos una excepción genérica de tipo exception.


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

podemos observar que en la clase antes mencionada la captura por tiene una correspondencia en los tipos de la excepción de input del metodo afterThrowing. si mi ejemplo generara una exception de tipologia asociado a base de datos por ejemplo, de seguro no interceptaria la misma dado que el advice no lo esta soportando.

Nota: sobre su configuración en el archivo mencionado no hablaremos dado que no presenta a esta altura grandes desafios.

defino el bean para la implementacion del advice

<!-- definimos el bean de la implementacion del advice Throws -->

   <bean id="EventoThrowsAdvice" class="com.company.aop.EventoThrowsAdvice" />

defino el pointcut donde indicare por ejemplo que se dispare en todas las excepciones que se generen para la clase de implementacion.

<!-- definir el patron para los metodos que seran interceptados por los advice -->
   <bean id = "ExepcionPointCut" class="org.springframework.aop.support.RegexpMethodPointcutAdvisor">
       <!-- dentro de los patrones puedo definir  -->
        <property name = "pattern">
            <value>.*</value>
        </property>        
        <property name = "advice">
            <ref bean = "EventoThrowsAdvice"/>
        </property>

  </bean>

y por ultimo agregare el pointcut al proxy dinámico que tiene la información de  la interfaz , la lista de interceptores y por supuesto la referencia del bean que hace referencia a la implementacion de la interfaz.

<!-- definimos mi ProxyFactoryBean: Es la clase de Spring Framework para crear proxys dinámicos, al estilo
   Spring. Deja un bean configurado dentro del contexto, listo para ser usado.  -->
   <bean id = "usuarioService" class = "org.springframework.aop.framework.ProxyFactoryBean">  
        <!-- Definicion la interfaz que tiene el metodo a interceptar-->
        <property name = "proxyInterfaces">
            <value>com.company.services.UsuarioService</value>
        </property>
        <!--     defino el bean interceptor -->
        <property name = "interceptorNames">
            <list>
                <value>consultaPointCut</value>
                <value>actualizaPointCut</value>
                <value>procesaPointCut</value>
                <value>ExepcionPointCut</value>
            </list>
        </property>
        <!-- Referencia a un Bean que implementa los metodos a los que vamos aplicar el advice-->
        <property name = "target">
            <ref bean = "usuarioServiceTarget"/>
        </property>
    </bean>

con todo esto el resultado final de la ejecucion demostraría nuestra intercepcion a la excepcion generada por codigo demostrando el paso por el código de la implementacion del advice.




El código final del proyecto se puede descargar desde aqui. codigofuente.zip



No hay comentarios:

Publicar un comentario