介绍
在做开发的过程中经常会遇到属性的复制,如果手动逐个进行复制会很麻烦,所以需要一个工具类来简化开发。主要考虑三种情况:
1、Pojo -> Pojo
2、List<Pojo> -> List<Pojo>
3、Map -> Pojo
下面介绍两种工具类的编写,一个是借助spring框架的BeanUtils,另一个是自己封装。
借助BeanUtils完成工具类
借助spring的BeanUtils有限制,只能在spring环境下使用,好处就是工具类很好编写,目前大多数项目都是使用spring,所以实用性还是很不错的
下面看代码:
import org.springframework.beans.BeanUtils;
import java.beans.PropertyDescriptor;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.stream.Collectors;
/**
* 属性复制工具类,有几点要求:
* 1、如果目标对象不是实例,则目标对象要有无参构造器
* 2、源对象要有getter方法,目标对象要有setter方法,并且属性类型一致或源对象属性是目标对象属性的子类
* 3、字段的泛型类型要一致或源对象是目标对象的子类型,否则取值时会报错{@link ClassCastException}
*/
public class CopyPropertiesUtil {
/*私有化构造器*/
private CopyPropertiesUtil() {
}
//两个pojo对象之间属性的复制
public static <T> T copyProperties(Object source, T target) {
validate(source, target);
BeanUtils.copyProperties(source, target);
return target;
}
//通过无参构造器实例化对象,进行属性复制
public static <T> T copyProperties(Object source, Class<T> target) {
T t = getInstance(target);
return copyProperties(source, t);
}
//复制集合中元素属性,使用无参构造器实例化对象
public static <T> List<T> copyListProperties(List<?> source, Class<T> target) {
Objects.requireNonNull(source, "source can not be null!");
return source.stream().map(sou -> copyProperties(sou, target)).collect(Collectors.toList());
}
//复制Map中的属性,key是属性名称,value是属性值
public static <T> T copyMapProperties(Map<String, ?> map, T target) {
validate(map,target);
return copyMapProperties(map, target, target.getClass());
}
//复制Map中的属性,key是属性名称,value是属性值,使用无参构造器实例化对象
public static <T> T copyMapProperties(Map<String, ?> map, Class<T> target) {
validate(map,target);
T t = getInstance(target);
return copyMapProperties(map, t, target);
}
@SuppressWarnings("unchecked")
private static <T> T copyMapProperties(Map<String, ?> map, Object target, Class<?> targetClazz) {
//获取Class对象中所有的属性描述符
PropertyDescriptor[] propertyDescriptors = BeanUtils.getPropertyDescriptors(targetClazz);
//遍历进行属性复制
for (PropertyDescriptor propertyDescriptor : propertyDescriptors) {
Method writeMethod = propertyDescriptor.getWriteMethod();
//写方法为空,进行下一个属性复制(即setter方法)
if (writeMethod == null) {
continue;
}
//获取属性的类型
Class<?> propertyType = propertyDescriptor.getPropertyType();
//获取属性的名称
String name = propertyDescriptor.getName();
//获取属性对应的值
Object o = map.get(name);
//如果属性值不为空,并且属性值对象可以赋值给目标属性
if (o != null && propertyType.isAssignableFrom(o.getClass())) {
try {
//设置写方法可执行
if (!writeMethod.isAccessible()) {
writeMethod.setAccessible(true);
}
writeMethod.invoke(target, o);
} catch (Exception e) {
throw new RuntimeException("invoke method " + writeMethod.getName() + " failed!", e);
}
}
}
return (T) target;
}
//无参构造器实例化对象
private static <T> T getInstance(Class<T> clazz) {
try {
Constructor<T> constructor = clazz.getDeclaredConstructor();
if (!constructor.isAccessible()) {
constructor.setAccessible(true);
}
return constructor.newInstance();
} catch (Exception e) {
throw new RuntimeException(clazz.getName() + " must have non arg constructor!", e);
}
}
//验证,源对象和目标对象都不能为空
private static void validate(Object source, Object target) {
Objects.requireNonNull(source, "source can not be null!");
Objects.requireNonNull(target, "target can not be null!");
}
}
解释下 “属性类型一致或源对象属性是目标对象属性的子类”
“属性类型一致”很好理解,例如,Integer可以赋值给Integer
“源对象属性是目标对象属性的子类”的意思是,从源对象中获取的属性值得类型是目标对象属性值类型的子类型,例如:源对象是:Integer,目标对象是:Number,Integer可以赋值给Number
当然,一般情况下,源对象和目标对象的属性类型都是一致的
自己手动封装,不借助框架的工具类
自己手动封装的好处就是在任何框架下都能使用,缺点就是工具类的编写比较麻烦。
首先认识一个与属性相关的类PropertyDescriptor,它是在java.beans包下,属于jdk自带的,下面看看这个类中常用的方法:
1、java.beans.PropertyDescriptor#PropertyDescriptor(String,Class<?>)
PropertyDescriptor的构造器,第一个参数是属性名称,第二个参数是属性所在类的Class对象
2、java.beans.FeatureDescriptor#getName()
父类中的方法,获取属性名称
3、java.beans.PropertyDescriptor#getReadMethod()
获取读方法,即getter方法
4、java.beans.PropertyDescriptor#getWriteMethod()
获取写方法,即setter方法
5、java.beans.PropertyDescriptor#getPropertyType()
获取属性的Class类型
PropertyDescriptor的源码还是比较简单的,有兴趣的可以看看
自定义反射器获取类的所有属性的PropertyDescriptor对象
下面是具体的实现代码:
import java.beans.IntrospectionException;
import java.beans.PropertyDescriptor;
import java.lang.reflect.Field;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
/**
* 属性查找规则:只找字段对应的属性(针对pojo对象,适用大部分情况,已经够用了)
* 更具体规则,查找getter和setter方法对应的属性
*/
public class Reflector {
private Class<?> clazz;
//存放属性和对应的属性描述符对象
private Map<String, PropertyDescriptor> descriptorMap;
public Reflector(Class<?> clazz) {
Objects.requireNonNull(clazz, "class can not be null!");
this.clazz = clazz;
descriptorMap = new HashMap<>();
init();
}
//查看是否含有对应的属性
public boolean containsProperty(String name) {
return descriptorMap.containsKey(name);
}
//返回属性描述的不可变的Map对象,防止无意识的修改
public Map<String, PropertyDescriptor> getDescriptorMap() {
return Collections.unmodifiableMap(descriptorMap);
}
//初始化descriptorMap集合
private void init() {
Class<?> currentClass = clazz;
//遍历类的继承体系
while (currentClass != Object.class) {
//只查找字段对应的属性
Field[] fields = currentClass.getDeclaredFields();
for (Field field : fields) {
String name = field.getName();
try {
PropertyDescriptor descriptor = new PropertyDescriptor(name, currentClass);
descriptorMap.put(name, descriptor);
} catch (IntrospectionException e) {
throw new RuntimeException("can not get " + name + " descriptor", e);
}
}
currentClass = currentClass.getSuperclass();
}
}
}
改造CopyPropertiesUtil工具类
看代码:
import java.beans.PropertyDescriptor;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.stream.Collectors;
/**
* 属性复制工具类,有几点要求:
* 1、如果目标对象不是实例,则目标对象要有无参构造器
* 2、源对象要有getter方法,目标对象要有setter方法,并且属性类型一致或源对象属性是目标对象属性的子类
* 3、字段的泛型类型要一致或源对象是目标对象的子类型,否则取值时会报错{@link ClassCastException}
*/
public class CopyPropertiesUtil {
//缓存
private static Map<Class<?>, Reflector> reflectorMap = new HashMap<>();
/*
* 锁对象,当然可以使用ConcurrentHashMap#putIfAbsent,但是在并发情况下可能会出现两次初始化Reflector对象
* */
/*
*
* private static Reflector getReflector(Class<?> clazz){
* ConcurrentHashMap<Class<?>, Reflector> reflectorMap = new ConcurrentHashMap<>();
* Reflector reflector = reflectorMap.get(clazz);
* if (reflector == null) {
* 当两个线程同时执行到此处时,会初始化两次Reflector对象,虽然没什么影响,但使用lock更好一点
* reflector = new Reflector(clazz);
* reflectorMap.putIfAbsent(clazz, reflector);
* }
* return reflector;
*}
* */
private static final Object lock = new Object();
/*私有化构造器*/
private CopyPropertiesUtil() {
}
//根据指定的Class对象获取Reflector对象
private static Reflector getReflector(Class<?> clazz) {
Reflector reflector = reflectorMap.get(clazz);
if (reflector == null) {
synchronized (lock) {
reflector = reflectorMap.get(clazz);
if (reflector == null) {
reflector = new Reflector(clazz);
reflectorMap.put(clazz, reflector);
}
}
}
return reflector;
}
//两个pojo对象之间属性的复制
public static <T> T copyProperties(Object source, T target) {
validate(source, target);
copyProperties(source, target, target.getClass());
return target;
}
//通过无参构造器实例化对象,进行属性复制
public static <T> T copyProperties(Object source, Class<T> target) {
validate(source, target);
T t = getInstance(target);
copyProperties(source, t, target);
return t;
}
private static void copyProperties(Object source, Object target, Class<?> targetClass) {
//获取源对象的ReflectorSupport
ReflectorSupport sourceReflectorSupport = getReflectorSupport(source.getClass(), source);
//获取目标对象的ReflectorSupport
ReflectorSupport targetReflectorSupport = getReflectorSupport(targetClass, target);
//属性复制
copyProperties(sourceReflectorSupport, targetReflectorSupport);
}
private static void copyProperties(ReflectorSupport sourceReflectorSupport, ReflectorSupport targetReflectorSupport) {
Map<String, PropertyDescriptor> sourceDescriptorMap = sourceReflectorSupport.getDescriptorMap();
Map<String, PropertyDescriptor> targetDescriptorMap = targetReflectorSupport.getDescriptorMap();
//遍历源对象的属性描述符map对象
sourceDescriptorMap.forEach((name, sourceDescriptor) -> {
//获取目标对象的属性描述符
PropertyDescriptor targetDescriptor = targetDescriptorMap.get(name);
//为空则继续下一个
if (targetDescriptor == null) {
return;
}
//获取目标对象的写方法(setter方法)
Method writeMethod = targetDescriptor.getWriteMethod();
//获取源对象的读方法(getter方法)
Method readMethod = sourceDescriptor.getReadMethod();
//有一个为空,则继续下一个
if (writeMethod == null || readMethod == null) {
return;
}
//获取目标对象属性的Class对象
Class<?> targetPropertyType = targetDescriptor.getPropertyType();
//获取源对象属性的Class对象
Class<?> sourcePropertyType = sourceDescriptor.getPropertyType();
//源对象属性的要可赋值给目标对象属性
if (targetPropertyType.isAssignableFrom(sourcePropertyType)) {
if (!writeMethod.isAccessible()) {
writeMethod.setAccessible(true);
}
if (!readMethod.isAccessible()) {
readMethod.setAccessible(true);
}
try {
writeMethod.invoke(targetReflectorSupport.object, readMethod.invoke(sourceReflectorSupport.object));
} catch (Exception e) {
throw new RuntimeException("method invoke failed and property is " + name, e);
}
}
});
}
private static ReflectorSupport getReflectorSupport(Class<?> clazz, Object object) {
Reflector reflector = getReflector(clazz);
return new ReflectorSupport(reflector, object);
}
//内部辅助类
private static class ReflectorSupport {
Reflector reflector;
Object object;
public ReflectorSupport(Reflector reflector, Object object) {
this.reflector = reflector;
this.object = object;
}
public Map<String, PropertyDescriptor> getDescriptorMap() {
return reflector.getDescriptorMap();
}
}
//复制集合中元素属性,使用无参构造器实例化对象
public static <T> List<T> copyListProperties(List<?> source, Class<T> target) {
Objects.requireNonNull(source, "source can not be null!");
return source.stream().map(sou -> copyProperties(sou, target)).collect(Collectors.toList());
}
//复制Map中的属性,key是属性名称,value是属性值
public static <T> T copyMapProperties(Map<String, ?> map, T target) {
validate(map, target);
return copyMapProperties(map, target, target.getClass());
}
//复制Map中的属性,key是属性名称,value是属性值,使用无参构造器实例化对象
public static <T> T copyMapProperties(Map<String, ?> map, Class<T> target) {
validate(map, target);
T t = getInstance(target);
return copyMapProperties(map, t, target);
}
@SuppressWarnings("unchecked")
private static <T> T copyMapProperties(Map<String, ?> map, Object target, Class<?> targetClazz) {
//获取Class的Reflector对象
Reflector reflector = getReflector(targetClazz);
//获取所有的属性描述符
Map<String, PropertyDescriptor> descriptorMap = reflector.getDescriptorMap();
//遍历map对属性进行赋值
map.forEach((name, value) -> {
//根据属性名称获取属性的描述符对象
PropertyDescriptor propertyDescriptor = descriptorMap.get(name);
//为空则继续下一个
if (propertyDescriptor == null) {
return;
}
//获取写方法(setter方法)
Method writeMethod = propertyDescriptor.getWriteMethod();
//为空则继续下一个
if (writeMethod == null) {
return;
}
//获取属性的类型
Class<?> propertyType = propertyDescriptor.getPropertyType();
//如果属性值不为空,并且属性值对象可以赋值给目标属性
if (value != null && propertyType.isAssignableFrom(value.getClass())) {
//设置方法可执行
if (!writeMethod.isAccessible()) {
writeMethod.setAccessible(true);
}
try {
writeMethod.invoke(target, value);
} catch (Exception e) {
throw new RuntimeException("method invoke failed and property is " + name, e);
}
}
});
return (T) target;
}
//无参构造器实例化对象
private static <T> T getInstance(Class<T> clazz) {
try {
Constructor<T> constructor = clazz.getDeclaredConstructor();
if (!constructor.isAccessible()) {
constructor.setAccessible(true);
}
return constructor.newInstance();
} catch (Exception e) {
throw new RuntimeException(clazz.getName() + " must have non arg constructor!", e);
}
}
//验证,源对象和目标对象都不能为空
private static void validate(Object source, Object target) {
Objects.requireNonNull(source, "source can not be null!");
Objects.requireNonNull(target, "target can not be null!");
}
}
改造过后的工具类,可以在摆脱框架的束缚。
总结
没有考虑过多的业务场景,但是已经可以适用大部分情况了。