BeanUtils.copyProperties() 在 spring 和 apache.commons 区别

BeanUtils.copyProperties() 在 spring 和 apache.commons 实现区别

BeanUtils.copyProperties() 方法在java中被广泛使用,用于复制两个bean里面相同的field。最近有个同事代码遇到一个bug,有个bean类是这样书写

class Test {
	private Integer t;//声明是包装类
	public int getT() {   //返回是基本类型
        return t;
  	}

    public void setT(Integer t) {
        this.t= t;
    }
}

bug就是当把一个含有 t 属性的bean 使用apache的copyProperties 复制给 Test类的时候,这时候Test的t是null。
而使用spring的copyProperties则不会出现该bug,即Test的t是有值的。
当然问题找到了,解决方法也很简单,就是把return 类型 要改成跟成员变量一样,这也是java规范。
问题到这解决了。
当然,我就想看下源码,看看这两个大社区的开发者究竟是怎么实现该方法的。为什么会由此差异。
先不看源码假设让自己实现的话,就是用反射原理,从新bean找出field,然后在原bean的根据name和类型找到对应的filed,假如原bean有get方法,新bean有set方法,就利用invoke进行方法执行。

apache社区的实现呢,为什么就不行呢,源码如下:

final PropertyDescriptor[] origDescriptors =
                getPropertyUtils().getPropertyDescriptors(orig);
                //获取原bean的PropertyDescriptor。一个PropertyDescriptor就是代表一个java bean的属性(包括他的set和get方法)
            for (PropertyDescriptor origDescriptor : origDescriptors) {
                final String name = origDescriptor.getName();
                if ("class".equals(name)) {
                    continue; // No point in trying to set an object's class
                }
                //判断原bean的 名为name的属性的get方法和新bean对应的属性的set方法是否都有且可读
                if (getPropertyUtils().isReadable(orig, name) &&
                    getPropertyUtils().isWriteable(dest, name)) {
                    try {
                        final Object value =
                            getPropertyUtils().getSimpleProperty(orig, name);
                        copyProperty(dest, name, value);
                    } catch (final NoSuchMethodException e) {
                        // Should not happen
                    }
                }
            }

从源码最外层来看,是根据原bean的各个属性,然后在新bean找出对应属性,看原bean的get方法和新bean的set方法是否都有且可读,如果有则进行复制。
目前看不出问题。我通过断点,发现我们产生的bug的情况,在isReadable是true,在isWriteable是false。进入isWriteable方法,可以看到一行。

final PropertyDescriptor desc =
                    getPropertyDescriptor(bean, name);

也是获取PropertyDescriptor,然后对这个属性做判断。那看来是该属性对写入方法判断出现了问题。PropertyDescriptor 是java官方类。getPropertyDescriptor是apache写的。我打断点发现,当对新bean解析后,PropertyDescriptor结果显示,新bean的t属性是int,然后没有writable方法,很明显这个属性解析错了,新bean的t还是integer,只是read方法返回写成了int。于是看下PropertyDescriptor 里面的源码,解析bean类型的方法如下:
/**
* Returns the property type that corresponds to the read and write method.
* The type precedence is given to the readMethod.
*
* @return the type of the property descriptor or null if both
* read and write methods are null.
* @throws IntrospectionException if the read or write method is invalid
*/

Class<?> propertyType = null;
        try {
            if (readMethod != null) {
                Class<?>[] params = getParameterTypes(getClass0(), readMethod);
                if (params.length != 0) {
                    throw new IntrospectionException("bad read method arg count: "
                                                     + readMethod);
                }
                propertyType = getReturnType(getClass0(), readMethod);
                if (propertyType == Void.TYPE) {
                    throw new IntrospectionException("read method " +
                                        readMethod.getName() + " returns void");
                }
            }
            if (writeMethod != null) {
                Class<?>[] params = getParameterTypes(getClass0(), writeMethod);
                if (params.length != 1) {
                    throw new IntrospectionException("bad write method arg count: "
                                                     + writeMethod);
                }
                if (propertyType != null && !params[0].isAssignableFrom(propertyType)) {
                    throw new IntrospectionException("type mismatch between read and write methods");
                }
                propertyType = params[0];
            }
        } catch (IntrospectionException ex) {
            throw ex;
        }
        return propertyType;

其实看注释已经知道了,原来解析对应属性的返回类型是根据read和write方法判断的,然后read方法优先。
到此破案了。 原来是read方法返回是int,所以该属性类型就是int了。然后根据int找对应的write方法自然就找不到了。所以就没复制了。

接下来看spring社区的实现。大概实现类似。代码如下:

//actualEditable就是新bean的class类,getPropertyDescriptors 根据class获取对应的bean内容
PropertyDescriptor[] targetPds = getPropertyDescriptors(actualEditable);
        List<String> ignoreList = ignoreProperties != null ? Arrays.asList(ignoreProperties) : null;
        PropertyDescriptor[] var7 = targetPds;//var7就代表新bean里面的field内容
        int var8 = targetPds.length;

        for(int var9 = 0; var9 < var8; ++var9) {
            PropertyDescriptor targetPd = var7[var9];
            //循环遍历新bean的field,找到对应的写方法
            Method writeMethod = targetPd.getWriteMethod();/
            if (writeMethod != null && (ignoreList == null || !ignoreList.contains(targetPd.getName()))) {
            //getPropertyDescriptor 根据原bean的class以及要写入的filed name找到对应的filed
                PropertyDescriptor sourcePd = getPropertyDescriptor(source.getClass(), targetPd.getName());
                if (sourcePd != null) {
                //找到对应的写方法
                    Method readMethod = sourcePd.getReadMethod();
                    if (readMethod != null && ClassUtils.isAssignable(writeMethod.getParameterTypes()[0], readMethod.getReturnType())) {
                    //找到对应的写方法且类型一致
                        try {
                            if (!Modifier.isPublic(readMethod.getDeclaringClass().getModifiers())) {
                                readMethod.setAccessible(true);
                            }

                            Object value = readMethod.invoke(source);
                            if (!Modifier.isPublic(writeMethod.getDeclaringClass().getModifiers())) {
                                writeMethod.setAccessible(true);
                            }
                        //则执行
                            writeMethod.invoke(target, value);
                        } catch (Throwable var15) {
                            throw new FatalBeanException("Could not copy property '" + targetPd.getName() + "' from source to target", var15);
                        }
                    }
                }
            }
        }

其实核心方法就是获取Property getPropertyDescriptors(actualEditable);
这个方法里面内容跟apache实现很像。但是在往里面看,可以看到spring 在获取了property后做了一层处理。

if (writeMethodToUse == null && readMethodToUse != null) {
			// Fallback: Original JavaBeans introspection might not have found matching setter
			// method due to lack of bridge method resolution, in case of the getter using a
			// covariant return type whereas the setter is defined for the concrete property type.
			Method candidate = ClassUtils.getMethodIfAvailable(
					this.beanClass, "set" + StringUtils.capitalize(getName()), (Class<?>[]) null);
			if (candidate != null && candidate.getParameterTypes().length == 1) {
				writeMethodToUse = candidate;
			}
		}

这个方法是对当能获取到get方法,却没有set方法的处理。看注释也都写了,这个就是为了防止像事例这种bug,int 和integer 没有统一。所以spring做了个兼容。就不会出现相关情况了

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值