%@ page language="java" contentType="text/html; charset=ISO-8859-1" pageEncoding="ISO-8859-1"%> <%@ include file="/WEB-INF/jsp/include.jsp" %>
La Programación Orientada a Aspectos, Aspect-Oriented Programming (AOP), es un nuevo modelo de entender la programación, tanto su estructura como el flujo de control. Se centra en aspectos, entendiendo por tal un servicio que es transversal, es decir, que es utilizado en gran cantidad de clases de nuestra aplicación. En inglés se dice que un aspecto es un asunto de corte transversal o "cruzado" (crosscutting concern). Ejemplos: uso de log, gestión de transacciones, etc.
El contenedor IoC de Spring no depende de la implementación AOP, por tanto el uso de AOP no es obligado.
Las capacidades AOP que vamos a utilizar están cubiertas por la librería spring.jar.
Supongamos una empresa que quiere gestionar la asignación de recursos (por ejemplo paquetes) a otros recursos (por ejemplo camiones). Tenemos el interfaz Recurso, implementado por RecursoGeneral. Las ordenes de asignar o liberar recursos llegan a GestorRecursos, que implementa el interface Gestor. Con esta situación podemos hacer algo como:
Gestor gestor = new GestorRecursos(); // Creamos gestor
//// Creamos recursos
Recurso camion = new RecursoGeneral();
camion.setIdentificador("Camión 892");
Recurso paq = new RecursoGeneral();
paq.setIdentificador("Paquete x09");
//// El gestor asigna y libera recursos
gestor.asignar( paq, camion);
gestor.liberar( paq, camion);
Las clases involucradas en el problema son:
public interface Recurso {
public void setIdentificador( String identificador );
public String getIdentificador();
public String toString();
}
public class RecursoGeneral implements Recurso {
private String identificador;
public void setIdentificador( String identificador ) {
this.identificador = identificador;
}
public String getIdentificador() {
return identificador;
}
public String toString() {
return getIdentificador();
}
}
public interface Gestor {
public boolean asignar( Recurso herramienta, Recurso receptor);
public boolean liberar( Recurso herramienta, Recurso receptor);
}
public class GestorRecursos implements Gestor {
public boolean asignar( Recurso herramienta, Recurso receptor) {
System.out.println( "Estamos en " + this.getClass().getName() +
" y asigno " + herramienta.toString() + " a " + receptor.toString());
return true;
}
public boolean liberar( Recurso herramienta, Recurso receptor) {
System.out.println( "Estamos en " + this.getClass().getName() +
" y libero " + herramienta.toString() + " de " + receptor.toString());
return true;
}
}
Se puede ver que el ejemplo es desde el punto de vista de implementación muy sencillo.
Supongamos que queremos añadir un aspecto (cross-cutting concern): un control de operaciones: de tal forma que cada vez que se asigna, libera o da de baja un recurso, se dispare una llamada al Control de Operaciones (ControlOperaciones).
Algunas consideraciones:
Una funcionalidad que aparezca en numerosos lugares de nuestro código y no sea añadida de una forma fácil utilizando herencia o el patrón Observador, puede ser denominado aspecto o cross-cutting concern. De esta forma añadiremos una nueva funcionalidad sin tener que tocar los objetos de negocio.
En Spring AOP la forma de implementar un aspecto es crear un Advice.
Una forma de implementar un aspecto en AOP es crear un Advice, que se dispara cada vez que hay una llamada al objeto destino (target object). En nuestro caso, después de cada llamada a GestorRecursos se invocará a ControlOperaciones.afterReturning(). ControlOperaciones implementa el interface de Spring AOP AfterReturningAdvice, cuyo único método es afterReturning(). El Advice se comporta "como un" interceptor posterior a la invocación al objeto destino. Definición Spring: un Advice es una acción que realiza un aspecto. Cuando hablamos de joint point nos referimos a la unión que se establece entre un punto de ejecución (un método) del objeto destino (por ejemplo, el método liberar()) y un Advice.
Necesitamos que después de disparar el método GestorRecursos.asignar() o GestorRecursos.liberar() se puedan hacer determinadas operaciones en ControlOperaciones.
ControlOperaciones se comporta como un Advice, concretamente como un AfterReturningAdvice.
import java.lang.reflect.Method;
import org.springframework.aop.AfterReturningAdvice;
public class ControlOperaciones implements AfterReturningAdvice {
public void afterReturning(Object returnValue, Method method, Object[] args, Object target) throws Throwable {
...
}
}
Diagrama de secuencia:
El diagrama de secuencia muestra lo siguiente:
El primer objeto es el proxy, que hemos denominado proxyGestor. Una de sus propiedades es el target object, del tipo GestorRecursos, que es creado por Spring. Más adelante, cuando veamos el código fuente del cliente podremos observar que el cliente no instancia ni al proxy ni al target object.
Otra propiedad del proxy es el Advice (interceptorNames), que también es instanciado por Spring.
Existen dos tipos de proxy. En este ejemplo se usa el proxy Spring. Si queremos un proxy JDK, que utiliza java.lang.reflect.Proxy, debemos indicar la propiedad proxyInterfaces, que en nuestro ejemplo la hemos puesto en comentario.
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" "http://www.springframework.org/dtd/spring-beans.dtd">
<beans>
<!-- El proxy recibe llamadas que deriva al
"target object" GestorRecursos. El Advice es del tipo ControlOperaciones -->
<bean id="proxyGestor" class="org.springframework.aop.framework.ProxyFactoryBean">
<property name="target">
<bean class="org.ejemplo.GestorRecursos">
</bean>
</property>
<!-- Usa un Proxy JDK (por medio de java.lang.reflect.Proxy)
<property name="proxyInterfaces">
<list>
<value>org.ejemplo.Gestor</value>
</list>
</property>
-->
<property name="interceptorNames">
<list>
<idref bean="controlador" />
</list>
</property>
</bean>
<bean id="controlador" class="org.ejemplo.ControlOperaciones">
</bean>
</beans>
El proxy es instanciado por Spring y lo obtiene el cliente por medio de factory.getBean( "proxyGestor"). Es interesante observar que el proxy es referenciado usando el interface Gestor, que es el interface que implementa el objeto destino GestorRecursos. Las llamadas a asignar() y liberar() desencadenan las invocaciones al Advice (al terminar cada una de las llamadas, ya que estamos utilizando un afterReturningAdvice).
import org.springframework.beans.factory.xml.XmlBeanFactory;
import org.springframework.core.io.ClassPathResource;
public class Inicio {
public static void main(String[] args) {
try {
XmlBeanFactory factory = new XmlBeanFactory(new ClassPathResource("org/xml/application-context.xml"));
Gestor proxy = (Gestor) factory.getBean( "proxyGestor");
Recurso camion = new RecursoGeneral();
camion.setIdentificador("Camión 892");
Recurso paq = new RecursoGeneral();
paq.setIdentificador("Paquete x09");
proxy.asignar( paq, camion);
proxy.liberar( paq, camion);
}
catch (Exception e) {
e.printStackTrace();
}
}
}
El resto de clases del ejemplo siguen siendo válidas. Lo único que ha cambiado es la inclusión del Advice, que tiene una sencilla salida por pantalla. El método afterReturning recibe como argumentos:
import java.lang.reflect.Method;
import org.springframework.aop.AfterReturningAdvice;
public class ControlOperaciones implements AfterReturningAdvice {
public void afterReturning(Object returnValue, Method method, Object[] args, Object target) throws Throwable {
mostrar( "Estamos en " + this.getClass().getName() + ". En afterReturning()",
returnValue, method, args, target );
}
public void mostrar( String mensaje, Object returnValue, Method method, Object[] args, Object target) {
System.out.println( mensaje );
System.out.println( " Target: " + (target==null ? "Null" : target.getClass().getName()));
System.out.print( " Argumentos: " );
for ( Object obj: args )
System.out.print( obj.toString() + "("+ obj.getClass().getName()+") ");
System.out.println( "");
System.out.println( " Método: " + method.getName());
System.out.println( " Retorna: " + returnValue.toString());
}
}
Los tipos de Advice:
La salida por pantalla muestra que primero realiza el método correspondiente (liberar() o asignar()) y después afterReturning:
Estamos en org.ejemplo.GestorRecursos y asigno Paquete x09 a Camión 892 Estamos en org.ejemplo.ControlOperaciones. En afterReturning() Target: org.ejemplo.GestorRecursos Argumentos: Paquete x09(org.ejemplo.RecursoGeneral) Camión 892(org.ejemplo.RecursoGeneral) Método: asignar Retorna: true Estamos en org.ejemplo.GestorRecursos y libero Paquete x09 de Camión 892 Estamos en org.ejemplo.ControlOperaciones. En afterReturning() Target: org.ejemplo.GestorRecursos Argumentos: Paquete x09(org.ejemplo.RecursoGeneral) Camión 892(org.ejemplo.RecursoGeneral) Método: liberar Retorna: true