GWT Reflection

diciembre 5th, 2009 by Enrique Leave a reply »

Intro

Siguiendo en la linea de la introspección, hoy vamos a ver una simple reflexión que nos va a permitir invocar sobre una instancia de una clase a métodos por su nombre.

What for?

La reflexión es una característica interesante, que se hace fundamental a la hora de diseñar componentes genéricos. Nos permite crear instancias de determinada clase con el nombre de la clase o también invocar métodos sobre una instancia de una clase teniendo el nombre del método. Esto, por ejemplo, nos simplifica enormemente la implementación del patrón command.

How? What?!

En GWT (al menos hasta la 1.7) no hay reflexión. Lo que vamos a hacer en esta implementación de reflexión, es proveer un mecanismo para invocar métodos por nombre. Suponiendo que tenemos una clase llamada MiClase que tiene un método llamado miMetodo buscamos poder hacer algo así:

MiClase instancia = new MiClase();
String nombreMetodo = “miMetodo”;
Object[] parametros = new Object[]{...};
invocar(instancia, nombreMetodo, parametros);

Enter generators!

Lo que vamos a hacer es que GWT genere la clase MiClaseReflection. ¿Cómo? Usando generators. Esta es una característica bien interesante de GWT. El foco de este artículo no está en explicar generators, hay mucha literatura en Internet. Acá va un link: http://code.google.com/intl/es/webtoolkit/doc/1.6/DevGuideCodingBasics.html#DevGuideDeferredBinding. Igual no hay que ser un experto para entender lo que vamos a hacer.

Básicamente vamos a escribir una clase que genere el código de MiClaseReflection, que tiene un único método -invocar- que es una cadena if..elseif..else que pregunta por cada método de MiClase y si alguna coincide, se lo invoca. Sería algo como esto:

if (nombreMetodo.equals(“miMetodo1”)) {
    target.miMetodo1(params);  // void
} else if (nombreMetodo.equals(“miMetodo2”)) {
    return target.miMetodo2(params); // no void
} else {
    throw new Exception(“método no encontrador: ” + nombreMetodo);
}

Obviamente falta chequeos de parámetros.

Para no tener que realizar ningún paso previo a la compilación generando la clase con alguna utilidad externa vamos a usar la API que tiene GWT que permite extender el compilador (com.google.gwt.core.ext). NOTA: Los que hayan utilizado APT de Java van ver unas cuantas similitudes (http://java.sun.com/javase/6/docs/technotes/guides/apt/index.html).

Cuando el compilador de GWT encuentra con la instrucción GWT.create(class) –que devuelve una implementación de la interfaz class– invoca al generador asociado a la interfaz class (esta asociación se declara en el gwt.xml del módulo). En este generador se “escribe” el código que implementa la interfaz class. Para escribir este código tenemos la asistencia de la API com.google.gwt.core.ext.typeinfo que recuerda mucho a la API Mirror Reflection de Java.

Generate

La instrucción GWT.create() toma una interfaz y devuelve una implementación de la misma. Entonces vamos definir una interfaz de marca y la vamos a llamar Reflectable. Toda clase que queramos tener soporte de reflexión va a tener que implementar esta interfaz:

public interface Reflectable {
}

Entonces la clase MiClase:

public class MiClase implements Reflectable {
    void miMetodo1();
    int miMetodo2();
}

Además, tenemos que tener la interfaz de idioma de la reflexión:

public interface Reflection<I extends Reflectable> {
    Object invoke(I target, String methodName, Object... params)
    throws InvocationException;
}

Para obtener una reflexión y usarla suponiendo que miMetodo no tiene parámetros:

MiClase objeto = new MiClase();
Reflection<MiClase> reflection = GWT.create(MiClase.class);
reflection.invoke(objeto, “miMetodo”);

Para asociar el generador con la interfaz Reflectable, agregamos el siguiente fragmento al :

    <generate-with class="com.aquait.utils.gwt.rebind.ReflectionGenerator">
        <when-type-assignable class="com.aquait.utils.gwt.reflection.Reflectable"/>
    </generate-with>

Así, cuando el compilador de GWT encuentra GWT.create(MiClase.class), llama al generador pues la clase MiClase implementa Reflectable. Luego, tenemos una implementación de la interfaz Reflectable para la clase MiClase.

Para usarlo en un proyecto, hay que agregar esto en el <modulo>.gwt.xml:

<inherits name="com.aquait.utils.gwt.Reflection"/>

El JAR se puede descargar de acá: introspection-reflection4gwt.jar

Nota: Este JAR tiene una versión actualizada de la introspección que elimina los métodos getIntrospectable() y setIntrospectable() de la interfaz Introspection. Por lo tanto, los métodos getProperty() y setProperty() toman como primer parámetro al Introspectable.

Blogger PostDiggRedditGoogle ReaderShare
Advertisement

3 comments

  1. pepe dice:

    Todo bien, pero sigue siendo inutil puesto que al tener que pasarle el class literal MiClase.class, estas obligado a saber de antemano que clase es y tener una referencia a la misma. Como haces si solo sabes el nombre de la clase a instanciar en tiempo de ejecucion? No podes puesto que gwt no te lo permite hacer. La unica solucion a este problema es tener un conjunto acotado de clases ya pre-procesadas por el generator e insertadas en una libreria o registro. Por ej. register.add(new GWT.create(MiClase.class)) asi, luego invocar seria register.invocarmetodo(“clase”,”método”). Esto deriva en el problema de saber de antemano todas las posibles classes a usar.

  2. Enrique Enrique dice:

    Todo lo que decís, es así, excepto lo de que es inútil :) .
    También me encontré con esos problemas y terminé implementando el registro igual que como decís.
    Ahí me di cuenta de que GWT no soporta inicializadores estáticos al momento de “cargar” la clase (aunque si los ejecuta al momento del new).
    Igualmente, a nosotros nos dio buenos resultados. Hace tiempo de esto ya, pero creo que lo utilizamos para hacer un componente genérico de menú que el usuario lo podía configurar y se almacenaba en la base de datos o algo así.
    Saludos y gracias por el aporte.

  3. pepe dice:

    Perdon por lo de inutil, no me supe expresar. Yo lo he utilizado para mediante anotaciones en las clases de dominio generar automaticamente la interfase de usuario. Un enfoque similar a OpenXava pero con GWT. Basicamente “leo” las anotaciones de JPA y las mias propias en tiempo de ejecucion y creo un Wrapper que contiene lo necesario para obtener informacion hacerca de esa clase. Luego en Ex-Gwt tengo un componente AbmMenuItem y cuando se ejecuta te muestra un Abm completo para esa clase, que incluye filtros, el form de ingreso con componentes especiales para buscar un objeto relacionado mediante ManyToOne por ej.
    Saludos.

Deja un comentario

Spam protection by WP Captcha-Free