Fork me on GitHub

java中复合bean的copy工具类

诞生由来

之前项目里就经常有bean之间的属性copy,一直都是用spring自带的工具类来解决,方法如下:

1
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。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
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:

      1
      2
      Class<?> newToClass = toClass.getDeclaredField(field.getName()).getType();
      Object newToBean = newToClass.newInstance();
    • 再次调用copy方法将fieldValue写入newToBean,然后执行set方法将newToBean写入toBean。

  • 最终返回toBean即目标结果。

判断是否是普通字段属性

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
/**
* @Author fangyang
* @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;
}

整体工具类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
/**
* @Auther: fangyang
* @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 fangyang
* @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 fangyang
* @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 fangyang
* @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;
}
}

简单测试

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
@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,打印对比测试:

1
2
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)]])

结语

以上为博主应需写出的工具类,肯定不尽完美,读者发现有问题,可以提出来,博主很乐意一直完善该工具类~

-------------本文结束感谢您的阅读-------------
如果您对博主的原创满意,欢迎您继续支持下博主~