是否可以使用Commons Bean Utils自动实例化嵌套属性?

时间:2011-02-02 17:32:46

标签: java reflection apache-commons-beanutils

我正在使用Apache Commons Bean Utils的 PropertyUtils.setProperty(object,name,value)方法:

提供这些课程:

public class A {
    B b;
}

public class B {
    C c;
}

public class C {
}

而且:

A a = new A();
C c = new C();
PropertyUtils.setProperty(a, "b.c", c); //exception

如果我尝试我得到: org.apache.commons.beanutils.NestedNullException:bean类“A类”上的'b.c'的空属性值

是否有可能告诉PropertyUtils,如果嵌套属性具有空值,尝试在尝试深入之前实例化它(默认构造函数)?

还有其他办法吗?

谢谢

7 个答案:

答案 0 :(得分:9)

我这样解决了这个问题:

private void instantiateNestedProperties(Object obj, String fieldName) {
    try {
        String[] fieldNames = fieldName.split("\\.");
        if (fieldNames.length > 1) {
            StringBuffer nestedProperty = new StringBuffer();
            for (int i = 0; i < fieldNames.length - 1; i++) {
                String fn = fieldNames[i];
                if (i != 0) {
                    nestedProperty.append(".");
                }
                nestedProperty.append(fn);

                Object value = PropertyUtils.getProperty(obj, nestedProperty.toString());

                if (value == null) {
                    PropertyDescriptor propertyDescriptor = PropertyUtils.getPropertyDescriptor(obj, nestedProperty.toString());
                    Class<?> propertyType = propertyDescriptor.getPropertyType();
                    Object newInstance = propertyType.newInstance();
                    PropertyUtils.setProperty(obj, nestedProperty.toString(), newInstance);
                }
            }
        }
    } catch (IllegalAccessException e) {
        throw new RuntimeException(e);
    } catch (InvocationTargetException e) {
        throw new RuntimeException(e);
    } catch (NoSuchMethodException e) {
        throw new RuntimeException(e);
    } catch (InstantiationException e) {
        throw new RuntimeException(e);
    }
}

答案 1 :(得分:5)

我知道问题是关于apache commons PropertyUtils.setProperty,但是有非常类似的功能 Spring Expression Language "SpEL“它完全符合你的要求。更好的是它也处理列表和数组。上面的doc链接适用于spring 4.x但是下面的代码适用于我在春季3.2.9。

    StockOrder stockOrder = new StockOrder(); // Your root class here

    SpelParserConfiguration config = new SpelParserConfiguration(true,true);   // auto create objects if null
    ExpressionParser parser = new SpelExpressionParser(config);
    StandardEvaluationContext modelContext = new StandardEvaluationContext(stockOrder);

    parser.parseExpression("techId").setValue(modelContext, "XXXYYY1");
    parser.parseExpression("orderLines[0].partNumber").setValue(modelContext, "65498");
    parser.parseExpression("orderLines[0].inventories[0].serialNumber").setValue(modelContext, "54686513216");

    System.out.println(ReflectionToStringBuilder.toString(stockOrder));

答案 2 :(得分:2)

稍作修正:

String fn = fieldNames[i];
if (i != 0) {
        nestedProperty.append(".");
}
nestedProperty.append(fn);
Object value = PropertyUtils.getProperty(obj, nestedProperty.toString());

答案 3 :(得分:0)

我只使用了没有Apache库的反射来实现这一点。假设所有要遍历的对象都是POJO,默认构造是公共可访问的。这样,就不需要为每个循环构造引用路径。

public Object getOrCreateEmbeddedObject(Object inputObj,String[] fieldNames) throws Exception {

    Object cursor = inputObj;

    //Loop until second last index
    for (int i = 0; i < fieldNames.length - 1; i++){
        Field ff = getClassFieldFrom(cursor,fieldNames[i]);
        Object child = ff.get(cursor);
        if(null == child) {
            Class<?> cls=ff.getType();
            child = cls.newInstance();
            ff.set(cursor, child);
        }
        cursor = child;
    }

    return cursor;
}

private Field getClassFieldFrom(Object object, String fieldStr)
            throws NoSuchFieldException {
        java.lang.reflect.Field ff = object.getClass().getDeclaredField(fieldStr);
        ff.setAccessible(true);
        return ff;
}

如果您对我的解决方案有任何建议,请告诉我。

答案 4 :(得分:0)

我选择了默认情况下实例化每个对象的基本方法:

public class A {
    B b = new B();
}

public class B {
    C c = new C();
}

public class C {
}

不理想,但它适用于我的情况,并没有涉及复杂的修复。

答案 5 :(得分:0)

恕我直言,最好的解决方案是摆脱commons-beanutils并使用Spring Framework org.springframework.beans.PropertyAccessorFactory

BeanWrapper wrapper = PropertyAccessorFactory.forBeanPropertyAccess(targetObject);
wrapper.setAutoGrowNestedPaths(true);

我不会深入研究它的工作原理,但是如果您想查看它,请看一下上面的链接,该API非常直观,但是您需要具有Spring Framework Core是在您的类路径中配置的,因此我不建议您仅出于此功能而添加spring。

但是

如果您只有 commons-beanutils 作为盟友,则以下代码段可帮助您在设置值时扩展嵌套路径,因此,您无需担心路径属性中的空对象。

在此示例中,我与JPA元组查询一起使用,构造了一个带有一些特定属性路径的自定义对象,并设置了相应的值。

import java.util.ArrayList;
import java.util.List;

import javax.persistence.Tuple;
import javax.persistence.TupleElement;

import org.apache.commons.beanutils.PropertyUtils;
import org.apache.commons.beanutils.expression.DefaultResolver;
public class TupleToObject<T> {


    public List<T> transformResult(List<Tuple> result, Class<T> targetClass) {
        try {
            List<T> objects = new ArrayList<>();
            for (Tuple tuple : result) {
                T target = targetClass.newInstance();

                List<TupleElement<?>> elements = tuple.getElements();
                for (TupleElement<?> tupleElement : elements) {
                    String alias = tupleElement.getAlias();
                    Object value = tuple.get(alias);

                    if (value != null) {
                        instantiateObject(target, alias);
                        PropertyUtils.setNestedProperty(target, alias, value);
                    }

                }
                objects.add(target);
            }
            return objects;
        } catch (Exception e) {
            throw new RuntimeException(e);
        }

    }

    private void instantiateObject(T target, String propertyPath) throws Exception {
        DefaultResolver resolver = new DefaultResolver();
        Object currentTarget = target;
        while (resolver.hasNested(propertyPath)) {
            final String property = resolver.next(propertyPath);
            Object value = PropertyUtils.getSimpleProperty(currentTarget, property);
            if (value == null) {
                Class<?> propertyType = PropertyUtils.getPropertyType(currentTarget, property);
                value = propertyType.newInstance();
                PropertyUtils.setSimpleProperty(currentTarget, property, value);
            }
            currentTarget = value;

            propertyPath = resolver.remove(propertyPath);
        }
    }
}

此代码使用commons-beanutils-1.9.3.jar

答案 6 :(得分:-1)

在做了一些研究之后,“是否有可能......”的简短回答是