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
Recursive BeanUtils.describe()
提问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.
几个警告。
- I'm wasn't sure how commons BeanUtils formatted attributes in collections, so i went with "attribute[index]".
- I'm wasn't sure how it formatted attributes in maps, so i went with "attribute[key]".
- 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.
- 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.
- This is alpha code, not garunteed to be bug free.
- I am assuming that you have the latest version of commons beanutils
- 我不确定 BeanUtils 如何在集合中格式化属性,所以我选择了“属性 [索引]”。
- 我不确定它是如何格式化地图中的属性的,所以我选择了“attribute[key]”。
- 对于名称冲突,优先级是这样的:首先从超类的字段中加载属性,然后是类,然后是 getter 方法。
- 我还没有分析这个方法的性能。如果您的对象包含大量的对象集合并且也包含集合,那么您可能会遇到一些问题。
- 这是 alpha 代码,不能保证没有错误。
- 我假设您拥有最新版本的 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 StackOverflowError
because 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 为其提供读取方法的整个属性集。