递归BeanUtils.describe()

是否有一个版本的BeanUtils.describe(客户)以’customer’的复杂属性递归调用describe()方法。

class Customer { String id; Address address; } 

在这里,我想使用describe方法来检索address属性的内容。

目前,我所有人都可以看到该类的名称如下:

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

有趣的是,我想使用describe方法来检索嵌套属性的内容,我不明白为什么它没有。 不过,我继续前进并自己动手。 在这里,你可以打电话:

 Map beanMap = BeanUtils.recursiveDescribe(customer); 

一些警告。

  1. 我不确定公共BeanUtils如何在集合中格式化属性,所以我选择了“attribute [index]”。
  2. 我不确定它是如何在地图中格式化属性的,所以我选择了“attribute [key]”。
  3. 对于名称冲突,优先级如下:第一个属性从超类的字段加载,然后是类,然后从getter方法加载。
  4. 我没有分析这种方法的性能。 如果您的对象具有包含集合的大型对象集合,则可能存在一些问题。
  5. 这是alpha代码,不是没有bug的。
  6. 我假设你有最新版本的commons beanutils

另外,fyi,这大致取自我一直在研究的项目,亲切地称为java in jail,所以你可以下载它然后运行:

 Map beanMap = new SimpleMapper().toMap(customer); 

但是,您会注意到它返回String []而不是String,这可能无法满足您的需求。 无论如何,下面的代码应该工作,所以有它!

 public class BeanUtils { public static Map recursiveDescribe(Object object) { Set cache = new HashSet(); return recursiveDescribe(object, null, cache); } private static Map 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 beanMap = new TreeMap(); Map 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 getProperties(Object object) { Map propertyMap = getFields(object); //getters take precedence in case of any name collisions propertyMap.putAll(getGetterMethods(object)); return propertyMap; } private static Map getGetterMethods(Object object) { Map result = new HashMap(); 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 getFields(Object object) { return getFields(object, object.getClass()); } private static Map getFields(Object object, Class classType) { Map result = new HashMap(); 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 convertAll(Collection values, String key, Set cache) { Map valuesMap = new HashMap(); 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 convertMap(Map values, String key, Set cache) { Map valuesMap = new HashMap(); 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 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 valueMap = new HashMap(); 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); } } } 

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

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

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

并像这样初始化它:

  root / \ AB 

现在在root上调用describe。 将导致非递归调用

 {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调用describe。

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

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

 Map result = PropertyUtils.describe(obj); 

返回指定bean提供read方法的整个属性集。