Рекурсивный BeanUtils.describe () - PullRequest
12 голосов
/ 26 мая 2011

Существует ли версия BeanUtils.describe (customer) , которая рекурсивно вызывает метод description () для сложных атрибутов 'customer'.

class Customer {

String id;
Address address;

}

Здесь я хотел быкак и метод description для получения содержимого атрибута address.

В настоящее время все, что у меня есть, может видеть имя класса следующим образом:

{id=123, address=com.test.entities.Address@2a340e}

Ответы [ 3 ]

11 голосов
/ 07 декабря 2011

Забавно, я хотел бы, чтобы метод description также извлекал содержимое вложенных атрибутов, я не понимаю, почему это не так.Я пошел вперед и свернул свой собственный, хотя.Вот, вы можете просто позвонить:

Map<String,String> beanMap = BeanUtils.recursiveDescribe(customer); 

Несколько предостережений.

  1. Я не уверен, как общие атрибуты BeanUtils форматируют в коллекциях, поэтому я пошелс "атрибутом [индексом]".
  2. Я не был уверен, как он форматировал атрибуты в картах, поэтому я выбрал «attribute [key]».
  3. Для коллизий имен приоритет таков: сначала загружаются свойства из полей суперклассов, затем из класса, затем из методов получения.
  4. Я не анализировал производительность этогометод.Если у вас есть объекты с большими коллекциями объектов, которые также содержат коллекции, у вас могут возникнуть некоторые проблемы.
  5. Это альфа-код, не гарантирующий отсутствие ошибок.
  6. Я предполагаю, что у вас естьпоследняя версия commons beanutils

Кроме того, к вашему сведению, это примерно взято из проекта, над которым я работаю, называется, ласково, Java в тюрьмах , так что вы можете просто скачатьзатем запустите:

Map<String, String[]> beanMap = new SimpleMapper().toMap(customer);

Хотя вы заметите, что он возвращает String [] вместо String, что может не сработать для ваших нужд.В любом случае, приведенный ниже код должен работать, так что имейте это в виду!

public class BeanUtils {
    public static Map<String, String> recursiveDescribe(Object object) {
        Set cache = new HashSet();
        return recursiveDescribe(object, null, cache);
    }

    private static Map<String, String> recursiveDescribe(Object object, String prefix, Set cache) {
        if (object == null || cache.contains(object)) return Collections.EMPTY_MAP;
        cache.add(object);
        prefix = (prefix != null) ? prefix + "." : "";

        Map<String, String> beanMap = new TreeMap<String, String>();

        Map<String, Object> properties = getProperties(object);
        for (String property : properties.keySet()) {
            Object value = properties.get(property);
            try {
                if (value == null) {
                    //ignore nulls
                } else if (Collection.class.isAssignableFrom(value.getClass())) {
                    beanMap.putAll(convertAll((Collection) value, prefix + property, cache));
                } else if (value.getClass().isArray()) {
                    beanMap.putAll(convertAll(Arrays.asList((Object[]) value), prefix + property, cache));
                } else if (Map.class.isAssignableFrom(value.getClass())) {
                    beanMap.putAll(convertMap((Map) value, prefix + property, cache));
                } else {
                    beanMap.putAll(convertObject(value, prefix + property, cache));
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        return beanMap;
    }

    private static Map<String, Object> getProperties(Object object) {
        Map<String, Object> propertyMap = getFields(object);
        //getters take precedence in case of any name collisions
        propertyMap.putAll(getGetterMethods(object));
        return propertyMap;
    }

    private static Map<String, Object> getGetterMethods(Object object) {
        Map<String, Object> result = new HashMap<String, Object>();
        BeanInfo info;
        try {
            info = Introspector.getBeanInfo(object.getClass());
            for (PropertyDescriptor pd : info.getPropertyDescriptors()) {
                Method reader = pd.getReadMethod();
                if (reader != null) {
                    String name = pd.getName();
                    if (!"class".equals(name)) {
                        try {
                            Object value = reader.invoke(object);
                            result.put(name, value);
                        } catch (Exception e) {
                            //you can choose to do something here
                        }
                    }
                }
            }
        } catch (IntrospectionException e) {
            //you can choose to do something here
        } finally {
            return result;
        }

    }

    private static Map<String, Object> getFields(Object object) {
        return getFields(object, object.getClass());
    }

    private static Map<String, Object> getFields(Object object, Class<?> classType) {
        Map<String, Object> result = new HashMap<String, Object>();

        Class superClass = classType.getSuperclass();
        if (superClass != null) result.putAll(getFields(object, superClass));

        //get public fields only
        Field[] fields = classType.getFields();
        for (Field field : fields) {
            try {
                result.put(field.getName(), field.get(object));
            } catch (IllegalAccessException e) {
                //you can choose to do something here
            }
        }
        return result;
    }

    private static Map<String, String> convertAll(Collection<Object> values, String key, Set cache) {
        Map<String, String> valuesMap = new HashMap<String, String>();
        Object[] valArray = values.toArray();
        for (int i = 0; i < valArray.length; i++) {
            Object value = valArray[i];
            if (value != null) valuesMap.putAll(convertObject(value, key + "[" + i + "]", cache));
        }
        return valuesMap;
    }

    private static Map<String, String> convertMap(Map<Object, Object> values, String key, Set cache) {
        Map<String, String> valuesMap = new HashMap<String, String>();
        for (Object thisKey : values.keySet()) {
            Object value = values.get(thisKey);
            if (value != null) valuesMap.putAll(convertObject(value, key + "[" + thisKey + "]", cache));
        }
        return valuesMap;
    }

    private static ConvertUtilsBean converter = BeanUtilsBean.getInstance().getConvertUtils();

    private static Map<String, String> convertObject(Object value, String key, Set cache) {
        //if this type has a registered converted, then get the string and return
        if (converter.lookup(value.getClass()) != null) {
            String stringValue = converter.convert(value);
            Map<String, String> valueMap = new HashMap<String, String>();
            valueMap.put(key, stringValue);
            return valueMap;
        } else {
            //otherwise, treat it as a nested bean that needs to be described itself
            return recursiveDescribe(value, key, cache);
        }
    }
}
7 голосов
/ 26 мая 2011

Задача (или показать стопор) - это проблема, с которой нам приходится иметь дело с объектом graph вместо простого дерева.График может содержать циклы, и для этого требуется разработать некоторые пользовательские правила или требования для критериев остановки внутри рекурсивного алгоритма.

Посмотрите на мертвый простой бин (древовидная структура, получатели приняты, но не показаны):

public class Node {
   private Node parent;
   private Node left;
   private Node right;
}

и инициализируйте его следующим образом:

        root
        /  \
       A    B

Теперь вызовите описание для root.Нерекурсивный вызов может привести к

{parent=null, left=A, right=B}

. Вместо этого рекурсивный вызов будет выполнять

1: describe(root) =>
2: {parent=describe(null), left=describe(A), right=describe(B)} =>
3: {parent=null, 
     {A.parent=describe(root), A.left=describe(null), A.right= describe(null)}
     {B.parent=describe(root), B.left=describe(null), B.right= describe(null)}}

и работать с StackOverflowError, потому что описание вызывается с объектами root, A иB снова и снова.

Одним из решений для пользовательской реализации может быть запоминание всех объектов, которые были описаны до настоящего времени (запишите эти экземпляры в наборе, остановите, если set.contains (bean) верните true) и сохраните какую-нибудь ссылку в вашем объекте результата.

4 голосов
/ 16 июля 2014

Вы можете просто использовать из того же commom-beanutils:

Map<String, Object> result = PropertyUtils.describe(obj);

Возвращает весь набор свойств, для которых указанный компонент предоставляет метод чтения.

...