miércoles, 18 de agosto de 2010

Programación orientada a aspectos con la clase Proxy

Java

La programación orientada aspectos (AOP) es uno de los conceptos que en estos días se usa muy habitualmente en el desarrollo de aplicaciones Java. Básicamente, nos permite separar cierta lógica de soporte de la lógica propia de la aplicación, por ejemplo, se puede utilizar si necesitamos medir el tiempo que tarda en ejecutarse un determinado método, cuantas veces se llama, sacar trazas, aplicar seguridad, obtener conexiones a base de datos, gestionar la transaccionalidad, etc... En vez de añadir el código de estas funcionalidades a las clases de nuesta aplicación lo podemos añadir a una clase que se encarge de implementar el aspecto.

La librería Spring proporciona un amplio soporte para la programación orientada a aspectos, sin embargo, hay veces en el que no tenemos control sobre las librerías que podemos utilizar en nuestro proyecto. En este caso nos tenemos que limitar a lo que nos proporciona el propio JDK.

A pesar de que hoy el concepto es muy conocido se puede venir haciendo desde el JDK 1.3 (desde el año 2000) a través de la clase Proxy y su uso es realmente sencillo para las ventajas que aporta al código de una aplicación.

Supongamos que necesitamos medir cuanto tiempo tardan en ejecutarse los métodos de un determinado objeto (en realidad de una interfaz). En vez de calcular el tiempo dentro de la propia clase que queremos medir podemos implementarlo mediante un aspecto con la clase Proxy de la siguiente forma (el método main no es realmente necesario solo está incluido para hacer simple el probar el ejemplo):

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.Date;
import java.util.HashSet;
import java.util.Set;
import java.util.TreeSet;

public class ProfileProxy implements InvocationHandler {

    protected Object object;
    protected Proxy proxy;

    public ProfileProxy(Object object) {
        this.object = object;
        // Creación del proxy que intercepta las llamadas a los métodos del objeto
        proxy = (Proxy) roxy.newProxyInstance(object.getClass().getClassLoader(), object.getClass().getInterfaces(), this);
    }

    public Proxy getProxy() {
        return proxy;
    }

    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        long start = new Date().getTime();
            
        // Invocar al servicio
        Object o = method.invoke(object, args);
            
        long end = new Date().getTime();
        System.out.println("Tiempo: " + (end - start) + "ms");
        
        return o;
    }
    
    public static void main(String[] args) throws Exception {
        Set data = new HashSet(); 
        for (int i = 0; i < 100000; ++i) {
            data.add(new Integer((int) (Math.random() * 100000)));
        }
        
        HashSet hashSet  = new HashSet();
        TreeSet treeSet  = new TreeSet();
        
        Set proxyHashSet  = (Set) new ProfileProxy(hashSet).getProxy();
        Set proxyTreeSet  = (Set) new ProfileProxy(treeSet).getProxy();
        
        proxyHashSet.addAll(data);
        proxyTreeSet.addAll(data);
    }
}


El método main del ejempo trata de medir cuanto tiempo se tarda en añadir los elementos de un Set a otro para un HashSet y para un TreeSet. Dado que la clase TreeSet mantiene los elementos ordenados según su orden natural es de esperar que tarde más tiempo.

En mi equipo añadir cien mil elementos de un Set a un HashSet tarda 62ms y añadir el mismo número de elementos a un TreeSet 110ms.

Pero todo no son cosas buenas, una pequeña limitación de utilizar la clase Proxy es que la interceptación de las llamadas se tienen que hacer sobre los métodos de una interfaz que implemente el objeto.