【问题标题】:Copy non-null properties from one object to another using BeanUtils or similar使用 BeanUtils 或类似方法将非空属性从一个对象复制到另一个对象
【发布时间】:2016-12-13 16:00:35
【问题描述】:

我的目标是将一个对象的字段复制到另一个对象中,但仅限于那些不为空的。我不想明确分配它。更通用的解决方案将非常有用且更易于维护,即用于在 REST API 中实现 PATCH,您只允许提供特定字段。

我看到了这个类似的帖子,我正在尝试从这里实现一些想法:Helper in order to copy non null properties from object to another ? (Java)

但程序执行后对象不会以任何方式改变。

所以这是我创建的示例类,例如:

class Person {
    String name;
    int age;
    Pet friend;

    public Person() {
    }

    public Person(String name, int age, Pet friend) {
        this.name = name;
        this.age = age;
        this.friend = friend;
    }

    // getters and setters here
}

class Pet {
    String name;
    int age;

    public Pet(String name, int age) {
        this.name = name;
        this.age = age;
    }

    // getters and setters here
}

这是我重写的 copyProperty 方法:

import org.apache.commons.beanutils.BeanUtilsBean;
import java.lang.reflect.InvocationTargetException;

public class MyBeansUtil extends BeanUtilsBean {

@Override
public void copyProperty(Object dest, String name, Object value)
        throws IllegalAccessException, InvocationTargetException {
    if(value == null) return;
    super.copyProperty(dest, name, value);
}
}

...这里是我尝试在一些示例上对其进行测试的地方:

public class SandBox {
    public static void main(String[] args) {
        Person db = new Person("John", 36, new Pet("Lucy", 3));
        Person db2 = new Person("John", 36, new Pet("Lucy", 2));
        Person db3 = new Person("John", 36, new Pet("Lucy", 4));

        Person in = new Person();
        in.age = 17;
        in.name = "Paul";
        in.friend = new Pet(null, 35);

        Person in2 = new Person();
        in2.name = "Damian";

        Person in3 = new Person();
        in3.friend = new Pet("Lup", 25);

        try {
            BeanUtilsBean notNull  =new MyBeansUtil();
            notNull.copyProperties(db, in);
            notNull.copyProperties(db2, in2);
            notNull.copyProperties(db3, in3);

        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        }
    }
}

不幸的是,原始对象 db、db1、db2 保持不变。我在这里做错了吗?

【问题讨论】:

  • 将类声明的访问修饰符更改为 public 以使其工作。 public class Person { } 会解决这个问题
  • 感谢您的回答 - 我已经找到了一个对我更好的解决方案,因为我避免依赖 apache.commons.beanutilsbean。现在我知道这两种方式:)
  • @kiedysktos 我有一个类似的用例,很想知道还有什么其他解决方案适合你。如果你能详细说明,那就太好了:)
  • 不幸的是,接受的答案代表了我尝试过的唯一方法

标签: java spring javabeans


【解决方案1】:

我最终使用了 Spring BeanUtils 库。这是我的工作方法:

import org.springframework.beans.BeanWrapper;
import org.springframework.beans.BeanWrapperImpl;

import java.lang.reflect.Field;
import java.util.Collection;

public class MyBeansUtil<T> {
    public T copyNonNullProperties(T target, T in) {
        if (in == null || target == null || target.getClass() != in.getClass()) return null;

        final BeanWrapper src = new BeanWrapperImpl(in);
        final BeanWrapper trg = new BeanWrapperImpl(target);

        for (final Field property : target.getClass().getDeclaredFields()) {
            Object providedObject = src.getPropertyValue(property.getName());
            if (providedObject != null && !(providedObject instanceof Collection<?>)) {
                trg.setPropertyValue(
                        property.getName(),
                        providedObject);
            }
        }
        return target;
    }
}

它工作正常,但请注意它会忽略作为集合的字段。这是故意的,我分开处理。

【讨论】:

  • BeanWrapperImpl 仅作为内部文档记录,因此不应使用。我认为这里提供的基于 commons-beanutils 的答案更好:stackoverflow.com/questions/1301697/…
  • 是的,这个链接在我的问题中,但它对我不起作用 - 这就是我以这种方式实现它的原因
  • 到@loicmathieu 的观点,而不是 new BeanWrapperImpl(target) 使用 PropertyAccessorFactory.forBeanPropertyAccess(target)
【解决方案2】:

您可以创建自己的方法来复制属性,同时忽略空值。

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);
}

// then use Spring BeanUtils to copy and ignore null
public static void myCopyProperties(Object src, Object target) {
    BeanUtils.copyProperties(src, target, getNullPropertyNames(src))
}

【讨论】:

    【解决方案3】:

    使用 BeanUtils 和 java8 我们可以实现:

    BeanUtils.copyProperties(Object_source, Object_target, getNullPropertyNames(Object_source));
    
    private String[] getNullPropertyNames(Object source) {
            final BeanWrapper wrappedSource = new BeanWrapperImpl(source);
            return Stream.of(wrappedSource.getPropertyDescriptors()).map(FeatureDescriptor::getName)
                    .filter(propertyName -> wrappedSource.getPropertyValue(propertyName) == null).toArray(String[]::new);
        }
    

    【讨论】:

      【解决方案4】:

      使用 ProprtyUtils,我们可以通过以下方式实现:

          private void copyNonNullProperties(Object destination,
                  Object source) {
              try {
                  PropertyUtils.describe(source).entrySet().stream()
                          .filter(source -> source.getValue() != null)
                          .filter(source -> !source.getKey().equals("class"))
                          .forEach(source -> {
                              try {
                                  PropertyUtils.setProperty(destination, source.getKey(), source.getValue());
                              } catch (Exception e22) {
                                  log.error("Error setting properties : {}", e22.getMessage());
                              }
                          });
      
              } catch (Exception e1) {
                  log.error("Error setting properties : {}", e1.getMessage());
              }
      
          }
      

      【讨论】:

        【解决方案5】:

        我最近遇到了类似的问题。我被要求实现一个通用解决方案,用于在 REST API 中实现 PATCH,您只允许提供特定字段。

        该项目是一个带有 MongoDB 的 Java 项目。

        一开始,我认为可以使用 Mongo java 驱动程序和 $set 操作来解决,该操作只传递应该修改的字段的文档。经过广泛的研究,我意识到它不是这样工作的。如果您有嵌套类,它不会选择性地更新内部类,而是替换它。我已经尝试了几个直接使用 Mongo java 驱动程序和 SpringMongoDB java API 的选项。

        然后我去了作者@kiedysktos描述的BeanUtils解决方案。

            public class MyBeansUtil extends BeanUtilsBean {
        
            @Override
            public void copyProperty(Object dest, String name, Object value)
                throws IllegalAccessException, InvocationTargetException {
                if(value == null) return;
                super.copyProperty(dest, name, value);
            }
            }
        

        事实证明,只这样做它也不会正常工作。想象一下,你用以下方式调用你的 PATCH

        { "name": "John Doe", “朋友”:{ “年龄”:2 } }

        此调用的目的是将 John Doe 的单个宠物的年龄更新为 2。但是上面覆盖的代码会将整个 Pet 结构替换为

        { "名称": null, “年龄”:2
        } 删除名称信息。

        我的最终解决方案是在找到嵌套内部类的地方递归调用。这样,每一个都将被复制并保留以前的信息。为此,所涉及的每个类都应实现一个标记接口。

            Person implements NonNullCopy
            Pet implements NonNullCopy
        

        最后是代码:

        class NullAwareBeanUtils extends BeanUtilsBean {
            
            
            @Override
            public void copyProperty(Object dest, String name, Object value)
                    throws IllegalAccessException, InvocationTargetException {
                if (value == null)
                    return;
                else if(value instanceof NonNullCopy) {
                    Class<?> destClazz = value.getClass();
                        Class<?> origClazz = dest.getClass();
                        String className = destClazz.getSimpleName();
                
                        //Recursively invokes copyProperties
                        for(Method m : origClazz.getDeclaredMethods()) {
                            if(m.getReturnType().equals(destClazz)) {
                                copyProperties(m.invoke(dest, Collections.EMPTY_LIST.toArray()),value);
                            }                       
                        }
                        return;
                }
        
                super.copyProperty(dest, name, value);
            }
        
               
        }
        
        
        

        请注意,如果类实现了标记接口,则此解决方案是通用的。

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2016-12-21
          • 1970-01-01
          • 2014-12-08
          • 2010-11-21
          • 2018-02-01
          相关资源
          最近更新 更多