Mi granito de java: Api Reflection

viernes, 7 de septiembre de 2012

Api Reflection

Api Reflection

Java posee un API muy poderosa que no se encuentra en la gran mayoría de los lenguajes. Se trata del API Reflection, que permite comunicarse casi directamente con la máquina virtual de java.
Dicho de otra forma, permite inspeccionar y manipular clases y objetos sin conocer a priori con que objetos estamos trabajando. Este API es utilizada por la gran mayoría de los frameworks como por ejemplo Hibernate o Spring.

La clase Class.

Todos los objetos en java heredan de java.lang.Object y por ello estan dotados del método public final Class getClass(). Este método nos devuelve un objeto java.lang.Class, que va a ser nuestro punto de entrada al API Reflection. En nuestro caso vamos a usar una clase sencilla como ejemplo. La clase Persona, que tiene 4 atributos con sus getters y setters:



Hay diversas formas de obtener el objeto Class. Las más comunes son:


En el primer caso la obtengo de una clase en particular. Si estoy parado en dicha clase también es posible hacer this.getClass(). En el segundo caso obtengo Class a partir de un objeto. Y en el tercer caso obtengo Class a partir del nombre de la clase.

Una vez obtenida la clase es posible acceder a un montón de métodos muy útiles. Los principales son:
  • Field getField(String name): devuelve un campo público de la clase, a partir de su nombre. Si la clase no contiene ningún campo con ese nombre, se comprueban sus superclases recursivamente, y en caso de no encontrar finalmente el campo, se lanzará la excepcion java.lang.NoSuchFieldException. 
  • Field[] getFields(): devuelve un array con todos los campos públicos de la clase, y de sus superclases. 
  • Method[] getMethods(): devuelve un array con todos los métodos públicos de la clase, y de sus superclases. También existe el método getMethod que se le puede pedir un método en particular.
  • Class[] getInterfaces(): devuelve un array con todos los interfaces que implementa la clase. 
  • Constructor[] getConstructors(): devuelve un array con todos los constructores públicos.
  • También es posible crear un objeto de una clase en particular en tiempo de ejecución gracias al método: public T newInstance()
Nos devuelve un Object que luego podemos castear a la clase correspondiente.


Hay varios métodos más que se pueden utilizar. La lista completa esta acá: http://docs.oracle.com/javase/7/docs/api/java/lang/Class.html

Si observamos la lista veremos que varios se repiten pero con la palabra Declared en la firma. Por ejemplo, tenemos getMethods y getDeclaredMethods. La diferencia radica en que el primero obtiene los métodos públicos de la clase y los heredados y el segundo los métodos públicos y privados pero no los heredados.

java.lang.reflect.Field

Field es una clase que representa un atributo de una clase en al API reflection. Tiene muchos metodos interesantes como por ejemplo:


public String getName(): es el nombre del atributo.
public Class getType(): devuelve la clase del atributo.
public int getModifiers(): devuelve los modificadores de un campo representados por un entero que se corresponde con las constantes definidas en la clase java.lang.reflect.Modifier.
public Object get(Object obj): devuelve el valor del atributo.
public void set(Object obj, Object value): setea un valor al atributo.




java.lang.reflect.Method.

Como se habrán imaginado Method tes una clase que representa un método. También tiene varios métodos muy útiles, como por ejemplo:


public String getName(): nombre del método.
public Class[] getParameterTypes(): array con las clases de los parámetros del método.
public Class[] getExceptionTypes():  array con las clases de las excepciones.
public Class getReturnType(): devuelve la clase del valor que devuelve el método.
public Object invoke(Object obj, Object... args): ejecuta el método sobre un objeto, pasándole los parámetros como segundo argumento si es que los necesita. Veremos un ejemplo sobre su uso al final de este post.

Veremos que diferencia hay entre los metodos getMethods() y getDeclaredMethods().




Como se puede observar getDeclaredMethods() puede ser más util en la mayoría de los casos.

Un ejemplo integrador.

Les voy a mostrar un ejemplo que tuve que hacer para el trabajo. Si bien no es exactamente el mismo, es bastante similar. Resulta que nuestra aplicación enviar por web service ciertos datos para que otra aplicación lo pueda mostrar por una página web. Básicamente envía objetos simples con atributos que sean primitivos o Strings con sus getters y setters. 
Esta otra aplicación es un semi enlatado que necesita los datos "escapeados". Esto quiere decir, sin acentos ni caracteres raros, sino con el escape de html.
Nos avisaron de esto muy poco tiempo antes de entregar el producto como no podía ser de otra manera. Por suerte existe una clase en el apache commons lang que nos ayudó mucho ya que tiene un método que convierte un String normal en uno escapeado: StringEscapeUtils.escapeHtml(String s).

El problema es que teniamos que recorrer cada uno de nuestros objetos y escapear cuando sean Strings. Y la realidad es que los seteabamos por toda nuestra aplicación y no lo podiamos hacer en la misma clase (que  igual era muy molesto) debido a que también guardabamos el valor en nuestra base de datos y allí no tenía que estar escapeado.

Por ello decidimos utilizar reflaction para que le podamos enviar cualquier objeto y por reflection cambie todos sus strings por strings escapeados:



 Y su uso sería el siguiente:



Conclusión.

Este API es muy poderosa y permite realizar muchas cosas que en otro lenguaje sería casi imposible. Pero uno debe evitar a toda costa abusar de la misma. Las personas que hace poco la conocen quieren utilizarla en todos lados y realmente puede hacer mucho lío en el código y en su lectura.




6 comentarios:

Anónimo dijo...

Muchas gracias por este post Maximiliano. Recuerdo haber visto ejemplos de uso de esta api pero no recordaba como se llamaba la API ni en que consistia. Gracias por el util post de nuevo!

Max dijo...

Un placer!

Agustin Moreno dijo...

Hola Maxmiliano muy bueno el post y la explicación mu concreta.
Saludos!

Max dijo...

Gracias Agustin, me alegro que te haya ayudado.

Anónimo dijo...

Excelente Post!!!
Ejemplos claros y la manera de redactar esta muy bien, gracias por este aporte de un tema no muy difundido!

Miguel García dijo...

En .NET también existe