viernes, 5 de junio de 2015

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.

No hay comentarios:

Publicar un comentario en la entrada