诞生由来
之前项目里就经常有bean之间的属性copy,一直都是用spring自带的工具类来解决,方法如下:
org.springframework.beans.BeanUtils#copyProperties(java.lang.Object, java.lang.Object);
该方法只能对一个bean中普通属性字段进行copy,如果这个bean中有其他的bean,即bean中bean,就需要将其值取出,单独对这个子bean进行copy,然后set进父bean,这种copy如果在bean层次少的时候还无所谓,如果在bean的层次比较深的时候,就会发现很是繁琐了,最近遇到的项目里面有bean和bo的转换,其深度就有四五层,所以如果能想出一个新的工具类,能够一次性copy其bean中bena肯定最好不过了。
以前用的简单包装工具类
- 满足单层bean的copy。
- 满足单层bean集合的copy。
public class CopyUtil {
@SuppressWarnings("all")
public static <T> T copy(Object fromBean, Class<T> clz)
throws InstantiationException, IllegalAccessException {
if (fromBean == null || clz == null) {
return null;
}
T result = clz.newInstance();
BeanUtils.copyProperties(fromBean, result);
return result;
}
public static <T> List<T> copy(Collection<?> fromBeans, Class<T> cls)
throws InstantiationException, IllegalAccessException {
if (CollectionUtils.isEmpty(fromBeans) || null == cls) {
return null;
}
List<T> list = new ArrayList<>();
for (Object fromBean : fromBeans) {
T t = copy(fromBean, cls);
if (t != null) {
list.add(t);
}
}
return list;
}
}
通过反射写出的新的一套工具类
- 满足单层bean的copy。
- 满足单层bean集合的copy。
- 满足多层bean的复合copy。
思路
- 获取fromBean中的属性字段,如果toBean中也有其属性字段,则继续下一步。
- 反射执行get/is获取其属性field,得到属性类型type,属性值fieldValue。
- 如果type是普通类型,则直接反射执行set方法将值写入toBean。
- 如果type是List集合类型,调用getFieldIfList获取对应toBean中的该属性值toList:
- 获取这个List类型属性里面包裹的属性类型type_child。
- 如果type_child仍是List集合类型,递归调用getFieldIfList去掉List外壳,并将值add进其父List。
- 如果type_child不是集合类型,通过Class.forName获取其class:
- 如果type_child是一般类型,直接将传进来的list值add进toList返回。
- 如果type_child是child_bean类型,遍历传进来的list,对其每个值又重新调用copy方法set进new出来的新child_bean实例中,返回。
- 如果type是Map类型,暂不考虑。
- 如果type不是以上类型(忽略考虑了java里面或者其他类里面定义的内置bean类型),判定为bean中bean类型:
- 反射获取toBean中对应字段的class:
Class<?> newToClass = toClass.getDeclaredField(field.getName()).getType(); Object newToBean = newToClass.newInstance();
- 再次调用copy方法将fieldValue写入newToBean,然后执行set方法将newToBean写入toBean。
- 反射获取toBean中对应字段的class:
- 最终返回toBean即目标结果。
判断是否是普通字段属性
/**
* @Author ifdream
* @Description 返回该字段的类型是否是普通类型
* @Date 10:14 2019/9/21
* @Param [clazz]
* @return boolean
**/
private static boolean isNormalField(Class clazz) {
return clazz == int.class || clazz == String.class || clazz == float.class
|| clazz == double.class || clazz == char.class || clazz == byte.class
|| clazz == short.class || clazz == Date.class || clazz == boolean.class
|| clazz == Integer.class || clazz == Float.class
|| clazz == Double.class || clazz == Character.class || clazz == Byte.class
|| clazz == Short.class || clazz == Boolean.class
|| clazz == long.class || clazz == Long.class;
}
整体工具类
/**
* @Auther: ifdream
* @Date: 2019/9/21 09:39
* @Description:
*/
public class CopyUtil {
private final static String LIST_STR = "java.util.List";
private final static String SERIAL_FIELD_STR = "serialVersionUID";
private final static int LIST_STR_LEN = "java.util.List".length();
@SuppressWarnings("all")
public static <T> T copy(Object fromBean, Class<T> clz)
throws NoSuchMethodException, InvocationTargetException, IllegalAccessException,
NoSuchFieldException, ClassNotFoundException, InstantiationException {
if (fromBean == null || clz == null) {
return null;
}
if (isNormalField(clz)) {
if (fromBean.getClass() == clz) {
// 类型一致
return (T) fromBean;
}
throw new IllegalArgumentException("this fromBean is not " + clz + " type");
}
return copy(fromBean, clz.newInstance());
}
public static <T> List<T> copy(Collection<?> fromBeans, Class<T> cls)
throws NoSuchMethodException, InvocationTargetException, IllegalAccessException,
NoSuchFieldException, ClassNotFoundException, InstantiationException {
if (CollectionUtils.isEmpty(fromBeans) || null == cls) {
return null;
}
List<T> list = new ArrayList<>();
for (Object fromBean : fromBeans) {
T t = copy(fromBean, cls);
if (t != null) {
list.add(t);
}
}
return list;
}
/**
* @Author ifdream
* @Description 要求 bean 中对应字段的属性名必须一致,普通类型字段要求字段类型一致
* @Date 10:12 2019/9/21
* @Param [fromBean, toBean] 从 fromBean 复制到 toBean
* @return E
**/
@SuppressWarnings("all")
public static <T, E> E copy(Object fromBean, E toBean)
throws NoSuchMethodException, InvocationTargetException, IllegalAccessException,
NoSuchFieldException, ClassNotFoundException, InstantiationException {
if (fromBean == null || toBean == null) return null;
Class<?> fromClass = fromBean.getClass();
Class<?> toClass = toBean.getClass();
for (Field field : fromClass.getDeclaredFields()) {
if (field.getName().equals(SERIAL_FIELD_STR)) {
// 序列化字段,忽略
continue;
}
try {
if (toClass.getDeclaredField(field.getName()) == null) {
// toClass 中没有该属性字段
continue;
}
if (isNormalField(field.getType())
&& toClass.getDeclaredField(field.getName()).getType() != field.getType()) {
// 属性是普通字段,且二者对应属性字段的类型不同
continue;
}
} catch (NoSuchFieldException e) {
continue;
}
String getMethodName = "get" + StringUtils.capitalize(field.getName());
String setMethodName = "set" + StringUtils.capitalize(field.getName());
String isMethodName = "is" + StringUtils.capitalize(field.getName());
Object fieldValue;
if (field.getType() == boolean.class || field.getType() == Boolean.class) {
// boolean 类型
fieldValue = fromClass.getDeclaredMethod(isMethodName).invoke(fromBean);
} else {
fieldValue = fromClass.getDeclaredMethod(getMethodName).invoke(fromBean);
}
if (null == fieldValue) {
// fromBean 无此属性值
continue;
}
if (isNormalField(field.getType())) {
Method method = toClass.getDeclaredMethod(setMethodName, field.getType());
method.invoke(toBean, fieldValue);
} else if (field.getType() == List.class) {
List<T> list = (List<T>) fieldValue;
String str = toClass.getDeclaredField(field.getName()).getGenericType().toString();
List<Object> toList = getFieldIfList(list, str);
Method method = toClass.getDeclaredMethod(setMethodName, List.class);
method.invoke(toBean, toList);
} else if (field.getType() == Map.class){
// TODO Map 暂不处理
} else {
// bean 中 bean
Class<?> newToClass = toClass.getDeclaredField(field.getName()).getType();
Object newToBean = newToClass.newInstance();
copy(fieldValue, newToBean);
Method method = toClass.getDeclaredMethod(setMethodName, newToClass);
method.invoke(toBean, newToBean);
}
}
return toBean;
}
/**
* @Author ifdream
* @Description 获取当源属性类型是 {@link List} 时的目标属性值
* @Date 12:13 2019/9/22
* @Param [list, toBeanClassStr]
* @return java.util.List<java.lang.Object>
**/
@SuppressWarnings("all")
private static <T, E> List<Object> getFieldIfList(List<T> list, String toBeanClassStr)
throws IllegalAccessException, InstantiationException, ClassNotFoundException,
NoSuchMethodException, NoSuchFieldException, InvocationTargetException {
String newBeanClassStr = toBeanClassStr.substring(LIST_STR_LEN + 1,
toBeanClassStr.length() - 1);
if (newBeanClassStr.contains(LIST_STR)) {
List<Object> result = new ArrayList<>();
for (T t : list) {
// 递归去掉 list 外壳
List<Object> toT = getFieldIfList((List<E>) t, newBeanClassStr);
result.add(toT);
}
return result;
}
Class<?> toBeanClass = Class.forName(newBeanClassStr);
List<Object> toList = new ArrayList<>();
if (isNormalField(toBeanClass)) {
// 说明 list 里面的元素是一般属性字段
toList.addAll(list);
} else {
for (T t : list) {
Object toT = copy(t, toBeanClass.newInstance());
toList.add(toT);
}
}
return toList;
}
/**
* @Author ifdream
* @Description 返回该字段的类型是否是普通类型
* @Date 10:14 2019/9/21
* @Param [clazz]
* @return boolean
**/
private static boolean isNormalField(Class clazz) {
return clazz == int.class || clazz == String.class || clazz == float.class
|| clazz == double.class || clazz == char.class || clazz == byte.class
|| clazz == short.class || clazz == Date.class || clazz == boolean.class
|| clazz == Integer.class || clazz == Float.class
|| clazz == Double.class || clazz == Character.class || clazz == Byte.class
|| clazz == Short.class || clazz == Boolean.class
|| clazz == long.class || clazz == Long.class;
}
}
简单测试
@Data
public class Bean1 {
private String name;
private List<Bean1_1> list;
private Bean1_2 testBean;
private List<String> aList;
private List<List<Integer>> bList;
private List<List<Bean1_3>> cList;
}
@Data
public class Bean2 {
private String name;
private List<Bean2_1> list;
private Bean2_2 testBean;
private List<String> aList;
private List<List<Integer>> bList;
private List<List<Bean2_3>> cList;
}
... 里面都是一些简单的bean,就不一一展开
main方法简单set值进行copy,打印对比测试:
Bean1(name=aaa, list=[Bean1_1(name=name0, id=6, testBean=[Bean1_1_1(age=17, birthday=Sun Sep 22 14:11:47 CST 2019), Bean1_1_1(age=16, birthday=Sun Sep 22 14:11:47 CST 2019)], success=true), Bean1_1(name=name1, id=0, testBean=[Bean1_1_1(age=15, birthday=Sun Sep 22 14:11:47 CST 2019), Bean1_1_1(age=15, birthday=Sun Sep 22 14:11:47 CST 2019)], success=true)], testBean=Bean1_2(test=100), aList=[a1, a2], bList=[[1, 2], [1, 2]], cList=[[Bean1_3(id=10), Bean1_3(id=10), Bean1_3(id=10)], [Bean1_3(id=11), Bean1_3(id=11), Bean1_3(id=11)]])
Bean2(name=aaa, list=[Bean2_1(name=name0, id=6, testBean=[Bean2_1_1(age=17, birthday=Sun Sep 22 14:11:47 CST 2019), Bean2_1_1(age=16, birthday=Sun Sep 22 14:11:47 CST 2019)], success=true), Bean2_1(name=name1, id=0, testBean=[Bean2_1_1(age=15, birthday=Sun Sep 22 14:11:47 CST 2019), Bean2_1_1(age=15, birthday=Sun Sep 22 14:11:47 CST 2019)], success=true)], testBean=Bean2_2(test=100), aList=[a1, a2], bList=[[1, 2], [1, 2]], cList=[[Bean2_3(id=10), Bean2_3(id=10), Bean2_3(id=10)], [Bean2_3(id=11), Bean2_3(id=11), Bean2_3(id=11)]])
结语
以上为博主应需写出的工具类,肯定不尽完美,读者发现有问题,可以提出来,博主很乐意一直完善该工具类~
如果您看到了这里,欢迎和我沟通交流!
一个95后码农
个人博客:fy-blog