java 递归 BeanUtils.describe()

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/6133660/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-10-30 14:29:45  来源:igfitidea点击:

Recursive BeanUtils.describe()

javareflectionapache-commons-beanutils

提问by TheLameProgrammer

Is there a version of BeanUtils.describe(customer)that recursively calls the describe() method on the complex attributes of 'customer'.

是否有一个BeanUtils.describe(customer)版本,它对“customer”的复杂属性递归调用 describe() 方法。

class Customer {

String id;
Address address;

}

Here, I would like the describe method to retrieve the contents of the address attribute as well.

在这里,我希望描述方法也检索地址属性的内容。

Currently, all I have can see the name of the class as follows:

目前,我所能看到的类的名称如下:

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

采纳答案by Peter Anthony

Funny, I would like the describe method to retrieve the contents of nested attributes as well, I don't understand why it doesn't. I went ahead and rolled my own, though. Here it is, you can just call:

有趣的是,我也希望 describe 方法能够检索嵌套属性的内容,我不明白为什么不这样做。不过,我继续推出自己的产品。在这里,您可以调用:

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

A couple of caveats.

几个警告。

  1. I'm wasn't sure how commons BeanUtils formatted attributes in collections, so i went with "attribute[index]".
  2. I'm wasn't sure how it formatted attributes in maps, so i went with "attribute[key]".
  3. For name collisions the precedence is this: First properties are loaded from the fields of super classes, then the class, then from the getter methods.
  4. I haven't analyzed the performance of this method. If you have objects with large collections of objects that also contain collections, you might have some issues.
  5. This is alpha code, not garunteed to be bug free.
  6. I am assuming that you have the latest version of commons beanutils
  1. 我不确定 BeanUtils 如何在集合中格式化属性,所以我选择了“属性 [索引]”。
  2. 我不确定它是如何格式化地图中的属性的,所以我选择了“attribute[key]”。
  3. 对于名称冲突,优先级是这样的:首先从超类的字段中加载属性,然后是类,然后是 getter 方法。
  4. 我还没有分析这个方法的性能。如果您的对象包含大量的对象集合并且也包含集合,那么您可能会遇到一些问题。
  5. 这是 alpha 代码,不能保证没有错误。
  6. 我假设您拥有最新版本的 commons beanutils

Also, fyi, this is roughly taken from a project I've been working on called, affectionately, java in jailsso you could just download it and then run:

另外,仅供参考,这大致取自我一直在从事的一个项目,亲切地称为jails中的java,因此您可以下载它然后运行:

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

Though, you'll notice that it returns a String[], instead of a String, which may not work for your needs. Anyway, the below code should work, so have at it!

但是,您会注意到它返回一个 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);
        }
    }
}

回答by Andreas Dolk

The challenge (or show stopper) is problem that we have to deal with an object graphinstead of a simple tree. A graph may contain cycles and that requires to develop some custom rules or requirements for the stop criteria inside the recursive algorithm.

挑战(或显示停止器)是我们必须处理对象而不是简单的树的问题。图可能包含循环,这需要为递归算法内的停止标准开发一些自定义规则或要求。

Have a look at a dead simple bean (a tree structure, getters are assumed but not shown):

看看一个死的简单 bean(一个树结构,假设了 getter 但没有显示):

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

and initialize it like this:

并像这样初始化它:

        root
        /  \
       A    B

Now call a describe on root. A non-recursive call would result in

现在调用描述root。非递归调用将导致

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

A recursive call instead would do a

相反,递归调用会执行

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)}}

and run into a StackOverflowErrorbecause describe is called with objects root, A and B over and over again.

并遇到 aStackOverflowError因为 describe 被对象根、A 和 B 一遍又一遍地调用。

One solution for a custom implementation could be to remember all objects that have been describedso far (record those instances in a set, stop if set.contains(bean) return true) and store some kind of linkin your result object.

自定义实现的一种解决方案可能是记住迄今为止描述的所有对象(将这些实例记录在一个集合中,如果 set.contains(bean) 返回 true,则停止)并在结果对象中存储某种链接

回答by Heitor

You can simple use from the same commom-beanutils:

您可以从相同的 commom-beanutils 中简单使用:

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

Return the entire set of properties for which the specified bean provides a read method.

返回指定 bean 为其提供读取方法的整个属性集。