BeanUtils的小坑

org.springframework.beans.BeanUtils

存在的问题:
会将源对象中字段为null的值,覆盖到目标有 值字段。

如下代码:

public class Test {

    public static void main(String []args){
       Demo demo=new Demo();
       demo.a="aaa";
       demo.b="bbb";

        Demo demo1=new Demo();

        demo1.c="ccc";

        BeanUtils.copyProperties(demo, demo1);

        System.out.println("demo:"+demo.toString());
        System.out.println("demo1:"+demo1.toString());

    }


    static class Demo{
        public   String a;
        public String b;
        public  String c;

        public String getA() {
            return a;
        }

        public void setA(String a) {
            this.a = a;
        }

        public String getB() {
            return b;
        }

        public void setB(String b) {
            this.b = b;
        }

        public String getC() {
            return c;
        }

        public void setC(String c) {
            this.c = c;
        }

        @Override
        public String toString() {
            return "Demo{" +
                    "a='" + a + '\'' +
                    ", b='" + b + '\'' +
                    ", c='" + c + '\'' +
                    '}';
        }
    }
}

运行结果:

demo:Demo{a=‘aaa’, b=‘bbb’, c=‘null’}
demo1:Demo{a=‘aaa’, b=‘bbb’, c=‘null’}

源码分析:

private static void copyProperties(Object source, Object target, Class<?> editable, String... ignoreProperties)
            throws BeansException {
        // 检查source和target对象是否为null,否则抛运行时异常
        Assert.notNull(source, "Source must not be null");
        Assert.notNull(target, "Target must not be null");
        // 获取target对象的类信息
        Class<?> actualEditable = target.getClass();
        // 若editable不为null,检查target对象是否是editable类的实例,若不是则抛出运行时异常
        // 这里的editable类是为了做属性拷贝时限制用的
        // 若actualEditable和editable相同,则拷贝actualEditable的所有属性
        // 若actualEditable是editable的子类,则只拷贝editable类中的属性
        if (editable != null) {
            if (!editable.isInstance(target)) {
                throw new IllegalArgumentException("Target class [" + target.getClass().getName() +
                        "] not assignable to Editable class [" + editable.getName() + "]");
            }
            actualEditable = editable;
        }
        // 获取目标类的所有PropertyDescriptor,getPropertyDescriptors这个方法请看下方
        PropertyDescriptor[] targetPds = getPropertyDescriptors(actualEditable);
        List<String> ignoreList = (ignoreProperties != null ? Arrays.asList(ignoreProperties) : null);
 
        for (PropertyDescriptor targetPd : targetPds) {
            // 获取该属性对应的set方法
            Method writeMethod = targetPd.getWriteMethod();
            // 属性的set方法存在 且 该属性不包含在忽略属性列表中
            if (writeMethod != null && (ignoreList == null || !ignoreList.contains(targetPd.getName()))) {
                // 获取source类相同名字的PropertyDescriptor, getPropertyDescriptor的具体实现看下方
                PropertyDescriptor sourcePd = getPropertyDescriptor(source.getClass(), targetPd.getName());
                if (sourcePd != null) {
                    // 获取对应的get方法
                    Method readMethod = sourcePd.getReadMethod();
                    // set方法存在 且 target的set方法的入参是source的get方法返回值的父类或父接口或者类型相同
                    // 具体ClassUtils.isAssignable()的实现方式请看下面详解
                    if (readMethod != null &&
                            ClassUtils.isAssignable(writeMethod.getParameterTypes()[0], readMethod.getReturnType())) {
                        try {
                            //get方法是否是public的
                            if (!Modifier.isPublic(readMethod.getDeclaringClass().getModifiers())) {
                                //暴力反射,取消权限控制检查
                                readMethod.setAccessible(true);
                            }
                            //获取get方法的返回值
                            Object value = readMethod.invoke(source);
                            // 原理同上
                            if (!Modifier.isPublic(writeMethod.getDeclaringClass().getModifiers())) {
                                writeMethod.setAccessible(true);
                            }
                            // 将get方法的返回值 赋值给set方法作为入参
                            writeMethod.invoke(target, value);
                        }
                        catch (Throwable ex) {
                            throw new FatalBeanException(
                                    "Could not copy property '" + targetPd.getName() + "' from source to target", ex);
                        }
                    }
                }
            }
        }

解决方案:

public static String[] getNullPropertyNames (Object source) {
        final BeanWrapper src = new BeanWrapperImpl(source);
        java.beans.PropertyDescriptor[] pds = src.getPropertyDescriptors();

        Set<String> emptyNames = new HashSet<String>();
        for(java.beans.PropertyDescriptor pd : pds) {
            Object srcValue = src.getPropertyValue(pd.getName());
            if (srcValue == null) emptyNames.add(pd.getName());
        }
        String[] result = new String[emptyNames.size()];
        return emptyNames.toArray(result);
    }

    public static void copyPropertiesIgnoreNull(Object src, Object target){
        BeanUtils.copyProperties(src, target, getNullPropertyNames(src));
    }

然后在调用这个方法进行复制:
BeanUtils.copyProperties(examLifeStyle, examDetail, getNullPropertyNames(examLifeStyle));

延伸拓展:

BeanUtils常用方法

1 populate(Object bean, Map<String,String[]>properties)

将Map数据封装到指定Javabean中,一般用于将表单的所有数据封装到javabean

2 setProperty(Object obj,String name,Object value)

设置属性值

3 getProperty(Object obj,String name)

获得属性值

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值