Ir al contenido principal

EJEMPLO REFLECTION EN JAVA

Hoy vamos a realizar un ejemplo completo con el API Reflection de Java. Con este API podemos por ejemplo obtener el nombre y el valor de los atributos de cualquier clase, crear una instancia de ella utilizando cualquiera de sus constructores o ejecutar sus métodos.

El objetivo del artículo es crear un clase genérica que a partir de una lista genérica de items sea capaz de generar código JSON cuyo nombre sea el resultado de la ejecución de un método concreto y como parámetros sean los nombres y valores de de sus atributos.

Por tanto en el ejemplo aglutinamos varios conceptos que seguramente nos encontraremos a lo largo de nuestra trayectoria profesional como programadores:

  • API Reflection
  • Genericidad
  • JSON, muy utilizado para el intercambio de información entre aplicaciones. La implementación que se utiliza para realizar el mapping es la librería jackson.



Creamos una clase parametrizada por una clase. Dicha clase recibirá una lista de items de una clase determinada G. Definimos como constantes varios prefijos que seran los que utilizamos para dar nombre a cada nodo JSON para genera el código asociado:


  •  item, se utiliza a nivel global para cada elemento de la clase G de la lista de items (item1, item2, itemN)

  • name, representa el nombre de cada item. En este ejemplo se extraerá de llamar al método llamado getItemNameValue. Si la clase G no implementa este método, se devolverá la cadena vacia.

  • data, representa los parámetros del item. Dichos parámetros se extraen de los atributos de la clase G que tengan un método get publico asociado.

  • name, value, representan el nombre y el valor de cada atributo de la clase.


public class JsonGenerator<G> {

    private static final String METHOD_ITEM_NAME = "getItemNameValue";
    private static final String PREFFIX_DATA = "data";
    private static final String PREFFIX_ITEM = "item";
    private static final String PREFFIX_NAME = "name";
    private static final String PREFFIX_VALUE = "value";
    private static final String GET = "get";

    /**
     * Lista de items que se parsea para obtener la salida JSON
     */
    private List items;

A continuación creamos un método que obtiene el valor del método getItemNamevalue. Es un método que obtiene una instancia de la clase java.lang.reflect.Method para invocar un determinado método de una clase genérica G. El método se obtiene con la operación getDeclaredMethod de la clase Class pasando como parámetro el nombre del método. Utilizando el método invoke de la clase Method ya tenemos el valor de cada item.

NOTA: Notad que se utiliza la nueva notación de Java 7  para la definición de excepcion con los separadores barra.


private String getItemNameValue(Class cls) {
        // Buscando el método
        Method method = null;
        // no paramater  
        Class noparams[] = {};
        try {
            Object obj = cls.newInstance();
            method = cls.getDeclaredMethod(METHOD_ITEM_NAME, noparams);
            String res = (String) method.invoke(obj, new Object[0]);
            return res;
        } catch (SecurityException | NoSuchMethodException | IllegalArgumentException | IllegalAccessException | InvocationTargetException | InstantiationException e) {
            return StringUtils.EMPTY;
        }
    }

Tendremos un método para obtener los campos de la clase G. Este método devuelve un array de la clase del API Reflection java.lang.reflect.Field. Esta clase representa un atributo concreto de una clase y nos servirá como punto de partida para acceder al nombre como al valor del mismo. Observad que el método es recursivo, primero obtiene los atributos de la clase y posteriormente se llama a sí mismo para recuperar los atributos definidos en la superclase. Fijaos también que los atributos con el modificador static se evita añadirlos a la lista, sólo se recuperan los atributos sin el modificador static para evitar añadir constantes.


private Field[] getAllFields(Class klass) {
        List fields = new ArrayList<>();
        fields.addAll(Arrays.asList(klass.getDeclaredFields()));
        if (klass.getSuperclass() != null) {
            fields.addAll(Arrays.asList(getAllFields(klass.getSuperclass())));
        }

        Iterator fAux = fields.iterator();
        while (fAux.hasNext()) {
            Field field = fAux.next();
            if (java.lang.reflect.Modifier.isStatic(field.getModifiers())) {
                fAux.remove();
            }
        }
        return fields.toArray(new Field[]{});
    }

Una vez que tengamos todos los atributos de la instancia de una clase necesitaremos métodos para acceder a su nombre e invocar al método get asociado para obtener su valor. Estos dos métodos se definen a continuación:


private Object getFieldValue(Object userInstance, Field field) {
        try {
            String methodName = GET + StringUtils.capitalize(field.getName());
            return MethodUtils.invokeExactMethod(userInstance, methodName, new Object[0]);        
        } catch (IllegalArgumentException | IllegalAccessException | NoSuchMethodException | InvocationTargetException e) {
            return StringUtils.EMPTY;
        }
    }

private List<String> getFieldsNames(Field[] fields) {
        List l = new ArrayList<>();
        for (Field field : fields) {
            l.add(field.getName());
        }
        return l;
    }

private List<String> getFieldsValues(Field[] fields, Object obj) {
        List l = new ArrayList<>();
        for (Field field : fields) {
            Object value = getFieldValue(obj, field);
            l.add((value == null) ? StringUtils.EMPTY : value.toString());
        }
        return l;
    }


Una vez que tenemos toda la información de la clase G únicamente nos queda generar el código JSON. Para ello vamos a utilizar la clase org.codehaus.jackson.node.ObjectNode, que representa un elemento JSON y la clase org.codehaus.jackson.node.JsonNodeFactory que se encargará de ir creando cada nodo JSON. Finalmente con la clase org.codehaus.jackson.map.ObjectMapper generaremos una cadena a partir de un nodo JSON raíz que representa el código JSON de la clase.


 private void addSimpleObject(ObjectNode node, String name, String value) {
        node.put(name, value);
    }

private void addComplexObject(ObjectNode node, String name, ObjectNode value) {
        node.put(name, value);
    }

private void addListObject(ObjectNode node, List names, List values) {

        for (int i = 0; i < names.size(); i++) {
            String name = names.get(i);
            String value = values.get(i);
            ObjectNode atributos = factory.objectNode();
            atributos.put(preffixName, name);
            atributos.put(preffixValue, (value == null) ? StringUtils.EMPTY : value);
            node.put(preffixData + (i + 1), atributos);
        }

    }


Y el método pegamento que utiliza todo lo anterior para generar el código JSON:



public String generateJson() throws JsonGenerationException, JsonMappingException, IOException {
        if (CollectionUtils.isEmpty(items)) {
            return StringUtils.EMPTY;
        }
        for (int i = 0; i < items.size(); i++) {
            G item = items.get(i);
            ObjectNode elementoItem = factory.objectNode();

            String itemNameValue = getItemNameValue(item.getClass());
            addSimpleObject(elementoItem, preffixName, itemNameValue);

            List<String> names = getFieldsNames(getAllFields(item.getClass()));
            List<String> values = getFieldsValues(getAllFields(item.getClass()), item);

            addListObject(elementoItem, names, values);
            addComplexObject(root, preffixItem + (i + 1), elementoItem);
        }     
        return mapper.writerWithDefaultPrettyPrinter().writeValueAsString(root);
    }


Para cada uno de los elementos de la lista de tipo G:


  • Se añade el nodo JSON simple con el nombre del item (instancia de clase G) al nodo JSON que representa al item.
  • Se añade el nodo JSON complejo para cada uno de los atributos del item (instancia de clase G) al nodo JSON que representa al item.
  • Se añade el nodo JSON que representa al item al nodo raiz.

Vamos a ejecutar la clase con una lista de 3 items de tres clases diferentes dentro de una jerarquía: Nivel1, Nivel2 y Nivel3. Nivel1 es padre de Nivel2 y Nivel2 es a su vez padre de Nivel. Cada una de ellas encapsula un atributo llamado campo1, campo2 y campo3 respectivamente.




...
...
     List<nivel1> elementos = new ArrayList<>();

     Nivel1 nivel1 = new Nivel1("1");
     Nivel2 nivel2 = new Nivel2("2", "3");
     Nivel3 nivel3 = new Nivel3("4","5","6");
     
     elementos.add(nivel1);
     elementos.add(nivel2);
     elementos.add(nivel3);
     
     JsonGenerator<nivel1> jsonGenerator = new JsonGenerator<>();
     jsonGenerator.setItems(elementos);
  
     System.out.println(jsonGenerator.generateJson());
...
...


La salida para este caso concreto sería:

{
  "item1" : {
    "name" : "",
    "data1" : {
      "name" : "campo1",
      "value" : "1"
    }
  },
  "item2" : {
    "name" : "",
    "data1" : {
      "name" : "campo1",
      "value" : "2"
    },
    "data2" : {
      "name" : "campo2",
      "value" : "3"
    }
  },
  "item3" : {
    "name" : "",
    "data1" : {
      "name" : "campo1",
      "value" : "4"
    },
    "data2" : {
      "name" : "campo2",
      "value" : "5"
    },
    "data3" : {
      "name" : "campo3",
      "value" : "6"
    }
  }
}

El código del proyecto en Netbeans lo podéis descargar de aquí.

Saludos.

Comentarios

Entradas populares de este blog

Soluciones Alchemy Classic 389 elementos

Hace algún tiempo salió una actualización del Juego Alchemy Classic en la que aparecían más elementos (389 en lugar de 238). Aparte de añadir elementos mejoran algunas traducciones en castellano y mejoran la interfaz, aunque todavía hay algún error en algunos nombres de elementos. Aquí os dejo las soluciones para los que estén atascados y no puedan dormir por las noches: Sustancia primaria Aire=Elemento primario  Fuego=Elemento primario  Agua=Elemento primario  Tierra=Sustancia Primaria Arena=Piedra + Aire Piedra=Tierra + Fuego Arcilla=Arena + Pantano Caliza=Tierra + Amonitas Carbono=Fuego + Madera Cloro=Fuego + Sal + Electricidad CO2(Dióxido de Carbono)=Ceniza + Ácido nítrico Electricidad=Relámpago+ Metales Gas natural= Yacimiento de gas + Pozo Helio=Refinería de gas + Gas Natural Hidrógeno=Electricidad + Agua Hielo=Frío + Agua Imán=Piedra + Metales Metano=Deshechos Vegetales + Pantano Oxígeno=Electricidad + Agua Petróleo=Unidad

JAXB: Leer y escribir ficheros XML

Muchas veces en nuestras aplicaciones debemos manejar documentos XML ( Extensible Markup Language ). Este lenguaje se ha convertido en un estándar para intercambio de datos entre programas y aplicaciones a través de Internet. En un esquema XML (o  XSD ) podemos definir los elementos que pueden aparecer en un documento XML así como las relaciones entre los mismos. JAXB ( Java Architecture for XML Binding ) es un estándar Java para transformar un esquema XML (o  XSD ) en una representación a objetos java. Mediante la API de JAXB podemos mapear un objeto Java a un documento XML ( "marshall" ) y el proceso contrario, es decir, a partir de un esquema XML crear su conjunto de objeto Java asociado ( "unmarshall" ). JAXB Resumiendo lo que nos proporciona JAXB es: Generación de objetos Java a partir de un XSD a través de un compilador Proporciona capacidades de marshall/unmarshall (escribir fichero XML desde java y al contrario) Integración con Maven a través de xj

Matemáticas y cine.

El otro día estaba viendo por la televisión una película llamada 21 blackjack . En una escena de la película el profesor de matemáticas ( Kevin Spacey ) le presenta a uno de sus alumnos la siguiente situación: se encuentra en un concurso en la que debe escoger entre tres puertas (1,2 y 3). En dos de ellas hay una cabra, sin embargo en una de las 3 hay un flamante coche nuevo. El alumno responde que quiere abrir la puerta. El presentador, conocedor de lo que hay detrás de cada puerta decide abrir otra puerta diferente mostrando detrás de ella una cabra. El profesor se dirige al alumno y le pregunta, ¿cambiarías la puerta o te quedarías con la puerta que tienes? Muchos de nosotros cambiaríamos de puerta pensando que es una treta del presentador para engañarnos. ¿Cual elegiríais vosotros? Al comienzo tenemos 1/3 de probabilidades de acertar la puerta donde está el coche. Una vez que el presentador abre la puerta con una cabra, la mayoría de gente piensa que hay la misma probabilidad de