PropertyUtils.copyProperties 属性值复制失败

这两天碰到了一个令人头秃的问题,花费了我一整天的时间最终跟踪到了问题根源:

    @Test
    public void testOrderPage(){
        Page page = new Page();
        Pagination pagination = new Pagination(2,5);
        try {
            // 这里copy时,pagination中的current(2)和size(5)并没有copy到page对象
            PropertyUtils.copyProperties(page, pagination);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
public class Page {
    // ...
    protected long current = 1;
    // ...
    public long getCurrent() {
        return this.current;
    }
    // 根源就是在这里的set方法返回值不是void
    public Page setCurrent(long current) {
        this.current = current;
        return this;
    }
    // ...
}

public class Pagination {
    // ...
    public Pagination(long current, long size) {
        if (current > 1) {
            this.current = current;
        }
        this.size = size;
    }
    // ...
    private long size = 10;
    private long current = 1;
    // ...
    public long getSize() {
        return size;
    }
    // ...
    public void setSize(long size) {
        this.size = size;
    }
    // ...
    public long getCurrent() {
        return current;
    }
    public void setCurrent(int current) {
        this.current = current;
    }
}

一路跟踪下去

org\apache\commons\beanutils\PropertyUtils.java

public static void copyProperties(final Object dest, final Object orig){
    PropertyUtilsBean.getInstance().copyProperties(dest, orig);
}

org\apache\commons\beanutils\PropertyUtilsBean.java

public void copyProperties(final Object dest, final Object orig){
    if (...) {
        // ...
    } else /* if (orig is a standard JavaBean) */ {
        final PropertyDescriptor[] origDescriptors = getPropertyDescriptors(orig);
        for (PropertyDescriptor origDescriptor : origDescriptors) {
            final String name = origDescriptor.getName();
            // 跟踪到这里的isWriteable,是判断目标对象的name属性是否可写
            if (isReadable(orig, name) && isWriteable(dest, name)) {
                // ...
            }
        }
    }
}

public boolean isWriteable(Object bean, String name) {
    // ...
    // 为null的原因在这里 ↓
    final PropertyDescriptor desc = getPropertyDescriptor(bean, name);
    if (desc != null) {
        // 这里应该拿到目标对象属性的set方法,但是为null,见上面 ↑
        Method writeMethod = getWriteMethod(bean.getClass(), desc);
        if (writeMethod == null) {
            // ...
        }
        return (writeMethod != null);
    } else {
        return (false);
    }
}


public PropertyDescriptor getPropertyDescriptor(Object bean, String name){
    // ...
    // 通过BeanIntrospectionData 对象的 getDescriptor 获取属性描述对象
    final BeanIntrospectionData data = getIntrospectionData(bean.getClass());
    PropertyDescriptor result = data.getDescriptor(name);
    // ...
    return result;
}

private BeanIntrospectionData getIntrospectionData(final Class<?> beanClass) {
    // ...
    // 这里还用了个缓存 descriptorsCache,通过 beanClass 获取 BeanIntrospectionData 对象
    BeanIntrospectionData data = descriptorsCache.get(beanClass);
    if (data == null) {
        data = fetchIntrospectionData(beanClass);
        descriptorsCache.put(beanClass, data);
    }
    return data;
}

private BeanIntrospectionData fetchIntrospectionData(final Class<?> beanClass) {
    final DefaultIntrospectionContext ictx = new DefaultIntrospectionContext(beanClass);
    for (final BeanIntrospector bi : introspectors) {
        try {
            // 这里就是处理获取 PropertyDescriptors 的地方,然后追踪到 org.apache.commons.beanutils.DefaultBeanIntrospector#introspect
            bi.introspect(ictx);
        } catch (final IntrospectionException iex) {
            log.error("Exception during introspection", iex);
        }
    }
    // 这里会把处理后的 PropertyDescriptors 设置到 BeanIntrospectionData 里并返回
    return new BeanIntrospectionData(ictx.getPropertyDescriptors());
}

 

org\apache\commons\beanutils\DefaultBeanIntrospector.java

public void introspect(final IntrospectionContext icontext) {
    // 通过 jdk 的 Introspector.getBeanInfo -> beanInfo.getPropertyDescriptors() 获取 PropertyDescriptor[]
    BeanInfo beanInfo = null;
    try {
        beanInfo = Introspector.getBeanInfo(icontext.getTargetClass());
    } catch (final IntrospectionException e) { //... }
    PropertyDescriptor[] descriptors = beanInfo.getPropertyDescriptors();
    // ...
    icontext.addPropertyDescriptors(descriptors);
}

java\beans\Introspector.java

public static BeanInfo getBeanInfo(Class<?> beanClass){
    // ...
    // 这里获取的 BeanInfo 
    beanInfo = new Introspector(beanClass, null, USE_ALL_BEANINFO).getBeanInfo();
    // ...
    return beanInfo;
}

private BeanInfo getBeanInfo() throws IntrospectionException {
    // ...
    // 这里获取的 PropertyDescriptor[]
    PropertyDescriptor pds[] = getTargetPropertyInfo();
    return new GenericBeanInfo(bd, esds, defaultEvent, pds,
                    defaultProperty, mds, explicitBeanInfo);
}


private PropertyDescriptor[] getTargetPropertyInfo() {
    // ...
    // 跟踪到这里发现在 PropertyInfo 中就没有获取到属性的set方法,最终锁定 ClassInfo.getProperties() 方法
    for (Map.Entry<String,PropertyInfo> entry : ClassInfo.get(this.beanClass).getProperties().entrySet()) {
        addPropertyDescriptor(null != entry.getValue().getIndexed()
                ? new IndexedPropertyDescriptor(entry, this.propertyChangeSource)
                : new PropertyDescriptor(entry, this.propertyChangeSource));
    }
    // ...
}

com\sun\beans\introspect\ClassInfo.java

public Map<String,PropertyInfo> getProperties() {
    if (this.properties == null) {
        synchronized (this.mutex) {
            if (this.properties == null) {
                this.properties = PropertyInfo.get(this.type);
            }
        }
    }
    return this.properties;
}

com\sun\beans\introspect\PropertyInfo.java

public static Map<String,PropertyInfo> get(Class<?> type) {
    List<Method> methods = ClassInfo.get(type).getMethods();
    if (methods.isEmpty()) {
        return Collections.emptyMap();
    }
    Map<String,PropertyInfo> map = new TreeMap<>();
    for (Method method : methods) {
        if (!Modifier.isStatic(method.getModifiers())) {
            Class<?> returnType = method.getReturnType();
            String name = method.getName();
            switch (method.getParameterCount()) {
                case 0:
                    if (returnType.equals(boolean.class) && isPrefix(name, "is")) {
                        PropertyInfo info = getInfo(map, name.substring(2), false);
                        info.read = new MethodInfo(method, boolean.class);
                    } else if (!returnType.equals(void.class) && isPrefix(name, "get")) {
                        PropertyInfo info = getInfo(map, name.substring(3), false);
                        info.readList = add(info.readList, method, method.getGenericReturnType());
                    }
                    break;
                case 1:
                    // 最终就是在这里跳过了 set 方法,因为 returnType 不是 void
                    if (returnType.equals(void.class) && isPrefix(name, "set")) {
                        PropertyInfo info = getInfo(map, name.substring(3), false);
                        info.writeList = add(info.writeList, method, method.getGenericParameterTypes()[0]);
                    } else if (!returnType.equals(void.class) && method.getParameterTypes()[0].equals(int.class) && isPrefix(name, "get")) {
                        PropertyInfo info = getInfo(map, name.substring(3), true);
                        info.readList = add(info.readList, method, method.getGenericReturnType());
                    }
                    break;
                case 2:
                    if (returnType.equals(void.class) && method.getParameterTypes()[0].equals(int.class) && isPrefix(name, "set")) {
                        PropertyInfo info = getInfo(map, name.substring(3), true);
                        info.writeList = add(info.writeList, method, method.getGenericParameterTypes()[1]);
                    }
                    break;
            }
        }
    }
    Iterator<PropertyInfo> iterator = map.values().iterator();
    while (iterator.hasNext()) {
        if (!iterator.next().initialize()) {
            iterator.remove();
        }
    }
    return !map.isEmpty()
            ? Collections.unmodifiableMap(map)
            : Collections.emptyMap();
}

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值