使用 BeanUtils 或类似工具将非空属性从一个对象复制到另一个对象
Copy non-null properties from one object to another using BeanUtils or similar
我的目标是将一个对象的字段复制到另一个对象中,但仅限那些不为空的字段。我不想明确分配它。更通用的解决方案将非常有用且更易于维护,即在 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 保持原样。我是不是做错了什么?
我最终使用了 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;
}
}
它工作正常,但请注意 它忽略了作为集合的字段。那是故意的,我分开处理。
您可以创建自己的方法来复制属性,同时忽略空值。
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))
}
使用 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());
}
}
使用 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);
}
我最近遇到了类似的问题。我被要求实现一个通用解决方案,用于在 REST API 中实现 PATCH,您只允许提供特定字段。
该项目是一个 Java 项目,具有 MongoDB。
一开始我认为可以使用 Mongo java 驱动程序和操作 $set 来解决只传递应修改的字段的文档。经过广泛的研究后,我意识到这种方式行不通。如果您嵌套了 classes,它不会有选择地更新内部 class,而是替换它。我尝试了几种直接使用 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",
"friend": {
"age":2
}
}
此调用的目的是将 John Doe 的单个宠物的年龄更新为 2。但是上面覆盖的代码会将整个 Pet 结构替换为
{ "name": null,
"age" : 2
}
erasing the name information.
我最终的解决方案是在我发现嵌套内部 class 的地方递归调用。这样每一个都将被复制并保持以前的信息。为此,涉及的每个 class 都应该实现一个标记接口。
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);
}
}
请注意,如果 class 实现了标记接口,则此解决方案是通用的。
我的目标是将一个对象的字段复制到另一个对象中,但仅限那些不为空的字段。我不想明确分配它。更通用的解决方案将非常有用且更易于维护,即在 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 保持原样。我是不是做错了什么?
我最终使用了 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;
}
}
它工作正常,但请注意 它忽略了作为集合的字段。那是故意的,我分开处理。
您可以创建自己的方法来复制属性,同时忽略空值。
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))
}
使用 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());
}
}
使用 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);
}
我最近遇到了类似的问题。我被要求实现一个通用解决方案,用于在 REST API 中实现 PATCH,您只允许提供特定字段。
该项目是一个 Java 项目,具有 MongoDB。
一开始我认为可以使用 Mongo java 驱动程序和操作 $set 来解决只传递应修改的字段的文档。经过广泛的研究后,我意识到这种方式行不通。如果您嵌套了 classes,它不会有选择地更新内部 class,而是替换它。我尝试了几种直接使用 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", "friend": { "age":2 } }
此调用的目的是将 John Doe 的单个宠物的年龄更新为 2。但是上面覆盖的代码会将整个 Pet 结构替换为
{ "name": null, "age" : 2
} erasing the name information.
我最终的解决方案是在我发现嵌套内部 class 的地方递归调用。这样每一个都将被复制并保持以前的信息。为此,涉及的每个 class 都应该实现一个标记接口。
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);
}
}
请注意,如果 class 实现了标记接口,则此解决方案是通用的。