martes, 24 de octubre de 2006

Reflexión en Java

El presente artículo es una introducción a la reflexión en Java. Esta técnica permite "destripar" las clases de Java, obteniendo la información de su estructura a bajo nivel. ¿Qué ventajas tiene ésto?. La verdad es que muchas, pero os voy a ilustrar con un ejemplo.

Imaginemos el desarrollo de un gateway que está basado en servicios. Cada servicio es atendido por una clase especializada, dependiendo de un tipo de mensaje.

Si queremos ejecutar un servicio específico que atienda una solicitud determinada, lo normal es hacer un anidamiento de sentencias IF para que instancie y ejecute cada clase según el caso. Esto tiende a complicar el desarrollo, hace ilegible el código, no es óptimo, y para integrar este gateway en otros proyectos, habrá que borrar IF's y crear otros para las clases de servicio que correspondan.

Pero si utilizamos la reflexión, este anidamiento de IF's desaparece, dejando lugar a una única instanciación dinámica, que puede ser recogida de una base de datos o de un fichero. ¿Suena bien, verdad?

Para simplificar este caso, se ha realizado una simulación con clases básicas.

Lo primero es definir una interfaz, la cual servirá para definir la estructura de las clases que harán de servicio, creando los métodos por los cuales se invocan.


public interface IInterface {
public int operacion(int a, int b);
}



Una vez definida la interfaz, se crean las clases servicio basadas en dicha interfaz:

Clase A


public class A implements IInterface{
private int s1, s2;

public A() {
s1 = 0;
s2 = 0;
}

public A(int a, int b) {
s1 = a;
s2 = b;
}

public int operacion(int a, int b) {
return a+b;
}

}



Clase B


public class B implements IInterface{

public int operacion (int a, int b) {
return a-b;
}

}



Para obtener un objeto a partir de una clase, de la cual no se conoce el nombre, se utiliza el siguiente código:


String clase = "A";

Class c = Class.forName(clase);


A partir de ahora, el objeto "c" contendrá la información referida a dicho objeto. A partir de aquí se podrá saber si es una clase, una interfaz, si es pública, privada, protegida, sincronizada, abstracta, etc. Se podrá acceder a todos sus constructores, métodos, parámetros, etc. (mirar código de ejemplo más abajo).

Lo realmente importante para nuestro ejemplo, es poder instanciar dicha clase y ejecutar sus métodos. Para ello se crea la instancia mediante la interfaz, que define el tipo de clase, y a través del objeto obtenido anteriormente:


String clase = "A";

Class c = Class.forName(clase);

// Instanciacion dinamica
IInterface objeto = (IInterface) c.newInstance();
System.out.println("Resultado: " + objeto.operacion(3, 2));


Como se puede apreciar, ya no estamos limitados por los nombres de las clases escritos estáticamente en el código, si no que podemos obtenerlos de un fichero o de una tabla de base de datos, e ir instanciando dinámicamente.

En el siguiente ejemplo, se ilustra cómo obtener algunas propiedades de las clases que deseamos instanciar.


import java.lang.reflect.*;

public class MainClass {
public MainClass() {
}

public static void main(String[] args) {
MainClass mainclass = new MainClass();
String clase="B";
String aux = "";
try {
Class c = Class.forName(clase);
Class i[] = c.getInterfaces();

System.out.println("Nombre de la clase: " + c.getName());
int m = c.getModifiers();
System.out.println("Modificadores: " + m);

aux = "\n - ATRIBUTOS -\n";

// Obtencion de atributos de clase o interfaz
aux += "\nInterface: ";

if (Modifier.isInterface(m))
aux += "SI";
else {
aux += "NO";

aux += "\nClase estatica: ";

if (Modifier.isStatic(m))
aux += "SI";
else
aux += "NO";

aux += "\nClase final: ";

if (Modifier.isFinal(m))
aux += "SI";
else
aux += "NO";

aux += "\nClase anonima: ";

if (c.isAnonymousClass())
aux += "SI";
else
aux += "NO";

aux += "\nClase publica: ";

if (Modifier.isPublic(m))
aux += "SI";
else
aux += "NO";

aux += "\nClase privada: ";

if (Modifier.isPrivate(m))
aux += "SI";
else
aux += "NO";

aux += "\nClase protegida: ";

if (Modifier.isProtected(m))
aux += "SI";
else
aux += "NO";

aux += "\nClase abstracta: ";

if (Modifier.isAbstract(m))
aux += "SI";
else
aux += "NO";

aux += "\nClase sincronizada: ";

if (Modifier.isSynchronized(m))
aux += "SI";
else
aux += "NO";

aux += "\nInterfaces implementadas: ";

for (int n =0; n<i.length; n++)
aux+="\n " + i[n].getName();
}

System.out.println(aux);

// Obtencion de propiedades (variables publicas)
aux = "\n- PROPIEDADES -\n";
Field[] f = c.getDeclaredFields();

for (int n=0; n<f.length; n++) {
aux+= "\nNombre: " + f[n].getName();
aux+= " - Tipo: " + f[n].getType().getName();
}

System.out.println(aux);

// Obtencion de constructores
aux = "\n- CONSTRUCTORES -\n";
Constructor[] co = c.getDeclaredConstructors();

for (int n=0; n<co.length; n++) {
aux+= "\nNombre: " + co[n].getName();
Class pa[] = co[n].getParameterTypes();
for (int y=0; y<pa.length; y++)
aux += " - Parametro: " + pa[y].getName();
}

System.out.println(aux);


// Instanciacion dinamica
IInterface objeto = (IInterface) c.newInstance();
System.out.println("Resultado: " + objeto.operacion(3, 2));
}
catch (Exception e) {
System.out.println("Error: " + e.toString());
}
}
}


Todo esto se aplica a clases ya compiladas, por lo que es posible realizar una ingeniería inversa a partir de un simple jar.

Espero que os haya gustado el artículo y que os sea muy útil en el futuro.

Enlace de interés: Trail: The reflection API