Skip navigation

Tengo un post en el hornito listo para salir, pero primero algunas curiosidades.

Estuve trabajando un poco con reflection, una de las características mas fuerte de Java y poco explotadas que si son bien manejadas pueden construirse lindos frameworks como Spring, Seam o implementar el stack de EJB. Por el otro lado, el exceso del uso impacta directamente en la performance de la aplicación y si estamos haciendo cosas complejas utilizando Proxies podemos perdernos y el flujo de ejecución termina lejos de donde esperabamos.

Vamos a ver de que hablamos cuando hablamos de reflection. Aclaro que lo que vallan a leer es solo por la experiencia de uso, no soy ningún experto en la materia, así que cualquier corrección es bienvenida.

Supongamos que tenemos una tarea simple, le queremos listar todas las posibles acciones que nuestro EJB puede hacer al consumidor. Obviamente la primera respuesta es tenerlo en archivo de configuración posiblemente un xml blah blah blah… pero la verdad es que toda esta información ya está escrita y está en la interfaz del EJB. La pregunta es como hacemos para transformar este código en información visible, y no voy a dar mas vueltas.

Si le damos una repasada a las instancias de la clase java.lang.Class vemos que cuenta los siguientes metodos ( ademas de unos cuantos más ) :

  • public Method[] getMethods();
  • public Field[] getFields();
  • public Constructor[] getConstructors();
  • public Annotation[] getAnnotations();

Con este grupo de métodos podemos sacar mucha información sobre una instancia ( sobre la clase de una instancia ) tanto sea un EJBs, un POJO o lo que sea.


Method[] ms = instancia.getClass().getMethods();
System.out.println("metodos de la clase " + instancia.getClass().getName() );
for(Method m : ms ){
    System.out.println("    nombre: " + m.getName() );
}

Esto es básico, si ahora queremos llamar a un metodo que eligió el usuario después de que le mostramos las opciones:


// obtenemos la referencia al metodo
method = obtenerElMetodoPorElNombre( opcion );
// y lo invocamos
method.invoke( instancia, parametro1, parametro2 );
// ó con un array de parametros
method.invoke( instancia, new Object[]{ parametro1, parametro2 } );

Excelente no? Esto sigue siendo magia para gnomos de clase baja… veamos ahora a algo mas complejo: Proxies.

Los proxies son instancias de ninguna clase

(??) Como es esto? Si no tiene una clase asociada como la instanciamos? Que métodos tiene asociado? Para eso antes tienen que saber que es un InvocationHandler ( “manejador de invocaciones” a métodos  ).

Un IH es una interfaz que nos va a permitir asociar un objeto a esta instancia mágica sin clase asociada, el proxy. Una ves que tenemos una instancia de un IH, creamos el proxy y le decimos que él será el encargado de recibir las invocaciones a los métodos que el proxy no pueda resolver, porque el proxy no tiene definición de clase por lo tanto no tiene un conjunto de métodos definidos ni implementados. Vamos a las fuentes:

Creemos nuestro IH:


public class ManejadorDeInvocaciones implements InvocationHandler {
    private String texto;
    public ManejadorDeInvocaciones(String texto){
        this.texto = texto;
    }
    // el unico metodo para implementar segun la interfaz
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println(texto + metodo.getName() );
        return true; // solo para que funcione el ejemplo siguiente
    }

}

Ahora creemos nuestro proxy mágico y asociemosle el IH. Para eso llamamos al método newInstance con tres parametros en el siguiente orden:

  • classloader : Como hay que crear la definición de una clase al vuelo, esta clase tiene que estar asociada a un classloader que por lo general es this.getClass().getClassLoader()
  • interfaces: la lista de interfaces que esta clase imaginaria implementa esto nos permite castear el proxy a cualquiera de estas interfaces y utilizarlo como si fuera una instancia de dicha interfaz, transparente para el usuario. Puede ser una lista vacía.
  • invocationHandler: nuestra instancia de IH que queremos que se encargue de recibir los llamados a los métodos de esta instancia proxy 

   Object unProxy = Proxy.newProxyInstance( this.getClass().getClassLoader() , new Class[]{ java.util.List.class }, new ManejadorDeInvocaciones("llamando al metodo ") );

Ahora, cualquier método que invoquemos a unProxy automáticamente invocará al método “invoke” del IH con la referencia al proxy, método y los argumentos correspondientes. Genial no? Pero si es un Object no podemos llamar a muchos métodos… para eso está el segundo parámetro, para poder castear nuestro proxy a alguna interfaz de nuestra elección. Por lo tanto lo siguiente es válido:


    List lista = (List) unProxy;
    ....
    lista.add( new Integer(4) );

Un detalle, tener en cuenta que el método de la interfaz en si limita un poco la respuesta del método invoke. Por ejemplo, en este caso si el IH devuelve null para la llamada al método boolean add( Object o ) vamos a recibir un lindo NullPointerException con toda razón.

Si para esta altura no se aburrieron de leer ( yo no me aburrí de escribir :P ) les voy a enseñar un truco mas con todo lo aprendido. 

Supongamos que queremos una lista que guarda números, pero solo números pares … (??) si un ejemplo bien básico :D Como harían esto? Sabemos que podemos filtar el tipo con List<Integer> pero no podemos filtrar los valores, para eso tendríamos que extender de ArrayList y reimplementar el método add para agregarle esta loca validación que se me acaba de ocurrir. Otra es hacer un proxy y usarlo como filtro, así:


    // definimos el filtro
    public class ManejadorDeInvocaciones implements InvocationHandler {
        private List&lt;integer&gt; target;
        public ManejadorDeInvocaciones(List&lt;/integer&gt;&lt;integer&gt; target){
            this.target = target;
        }
        // el unico metodo para implementar segun la interfaz
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            if ( method.getName().equals(&quot;add&quot;) ){
                Integer i = (Integer)args[0];
                if (i % 2 == 1) return true;
            }
            return method.invoke( target , args);
        }
    }
    //la lista que tendra los numeros
    List&lt;/integer&gt;&lt;integer&gt; lista = new ArrayList();

    //le asociamos el filtro
    lista = (List&lt;/integer&gt;&lt;integer&gt;)Proxy.newProxyInstance( this.getClass().getClassLoader() , new Class[]{ List.class }, new ManejadorDeInvocaciones(lista) );

    //trabajamos como si fuera una lista comun y corriente
    lista.add( new Integer(5) );
    lista.add( new Integer(2) );

Algo para destacar muy importante es el código del filtro queda completamente separado de la instancia _objetivo_, como vemos la definición de la lista ( List ) no conoce el filtro, la implementación de la lista ( ArrayList ) tampoco y ni siquiera el cliente, el usuario de la lista, porque no afecta para nada la manera en la que trabajamos con la lista.  Así como agregamos un filtro de números pares podemos agregar un filtro para transaccionabilidad, un filtro para seguridad con JAAS,  control de concurrencia pero si hacemos todo eso estaríamos haciendo algo muy parecido a lo que hizo JBoss para implementar el stack de EJB 3.0. Eso lo dejo para otro post.

La frutilla del postre viene ahora, con algo que encontré revisando las librerías que vienen con la jdk.

Resulta que cuando queremos llamar a un método usando reflection hay que tener cuidado al buscar la referencia al método que queremos llamar, ya que cuando utilizamos el método java.lang.Class.getMethod(String name, Class… paramTypes) la busqueda es explicita. Me refiero a que si buscamos el método X que resolvería la jvm para satisfacer Z cantidad de parámetros tenemos que simular la búsqueda del método según las superclases de los tipos de parámetros!!! Suena complicado, y lo es. Suena a que debería estar echo, y lo está… pero está oculto.   

Explicación “grafica” :

assert List.class.getMethod(&quot;add&quot;, Object.class) != null; // esta invocacion devuelve el metodo add
assert List.class.getMethod(&quot;add&quot;, Integer.class) != null; // esto tira la excepcion java.lang.NoSuchMethodException

Y tiene sentido, porque aunque cuando llamemos a java.util.List.add( Integer ) sepa a cual nos referimos, el método add( Integer ) no existe. 

Para hacer esto podemos mirar pero no usar ( si escucharon leyeron bien ) la clase java.beans.ReflectionUtils del recurso classes.jar, que no tiene javadoc pero tiene una muy linda explicación en la cabecera:

/**
 * A utility class for reflectively finding methods, constuctors and fields
 * using reflection.
*/

Al parecer es una clase protegida y solo puede ser invocada si estamos desde el mismo paquete, así que lo que yo aconsejo es copiar y pegar todo el código fuente =D total… no cambia a menos que corran en otra jdk. ( tener cuidado de eso )

De este “grupo de funciones” podemos usar Method getPublicMethod(Class declaringClass, String methodName, Class[] argClasses) para encontrar un método que satisfaga los tipo de parámetros que le estamos pasando. Si ven el código fuente se van a dar cuenta que es mucho mas complicado de lo que pensaron jeje.

Bueno eso es todo lo que tengo para decir por el momento de reflection, esto sirve como base para el próximo post sobre un custom interceptor de Seam para hacer algunas cosas locas y para un patrón dinamico de DTO que hice hace mucho, capas le sirve a alguno. Hasta entonces.

Anuncios

3 Comments

  1. buen post.. yo hoy justamente me preguntaba..
    si tengo una JPA entity por ejemplo:

    @Entity
    class MyEntity{
    @Id
    long id;

    setter/getter()

    }
    Tengo entendido que la annotation @Id puede ir tanto a nivel de property, o a nivel de accessor. La pregunta es entonces.. con reflection.. voy a encontrar la anotacion definida en los dos lados? (Field/Method) o solo Field, porque yo en la clase marque la property? Mas alla que la respuesta, es donde la marcaste (en este caso la property), no llego a entender bien como puede crearse un codigo generico para realizar esta comprobacion.

    • Si es verdad, no hay algo en la clase que te diga a partir de una anotación donde se encuentra, hay q hacer toda una búsqueda. Lo más genérico que se me ocurre es preguntarle a la anotación que tipo de Target tiene ( @Target ), si es Field buscar ahí y si es Method buscar entre los métodos también… entonces ese algoritmo de búsqueda te serviría para cualquier anotación. Se entiende? Puedo postear un ejemplo si queres, decime.

      • no todo bien.. es bastante feo el tema, ya que @Id tiene la particularidad de estar definida tanto como method y field.
        Entonces.. hay que recorrer toda la clase para ver donde se encuentra la annotation. Otro problema que causa esto es que cuando queres obtener el valor de la property anotada, necesitas hacer cochinadas para encontrar el accessor.
        La verdad muy artesanal para mi gusto.


Responder

Introduce tus datos o haz clic en un icono para iniciar sesión:

Logo de WordPress.com

Estás comentando usando tu cuenta de WordPress.com. Cerrar sesión / Cambiar )

Imagen de Twitter

Estás comentando usando tu cuenta de Twitter. Cerrar sesión / Cambiar )

Foto de Facebook

Estás comentando usando tu cuenta de Facebook. Cerrar sesión / Cambiar )

Google+ photo

Estás comentando usando tu cuenta de Google+. Cerrar sesión / Cambiar )

Conectando a %s

A %d blogueros les gusta esto: