Noticias Weblogs Foros Wiki Código
Sponsors:

Meta-Info

¿Que es?

Planeta Código es un agregador de weblogs sobre programación y desarrollo en castellano. Si eres lector te permite seguirlos de modo cómodo en esta misma página o mediante el fichero de subscripción.

rss subscripción

Sponsors

Anunciarse aquí

PlanetaCódigo en inglés

Puedes utilizar las siguientes imagenes para enlazar PlanetaCodigo:
planetacodigo

planetacodigo

Si tienes un weblog de programación y quieres ser añadido aquí, envíame un email solicitándolo.

Idea: Juanjo Navarro

Diseño: Albin

Home-made AOP

Junio 16th, 2005 - [Enlace local]

Hola, con estes post quiero proponeros una forma de hacer AOP sin necesidad de recurrir a ningún framework.

El ejemplo más claro de las ventajas de la AOP es la generación de “logs”
o trazas de nuestros programas. Lo que voy a ilustrar en el ejemplo
es cómo realizar logs de todos los accesos a los métodos de determinados
objetos. Estos objetos van a ser DAOs (Data Access Object).

Bien, vamos a suponer que tenemos un objeto Peronsa, una interfaz PersonaDao
y una implementación de esa interfaz: PersonaJdbcDao. El patrón DAO busca
encapsular la persistencia de nuestros objetos, es decir, el código necesario
para ‘hablar’ con la base de datos se encapsula en DAOs. Como hay muchos
mecanismos de persistencia hacemos una interfaz (PersonaDao) y una o varias
implementaciones (ej: PersonaJdbcDao usando el API JDBC). La responsabilidad
de instanciar una implementación concreta la delegamos a una factoría, así
que también haremos una clase llamada DaoFactory.

Esta es la clase Persona: (por simplicidad he puesto los campos públicos)

public class Persona {
	public long dni;
	public String nombre;
	public String apellidos;
	
	public String toString() {
		return \"Persona{ dni: \"+dni+\"; nombre: \"+nombre+\"; apellidos: \"+apellidos+\" }\";
	}
}

Esta es la interfaz PersonaDoa: (por simplicidad sólo habrá dos métodos)

public interface PersonaDao {
	public void create(Persona persona);
	public Persona read(long dni);
}

Esta es la implementación PersonaJdbcDao: (que realmente es un fake :P ).

public class PersonaJdbcDao implements PersonaDao {
	/* simulación de una implementación de PersonaDao supuestamente usando Jdbc */
	public void create(Persona persona) {
		System.out.println(\"\tinsert into...\");
	}
	public Persona read(long dni) {
		System.out.println(\"\tselect from...\");
		Persona persona = new Persona();
		persona.nombre = \"juan\";
		persona.apellidos = \"herrero\";
		persona.dni = dni;
		return persona;
	}
}

Esta es la factoría (que por el momento queda así):

public class DaoFactory {
	public static PersonaDao createPersonaDao() {
		return new PersonaJdbcDao();
	}
}

Este es el código para probar lo que hemos programado:

public class Test {
	public static void main(String[] args) {
		PersonaDao personaDao = DaoFactory.createPersonaDao();
	
		Persona persona = new Persona();
		persona.dni = 1234;
		persona.nombre = \"pepe\";
		persona.apellidos = \"garcía\";
		personaDao.create(persona);
	
		Persona persona2 = personaDao.read(4321);
	}
}

Lo ejecutamos y…

	insert into...
	select from...

Lo que esperábamos. Ahora sin modificar el códgio de PersonaJdbcDao vamos a
hacer que antes y después de la ejecución del método se muestre información
sobre su invocación. Esto lo vamos a conseguir usando la clase java.lang.reflect.Proxy. Vamos a envolver en tiempo de ejecución un objeto
y podremos interceptar las llamadas a sus métodos.

Voy a implementar una clase: la calse LogWrapper que contendrá un método
decorate() al que se le pasará el objeto que se desea ‘envolver’ y
devolverá el objeto ‘envuelto’. Simplemente hay que crear un Proxy
usando el método Proxy.newProxyInstance(). Le pasaremos un ClassLoader,
un array de objetos Class[] para indicar qué métodos de qué clases queremos
interceptar (en nuestro caso las interfaces del objeto; que será sólo una: PersonaDao), y finalmente un objeto que implemente InvocationHandler.
Este último objeto tendrá un método invoke() que será llamado cada
vez que un método será interceptado; nos dará información sobre el
método que se ha invocado y los argumentos que ha recibido.
Veamos el código…

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
	
public class LogWrapper {
	public static Object decorate(Object delegate) {
		return Proxy.newProxyInstance(delegate.getClass().getClassLoader(),
				delegate.getClass().getInterfaces(),
				new WrapperHandler(delegate));
	}
}
	
class WrapperHandler implements InvocationHandler {
	
	private Object delegate;
	
	WrapperHandler(Object delegate) {
		this.delegate = delegate;
	}
	
	public Object invoke(Object proxy, Method method, Object args[]) throws Throwable {
		/* hacer algo antes de la ejecución del método */
		// muestro el nombre del método, la clase del que procede, y sus argumentos
		String name = method.getDeclaringClass().getName()+\".\"+method.getName();
		System.out.print(\"ejecutando \"+name+\"(\");
		for (int i = 0; i < args.length; i++) {
			if(i != 0)
				System.out.print(\", \");
			System.out.print(args[i]);
		}
		System.out.println(\")\");
	
		/* ejecutar el método*/
		Object result = null;
		try {
			result = method.invoke(delegate, args);
		} catch(Throwable e) {
			/* en caso de error */
			// se produce una InvocationTargetException
			// la causa real se obtiene con getCause()
			Throwable cause = e.getCause();
			System.out.println(\"se produjo una excepción: \"+cause);
			throw cause;
		}
	
		/* hacer algo al final de la ejecución */
		// muestro el valor devuelto por el método
		if(method.getReturnType().equals(Void.TYPE))
			System.out.println(\"saliendo de \"+name+\"(); el método devuelve void\");
		else
			System.out.println(\"saliendo de \"+name+\"(); el método devuelve \"+result);
		/* devolver el resultado del método si todo ha ido bien */
		return result;
	}
	
}

Ahora modifico la factoría para envolver los DAOs…

public class DaoFactory {
	public static PersonaDao createPersonaDao() {
		return (PersonaDao) decorate(new PersonaJdbcDao());
	}
	private static Object decorate(Object object) {
		return LogWrapper.decorate(object);
	}
}

Vuelvo a ejecutar el código y…

ejecutando aop.PersonaDao.create(Persona{ dni: 1234; nombre: pepe; apellidos: garcía })
	insert into...
saliendo de aop.PersonaDao.create(); el método devuelve void
ejecutando aop.PersonaDao.read(4321)
	select from...
saliendo de aop.PersonaDao.read(); el método devuelve Persona{ dni: 4321; nombre: juan; apellidos: herrero }

He conseguido pues que el código que se encarga de hacer la traza del código esté centralizado: no esté disperso por el código y no sea intrusivo. Además lo puedo activar y desactivar modificando una sola línea de código. En el ejemplo los Daos no se han implementado de la mejor forma y la forma de generar la traza con los System.out.println() no es la más adecuada teniendo frameworks como Log4J :P pero como ejemplo vale, no?

Qué os ha parecido? Se ha entendido? Sugerencias?

» Leer más, comentarios, etc...