java中BO,VO,DTO,DO等之间的转换


1 介绍

阿里的java代码开发规范中,关于各个层的命名规范是这么定义
在这里插入图片描述
这里面有我们比较常见的POJO对应的实体定义格式,其中

VO(View Object):是针对于视图层,用于展示层(前端页面),它的作用是把某个视图需要展示的数据进行封装。

DTO(Data Transfer Object):数据传输对象,这个概念来源于J2EE的设计模式,原来的目的是为了EJB的分布式应用提供粗粒度的数据实体,以减少分布式调用的次数,从而提高分布式调用的性能和降低网络负载,但在这里,泛指用于展示层与服务层之间的数据传输对象。 其中以Query后缀,有时候也可以作为DTO的一部分。

DO(Domain Object):领域对象,就是从现实世界中抽象出来的有形或无形的业务实体。

PO(Persistent Object):持久化对象,它跟持久层(通常是关系型数据库)的数据结构形成一一对应的映射关系,如果持久层是关系型数据库,那么,数据表中的每个字段(或若干个)就对应PO的一个(或若干个)属性。

2 转换

常见的业务中,我们都是会将由视图层(View)的数据封装成对应的DTO,传递给对应的服务接口,服务接口在接收到对应的DTO数据之后,进行参数校验,比如空值判断,类型匹配,初始化值操作等。
接着,我们会将验证过后的DTO数据,交由Service层进行数据操作(或者是分布式场景下的RPC远程调度接口),Service层通过与之关联的DO业务数据进行转换之后,会形成一个新的DTO数据,再交由DAO层数据进行数据持久化
在这里插入图片描述
在这个业务场景下,一般的,我们都是会涉及到对应的BO,VO,DTO,DO等POJO类之间的转换问题。

下面就来简单的分析一下

2.1 通过get,set方法进行类型转换

这个是最原始,最直接的转换方式。比如视图层发起了一个商品入库的交易,对应的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方法充斥着业务逻辑,

2.2 基于反射的机制

我们可以基于java反射的机制进行对象之间的属性值的复制操作。我们可以创建一个抽象的模型转换器,AbstractModelConverter

/**
 * POJO 转换器
 *
 * @author zhoucg
 * @date 2019-12-25 16:37
 */
public abstract class AbstractModelConverter<T> {

    /**
     * 转换
     * @param clazz the class of convert object
     * @return to convert object
     */
    public T toPo(Class<T> clazz) {
        Map<String,String> convertFileMapper = convert();
        T t = Func.copy(this, clazz, CopyOptions.create().setFieldMapping(convertFileMapper));
        return t;
    }

    /**
     * 由子类重写该方法,作为ModelConvert->T 的属性映射关系
     * @return fieldMapper
     */
    public abstract Map<String,String> convert();

}

针对于相同或者不同的属性值,我们可以做两个POJO之间的属性映射Mapper,通过Map的方式存储,再交由反射进行属性填充。

字节码属性映射示例:

public static <T> Object convert(Object convertOld,Class<T> convertNew,List<String> excludes,Map<String,String> 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<String,Field> 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已经封装了大量的对象转换属性复制的方法,我们可以直接进行使用
在这里插入图片描述

2.3 基于cglib字节码修改

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 是无法完成的。

发布了55 篇原创文章 · 获赞 14 · 访问量 3万+

猜你喜欢

转载自blog.csdn.net/zcswl7961/article/details/103723278
今日推荐