应用场景
在前后端开发的过程中,经常会遇到需要将从数据库查询出来的 DTO 对象转换为传递给前端的 VO 对象,这里以Account 对象转换为 AuthorizeVO 对象为例子:
@Data
@AllArgsConstructor
@TableName("db_account")
public class Account {
@TableId(type = IdType.AUTO)
Integer id;
String username;
String password;
String email;
String role;
Date registerTime;
}
@Data
public class AuthorizeVO {
String username;
String role;
String token;
Date expire;
}
对象转换的过程如下:
// 从数据库中查询出对象
Account account = accountService.findAccountByNameOrEmail(user.getUsername());
AuthorizeVO vo = new AuthorizeVO();
vo.setExpire(jwtUtils.expireTime());
vo.setRole(account.getRole());
vo.setToken(token);
vo.setUsername(account.getUsername());
可以看到上面的转换过程需要用户新建一个 AuthorizeVO 对象,并且把原 Account 对象的属性一个一个设置到AuthorizeVO 对象中,非常的不优雅和简洁。
下面的介绍方法可以一次性把 DTO 对象里面的属性赋值到 VO 对象中,并且代码更加简洁,优雅。
源码
除了可以使用 BeanUtils.copyProperties(); 方法实现两个对象属性的快速赋值,我们还可以自己写一个接口方法,只需将 DTO 类继承此接口即可实现对象属性快速赋值,这里用到了 Java 中的反射机制,实现源码如下:
/**
* 用于DTO快速转换VO实现,只需将DTO类继承此类即可使用
*/
public interface BaseData {
/**
* 创建指定的VO类并将当前DTO对象中的所有成员变量值直接复制到VO对象中
* @param clazz 指定VO类型
* @param consumer 返回VO对象之前可以使用Lambda进行额外处理
* @return 指定VO对象
* @param <V> 指定VO类型
*/
default <V> V asViewObject(Class<V> clazz, Consumer<V> consumer) {
V v = this.asViewObject(clazz);
consumer.accept(v);
return v;
}
/**
* 创建指定的VO类并将当前DTO对象中的所有成员变量值直接复制到VO对象中
* @param clazz 指定VO类型
* @return 指定VO对象
* @param <V> 指定VO类型
*/
default <V> V asViewObject(Class<V> clazz) {
try {
Field[] fields = clazz.getDeclaredFields();
Constructor<V> 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转换时出现了一些错误", exception);
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 的接口,其中包含了三个方法和一个私有方法:
- asViewObject(Class<V> clazz, Consumer<V> consumer) 方法用于创建指定的 VO 类并将当前 DTO 对象中的所有成员变量值直接复制到 VO 对象中。在返回 VO 对象之前,可以使用 Lambda 表达式进行额外处理。该方法首先调用 asViewObject(Class<V> clazz) 方法创建 VO 对象,然后对其进行额外处理后返回。
- asViewObject(Class<V> clazz) 方法用于创建指定的 VO 类并将当前 DTO 对象中的所有成员变量值直接复制到 VO 对象中。该方法通过反射获取指定 VO 类的构造函数和字段,然后创建 VO 对象并将当前 DTO 对象中的成员变量值复制到 VO 对象中的同名字段中。
- convert(Field field, Object target) 方法为私有方法,用于在内部快速将当前类中目标对象字段的同名字段的值复制到目标对象字段上。该方法通过反射获取当前类和目标对象字段的值,并将当前类的字段值复制到目标对象字段上。
这段代码的作用是提供了一种便捷的方式来将 DTO 对象转换为 VO 对象,实现了字段值的直接复制。通过该接口,可以方便地实现 DTO 到 VO 的转换,减少重复的转换代码编写工作,并提高代码的可维护性和可读性
用例
使用自定义接口前的转换过程:
// 从数据库中查询出对象
Account account = accountService.findAccountByNameOrEmail(user.getUsername());
AuthorizeVO vo = new AuthorizeVO();
vo.setExpire(jwtUtils.expireTime());
vo.setRole(account.getRole());
vo.setToken(token);
vo.setUsername(account.getUsername());
使用自定义接口后的转换过程:
Account 对象实现 BaseData接口:
@Data
@AllArgsConstructor
@TableName("db_account")
public class Account implements BaseData {
@TableId(type = IdType.AUTO)
Integer id;
String username;
String password;
String email;
String role;
Date registerTime;
}
Account account = accountService.findAccountByNameOrEmail(user.getUsername());
AuthorizeVO vo = account.asViewObject(AuthorizeVO.class, v -> {
v.setExpire(jwtUtils.expireTime());
v.setToken(token);
});
可以看出使用了自定义的接口方法后,对象的转换变得优雅且简洁,提高了代码的可维护性和可读性