仿BeanUtils反射实现DTO速转VO

2023年 8月 23日 84.1k 0

微信图片_20230822230508.jpg

前言

在开发的过程中,我们要常常要实现DTO(数据传输对象)转VO(视图对象)。可能你会问,什么是DTO,什么是VO?

DTO(Data Transfer Object)数据传输对象1、在服务间的调用中,传输的数据对象2、个人理解,DTO是可以存在于各层服务中(接口、服务、数据库等等)服务间的交互使用DTO来解耦

VO (view object/value object)表示层对象1、前端展示的数据,在接口数据返回给前端的时候需要转成VO2、个人理解使用场景,接口层服务中,将DTO转成VO,返回给前台

那么,高效的实现DTO(数据传输对象)转VO(视图对象)非常重要。

业务场景举例

我们先来看一个业务场景。我们在请求登录成功的时候,我们毫无疑问要从数据库获取信息,并且将部分信息返回到前端。我们从数据库获取信息时的数据传输对象(dto),假设是这样的:

@Data
@TableName("db_account")
@AllArgsConstructor
public class Account implements BaseData {
    @TableId(type = IdType.AUTO)
    Integer id;
    String username;
    String password;
    String email;
    String role;
    @TableField("registerTime")
    Date registerTime;
}

而我们返回给前端的时候,不能返回过多的数据,比如密码这些我们肯定不能返回,我们通常返回部分数据回去。

假设我们使用jwt的校验方案,那么毫无疑问得返回token回去。

下面是我们要返回的表示层对象(vo)

@Data
public class AuthorizeVO {
    String username;
    String role;
    String token;
    Date expire;
}

那么从dto传输值到vo。我们有很多方案。

方案一:直接手动设置

如下,我们可以这样以get和set的方式,进行手动设置。但是问题也明显,代码冗余,可维护性差、可读性差、增加错误风险!后期的开发,我们会有很多的dto和vo对象,如果都是这样手动设置,显然非常低效。

vo.setUsername(account.getUsername());
vo.setToken(token);
vo.setRole(account.getRole());
vo.setExpire(utils.expireTime());

方案二:使用官方提供工具库

我们可以使用BeanUtils.copyProperties() 去除重复代码,BeanUtils.copyProperties()底层是使用了反射实现。但是这样我觉得依然不是很优雅,因为他是对相同的值进行设置,我们还要在后面去设置没有相同字段的值。

BeanUtils.copyProperties(account,vo);

方案三:自己实现dto转vo接口

我们知道,BeanUtils是通过反射机制实现,那我们也可以通过反射实现一个属于自己的dto转vo。我的代码如下:

通过反射机制实现了将DTO对象转换为VO对象的功能。它可以通过让DTO类实现BaseData接口并调用接口中的方法来实现转换。转换过程中,会将DTO对象中的字段值复制到VO对象中,从而实现数据的传输和转换。

package com.example.entity;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.util.Arrays;
import java.util.function.Consumer;

/**
 * 用于DTO快速转换VO实现,只需将DTO类继承此类即可使用
 */
public interface BaseData {

    /**
     * 创建指定的VO类并将当前DTO对象中的所有成员变量值直接复制到VO对象中
     * @param clazz 指定VO类型
     * @param consumer 返回VO对象之前可以使用Lambda进行额外处理
     * @return 指定VO对象
     * @param  指定VO类型
     */
    default  V asViewObject(Class clazz, Consumer consumer) {
        V v = this.asViewObject(clazz);
        consumer.accept(v);
        return v;
    }


    /**
     * 创建指定的VO类并将当前DTO对象中的所有成员变量值直接复制到VO对象中
     * @param clazz 指定VO类型
     * @return 指定VO对象
     * @param  指定VO类型
     */
    default  V asViewObject(Class clazz) {
        try {
            Field[] fields = clazz.getDeclaredFields();
            Constructor constructor = clazz.getConstructor();
            V v = constructor.newInstance();
            Arrays.asList(fields).forEach(field -> convert(field,v));
            return v;
        } catch (ReflectiveOperationException exception) {
            Logger logger = LoggerFactory.getLogger(BaseData.class);
            logger.error("在VO与DTO转换时出现了一些错误");
            throw new RuntimeException(exception.getMessage());
        }
    }

    /**
     * 内部使用,快速将当前类中目标对象字段同名字段的值复制到目标对象字段上
     * @param field 目标对象字段
     * @param target 目标对象
     */
    private void convert(Field field, Object target) {
        try {
            Field source = this.getClass().getDeclaredField(field.getName());
            field.setAccessible(true);
            source.setAccessible(true);
            field.set(target,source.get(this));
        } catch (IllegalAccessException | NoSuchFieldException ignored) {}
    }
}

  • 接口定义:BaseData是一个接口,用于提供DTO转换为VO的方法。通过让DTO类实现该接口,就可以使用接口中定义的方法进行转换操作。
  • asViewObject方法:这个方法用于创建指定类型的VO对象,并将当前DTO对象中的成员变量值直接复制到VO对象中。它接受一个Class参数,表示指定的VO类型。方法内部使用反射来获取VO类的构造函数,创建一个VO对象,并通过遍历VO类的所有字段,将DTO对象对应字段的值复制到VO对象中。
  • asViewObject方法重载:这个方法是对上述方法的重载,添加了一个额外的Consumer参数。这个参数可以使用Lambda表达式,用于在返回VO对象之前对其进行额外处理。例如,可以在这里对VO对象的某些字段进行修改或设置。
  • convert方法:这是一个私有方法,用于在内部快速将DTO对象字段的值复制到VO对象字段上。它接受一个目标对象字段和目标对象作为参数。方法内部使用反射来获取DTO对象字段的值,并将其设置到目标对象字段上。

使用方法:

AuthorizeVO vo = account.asViewObject(AuthorizeVO.class, v-> {
    v.setExpire(utils.expireTime());
    v.setToken(token);
});

这样,我们不仅可以实现类型BeanUtils的功能,还能利用Lambda,一步到位的实现,减少冗余代码。

相关文章

JavaScript2024新功能:Object.groupBy、正则表达式v标志
PHP trim 函数对多字节字符的使用和限制
新函数 json_validate() 、randomizer 类扩展…20 个PHP 8.3 新特性全面解析
使用HTMX为WordPress增效:如何在不使用复杂框架的情况下增强平台功能
为React 19做准备:WordPress 6.6用户指南
如何删除WordPress中的所有评论

发布评论