阿里的java代码开发规范中,关于各个层的命名规范是这么定义
这里面有我们比较常见的POJO对应的实体定义格式,其中
VO(View Object):是针对于视图层,用于展示层(前端页面),它的作用是把某个视图需要展示的数据进行封装。
DTO(Data Transfer Object):数据传输对象,这个概念来源于J2EE的设计模式,原来的目的是为了EJB的分布式应用提供粗粒度的数据实体,以减少分布式调用的次数,从而提高分布式调用的性能和降低网络负载,但在这里,泛指用于展示层与服务层之间的数据传输对象。 其中以Query后缀,有时候也可以作为DTO的一部分。
DO(Domain Object):领域对象,就是从现实世界中抽象出来的有形或无形的业务实体。
PO(Persistent Object):持久化对象,它跟持久层(通常是关系型数据库)的数据结构形成一一对应的映射关系,如果持久层是关系型数据库,那么,数据表中的每个字段(或若干个)就对应PO的一个(或若干个)属性。
常见的业务中,我们都是会将由视图层(View)的数据封装成对应的DTO,传递给对应的服务接口,服务接口在接收到对应的DTO数据之后,进行参数校验,比如空值判断,类型匹配,初始化值操作等。
接着,我们会将验证过后的DTO数据,交由Service层进行数据操作(或者是分布式场景下的RPC远程调度接口),Service层通过与之关联的DO业务数据进行转换之后,会形成一个新的DTO数据,再交由DAO层数据进行数据持久化
在这个业务场景下,一般的,我们都是会涉及到对应的BO,VO,DTO,DO等POJO类之间的转换问题。
下面就来简单的分析一下
这个是最原始,最直接的转换方式。比如视图层发起了一个商品入库的交易,对应的DTO如下:
@Data
public class GoodsDTO {
/**
* 商品价格
*/
private Double price;
/**
* 商品名称
*/
private String name;
/**
* 商品数量
*/
private Integer num;
}
通过Service层的入库时间确定,入库人确定等操作,最终形成的PO如下:
@Data
public class GoodsPO {
/**
* 商品价格
*/
private Double price;
/**
* 商品名称
*/
private String name;
/**
* 商品数量
*/
private Integer num;
/**
* 入库时间
*/
private Date createTime;
/**
* 操作人
*/
private String creatBy;
}
通过get set 方法
// 1,验证商品
check(goodsDTO);
GoodsPO goodsPO = new GoodsPO();
goodsPO.setName(goodsDTO.getName());
goodsPO.setNum(goodsDTO.getNum());
goodsPO.setPrice(goodsDTO.getPrice());
// 2,追加入库时间,入库人操作
goodsPO.setCreatBy("admin");
goodsPO.setCreateTime(new Date());
get set 的方式进行转换的方式很简单,但是有一个很验证的问题就是会导致代码过于臃肿,会出现大量的属性值赋值,大量的get set方法充斥着业务逻辑,
我们可以基于java反射的机制进行对象之间的属性值的复制操作。我们可以创建一个抽象的模型转换器,AbstractModelConverter
/**
* POJO 转换器
*
* @author zhoucg
* @date 2019-12-25 16:37
*/
public abstract class AbstractModelConverter {
/**
* 转换
* @param clazz the class of convert object
* @return to convert object
*/
public T toPo(Class clazz) {
Map convertFileMapper = convert();
T t = Func.copy(this, clazz, CopyOptions.create().setFieldMapping(convertFileMapper));
return t;
}
/**
* 由子类重写该方法,作为ModelConvert->T 的属性映射关系
* @return fieldMapper
*/
public abstract Map convert();
}
针对于相同或者不同的属性值,我们可以做两个POJO之间的属性映射Mapper,通过Map的方式存储,再交由反射进行属性填充。
字节码属性映射示例:
public static Object convert(Object convertOld,Class convertNew,List excludes,Map fieldMap) {
Field[] oldFields = convertOld.getClass().getDeclaredFields();
Field[] newFields = convertNew.getDeclaredFields();
Object newProxyObject = null;
try {
newProxyObject = convertNew.newInstance();
} catch (InstantiationException e) {
logger.error("初始化对象错误,当前错误信息:{}",e.getMessage());
} catch (IllegalAccessException e) {
logger.error("class类无对应的访问权限,当前错误信息:{}",e.getMessage());
}
Map newFieldsMap = Maps.newHashMap();
Arrays.stream(newFields).forEach(localfiled -> newFieldsMap.put(localfiled.getName(),localfiled));
for(Field field : oldFields) {
String fieldName = field.getName();
String fieldMapperValue;
//找到新的映射关系
if(fieldMap == null || fieldMap.size() == 0) {
fieldMapperValue = fieldName;
} else {
if(fieldMap.containsKey(fieldName)) {
fieldMapperValue = fieldMap.get(fieldName);
} else {
fieldMapperValue = fieldName;
}
}
if(excludes != null && excludes.contains(fieldName)) continue;
if(newFieldsMap.keySet().contains(fieldMapperValue)) {
try {
Field relationNewField = newFieldsMap.get(fieldMapperValue);
relationNewField.setAccessible(true);
field.setAccessible(true);
relationNewField.set(newProxyObject,field.get(convertOld));
} catch (IllegalAccessException e) {
logger.error("反射ClassFiled错误,当前错误信息:{}",e.getMessage());
}
}
}
return newProxyObject;
}
在Spring的源码中,BeanUtils已经封装了大量的对象转换属性复制的方法,我们可以直接进行使用
BeanCopier是cglib包下的一个类。它可以通过直接修改字节码的方式进行属性值间的复制
默认的情况下,它会进行同名,同类型属性的copier,如果类型不同,或者名称不同的化,会报错,我们可以通过实现自定义Convert的形式进行类型转换
BeanCopier copier = BeanCopier.create(goodsDTO.getClass(), goodsPO.getClass(), false);
假设在GoodsDTO中存在一个Date 类型的插入时间(insertTime),我们需要转换成对应字符类型的插入时间(insertTime)
BeanCopier copier = BeanCopier.create(goodsDTO.getClass(), goodsPO.getClass(), true);
copier.copy(goodsDTO, goodsPO, (value, target, context) -> {
return value.getClass().isAssignableFrom(Date.class) ? new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(value) : value;
});
BeanCopier 源码见Sping中spring-core
对应性能而言,最差的肯定是基于反射机制的方法,get /set是性能最好。
所以我们需要权衡代码的整洁性和性能,
个人不太建议使用cglib的BeanCopier ,针对于不同属性名的转换,BeanCopier 是无法完成的。