需求的来源是项目。
每次做项目,都要建立很多的数据体,有的是数据库表产生的ORM,有的是网络请求的参数体,有的是网络接口调用给的返回数据返回数据体。同一种数据,这几种数据具体的结构并不一样。
ORM往往跟数据表一致,并且做了映射。但是外键数据只有id,并不详细。
前端调用的数据往往要求比ORM更丰富,有些则不需要。虽说可以建立一些与ORM无关的字段来用,但是字段多了会很麻烦,所以需要建立专用的VO数据类。
接口请求的有些数据,比ORM的数据要少一些,并不需要前端传值,但是我们使用swagger做ApiDoc,不详细说明,前端也搞不清哪些必填,哪些可以不用,就连自己有时候也会搞乱,所以还是另外给前端建立一个request数据参数类比较合适。
但是这样一来,同一套数据就要在不同的数据对象之间读写不停,字段繁多的数据读写代码要写好多,看起来总觉得不舒服。
今天躺床上,一觉醒来,突然产生一种灵感:完全可以用反射来封装一个工具类,让程序自动判断匹配的字段,自动传值,不同的字段在做特殊处理,这样就会节省很多的代码。
说干就干,翻身起床,打开电脑,写一个demo。一会儿就写好了,调试通过。
一、建立一个实体类,模拟ORM插件生成那种,字段可以多一点。
@Entity(name = "tb_user")
public class UserEntity {
private int id;
private String name;
private String password;
private Integer age;
private String address;
... ...
}
二、创建一个用于请求类,两个类之间的字段有交集
public class UserRequest {
private String name;
private String password;
private String vcode;
......
}
三、开始写工具类EntityUtils.java,有详细的注释
public class EntityUtils {
/**
* 复制名称相同类型相同字段的值
*
* @param obj
* @param clazz2
* @param <T1>
* @param <T2>
* @return
*/
public static <T1, T2> T2 copyData(T1 obj, Class<T2> clazz2) {
//1. 获取源数据的类
Class clazz1 = obj.getClass();//源数据类
//2. 创建一个目标数据实例
final T2 obj2 = getInstance(clazz2);
//3. 获取clazz1和clazz2中的属性
Field[] fields1 = clazz1.getDeclaredFields();
Field[] fields2 = clazz2.getDeclaredFields();
//4. 遍历fields2
for (Field f1 : fields1) {
//4-1. 遍历fields1,逐字段匹配
for (Field f2 : fields2) {
// 复制字段
copyField(obj, obj2, f1, f2);
}
}
return obj2;
}
/**
* 按照字段表复制相同名称相同类型的字段的值
*
* @param obj
* @param clazz2
* @param fieldNames
* @param <T1>
* @param <T2>
* @return
*/
public static <T1, T2> T2 copyData(T1 obj, Class<T2> clazz2, String[] fieldNames) {
//1. 获取源数据的类
Class clazz1 = obj.getClass();//源数据类
//2. 创建一个目标数据实例
final T2 obj2 = getInstance(clazz2);
//3. 获取clazz1和clazz2中的属性
Field[] fields1 = clazz1.getDeclaredFields();
Field[] fields2 = clazz2.getDeclaredFields();
//4. 遍历字段列表
for (String fieldName : fieldNames) {
//5. 遍历fields1
for (Field f1 : fields1) {
//找到这个字段(找不到就不用遍历fields2)
if (fieldName.equals(f1.getName())) {
//5-1. 遍历fields2,逐字段匹配
for (Field f2 : fields2) {
//在fields2中也要有这个字段
if (fieldName.equals(f2.getName())) {
//复制字段
copyField(obj, obj2, f1, f2);
}
}
}
}
}
return obj2;
}
/**
* 复制相同名称相同类型的字段的值
*
* @param obj
* @param obj2
* @param f1
* @param f2
* @param <T1>
* @param <T2>
*/
private static <T1, T2> void copyField(T1 obj, T2 obj2, Field f1, Field f2) {
try {
//字段名要相同,字段类型也要相同
if (f1.getName().equals(f2.getName())
& f1.getType().getName().equals(f2.getType().getName())) {
System.out.println(f1.getName());
//3-2. 获取obj这个字段的值
f1.setAccessible(true);
Object val = f1.get(obj);
//3-3. 把这个值赋给obj2这个字段
f2.setAccessible(true);
f2.set(obj2, val);
//3-4. 访问权限还原
f2.setAccessible(false);
f1.setAccessible(false);
}
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
/**
* 获得泛型类的实例
*
* @param tClass
* @param <T>
* @return
*/
public static <T> T getInstance(Class<T> tClass) {
try {
T t = tClass.newInstance();
return t;
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
return null;
}
}
我喜欢使用的时候干干净净,所以习惯把异常在封装的时候解决掉,不喜欢抛出去。除非项目结构做了异常异常统一处理,需要throw.
四、写一个测试方法,来测试一下。
@Test
public void test() {
UserRequest userRequest = new UserRequest();
userRequest.setName("kalychen");
userRequest.setPassword("gk123456");
userRequest.setVcode("265847");
String[] fields = {"password"};
UserEntity userEntity = EntityUtils.copyData(userRequest, UserEntity.class, fields);
System.out.println(userEntity.getName());
System.out.println(userEntity.getPassword());
}
测试结果:
这测试的是带有字段列表的,可以看出没有在字段名称数组中出现的字段,即使是名称相同类型相同也不会复制。设计这样一个重载方法目的是方便控制。本来计划中还有一个使用注解来控制的重载,但是觉得未必会比前面两个更方便,所以放弃了。
今天是周末,感觉星期一我的项目代码又要删减好多了。繁琐的代码我看着会觉得很不舒服的。
补充一下,这些实体类都是没有继承的。
迭代了一下,对程序进行了优化和扩展
public class EntityUtils {
/**
* 复制名称相同类型相同的字段数据
*
* @param sourceObj
* @param clazz
* @param <T1>
* @param <T2>
* @return
*/
public static <T1, T2> T2 copyData(T1 sourceObj, Class<T2> clazz) {
//1. 获取源数据的类
Class<?> clazz1 = sourceObj.getClass();
//2. 创建一个目标数据对象
T2 targetObj = getInstance(clazz);
//3. 复制两个对象相同的字段
copyData(sourceObj, targetObj);
return targetObj;
}
/**
* 根据字段列表复制字段的值
*
* @param sourceObj
* @param clazz
* @param fields
* @param <T1>
* @param <T2>
* @return
*/
public static <T1, T2> T2 copyData(T1 sourceObj, Class<T2> clazz, String[] fields) {
//1. 获取源数据的类
Class<?> clazz1 = sourceObj.getClass();
//2. 创建一个目标数据对象
T2 targetObj = getInstance(clazz);
//3. 获取两个类字段集合
Field[] fields1 = clazz1.getDeclaredFields();
Field[] fields2 = clazz.getDeclaredFields();
//4. 复制字段
copyFieldValue(sourceObj, targetObj, fields, fields1, fields2);
return targetObj;
}
/**
* 复制两个对象中相同字段的值
*
* @param sourceObj
* @param targetObj
* @param <T1>
* @param <T2>
*/
public static <T1, T2> void copyData(T1 sourceObj, T2 targetObj) {
//1. 获取两个对象的类
Class<?> clazz1 = sourceObj.getClass();
Class<?> clazz2 = targetObj.getClass();
//3. 获取两个类字段集合
Field[] fields1 = clazz1.getDeclaredFields();
Field[] fields2 = clazz2.getDeclaredFields();
//4. 遍历fields1
for (Field f1 : fields1) {
//4-1. 遍历fields2
for (Field f2 : fields2) {
//4-2. 复制字段
copyFieldValue(sourceObj, targetObj, f1, f2);
}
}
}
/**
* 根据字段表复制两个对象中相同字段的值
*
* @param sourceObj
* @param targetObj
* @param fields
* @param <T1>
* @param <T2>
*/
public static <T1, T2> void copyData(T1 sourceObj, T2 targetObj, String[] fields) {
//1. 获取源两个对象的类
Class<?> clazz1 = sourceObj.getClass();
Class<?> clazz2 = targetObj.getClass();
//3. 获取两个类字段集合
Field[] fields1 = clazz1.getDeclaredFields();
Field[] fields2 = clazz2.getDeclaredFields();
//4. 复制字段
copyFieldValue(sourceObj, targetObj, fields, fields1, fields2);
}
/**
* 根据字段列表排除复制对象中相同字段的值
* 凡是在字段列表中出现的不进行复制
*
* @param sourceObj
* @param clazz
* @param fields
* @param <T1>
* @param <T2>
* @return
*/
public static <T1, T2> T2 copyDataExclude(T1 sourceObj, Class<T2> clazz, String[] fields) {
//1. 获取源数据的类
Class<?> clazz1 = sourceObj.getClass();
//2. 创建一个目标数据对象
T2 targetObj = getInstance(clazz);
//3. 获取两个类字段集合
Field[] fields1 = clazz1.getDeclaredFields();
Field[] fields2 = clazz.getDeclaredFields();
//4. 复制字段
copyFieldValueExclude(sourceObj, targetObj, fields, fields1, fields2);
return targetObj;
}
/**
* 根据字段列表排除复制两个对象中相同字段的值
* 凡是在字段列表中出现的不进行复制
*
* @param sourceObj
* @param targetObj
* @param fields
* @param <T1>
* @param <T2>
*/
public static <T1, T2> void copyDataExclude(T1 sourceObj, T2 targetObj, String[] fields) {
//1. 获取源两个对象的类
Class<?> clazz1 = sourceObj.getClass();
Class<?> clazz2 = targetObj.getClass();
//3. 获取两个类字段集合
Field[] fields1 = clazz1.getDeclaredFields();
Field[] fields2 = clazz2.getDeclaredFields();
//4. 复制字段
copyFieldValueExclude(sourceObj, targetObj, fields, fields1, fields2);
}
/**
* 排除字段复制
*
* @param sourceObj
* @param targetObj
* @param fields
* @param fields1
* @param fields2
* @param <T1>
* @param <T2>
*/
private static <T1, T2> void copyFieldValueExclude(T1 sourceObj, T2 targetObj, String[] fields, Field[] fields1, Field[] fields2) {
for (String fieldName : fields) {
//4-1. 遍历fields1
for (Field f1 : fields1) {
//4-3. 是否匹配这个字段
if (fieldName.equals(f1.getName())) {
//只要包含该字段就跳过
continue;
}
//4-4. 遍历fields2
for (Field f2 : fields2) {
//4-5. 复制字段
copyFieldValue(sourceObj, targetObj, f1, f2);
}
}
}
}
/**
* 根据字段列表复制两个对象中相同字段的值
*
* @param sourceObj
* @param targetObj
* @param fields
* @param fields1
* @param fields2
* @param <T1>
* @param <T2>
*/
private static <T1, T2> void copyFieldValue(T1 sourceObj, T2 targetObj, String[] fields, Field[] fields1, Field[] fields2) {
for (String fieldName : fields) {
//4-1. 遍历fields1
for (Field f1 : fields1) {
//4-3. 是否匹配这个字段
if (fieldName.equals(f1.getName())) {
//4-4. 遍历fields2
for (Field f2 : fields2) {
//4-3. 是否匹配这个字段
if (fieldName.equals(f2.getName())) {
//4-2. 复制字段
copyFieldValue(sourceObj, targetObj, f1, f2);
}
}
}
}
}
}
/**
* 复制字段的值
*
* @param sourceObj
* @param targetObj
* @param field1
* @param field2
* @param <T1>
* @param <T2>
*/
public static <T1, T2> void copyFieldValue(T1 sourceObj, T2 targetObj, Field field1, Field field2) {
try {
//1. 判断两个字段是否名称相同而且类型相同
if (field1.getName().equals(field2.getName())
&& equalFieldsType(field1, field2)) {
//2. 获取源数据字段的值
field1.setAccessible(true);
Object value = field1.get(sourceObj);
//3. 给目标数据字段赋值
field2.setAccessible(true);
field2.set(targetObj, value);
//4. 访问权限还原
field2.setAccessible(false);
field1.setAccessible(false);
}
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
/**
* 获取一个泛型的实例
*
* @param clazz
* @param <T>
* @return
*/
public static <T> T getInstance(Class<T> clazz) {
try {
return clazz.newInstance();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
return null;
}
/**
* 判断两个字段的类型是否相同
*
* @param field1 复制源
* @param field2 复制目标
* @return
*/
public static boolean equalFieldsType(Field field1, Field field2) {
String fTypeName1 = field1.getType().getSimpleName();
String fTypeName2 = field2.getType().getSimpleName();
System.out.println(fTypeName1 + ":" + fTypeName2);
//1. 处理基本数据类型和包装类
Map<String, String> map = new HashMap<String, String>();
map.put(int.class.getSimpleName(), Integer.class.getSimpleName());
map.put(byte.class.getSimpleName(), Byte.class.getSimpleName());
map.put(short.class.getSimpleName(), Short.class.getSimpleName());
map.put(char.class.getSimpleName(), Character.class.getSimpleName());
map.put(long.class.getSimpleName(), Long.class.getSimpleName());
map.put(float.class.getSimpleName(), Float.class.getSimpleName());
map.put(double.class.getSimpleName(), Double.class.getSimpleName());
map.put(boolean.class.getSimpleName(), Boolean.class.getSimpleName());
/**
* 在涉及包装类的判断逻辑中,源数据不能是包装类
* 因为包装类一旦为null,会引发异常
*/
Set<String> keySet = map.keySet();
for (String key : keySet) {
if (key.equals(fTypeName1) && map.get(key).equals(fTypeName2)) {
return true;
}
}
//2. 名称相同、类型相同
if (fTypeName1.equals(fTypeName2)) {
return true;
}
return false;
}
}
看看这个工具类的作用效果吧!!
我们做alipay的异步通知接口,要把异步通知结果发送到我们的消息中心,然后推送到手机端。alipay服务器发送的参数大约有28个,我们在支付处理服务建立了一个带有gson字段映射的类,很好地解析了接收的字段信息。转身,我们要将这个对象作为参数发送到消息中心。那么问题来了。我们用retrofit2调用消息中心的接口,gs使用gson将数据打包,类的字段又会根据字段映射解析为原来的样子。而消息中心接收数据是不去理会字段映射的。我们把建好的类复制到消息中心去接收,就会出现很多字段收不到的情况。我们总不能再去建一个类吧?或者改变原来的类?总之很麻烦,令人很不爽。没办法我们就只能一个字段一个字段去转移了。
不过,一旦用上这个工具类,原来的二三十行代码就可以直接缩减为一行了。我们把原来的类复制一个,去掉所有的字段映射。
看看效果:
//构建
AlipayNoticeVO alipayNoticeVO = EntityUtils.copyData(alipayNotice,AlipayNoticeVO.class);
// alipayNoticeVO.setAppId(alipayNotice.getAppId());
// alipayNoticeVO.setBody(alipayNotice.getBody());
// alipayNoticeVO.setBuyerId(alipayNotice.getBuyerId());
// alipayNoticeVO.setBuyerLogonId(alipayNotice.getBuyerLogonId());
// alipayNoticeVO.setBuyerPayAmount(alipayNotice.getBuyerPayAmount());
// alipayNoticeVO.setGmtClose(alipayNotice.getGmtClose());
// alipayNoticeVO.setGmtCreate(alipayNotice.getGmtCreate());
// alipayNoticeVO.setGmtPayment(alipayNotice.getGmtPayment());
// alipayNoticeVO.setGmtRefund(alipayNotice.getGmtRefund());
// alipayNoticeVO.setInvoiceAmount(alipayNotice.getInvoiceAmount());
// alipayNoticeVO.setNotifyId(alipayNotice.getNotifyId());
// alipayNoticeVO.setNotifyTime(alipayNotice.getNotifyTime());
// alipayNoticeVO.setNotifyType(alipayNotice.getNotifyType());
// alipayNoticeVO.setOutBizNo(alipayNotice.getOutBizNo());
// alipayNoticeVO.setOutTradeNo(alipayNotice.getOutTradeNo());
// alipayNoticeVO.setPointAmount(alipayNotice.getPointAmount());
// alipayNoticeVO.setReceiptAmount(alipayNotice.getReceiptAmount());
// alipayNoticeVO.setSellerEmail(alipayNotice.getSellerEmail());
// alipayNoticeVO.setSendBackFee(alipayNotice.getSendBackFee());
// alipayNoticeVO.setSellerId(alipayNotice.getSellerId());
// alipayNoticeVO.setSign(alipayNotice.getSign());
// alipayNoticeVO.setSignType(alipayNotice.getSignType());
// alipayNoticeVO.setSubject(alipayNotice.getSubject());
// alipayNoticeVO.setTotalAmount(alipayNotice.getTotalAmount());
// alipayNoticeVO.setTradeStatus(alipayNotice.getTradeStatus());
// alipayNoticeVO.setTradeNo(alipayNotice.getTradeNo());
// alipayNoticeVO.setFundBillList(alipayNotice.getFundBillList());
return alipayNoticeVO;
注释掉的是原来的代码。被第一行取代。